/**
 * 対応状況・備考の「編集モード」の下書き（純粋関数のみ。ユーザー指示 2026-10-06。D-165）。
 *
 * ★編集モードで変えた内容はすぐには送らない★（下書き）。★保存ボタンでまとめて送る★。
 * 対象はエラー一覧（/errors）とアラート（/alerts）の両画面 — ★両画面がこのモジュールを使う★。
 *
 * ★期待値表は `lib/prechecks/__tests__/edit-drafts.test.ts`★（P1〜P8 / D1〜D5 / S1〜S3）。
 *
 * 規則:
 *   - ★下書きは「行の値と違う項目」だけを持つ★。行の値と同じに戻したらその項目を消し、
 *     項目が0になったらエントリごと消す（変えた件数 ＝ Map の件数になる）
 *   - ★備考は行の null と "" を同じとみなす★（比較は素の文字列。トリムはサーバー側の規則。指示書AT §3-6）
 *   - ★送る本文の対応状況は必須・備考は変えたときだけ★（AT の規則のまま。`note` を省略すると備考は変えない）
 *   - ★`setDraft*` は入力の Map を書き換えない★（React の state にそのまま入れるため）
 */
import type { PrecheckItem, PrecheckStatus, PrecheckStatusUpdateRequest } from "@/lib/types";

/** 下書きの対象の行。★`ErrorReportItem` も同じ3つを持つので両画面で使える★ */
export type EditableRow = Pick<PrecheckItem, "id" | "resolution_status" | "note">;

/** 1行ぶんの下書き。★キーが無い項目は「変えていない」★ */
export interface ResolutionDraft {
  status?: PrecheckStatus;
  note?: string;
}

/** 行の id → 下書き */
export type ResolutionDrafts = ReadonlyMap<string, ResolutionDraft>;

/** 画面に出す対応状況（下書きがあればそれ、無ければ行の値） */
export function draftStatus(row: EditableRow, draft: ResolutionDraft | undefined): PrecheckStatus {
  return draft?.status ?? row.resolution_status;
}

/** 画面に出す備考（下書きがあればそれ、無ければ行の値。★行の null は ""★） */
export function draftNote(row: EditableRow, draft: ResolutionDraft | undefined): string {
  return draft?.note ?? row.note ?? "";
}

/** 行の値と同じ項目を落とした下書き（項目が0なら null） */
function normalizeDraft(row: EditableRow, draft: ResolutionDraft): ResolutionDraft | null {
  const next: ResolutionDraft = {};
  if (draft.status !== undefined && draft.status !== row.resolution_status) {
    next.status = draft.status;
  }
  if (draft.note !== undefined && draft.note !== (row.note ?? "")) {
    next.note = draft.note;
  }
  return next.status === undefined && next.note === undefined ? null : next;
}

function withDraft(
  drafts: ResolutionDrafts,
  row: EditableRow,
  patch: ResolutionDraft
): Map<string, ResolutionDraft> {
  const result = new Map(drafts);
  const normalized = normalizeDraft(row, { ...drafts.get(row.id), ...patch });
  if (normalized) result.set(row.id, normalized);
  else result.delete(row.id);
  return result;
}

/**
 * 対応状況の下書きを入れる。★新しい Map を返す（入力を書き換えない）★。
 * ★行の値と同じに戻したらその項目を消し、項目が0になったらエントリごと消す★。
 */
export function setDraftStatus(
  drafts: ResolutionDrafts,
  row: EditableRow,
  status: PrecheckStatus
): Map<string, ResolutionDraft> {
  return withDraft(drafts, row, { status });
}

/**
 * 備考の下書きを入れる。★新しい Map を返す（入力を書き換えない）★。
 * ★行の値と同じに戻したらその項目を消し、項目が0になったらエントリごと消す★（行の null と "" は同じ）。
 */
export function setDraftNote(
  drafts: ResolutionDrafts,
  row: EditableRow,
  note: string
): Map<string, ResolutionDraft> {
  return withDraft(drafts, row, { note });
}

/**
 * 下書きから PATCH の本文を作る。★送る必要が無ければ null★。
 * ★対応状況は必須（変えていなければ行の値）・備考は変えたときだけ `note` を入れる★（AT の規則のまま）。
 * ★備考を消す（"" にする）ときも `note: ""` を送る★ — サーバーが空文字を削除として扱う（指示書AT §3-6）。
 */
export function patchRequestFromDraft(
  row: EditableRow,
  draft: ResolutionDraft | undefined
): PrecheckStatusUpdateRequest | null {
  const normalized = draft ? normalizeDraft(row, draft) : null;
  if (!normalized) return null;
  const request: PrecheckStatusUpdateRequest = { status: draftStatus(row, normalized) };
  if (normalized.note !== undefined) request.note = normalized.note;
  return request;
}

/** `saveDrafts()` の結果 */
export interface SaveDraftsResult<T> {
  /** 成功した行（id → レスポンスの行。画面はこれで差し替える） */
  updated: Map<string, T>;
  /** 失敗した行（id → 投げられた例外そのまま） */
  failed: Map<string, unknown>;
  /** ★失敗した行の下書きだけ★（成功した行と、送る必要が無かった行の下書きは消える） */
  remaining: Map<string, ResolutionDraft>;
}

/**
 * 下書きをまとめて保存する。★`rows` の並び順に、送る必要がある行だけを1行ずつ順に `send` する★
 * （並列にしない。ユーザー指示 2026-10-06。D-165）。★1行の失敗で止めない★（残りも送る）。
 *
 * `send` は失敗を例外で返す。`onProgress` は ★送る行の数で数えて★ 1行終わるごとに `(done, total)` で呼ぶ
 * （成功・失敗どちらでも進む）。
 */
export async function saveDrafts<T extends EditableRow>(
  rows: readonly T[],
  drafts: ResolutionDrafts,
  send: (row: T, request: PrecheckStatusUpdateRequest) => Promise<T>,
  onProgress?: (done: number, total: number) => void
): Promise<SaveDraftsResult<T>> {
  const targets: { row: T; request: PrecheckStatusUpdateRequest }[] = [];
  for (const row of rows) {
    const request = patchRequestFromDraft(row, drafts.get(row.id));
    if (request) targets.push({ row, request });
  }

  const updated = new Map<string, T>();
  const failed = new Map<string, unknown>();
  const remaining = new Map<string, ResolutionDraft>();
  let done = 0;
  for (const { row, request } of targets) {
    try {
      updated.set(row.id, await send(row, request));
    } catch (error) {
      failed.set(row.id, error);
      const draft = drafts.get(row.id);
      if (draft) remaining.set(row.id, draft);
    }
    done += 1;
    onProgress?.(done, targets.length);
  }
  return { updated, failed, remaining };
}
