using System;
using System.Collections.Generic;
using System.IO;
using IvfTl.AutoFocus.Calib;
namespace IvfTl.AutoFocus.Storage
{
///
/// 标定结果存储入口(M2-04)。
/// 依据:项目文档/开发计划/2026-06-17-M2-本地自动对焦子计划.md (M2-04)、需求文档/12 §2.7、03 §4。
///
/// 职责:
/// 1) 真相源——把标定结果写本地 calibration.json(复用 CalibrationFile.Save;
/// 读回经 CalibrationFile.Load 已带 IncludeFields=true,public 字段不会静默丢失,03 §4)。
/// 2) 库内镜像——把单条 WellCalib 解包后通过注入的回调写入 house_autofocus_calibration
/// (scene 0 基准 upsert / 1 日常 append)。本程序集不引用 SqlSugar/Entity,故库写以
/// 委托回调形式由 control 端(AppData→DBService.SaveAutofocusCalibration)实现,避免反向依赖。
///
/// scene:0=出厂基准(调试页一键标定,M2-05) 1=日常对焦(放皿/门/定时触发,M2-03 走此路径)。
/// 任一步骤异常都被本类吞掉(经 Log 回调记日志),绝不向上抛,保证不崩对焦/采集线程。
///
public sealed class CalibrationStore
{
///
/// 库镜像回调:参数依次为
/// (tlSn, houseSn, wellSn, scene, focusZ, exposure, horizontalPulse, peakRatio,
/// circleFound, centerOffsetPct, note, source)。
/// 由 control 端绑定到 DBService.SaveAutofocusCalibration;为空则只写 JSON、不镜像库。
///
public Action DbMirror { get; set; }
///
/// M2-06 安全门降级用:读 scene=0 出厂基准的最清晰层 Z 脉冲(FocusZ)回调。
/// 参数:(tlSn, houseSn, wellSn);返回该 well 的基准 FocusZ,无基准返回 null。
/// 由 control 端绑定到 DBService.GetBaselineFocusZ(查 house_autofocus_calibration scene=0);
/// 为空或返回 null 时,安全门关闭路径无基准可用 → 本轮跳过该 well(不污染拍摄,记日志)。
/// 本程序集不引用 SqlSugar/Entity,故以委托回调形式由 control 端实现,避免反向依赖(同 DbMirror)。
///
public Func BaselineReader { get; set; }
/// 日志回调(可空)。
public Action Log { get; set; }
/// calibration.json 路径(真相源)。由部署配置注入,不写死测试外壳常量路径。
public string JsonPath { get; set; }
/// 结果来源标记,写入库镜像 source 列。
public string Source { get; set; } = "LOCAL_JSON";
///
/// 保存单个 well 的标定结果:先写 JSON 真相源(按 tlSn/houseSn/wellSn 合并到既有档案),
/// 再镜像库(scene 0 upsert / 1 append)。
///
public void SaveCalibration(WellCalib wc, string tlSn, int houseSn, int wellSn, int scene,
string port = null, int ccdIndex = 0, string ccdSn = null)
{
if (wc == null) return;
// ① 写 JSON 真相源(容错:JSON 失败不影响库镜像,反之亦然)。
try
{
WriteJson(wc, tlSn, houseSn, wellSn, port, ccdIndex, ccdSn);
}
catch (Exception ex)
{
Log?.Invoke($"[标定存储]写 calibration.json 失败:{ex.Message}");
}
// ② 镜像库(通过注入回调,由 control 端落 SqlSugar)。
try
{
DbMirror?.Invoke(tlSn, houseSn, wellSn, scene, wc.FocusZ, wc.Exposure, wc.HorizontalPulse,
(decimal)wc.PeakRatio, wc.CircleFound, (decimal)wc.CenterOffsetPct, wc.Note ?? "", Source);
}
catch (Exception ex)
{
Log?.Invoke($"[标定存储]镜像 house_autofocus_calibration 失败:{ex.Message}");
}
}
///
/// 把 WellCalib 合并写入 calibration.json:读出既有档案(无则新建),按 house/well 覆盖该 well 条目后回写。
///
private void WriteJson(WellCalib wc, string tlSn, int houseSn, int wellSn, string port, int ccdIndex, string ccdSn)
{
if (string.IsNullOrEmpty(JsonPath)) return;
CalibrationFile file = null;
if (File.Exists(JsonPath))
{
try { file = CalibrationFile.Load(JsonPath); }
catch (Exception ex) { Log?.Invoke($"[标定存储]读旧 calibration.json 失败,将重建:{ex.Message}"); }
}
if (file == null) file = new CalibrationFile();
if (string.IsNullOrEmpty(file.TlSn)) file.TlSn = tlSn ?? "";
file.Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
var house = file.Houses.Find(h => h.House == houseSn);
if (house == null)
{
house = new HouseCalib { House = houseSn, Port = port ?? "", CcdIndex = ccdIndex, CcdSn = ccdSn ?? "" };
file.Houses.Add(house);
}
else
{
if (port != null) house.Port = port;
if (ccdSn != null) house.CcdSn = ccdSn;
if (ccdIndex != 0) house.CcdIndex = ccdIndex;
}
// wc.Well 可能未被调用方填好,这里以入参 wellSn 为准对齐 well 序号。
wc.Well = wellSn;
int idx = house.Wells.FindIndex(w => w.Well == wellSn);
if (idx >= 0) house.Wells[idx] = wc;
else house.Wells.Add(wc);
file.Save(JsonPath);
}
}
}