import type {
  DiffFieldChange,
  TruckLaneVersionDiffLine,
} from "@/app/api/shop/actions";

export type VersionLogFieldEdit = {
  label: string;
  from: string;
  to: string;
};

export type VersionLogShopRow = {
  type: "added" | "deleted" | "moved" | "edited";
  shopName: string;
  shopCode: string;
  fromLane?: string;
  toLane?: string;
  truckRowId: number;
  /** 非新增/刪除時：各欄位 before → after（發車時段、順序等） */
  fieldEdits?: VersionLogFieldEdit[];
};

function pickField(
  changes: DiffFieldChange[],
  field: string,
): DiffFieldChange | undefined {
  return changes.find((c) => c.field === field);
}

function isBlankDiffValue(raw: string | null | undefined): boolean {
  return raw == null || String(raw).trim() === "";
}

/** 新版本已無此列：車線／店碼等由有值變空（勿用 line 上的 ctx fallback 當 to 車線） */
function isRowRemovedInDiff(ch: DiffFieldChange[]): boolean {
  const lane = pickField(ch, "truckLanceCode");
  const code = pickField(ch, "shopCode");
  if (!lane && !code) return false;
  const laneOk =
    !lane || (!isBlankDiffValue(lane.from) && isBlankDiffValue(lane.to));
  const codeOk =
    !code || (!isBlankDiffValue(code.from) && isBlankDiffValue(code.to));
  return laneOk && codeOk;
}

function isRowInsertedInDiff(ch: DiffFieldChange[]): boolean {
  const lane = pickField(ch, "truckLanceCode");
  const code = pickField(ch, "shopCode");
  if (!lane && !code) return false;
  const laneOk =
    !lane || (isBlankDiffValue(lane.from) && !isBlankDiffValue(lane.to));
  const codeOk =
    !code || (isBlankDiffValue(code.from) && !isBlankDiffValue(code.to));
  return laneOk && codeOk;
}

export const VERSION_LOG_LOADING_SEQUENCE_LABEL = "versionLogField_loadingSequence";

const VERSION_LOG_FIELD_LABEL: Record<string, string> = {
  departureTime: "versionLogField_departureTime",
  loadingSequence: VERSION_LOG_LOADING_SEQUENCE_LABEL,
  branchName: "versionLogField_branchName",
  districtReference: "versionLogField_districtReference",
  shopCode: "versionLogField_shopCode",
  storeId: "versionLogField_storeId",
  remark: "versionLogField_remark",
  truckLanceCode: "versionLogField_truckLanceCode",
  logisticId: "versionLogField_logisticId",
};

/** 版本 LOG 用：時段顯示為 HH:mm，避免 ISO / 帶秒過長 */
export function formatVersionLogTimeDisplay(
  raw: string | null | undefined,
): string {
  if (raw == null || !String(raw).trim()) return "—";
  const s = String(raw).trim();
  const afterT = s.includes("T") ? s.split("T")[1] ?? "" : s;
  const tail = afterT.split(".")[0] ?? afterT;
  const m = tail.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?/);
  if (m) {
    const h = Math.min(23, Math.max(0, Number(m[1]) || 0));
    const min = Math.min(59, Math.max(0, Number(m[2]) || 0));
    return `${String(h).padStart(2, "0")}:${String(min).padStart(2, "0")}`;
  }
  return s;
}

function formatVersionLogFieldValue(
  field: string,
  raw: string | null | undefined,
): string {
  if (raw == null || String(raw).trim() === "") return "—";
  if (field === "departureTime") return formatVersionLogTimeDisplay(raw);
  if (field === "loadingSequence") {
    const n = Number(raw);
    return Number.isFinite(n) ? String(Math.trunc(n)) : String(raw);
  }
  return String(raw).trim();
}

