#!/usr/bin/env node
import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

const ROOT = dirname(fileURLToPath(import.meta.url));
const CELLS_DIR = join(ROOT, 'cells');
const README_PATH = join(ROOT, 'README.md');

function stripQuotes(value) {
  return value.replace(/^["']|["']$/g, '');
}

function parseInlineArray(value) {
  const trimmed = value.trim();
  if (!trimmed.startsWith('[') || !trimmed.endsWith(']')) return stripQuotes(trimmed);
  const inner = trimmed.slice(1, -1).trim();
  if (!inner) return [];
  return inner
    .split(',')
    .map((part) => stripQuotes(part.trim()))
    .filter(Boolean);
}

function parseFrontmatter(markdown, fileName) {
  if (!markdown.startsWith('---\n')) {
    throw new Error(`${fileName} is missing YAML frontmatter`);
  }

  const end = markdown.indexOf('\n---\n', 4);
  if (end === -1) {
    throw new Error(`${fileName} has unterminated YAML frontmatter`);
  }

  const yaml = markdown.slice(4, end).split('\n');
  const data = {};
  let currentKey = null;

  for (const line of yaml) {
    if (!line.trim()) continue;

    const listItem = line.match(/^\s+-\s+(.+)$/);
    if (listItem && currentKey) {
      if (!Array.isArray(data[currentKey])) data[currentKey] = [];
      data[currentKey].push(stripQuotes(listItem[1].trim()));
      continue;
    }

    const pair = line.match(/^([a-zA-Z0-9_]+):\s*(.*)$/);
    if (!pair) continue;

    currentKey = pair[1];
    const rawValue = pair[2].trim();
    data[currentKey] = rawValue ? parseInlineArray(rawValue) : [];
  }

  return data;
}

function formatList(items) {
  if (!Array.isArray(items) || items.length === 0) return '-';
  return items.map((item) => `\`${item}\``).join('<br>');
}

const cells = readdirSync(CELLS_DIR)
  .filter((file) => file.endsWith('.md'))
  .sort()
  .map((file) => {
    const body = readFileSync(join(CELLS_DIR, file), 'utf8');
    return { file, meta: parseFrontmatter(body, file) };
  });

for (const { file, meta } of cells) {
  for (const key of [
    'cell_id',
    'title',
    'summary',
    'canonical_features',
    'code_anchors',
    'doc_anchors',
    'static_scan_hints',
    'cited_by',
  ]) {
    if (!(key in meta)) throw new Error(`${file} is missing frontmatter field: ${key}`);
  }
}

const rows = cells
  .map(({ file, meta }) => {
    const cellId = String(meta.cell_id);
    const title = String(meta.title);
    const summary = String(meta.summary);
    const features = Array.isArray(meta.canonical_features)
      ? meta.canonical_features.map((feature) => `\`${feature}\``).join(', ')
      : String(meta.canonical_features);
    const anchors = formatList(meta.code_anchors);
    return `| [\`${cellId}\`](cells/${file}) | ${title} | ${summary} | ${features} | ${anchors} |`;
  })
  .join('\n');

const readme = `<!-- GENERATED by ./generate-readme.mjs. Edit cells/*.md frontmatter, then run: node docs/architecture/ownership/generate-readme.mjs -->

# Architecture Ownership Map

本目录是 Cat Cafe 架构归属路由索引。它与 \`docs/architecture/2026-05-05-architecture-views.md\` 互补：architecture views 是叙事性架构快照；ownership cells 用来判断新 Feature 落在哪条架构线，以及本次是否改变边界。

Feature spec 和 implementation plan 应引用：

\`\`\`markdown
Architecture cell: {cell_id}
Map delta: none | update required | new cell required
Why: 一句话
\`\`\`

普通增量只引用已有 cell，不改 map。只有 Feature 改变归属、边界、扩展点或 canonical anchors 时才更新 cell。找不到 cell = Phase 0 架构发现还没做完。

## Cells

| Cell | Title | Summary | Canonical Features | Primary Code Anchors |
|------|-------|---------|--------------------|----------------------|
${rows}

## Generated Source

本 README 由 \`cells/*.md\` frontmatter 生成，不是真相源。不要手改上方表格。
`;

writeFileSync(README_PATH, readme);
