/**
 * Extract and load JSON/JSONL evidence paths cited in a REPORT body.
 * Paths must stay under projectRoot (no traversal). UTF-8 read only.
 */

import { existsSync, readFileSync, statSync } from "node:fs";
import { isAbsolute, join, normalize, relative, sep } from "node:path";

const CITED_PATH_RE =
  /(?:^|[\s`"'(（【])((?:[\w.-]+\/)+[\w./-]+\.(?:json|jsonl|csv))(?=$|[\s`"')）】,，;；])/gim;

export function extractCitedEvidencePaths(reportBody: string): string[] {
  const found = new Set<string>();
  const body = reportBody.replace(/\\/g, "/");
  for (const match of body.matchAll(CITED_PATH_RE)) {
    const p = String(match[1] ?? "")
      .trim()
      .replace(/\\/g, "/");
    if (p && !p.includes("..")) found.add(p);
  }
  return [...found].sort();
}

function resolveUnderProjectRoot(projectRoot: string, relPath: string): string | null {
  const root = normalize(projectRoot);
  const abs = normalize(
    isAbsolute(relPath) ? relPath : join(root, relPath.replace(/\//g, sep)),
  );
  const rel = relative(root, abs);
  if (!rel || rel.startsWith("..") || isAbsolute(rel)) return null;
  return abs;
}

export type CitedEvidenceFile = {
  path: string;
  ok: boolean;
  bytes?: number;
  parse_ok?: boolean;
  warning?: string;
};

/**
 * Read cited evidence files as UTF-8. Display encoding of the terminal is irrelevant.
 */
export function loadCitedEvidenceFiles(
  projectRoot: string,
  reportBody: string,
): CitedEvidenceFile[] {
  const out: CitedEvidenceFile[] = [];
  for (const rel of extractCitedEvidencePaths(reportBody)) {
    const abs = resolveUnderProjectRoot(projectRoot, rel);
    if (!abs) {
      out.push({ path: rel, ok: false, warning: "path_outside_project_root" });
      continue;
    }
    if (!existsSync(abs)) {
      out.push({ path: rel, ok: false, warning: "file_not_found" });
      continue;
    }
    try {
      const st = statSync(abs);
      if (!st.isFile()) {
        out.push({ path: rel, ok: false, warning: "not_a_file" });
        continue;
      }
      const raw = readFileSync(abs, { encoding: "utf8" });
      let parseOk = true;
      if (/\.jsonl$/i.test(rel)) {
        for (const line of raw.split(/\r?\n/)) {
          if (!line.trim()) continue;
          JSON.parse(line);
        }
      } else if (/\.json$/i.test(rel)) {
        JSON.parse(raw);
      }
      out.push({
        path: rel.replace(/\\/g, "/"),
        ok: true,
        bytes: st.size,
        parse_ok: parseOk,
      });
    } catch (err) {
      out.push({
        path: rel.replace(/\\/g, "/"),
        ok: false,
        warning: `read_or_parse_failed:${String(err)}`,
      });
    }
  }
  return out;
}