function buildFieldEditsForRow(
  line: TruckLaneVersionDiffLine,
  rowType: VersionLogShopRow["type"],
): VersionLogFieldEdit[] | undefined {
  const ch = line.changes ?? [];
  if (ch.length === 0) return undefined;
  // 新增/刪除整列時後端仍會列欄位差異，清單已有「新加入／移除」，不再重複細項
  if (rowType === "added" || rowType === "deleted") return undefined;

  const skipWhenMoved = new Set(["truckLanceCode", "remark"]);
  const out: VersionLogFieldEdit[] = [];
  for (const c of ch) {
    if (rowType === "moved" && skipWhenMoved.has(c.field)) continue;
    out.push({
      label: VERSION_LOG_FIELD_LABEL[c.field] ?? c.field,
      from: formatVersionLogFieldValue(c.field, c.from),
      to: formatVersionLogFieldValue(c.field, c.to),
    });
  }
  return out.length > 0 ? out : undefined;
}

/** 同車線編輯（如僅裝載順序）時用於 UI 顯示車線 */
export function resolveVersionLogLaneLabel(
  row: Pick<VersionLogShopRow, "fromLane" | "toLane">,
): string | undefined {
  const lane =
    row.toLane && row.toLane !== "—"
      ? row.toLane
      : row.fromLane && row.fromLane !== "—"
        ? row.fromLane
        : undefined;
  return lane;
}

export function formatLaneLabel(
  truckLanceCode?: string | null,
  remark?: string | null,
): string {
  const c = String(truckLanceCode ?? "").trim();
  const r =
    remark != null && String(remark).trim() !== "" ? String(remark).trim() : "";
  if (!c && !r) return "—";
  if (!r) return c;
  return `${c} · ${r}`;
}

export function resolveVersionActor(version: {
  modifiedBy?: string | null;
  createdBy?: string | null;
}): string {
  return String(version.modifiedBy ?? version.createdBy ?? "").trim();
}

export function splitVersionCreated(created: string | null | undefined): {
  date: string;
  time: string;
} {
  if (!created || !String(created).trim()) return { date: "—", time: "" };
  const raw = String(created).trim();
  const d = new Date(raw);
  if (!Number.isNaN(d.getTime())) {
    const yyyy = d.getFullYear();
    const mm = String(d.getMonth() + 1).padStart(2, "0");
    const dd = String(d.getDate()).padStart(2, "0");
    const hh = String(d.getHours()).padStart(2, "0");
    const mi = String(d.getMinutes()).padStart(2, "0");
    return { date: `${yyyy}-${mm}-${dd}`, time: `${hh}:${mi}` };
  }
  const parts = raw.split(/[T ]/);
  return { date: parts[0] || raw, time: (parts[1] ?? "").slice(0, 5) };
}

export function diffLinesToShopRows(
  lines: TruckLaneVersionDiffLine[],
): VersionLogShopRow[] {
  return lines.map(diffLineToShopRow);
}

export function diffLineToShopRow(
  line: TruckLaneVersionDiffLine,
): VersionLogShopRow {
  const ch = line.changes ?? [];
  const tc = pickField(ch, "truckLanceCode");
  const rem = pickField(ch, "remark");
  const br = pickField(ch, "branchName");
  const code = line.shopCode != null ? String(line.shopCode) : "";
  const shopName = String(br?.to ?? br?.from ?? (code || "—")).trim() || "—";

  const ctxCode =
    line.truckLanceCode != null && String(line.truckLanceCode).trim() !== ""
      ? String(line.truckLanceCode).trim()
      : undefined;
  const ctxRem =
    line.remark != null && String(line.remark).trim() !== ""
      ? String(line.remark).trim()
      : undefined;

  const fromLane = formatLaneLabel(tc?.from ?? ctxCode, rem?.from ?? ctxRem);
  const toLane = formatLaneLabel(tc?.to ?? ctxCode, rem?.to ?? ctxRem);
  const toLaneStrict = formatLaneLabel(tc?.to, rem?.to);

  let type: VersionLogShopRow["type"];
  if (isRowInsertedInDiff(ch)) type = "added";
  else if (isRowRemovedInDiff(ch)) type = "deleted";
  else if (fromLane !== toLane) type = "moved";
  else type = "edited";

  const fromEmpty = fromLane === "—";
  const toEmpty = type === "deleted" ? true : toLaneStrict === "—";

  return {
    type,
    shopName,
    shopCode: code,
    fromLane: fromEmpty ? undefined : fromLane,
    toLane: toEmpty ? undefined : toLane,
    truckRowId: line.truckRowId,
    fieldEdits: buildFieldEditsForRow(line, type),
  };
}

