/**
 * エラー一覧専用の下書き（指示書BE §5・§6。D-185）。
 *
 * アラートと D-Sec は `lib/prechecks/edit-drafts.ts` のまま（`error_list_id` を送らない）。
 * こちらの正規化は対応状況・備考に加えて `error_list_id` を持つ。行の値と同じなら消し、
 * 項目が0になったらエントリごと消す。
 */
import { draftNote, draftStatus } from "@/lib/prechecks/edit-drafts";
import type { EditableRow, ResolutionDraft } from "@/lib/prechecks/edit-drafts";
import type { ErrorListItem, ErrorStatusUpdateRequest, PrecheckStatus } from "@/lib/types";

export type ErrorResolutionDraft = ResolutionDraft & { error_list_id?: number };

export type ErrorResolutionDrafts = ReadonlyMap<string, ErrorResolutionDraft>;

/** エラー内容の下書きを載せられる行（`ErrorReportItem` はこれを満たす） */
export type ErrorEditableRow = EditableRow & {
  error_list_id: number | null;
  error_list: ErrorListItem | null;
  /** この行でエラー内容を選べるか。★false の行には備考必須を掛けない★（サーバーと同じ。指示書BD §3-3 手順4） */
  error_list_selectable: boolean;
};

/** その他を選んだのに備考が空のとき、行の直後に出す文言（表示時に `t()` を通す） */
export const NOTE_REQUIRED_ROW_MESSAGE = "その他を選んだときは備考を入力してください";

function normalizeErrorDraft(
  row: ErrorEditableRow,
  draft: ErrorResolutionDraft
): ErrorResolutionDraft | null {
  const next: ErrorResolutionDraft = {};
  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;
  }
  if (draft.error_list_id !== undefined && draft.error_list_id !== row.error_list_id) {
    next.error_list_id = draft.error_list_id;
  }
  if (
    next.status === undefined &&
    next.note === undefined &&
    next.error_list_id === undefined
  ) {
    return null;
  }
  return next;
}

function withErrorDraft(
  drafts: ErrorResolutionDrafts,
  row: ErrorEditableRow,
  patch: ErrorResolutionDraft
): Map<string, ErrorResolutionDraft> {
  const result = new Map(drafts);
  const normalized = normalizeErrorDraft(row, { ...drafts.get(row.id), ...patch });
  if (normalized) result.set(row.id, normalized);
  else result.delete(row.id);
  return result;
}

export function setErrorDraftStatus(
  drafts: ErrorResolutionDrafts,
  row: ErrorEditableRow,
  status: PrecheckStatus
): Map<string, ErrorResolutionDraft> {
  return withErrorDraft(drafts, row, { status });
}

export function setErrorDraftNote(
  drafts: ErrorResolutionDrafts,
  row: ErrorEditableRow,
  note: string
): Map<string, ErrorResolutionDraft> {
  return withErrorDraft(drafts, row, { note });
}

export function setErrorDraftErrorListId(
  drafts: ErrorResolutionDrafts,
  row: ErrorEditableRow,
  errorListId: number
): Map<string, ErrorResolutionDraft> {
  return withErrorDraft(drafts, row, { error_list_id: errorListId });
}

/**
 * PATCH の本文。対応状況は必須、備考と `error_list_id` は変えたときだけ。
 * ★送る必要が無ければ null★。
 */
export function errorPatchRequestFromDraft(
  row: ErrorEditableRow,
  draft: ErrorResolutionDraft | undefined
): ErrorStatusUpdateRequest | null {
  const normalized = draft ? normalizeErrorDraft(row, draft) : null;
  if (!normalized) return null;
  const request: ErrorStatusUpdateRequest = { status: draftStatus(row, normalized) };
  if (normalized.note !== undefined) request.note = normalized.note;
  if (normalized.error_list_id !== undefined) request.error_list_id = normalized.error_list_id;
  return request;
}

/** 適用後のエラー内容（下書きの id があれば選択肢から、無ければ保存済み） */
export function appliedErrorList(
  row: ErrorEditableRow,
  draft: ErrorResolutionDraft | undefined,
  catalog: readonly ErrorListItem[]
): ErrorListItem | null {
  if (draft?.error_list_id !== undefined) {
    return catalog.find((item) => item.id === draft.error_list_id) ?? null;
  }
  return row.error_list;
}

