using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using WordVersioning.Api.Data;
using WordVersioning.Api.Models;
namespace WordVersioning.Api.Services;
/// entry หนึ่ง field ใน `DocumentVersion.FieldsSnapshotJson` (เอกสารที่ผูก template)
public record FieldSnapshotEntry(string Value, bool Missing);
public record FieldSyncResult(string? SnapshotJson);
public interface IFieldSyncService
{
/// FR-11 — เทียบ fields ที่ส่งมากับ TemplateField ของ template ที่ document ผูกอยู่:
/// stage การ upsert `DocumentFieldValue` (latest value) ลง change tracker (**caller เป็นคน SaveChanges**)
/// แล้วคืน snapshot JSON ของ version นี้ไปเก็บใน `DocumentVersion.FieldsSnapshotJson`
///
/// policy ตาม spec: field ขาด (ผู้ใช้ลบ content control) → เก็บค่าเดิม + mark missing **ไม่ fail**;
/// field เกินนิยาม → ignore; `UpdatedByUserId` จาก JWT เสมอ
Task StageFieldSyncAsync(
Document document,
Guid sourceVersionId,
IReadOnlyDictionary sentFields,
DateTimeOffset now,
CancellationToken ct);
}
public class FieldSyncService(AppDbContext db, ICurrentUser currentUser) : IFieldSyncService
{
// camelCase ให้ตรงกับ JSON ที่ API คืนทั้งระบบ
private static readonly JsonSerializerOptions SnapshotJsonOptions = new(JsonSerializerDefaults.Web);
public async Task StageFieldSyncAsync(
Document document,
Guid sourceVersionId,
IReadOnlyDictionary sentFields,
DateTimeOffset now,
CancellationToken ct)
{
if (document.TemplateId is null)
{
// เอกสารไม่ผูก template (track เองจาก add-in) — ไม่มีนิยามให้เทียบ
// เก็บ map ดิบตามที่ส่งมาใน snapshot ไว้ดู history ได้ (Phase 2 behavior)
return sentFields.Count == 0
? new FieldSyncResult(null)
: new FieldSyncResult(JsonSerializer.Serialize(sentFields, SnapshotJsonOptions));
}
var fieldDefs = await db.TemplateFields.AsNoTracking()
.Where(f => f.TemplateId == document.TemplateId)
.OrderBy(f => f.SortOrder)
.ToListAsync(ct);
if (fieldDefs.Count == 0)
return new FieldSyncResult(null);
// tracked — update ทีหลังให้เข้า SaveChanges เดียวกับ version insert
var existingValues = await db.DocumentFieldValues
.Where(v => v.DocumentId == document.Id)
.ToDictionaryAsync(v => v.FieldKey, ct);
var snapshot = new Dictionary(fieldDefs.Count);
foreach (var def in fieldDefs)
{
if (sentFields.TryGetValue(def.Key, out var sentValue))
{
sentValue ??= string.Empty;
if (existingValues.TryGetValue(def.Key, out var row))
{
row.Value = sentValue;
row.SourceVersionId = sourceVersionId;
row.UpdatedByUserId = currentUser.UserId;
row.UpdatedAt = now;
}
else
{
db.DocumentFieldValues.Add(new DocumentFieldValue
{
DocumentId = document.Id,
FieldKey = def.Key,
Value = sentValue,
SourceVersionId = sourceVersionId,
UpdatedByUserId = currentUser.UserId,
UpdatedAt = now,
});
}
snapshot[def.Key] = new FieldSnapshotEntry(sentValue, Missing: false);
}
else
{
// field หายจากเอกสาร (ผู้ใช้ลบ content control) — เก็บค่าเดิม + mark missing ไม่ fail (AC-14)
var oldValue = existingValues.TryGetValue(def.Key, out var row) ? row.Value : string.Empty;
snapshot[def.Key] = new FieldSnapshotEntry(oldValue, Missing: true);
}
}
return new FieldSyncResult(JsonSerializer.Serialize(snapshot, SnapshotJsonOptions));
}
}