export function summarizeVersionRows(rows: VersionLogShopRow[]): {
  added: number;
  moved: number;
  deleted: number;
  /** 有欄位級 before→after 的列數（同列可同時算入移動） */
  fieldChanges: number;
} {
  const o = { added: 0, moved: 0, deleted: 0, fieldChanges: 0 };
  for (const r of rows) {
    if (r.type === "added") o.added++;
    else if (r.type === "deleted") o.deleted++;
    else if (r.type === "moved") o.moved++;
    if (r.fieldEdits != null && r.fieldEdits.length > 0) o.fieldChanges++;
  }
  return o;
}

export function resolveHeadVersionId(
  versions: Array<{ id?: unknown }>,
): number | null {
  const first = versions[0];
  const id = Number(first?.id);
  return Number.isFinite(id) && id > 0 ? id : null;
}

/** 版本 LOG modal「看板未儲存」小節：與後端 diff 分開列，避免與 server snapshot 混淆 */
export type StagedLogEntry =
  | { key: string; tag: "scheduled"; kind: "restore"; versionId: number }
  | { key: string; tag: "unsaved"; kind: "shop"; row: VersionLogShopRow }
  | {
      key: string;
      tag: "unsaved";
      kind: "text";
      titleKey: string;
      titleParams?: Record<string, string | number>;
    };

export type StagedDeleteMeta = {
  shopCode: string;
  branchName: string;
  fromLane: string;
};

/** Per shop row baseline at load / refresh (for staged move vs field-edit). */
export type ShopRowBaseline = {
  laneId: string;
  fromLaneLabel: string;
  departureTime: string;
  loadingSequence: number;
  districtDisplay: string;
};

function stagedDistrictDisplay(
  district: string | null | undefined,
): string {
  const value = String(district ?? "").trim();
  return value === "" ? "未分類" : value;
}

