import { existsSync, readFileSync, readdirSync } from "node:fs";
import { join, resolve } from "node:path";
import { formatId, parsePrd } from "./parse.js";
import type { PrdCollection, PrdDocument, PrdStatus } from "./types.js";

const TEMPLATE_FILE = "0000-template.md";
const INDEX_FILE = "README.md";

export class PrdCollectionError extends Error {
  readonly code = "OP-L-COLLECTION";
  constructor(message: string) {
    super(message);
    this.name = "PrdCollectionError";
  }
}

/** Load every `NNNN-*.md` in a `prd/` directory, plus the template and index. */
export function loadPrdCollection(dir: string): PrdCollection {
  const base = resolve(dir);
  if (!existsSync(base)) {
    throw new PrdCollectionError(`No PRD collection at ${base} — run \`logicsrc prd init\` first`);
  }

  const files = readdirSync(base)
    .filter((file) => file.endsWith(".md") && file !== INDEX_FILE)
    .sort();

  const documents: PrdDocument[] = [];
  const unparsed: PrdCollection["unparsed"] = [];
  let template: PrdDocument | null = null;

  for (const file of files) {
    const path = join(base, file);
    try {
      const doc = parsePrd(readFileSync(path, "utf8"), path);
      if (file === TEMPLATE_FILE) template = doc;
      else documents.push(doc);
    } catch (error) {
      unparsed.push({ file, reason: (error as Error).message });
    }
  }

  documents.sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0));

  const indexPath = join(base, INDEX_FILE);
  return {
    dir: base,
    template,
    documents,
    unparsed,
    indexRaw: existsSync(indexPath) ? readFileSync(indexPath, "utf8") : null
  };
}

/** The next free number: highest existing + 1, never reserved in advance. */
export function nextPrdNumber(collection: PrdCollection): string {
  const highest = collection.documents.reduce((max, doc) => {
    const n = Number.parseInt(doc.filePrefix ?? "", 10);
    return Number.isInteger(n) ? Math.max(max, n) : max;
  }, 0);
  return formatId(highest + 1);
}

export function findPrd(collection: PrdCollection, ref: string): PrdDocument | undefined {
  const normalized = /^\d+$/.test(ref) ? formatId(Number.parseInt(ref, 10)) : ref;
  return collection.documents.find(
    (doc) =>
      doc.frontMatter.id === normalized ||
      doc.filePrefix === normalized ||
      doc.file === ref ||
      doc.slug === ref
  );
}

export interface PrdSummary {
  id: string;
  title: string;
  status: PrdStatus | string;
  file: string;
  authors: string;
  tags: string;
  requirements: number;
  updated: string;
}

export function summarize(doc: PrdDocument): PrdSummary {
  const fm = doc.frontMatter;
  return {
    id: fm.id ?? doc.filePrefix ?? "????",
    title: fm.title ?? "(untitled)",
    status: fm.status ?? "(none)",
    file: doc.file,
    authors: (fm.authors ?? []).join(", "),
    tags: (fm.tags ?? []).join(", "),
    requirements: doc.requirements.length,
    updated: fm.updated ?? fm.created ?? ""
  };
}

/**
 * Render the `prd/README.md` index the standard calls for. Deterministic, so
 * `prd index` is idempotent and CI can diff it.
 */
export function renderIndex(collection: PrdCollection, options: { title?: string } = {}): string {
  const rows = collection.documents.map(summarize);
  const lines = [
    `# ${options.title ?? "LogicSRC PRDs"}`,
    "",
    "Numbered [OpenPRD](../docs/openprd.md) product requirements documents for this repo. One file",
    "per PRD at `prd/<id>-<slug>.md`, four-digit ids, no gaps. `0000-template.md` is the copy-paste",
    "starting point.",
    "",
    "Status lives in each file's front-matter and is the source of truth:",
    "`Draft → Review → Accepted → Final`, or `Rejected` / `Withdrawn` / `Superseded`.",
    "",
    "<!-- generated by `logicsrc prd index --write`; edit the PRDs, not this table -->",
    "",
    "| ID | Title | Status | Tags |",
    "| --- | --- | --- | --- |"
  ];

  if (rows.length === 0) {
    lines.push("| — | _No PRDs yet. Run `logicsrc prd new \"Title\"`._ | — | — |");
  }

  for (const row of rows) {
    const escape = (value: string) => value.replace(/\|/g, "\\|");
    lines.push(
      `| [${row.id}](./${row.file}) | ${escape(row.title)} | ${row.status} | ${escape(row.tags)} |`
    );
  }

  return `${lines.join("\n")}\n`;
}

export { TEMPLATE_FILE, INDEX_FILE };
