/**
 * Canonical PM→ADMIN final report selection (manual MCP > auto Runtime summary).
 */

import { isAckOnlyReportBody } from "../review/ReviewFactGate.ts";
import {
  isPmFinalSummaryTerminalStatus,
  isPmNonFinalAdminReportStatus,
  isPmToAdminReport,
  reportBoundToAdminRoot,
  taskIdPrefix,
} from "./reportParenting.ts";
import {
  isAutoGeneratedReciprocalReport,
  reportSortKey,
} from "./reportSubmitEligibility.ts";
import type { LedgerReportRecord } from "./types.ts";

export type SelectCanonicalPmFinalReportOpts = {
  rootTaskId: string;
  taskId?: string;
  threadKey?: string;
};

export type SelectCanonicalPmFinalReportResult = {
  canonical: LedgerReportRecord | null;
  superseded: LedgerReportRecord[];
  autoFallback: LedgerReportRecord[];
};

function bareThreadKey(threadKey: string): string {
  const tk = String(threadKey ?? "").trim();
  const hash = tk.indexOf("#");
  return hash >= 0 ? tk.slice(0, hash).trim() : tk;
}

function reportBody(
  report: LedgerReportRecord,
  bodies?: Map<string, string>,
): string {
  if (!bodies) return "";
  return (
    bodies.get(report.path) ??
    bodies.get(report.report_id) ??
    bodies.get(report.filename) ??
    ""
  );
}

/** Runtime auto thin summary or ReportGate reciprocal — not canonical when manual exists. */
export function isAutoPmFinalSummaryReport(
  report: LedgerReportRecord,
  body?: string,
): boolean {
  if (report.auto_final_summary === true) return true;
  const raw = body ?? "";
  if (raw && isAutoGeneratedReciprocalReport(raw)) return true;
  if (report.report_type?.trim() === "final_summary") {
    if (
      /Runtime\s*总线自动汇总|Runtime\s*总线|close_admin_task\s*草稿生成/i.test(
        raw,
      )
    ) {
      return true;
    }
  }
  if (/close_admin_task\s*草稿生成/i.test(raw)) return true;
  return false;
}

function threadMatches(
  report: LedgerReportRecord,
  threadKey: string | undefined,
): boolean {
  const want = String(threadKey ?? "").trim();
  if (!want) return true;
  const got = String(report.thread_key ?? "").trim();
  if (!got) return true;
  return bareThreadKey(got) === bareThreadKey(want);
}

export function isPmFinalReportCandidate(
  report: LedgerReportRecord,
  opts: SelectCanonicalPmFinalReportOpts,
  body?: string,
): boolean {
  if (!isPmToAdminReport(report.sender, report.recipient)) return false;
  if (!reportBoundToAdminRoot(opts.rootTaskId, report)) return false;
  if (!threadMatches(report, opts.threadKey)) return false;
  if (report.report_kind === "pm_to_admin_in_progress") return false;
  if (isPmNonFinalAdminReportStatus(report.status)) return false;
  if (body && isAckOnlyReportBody(body)) return false;
  if (!isPmFinalSummaryTerminalStatus(report.status)) return false;
  if (/-reciprocal/i.test(report.filename ?? "")) return false;
  return true;
}

function candidateScore(
  report: LedgerReportRecord,
  body: string,
): { manual: number; sortKey: string } {
  const auto = isAutoPmFinalSummaryReport(report, body);
  return {
    manual: auto ? 0 : 1,
    sortKey: reportSortKey(report.filename),
  };
}

export function selectCanonicalPmFinalReport(
  reports: LedgerReportRecord[],
  opts: SelectCanonicalPmFinalReportOpts,
  reportBodies?: Map<string, string>,
): SelectCanonicalPmFinalReportResult {
  const rootId = taskIdPrefix(opts.rootTaskId);
  if (!rootId) {
    return { canonical: null, superseded: [], autoFallback: [] };
  }

  const candidates: Array<{ report: LedgerReportRecord; body: string }> = [];
  for (const r of reports) {
    const body = reportBody(r, reportBodies);
    if (isPmFinalReportCandidate(r, { ...opts, rootTaskId: rootId }, body)) {
      candidates.push({ report: r, body });
    }
  }

  if (!candidates.length) {
    return { canonical: null, superseded: [], autoFallback: [] };
  }

  const sorted = [...candidates].sort((a, b) => {
    const sa = candidateScore(a.report, a.body);
    const sb = candidateScore(b.report, b.body);
    if (sb.manual !== sa.manual) return sb.manual - sa.manual;
    return sb.sortKey.localeCompare(sa.sortKey);
  });

  const canonical = sorted[0]!.report;
  const superseded: LedgerReportRecord[] = [];
  const autoFallback: LedgerReportRecord[] = [];

  for (const { report, body } of sorted.slice(1)) {
    superseded.push(report);
    if (isAutoPmFinalSummaryReport(report, body)) {
      autoFallback.push(report);
    }
  }

  return { canonical, superseded, autoFallback };
}

/** Mark superseded auto summaries in ledger classify phase (in-memory only). */
export function applyCanonicalPmFinalReportKinds(
  reports: LedgerReportRecord[],
  tasks: import("./types.ts").LedgerTaskRecord[],
  reportBodies?: Map<string, string>,
): LedgerReportRecord[] {
  const rootIds = new Set<string>();
  for (const t of tasks) {
    if (t.sender?.toUpperCase() === "ADMIN" && t.recipient?.toUpperCase() === "PM") {
      rootIds.add(taskIdPrefix(t.task_id));
    }
  }
  for (const r of reports) {
    if (r.parent_task_id) rootIds.add(taskIdPrefix(r.parent_task_id));
  }

  const supersededAutoIds = new Set<string>();
  for (const rootId of rootIds) {
    const threadKey = tasks.find((t) => taskIdPrefix(t.task_id) === rootId)
      ?.thread_key;
    const { canonical, autoFallback, superseded } = selectCanonicalPmFinalReport(
      reports,
      { rootTaskId: rootId, threadKey },
      reportBodies,
    );
    if (!canonical) continue;
    for (const r of [...autoFallback, ...superseded]) {
      if (r.report_id === canonical.report_id) continue;
      const body = reportBody(r, reportBodies);
      if (isAutoPmFinalSummaryReport(r, body)) {
        supersededAutoIds.add(r.report_id);
      }
    }
  }

  if (!supersededAutoIds.size) return reports;

  return reports.map((r) =>
    supersededAutoIds.has(r.report_id)
      ? { ...r, report_kind: "auto_final_summary_fallback" as const }
      : r,
  );
}
