// src/lib/spec/spec-content.ts
// Server-only reader for the cloned specification.website content.
// The markdown lives committed under src/content/spec/<category>/<slug>.md
// (see scripts/generate-spec-content.ts). Each file is the upstream raw markdown:
// a leading `--- ... ---` YAML frontmatter block followed by the body.
//
// We parse only the shape we render (title, status, category, summary, sources,
// relatedSlugs) with a small hand-rolled parser — no heavy YAML dependency for
// this known, stable frontmatter.
import "server-only";

import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { CHECKLIST } from "./checklist-data";
import type { SpecCategoryId, SpecStatus } from "./types";

const CONTENT_DIR = join(process.cwd(), "src", "content", "spec");

export type SpecSource = {
  title: string;
  url: string;
  publisher?: string;
};

export type SpecFrontmatter = {
  title: string;
  slug?: string;
  category?: string;
  status?: SpecStatus;
  url?: string;
  summary?: string;
  sources: SpecSource[];
  relatedSlugs: string[];
};

export type SpecPage = {
  frontmatter: SpecFrontmatter;
  /** Markdown body, with the leading frontmatter block stripped. */
  body: string;
};

/** Strip surrounding single/double quotes from a scalar value. */
function unquote(raw: string): string {
  const s = raw.trim();
  if (
    (s.startsWith('"') && s.endsWith('"')) ||
    (s.startsWith("'") && s.endsWith("'"))
  ) {
    // Unescape \" inside double-quoted strings.
    return s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
  }
  return s;
}

/** Parse an inline list `[a, b, c]` into trimmed, unquoted entries. */
function parseInlineList(raw: string): string[] {
  const inner = raw.trim().replace(/^\[/, "").replace(/\]$/, "").trim();
  if (!inner) return [];
  return inner
    .split(",")
    .map((s) => unquote(s))
    .filter(Boolean);
}

/**
 * Split a raw file into `{ frontmatter, body }`. Parses the subset of YAML the
 * spec frontmatter uses: top-level scalars, an inline `relatedSlugs: [...]`
 * list, and a `sources:` block of `- title/url/publisher` entries.
 */
export function parseFrontmatter(raw: string): SpecPage {
  const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
  if (!match) {
    // No frontmatter — treat the whole file as body with a placeholder title.
    return {
      frontmatter: { title: "", sources: [], relatedSlugs: [] },
      body: raw.trim(),
    };
  }
  const [, fmBlock, body] = match;
  const lines = fmBlock.split(/\r?\n/);

  const fm: SpecFrontmatter = { title: "", sources: [], relatedSlugs: [] };

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    // Sources block: `sources:` then indented `- title: ...` entries.
    if (/^sources:\s*$/.test(line)) {
      let j = i + 1;
      let current: Partial<SpecSource> | null = null;
      while (j < lines.length && /^\s+/.test(lines[j])) {
        const entry = lines[j];
        const itemMatch = entry.match(/^\s*-\s*(\w+):\s*(.*)$/);
        if (itemMatch) {
          // New source entry starts.
          if (current?.title && current.url) {
            fm.sources.push(current as SpecSource);
          }
          current = {};
          const [, key, val] = itemMatch;
          (current as Record<string, string>)[key] = unquote(val);
        } else {
          const propMatch = entry.match(/^\s*(\w+):\s*(.*)$/);
          if (propMatch && current) {
            const [, key, val] = propMatch;
            (current as Record<string, string>)[key] = unquote(val);
          }
        }
        j++;
      }
      if (current?.title && current.url) {
        fm.sources.push(current as SpecSource);
      }
      i = j - 1;
      continue;
    }

    const kv = line.match(/^(\w+):\s*(.*)$/);
    if (!kv) continue;
    const [, key, valueRaw] = kv;
    const value = valueRaw.trim();
    switch (key) {
      case "title":
        fm.title = unquote(value);
        break;
      case "slug":
        fm.slug = unquote(value);
        break;
      case "category":
        fm.category = unquote(value);
        break;
      case "status":
        fm.status = unquote(value) as SpecStatus;
        break;
      case "url":
        fm.url = unquote(value);
        break;
      case "summary":
        fm.summary = unquote(value);
        break;
      case "relatedSlugs":
        if (value.startsWith("[")) fm.relatedSlugs = parseInlineList(value);
        break;
      default:
        break;
    }
  }

  return { frontmatter: fm, body: body.trim() };
}

/** Validate a `<category>/<slug>` pair against an injection-safe shape. */
function isSafeSegment(seg: string): boolean {
  return /^[a-z0-9-]+$/.test(seg);
}

/**
 * Read a single cloned spec page from disk. Returns `null` if the category/slug
 * doesn't map to a committed file. Server-only (reads the filesystem).
 */
export function getSpecPage(category: string, slug: string): SpecPage | null {
  if (!isSafeSegment(category) || !isSafeSegment(slug)) return null;
  const file = join(CONTENT_DIR, category, `${slug}.md`);
  if (!existsSync(file)) return null;
  const raw = readFileSync(file, "utf8");
  return parseFrontmatter(raw);
}

/** List every cloned page as `{ category, slug }`, derived from CHECKLIST. */
export function listSpecPages(): { category: SpecCategoryId; slug: string }[] {
  return CHECKLIST.flatMap((cat) =>
    cat.items.map((item) => ({
      category: cat.id,
      // id is `${category}/${slug}` — take the trailing segment.
      slug: item.id.slice(item.id.indexOf("/") + 1),
    })),
  );
}
