/**
 * Check history per thread.
 *
 * Each thread (keyed by file:line|text-prefix) has an array of CachedCheck
 * entries. Newest first. Capped at MAX_HISTORY_PER_THREAD.
 *
 * TODO(perf): if this grows large (>1000 threads × 10 entries × ~2KB each =
 * ~20MB JSON), switch to per-thread files in a check_history/ directory or
 * to SQLite. For now a single JSON works fine — empirically Pavel has ~15
 * active threads.
 */
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { NOW_CHECK_HISTORY_FILE, NOW_CHECK_CACHE_FILE_LEGACY } from "./paths";

export type CheckVerdict = "CLOSE" | "KEEP" | "ESCALATE";

export type CachedCheck = {
  checkedAt: string; // ISO
  verdict: CheckVerdict | null;
  rationale: string;
  evidence: string[];
  suggested: string;
  rawText: string;
  sourcesUsed?: string[];
};

const MAX_HISTORY_PER_THREAD = 10;

export function itemKey(file: string, line: number, itemText: string): string {
  return `${file}:${line}|${itemText.slice(0, 80)}`;
}

/**
 * Load full history map. Migrates the legacy single-entry cache on first
 * read so existing verdicts aren't lost.
 */
export function loadCheckHistory(): Record<string, CachedCheck[]> {
  if (existsSync(NOW_CHECK_HISTORY_FILE)) {
    try {
      const data = JSON.parse(readFileSync(NOW_CHECK_HISTORY_FILE, "utf8"));
      // Each value should be an array; tolerate single-entry legacy migration left over.
      const out: Record<string, CachedCheck[]> = {};
      for (const [k, v] of Object.entries(data || {})) {
        if (Array.isArray(v)) out[k] = v as CachedCheck[];
        else if (v && typeof v === "object") out[k] = [v as CachedCheck];
      }
      return out;
    } catch { /* fall through to migration */ }
  }
  // Legacy migration: read single-entry cache once, convert to history.
  if (existsSync(NOW_CHECK_CACHE_FILE_LEGACY)) {
    try {
      const data = JSON.parse(readFileSync(NOW_CHECK_CACHE_FILE_LEGACY, "utf8"));
      const out: Record<string, CachedCheck[]> = {};
      for (const [k, v] of Object.entries(data || {})) {
        if (v && typeof v === "object") out[k] = [v as CachedCheck];
      }
      // Persist as history so the legacy file becomes irrelevant going forward.
      try { writeFileSync(NOW_CHECK_HISTORY_FILE, JSON.stringify(out, null, 2)); } catch {}
      return out;
    } catch {}
  }
  return {};
}

export function saveCheckHistory(history: Record<string, CachedCheck[]>) {
  writeFileSync(NOW_CHECK_HISTORY_FILE, JSON.stringify(history, null, 2));
}

/** Append a new entry; keeps newest first; trims to cap. Returns the new array. */
export function appendCheck(
  history: Record<string, CachedCheck[]>,
  key: string,
  entry: CachedCheck,
): CachedCheck[] {
  const arr = history[key] ? [entry, ...history[key]] : [entry];
  if (arr.length > MAX_HISTORY_PER_THREAD) arr.length = MAX_HISTORY_PER_THREAD;
  history[key] = arr;
  return arr;
}

/** Latest verdict for a thread, or undefined. */
export function latestCheck(history: Record<string, CachedCheck[]>, key: string): CachedCheck | undefined {
  return history[key]?.[0];
}

/** True if there's no entry, or the latest is older than maxAge. */
export function isStale(latest: CachedCheck | undefined, maxAgeMs: number = 6 * 3600 * 1000): boolean {
  if (!latest) return true;
  const checkedMs = Date.parse(latest.checkedAt);
  if (isNaN(checkedMs)) return true;
  return Date.now() - checkedMs > maxAgeMs;
}

/** Parse the agent's markdown response into structured fields. */
export function parseVerdict(rawText: string): Pick<CachedCheck, "verdict" | "rationale" | "evidence" | "suggested" | "rawText"> {
  const verdictMatch = rawText.match(/##\s*VERDICT:\s*(CLOSE|KEEP|ESCALATE)/i);
  const verdict: CheckVerdict | null = verdictMatch ? (verdictMatch[1].toUpperCase() as CheckVerdict) : null;

  const rationaleMatch = rawText.match(/##\s*VERDICT:[^\n]*\n+([\s\S]*?)(?=\n\*\*Evidence|\n##|$)/i);
  const rationale = rationaleMatch ? rationaleMatch[1].trim() : "";

  const evidenceBlock = rawText.match(/\*\*Evidence:?\*\*\s*\n([\s\S]*?)(?=\n\*\*Suggested|\n##|$)/i);
  const evidence = evidenceBlock
    ? evidenceBlock[1]
        .split("\n")
        .map((l) => l.trim())
        .filter((l) => l.startsWith("-") || l.startsWith("*"))
        .map((l) => l.replace(/^[-*]\s*/, ""))
    : [];

  const suggMatch = rawText.match(/\*\*Suggested(?:\s+resolution)?:?\*\*\s*\n?([\s\S]*?)(?=\n##|\n\*\*|$)/i);
  const suggested = suggMatch ? suggMatch[1].trim() : "";

  return { verdict, rationale, evidence, suggested, rawText };
}