export function buildStagedBoardLogEntries(input: {
  pendingRestoreVersionId: number | null;
  dirtyMoves: Map<number, string>;
  dirtyMoveDistrictHints?: ReadonlyMap<number, string>;
  dirtyDeletes: Set<number>;
  stagedDeleteMeta: ReadonlyMap<number, StagedDeleteMeta>;
  pendingShopAdds: Array<{
    tempTruckRowId: number;
    laneId: string;
    shopCode: string;
    shopName: string;
    loadingSequence: number;
  }>;
  pendingNewLanes: Array<{
    laneKey: string;
    payload: { truckLanceCode: string; remark?: string | null };
  }>;
  pendingLogisticMasterAdds: Array<{
    tempId: number;
    logisticName: string;
    carPlate: string;
  }>;
  pendingLogisticMasterEdits?: Array<{
    id: number;
    logisticName: string;
    carPlate: string;
    fromName?: string;
    fromPlate?: string;
  }>;
  pendingLogisticMasterDeletes?: Array<{
    id: number;
    logisticName: string;
  }>;
  pendingImport?: {
    fileName: string;
    sheetCount: number;
    rowCount: number;
  } | null;
  laneLogisticChanges: Array<{ laneId: string; logisticId: number | null }>;
  lanes: Array<{
    id: string;
    truckLanceCode?: string | null;
    remark?: string | null;
    shops: Array<{
      id: number;
      shopCode?: string | null;
      branchName?: string | null;
      districtReferenceRaw?: string | null;
      departureTime?: string;
      loadingSequence?: number;
    }>;
  }>;
  pendingEmptyDistrictsByLane: Record<string, string[]>;
  logisticNameById: ReadonlyMap<number, string>;
  shopDistrictBaseline: ReadonlyMap<number, string>;
  shopRowBaseline?: ReadonlyMap<number, ShopRowBaseline>;
}): StagedLogEntry[] {
  const out: StagedLogEntry[] = [];
  const {
    pendingRestoreVersionId,
    dirtyMoves,
    dirtyMoveDistrictHints,
    dirtyDeletes,
    stagedDeleteMeta,
    pendingShopAdds,
    pendingNewLanes,
    pendingLogisticMasterAdds,
    pendingLogisticMasterEdits,
    pendingLogisticMasterDeletes,
    pendingImport,
    laneLogisticChanges,
    lanes,
    pendingEmptyDistrictsByLane,
    logisticNameById,
    shopDistrictBaseline,
    shopRowBaseline,
  } = input;

  if (
    pendingRestoreVersionId != null &&
    Number.isFinite(pendingRestoreVersionId) &&
    pendingRestoreVersionId > 0
  ) {
    out.push({
      key: `restore-${pendingRestoreVersionId}`,
      tag: "scheduled",
      kind: "restore",
      versionId: pendingRestoreVersionId,
    });
  }

  const laneById = new Map(lanes.map((l) => [l.id, l]));

  if (pendingImport != null && pendingImport.rowCount > 0) {
    out.push({
      key: `import-${pendingImport.fileName}`,
      tag: "unsaved",
      kind: "text",
      titleKey: "diff_staged_importPending",
      titleParams: {
        file: pendingImport.fileName,
        sheets: pendingImport.sheetCount,
        rows: pendingImport.rowCount,
      },
    });
  }

  for (const p of pendingLogisticMasterAdds) {
    const tid = Number((p as { tempId?: number }).tempId);
    if (Number.isFinite(tid) && tid > 0) continue;
    const name = String(p.logisticName ?? "").trim() || "—";
    const plate = String(p.carPlate ?? "").trim() || "—";
    out.push({
      key: `plog-${p.tempId}`,
      tag: "unsaved",
      kind: "text",
      titleKey: "diff_staged_pendingLogisticMaster",
      titleParams: { name, plate },
    });
  }

  for (const p of pendingLogisticMasterEdits ?? []) {
    const name = String(p.logisticName ?? "").trim() || "—";
    const plate = String(p.carPlate ?? "").trim() || "—";
    const fromName = String(p.fromName ?? "").trim() || "—";
    const fromPlate = String(p.fromPlate ?? "").trim() || "—";
    out.push({
      key: `plog-edit-${p.id}`,
      tag: "unsaved",
      kind: "text",
      titleKey: "diff_staged_editLogisticMaster",
      titleParams: { name, plate, fromName, fromPlate },
    });
  }

  for (const p of pendingLogisticMasterDeletes ?? []) {
    const name = String(p.logisticName ?? "").trim() || "—";
    out.push({
      key: `plog-del-${p.id}`,
      tag: "unsaved",
      kind: "text",
      titleKey: "diff_staged_deleteLogisticMaster",
      titleParams: { name },
    });
  }

  const dirtyMoveShopIds = new Set<number>();

  dirtyMoves.forEach((laneId, shopId) => {
    if (!Number.isFinite(shopId) || shopId <= 0) return;
    dirtyMoveShopIds.add(shopId);
    const lane = laneById.get(laneId);
    const shop = lane?.shops.find((s) => s.id === shopId);
    if (!lane || !shop) return;
    const laneLabel = formatLaneLabel(lane.truckLanceCode, lane.remark);
    const shopName = String(shop.branchName ?? "").trim() || "—";
    const shopCode = String(shop.shopCode ?? "").trim();
    const base = shopRowBaseline?.get(shopId);

    if (base != null) {
      const fromLaneResolved = base.fromLaneLabel;
      const toLane = laneLabel;
      const fieldEdits: VersionLogFieldEdit[] = [];
      const curDep = formatVersionLogTimeDisplay(shop.departureTime);
      const baseDep = formatVersionLogTimeDisplay(base.departureTime);
      if (curDep !== baseDep) {
        fieldEdits.push({
          label: VERSION_LOG_FIELD_LABEL.departureTime ?? "departureTime",
          from: baseDep,
          to: curDep,
        });
      }
      const curSeq = Number(shop.loadingSequence ?? 0) || 0;
      if (curSeq !== base.loadingSequence) {
        fieldEdits.push({
          label: VERSION_LOG_FIELD_LABEL.loadingSequence ?? "loadingSequence",
          from: String(base.loadingSequence),
          to: String(curSeq),
        });
      }
      const curDist = stagedDistrictDisplay(shop.districtReferenceRaw);
      if (curDist !== base.districtDisplay) {
        fieldEdits.push({
          label: VERSION_LOG_FIELD_LABEL.districtReference ?? "districtReference",
          from: base.districtDisplay,
          to: curDist,
        });
      }
      const moved = base.laneId !== laneId;
      if (moved) {
        out.push({
          key: `dirty-${shopId}`,
          tag: "unsaved",
          kind: "shop",
          row: {
            type: "moved",
            shopName,
            shopCode,
            fromLane: fromLaneResolved,
            toLane,
            truckRowId: shopId,
            fieldEdits: fieldEdits.length > 0 ? fieldEdits : undefined,
          },
        });
      } else if (fieldEdits.length > 0) {
        out.push({
          key: `dirty-${shopId}`,
          tag: "unsaved",
          kind: "shop",
          row: {
            type: "edited",
            shopName,
            shopCode,
            fromLane: laneLabel,
            toLane: laneLabel,
            truckRowId: shopId,
            fieldEdits,
          },
        });
      } else {
        const hint =
          dirtyMoveDistrictHints != null
            ? dirtyMoveDistrictHints.get(shopId)
            : undefined;
        const districtPart =
          hint != null && String(hint).trim() !== ""
            ? String(hint).trim()
            : "";
        out.push({
          key: `dirty-${shopId}`,
          tag: "unsaved",
          kind: "text",
          titleKey: "diff_staged_shopPendingOnLane",
          titleParams: {
            name: shopName,
            code: shopCode || "—",
            lane: laneLabel,
            districtPart,
          },
        });
      }
      return;
    }

    const hint =
      dirtyMoveDistrictHints != null
        ? dirtyMoveDistrictHints.get(shopId)
        : undefined;
    const districtPart =
      hint != null && String(hint).trim() !== ""
        ? String(hint).trim()
        : "";
    out.push({
      key: `dirty-${shopId}`,
      tag: "unsaved",
      kind: "text",
      titleKey: "diff_staged_shopPendingOnLane",
      titleParams: {
        name: shopName,
        code: shopCode || "—",
        lane: laneLabel,
        districtPart,
      },
    });
  });

  for (const lane of lanes) {
    for (const shop of lane.shops) {
      const sid = shop.id;
      if (!Number.isFinite(sid) || sid <= 0) continue;
      if (dirtyDeletes.has(sid)) continue;
      if (dirtyMoveShopIds.has(sid)) continue;
      const base = shopDistrictBaseline.get(sid);
      if (base == null) continue;
      const cur = stagedDistrictDisplay(shop.districtReferenceRaw);
      if (cur === base) continue;
      const laneLabel = formatLaneLabel(lane.truckLanceCode, lane.remark);
      out.push({
        key: `distonly-${sid}`,
        tag: "unsaved",
        kind: "text",
        titleKey: "diff_staged_shopDistrictOnly",
        titleParams: {
          name: String(shop.branchName ?? "").trim() || "—",
          code: String(shop.shopCode ?? "").trim() || "—",
          lane: laneLabel,
          from: base,
          to: cur,
        },
      });
    }
  }

  dirtyDeletes.forEach((shopId) => {
    if (!Number.isFinite(shopId) || shopId <= 0) return;
    const meta = stagedDeleteMeta.get(shopId);
    if (meta) {
      out.push({
        key: `del-${shopId}`,
        tag: "unsaved",
        kind: "shop",
        row: {
          type: "deleted",
          shopName: meta.branchName.trim() || "—",
          shopCode: meta.shopCode.trim(),
          fromLane: meta.fromLane,
          truckRowId: shopId,
          fieldEdits: undefined,
        },
      });
    } else {
      out.push({
        key: `del-${shopId}`,
        tag: "unsaved",
        kind: "text",
        titleKey: "diff_staged_deleteUnknown",
        titleParams: { id: shopId },
      });
    }
  });

  for (const p of pendingShopAdds) {
    const lane = laneById.get(p.laneId);
    const toLane = lane
      ? formatLaneLabel(lane.truckLanceCode, lane.remark)
      : "—";
    out.push({
      key: `addshop-${p.tempTruckRowId}`,
      tag: "unsaved",
      kind: "shop",
      row: {
        type: "added",
        shopName: String(p.shopName || "").trim() || "—",
        shopCode: String(p.shopCode || "").trim(),
        toLane,
        truckRowId: p.tempTruckRowId,
        fieldEdits: undefined,
      },
    });
  }

  for (const p of pendingNewLanes) {
    const code = String(p.payload?.truckLanceCode ?? "").trim();
    const remark =
      p.payload?.remark != null && String(p.payload.remark).trim() !== ""
        ? String(p.payload.remark).trim()
        : null;
    out.push({
      key: `newlane-${p.laneKey}`,
      tag: "unsaved",
      kind: "text",
      titleKey: "diff_staged_newLane",
      titleParams: { lane: formatLaneLabel(code || "—", remark) },
    });
  }

  for (const c of laneLogisticChanges) {
    const lane = laneById.get(c.laneId);
    const laneLabel = lane
      ? formatLaneLabel(lane.truckLanceCode, lane.remark)
      : c.laneId;
    const lid = c.logisticId;
    const company =
      lid != null && lid > 0
        ? String(logisticNameById.get(lid) ?? "").trim() || `id=${lid}`
        : "—";
    out.push({
      key: `log-${c.laneId}`,
      tag: "unsaved",
      kind: "text",
      titleKey: "diff_staged_laneLogistic",
      titleParams: { lane: laneLabel, company },
    });
  }

  for (const [laneId, names] of Object.entries(pendingEmptyDistrictsByLane)) {
    const arr = Array.isArray(names) ? names.filter(Boolean) : [];
    if (arr.length === 0) continue;
    const lane = laneById.get(laneId);
    const laneLabel = lane
      ? formatLaneLabel(lane.truckLanceCode, lane.remark)
      : laneId;
    out.push({
      key: `dist-${laneId}-${arr.join(",")}`,
      tag: "unsaved",
      kind: "text",
      titleKey: "diff_staged_emptyDistricts",
      titleParams: { lane: laneLabel, names: arr.join("、") },
    });
  }

  return out;
}

/**
 * 異動列標題：優先用 shop 主檔名稱，其次版本 branchName（與代碼不同時），最後才代碼。
 */
export function resolveVersionLogShopHeadline(
  row: VersionLogShopRow,
  shopNameByCode: Map<string, string>,
): { headline: string; detail?: string } {
  const code = String(row.shopCode || "").trim();
  const lower = code.toLowerCase();
  const master = lower ? String(shopNameByCode.get(lower) ?? "").trim() : "";
  const snap = String(row.shopName || "").trim();
  const snapIsEmpty = !snap || snap === "—";
  const snapIsJustCode = snap.toLowerCase() === lower;

  const headline =
    (master ? master : "") ||
    (!snapIsEmpty && !snapIsJustCode ? snap : "") ||
    code ||
    "—";

  const bits: string[] = [];
  if (!snapIsEmpty && !snapIsJustCode && snap !== master)
    bits.push(`分店：${snap}`);
  const detail = bits.length > 0 ? bits.join(" · ") : undefined;

  return { headline, detail };
}