/**
 * 適用後のエラー内容が備考必須か（指示書BE §6）。
 * ★選べない行（`error_list_selectable` が false）は常に false★（サーバーも掛けない。指示書BD §3-3 手順4）。
 * 画面の placeholder「備考を入力（必須）」と警告色の枠も、保存前の検証も、この関数1つで決める。
 */
export function requiresNote(
  row: ErrorEditableRow,
  draft: ErrorResolutionDraft | undefined,
  catalog: readonly ErrorListItem[]
): boolean {
  if (!row.error_list_selectable) return false;
  return appliedErrorList(row, draft, catalog)?.note_required === true;
}

/**
 * 保存前に送らない行か（指示書BE §6）。
 * 下書きがエラー内容か備考を変えていて、適用後が `note_required`（`requiresNote()`）かつ備考（前後の空白を除く）が空。
 * ★対応状況だけの変更には掛けない★。★選べない行には掛けない★。
 */
export function blocksSaveForMissingNote(
  row: ErrorEditableRow,
  draft: ErrorResolutionDraft | undefined,
  catalog: readonly ErrorListItem[]
): boolean {
  if (!draft) return false;
  if (draft.error_list_id === undefined && draft.note === undefined) return false;
  if (!requiresNote(row, draft, catalog)) return false;
  return draftNote(row, draft).trim() === "";
}

export interface SaveErrorDraftsResult<T> {
  updated: Map<string, T>;
  failed: Map<string, unknown>;
  remaining: Map<string, ErrorResolutionDraft>;
}

/** 送った行の数の進み具合（`done` は送り終えた行数、`total` は送る行数。★送らなかった行は数えない★） */
export type SaveProgress = (done: number, total: number) => void;

/**
 * エラー一覧の下書きを順に送る。★1行の失敗で止めない★（`saveDrafts()` と同じ順）。
 * アラートと D-Sec はこの関数を使わない。★保存前の検証は掛けない★（画面は `saveErrorDraftsWithCheck()` を使う）。
 */
export async function saveErrorDrafts<T extends ErrorEditableRow>(
  rows: readonly T[],
  drafts: ErrorResolutionDrafts,
  send: (row: T, request: ErrorStatusUpdateRequest) => Promise<T>,
  onProgress?: SaveProgress
): Promise<SaveErrorDraftsResult<T>> {
  const targets: { row: T; request: ErrorStatusUpdateRequest }[] = [];
  for (const row of rows) {
    const request = errorPatchRequestFromDraft(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, ErrorResolutionDraft>();
  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 };
}

export interface SaveErrorDraftsWithCheckResult<T> extends SaveErrorDraftsResult<T> {
  /** 保存前の検証で送らなかった行（id → 行の直後に出す文言。表示時に `t()` を通す） */
  blocked: Map<string, string>;
}

/**
 * 保存（指示書BE §6）。★その他で備考が空の行（`blocksSaveForMissingNote()`）は送らない★。
 * ・送らなかった行は `blocked` に `NOTE_REQUIRED_ROW_MESSAGE` で入り、★下書きはそのまま `remaining` に残る★
 * ・ほかの行は `saveErrorDrafts()` で送る（1行の失敗で止めない。失敗した行の下書きも `remaining` に残る）
 * ・成功した行は `remaining` に入らない
 * 画面は `failed.size === 0 && blocked.size === 0` のときだけ通常モードに戻る。
 */
export async function saveErrorDraftsWithCheck<T extends ErrorEditableRow>(
  rows: readonly T[],
  drafts: ErrorResolutionDrafts,
  catalog: readonly ErrorListItem[],
  send: (row: T, request: ErrorStatusUpdateRequest) => Promise<T>,
  onProgress?: SaveProgress
): Promise<SaveErrorDraftsWithCheckResult<T>> {
  const blocked = new Map<string, string>();
  const sendable = new Map(drafts);
  for (const row of rows) {
    if (!blocksSaveForMissingNote(row, drafts.get(row.id), catalog)) continue;
    blocked.set(row.id, NOTE_REQUIRED_ROW_MESSAGE);
    sendable.delete(row.id);
  }
  const { updated, failed, remaining } = await saveErrorDrafts(rows, sendable, send, onProgress);
  for (const id of blocked.keys()) {
    const draft = drafts.get(id);
    if (draft) remaining.set(id, draft);
  }
  return { updated, failed, blocked, remaining };
}
