export const STATUSES = ['design', 'in-progress', 'shipped', 'superseded', 'abandoned', 'unknown'];

const DATE_RE = /^(\d{4}-\d{2}-\d{2})-/;

export function docParts(relPath) {
  const archived = relPath.startsWith('archived/');
  const type = /(^|\/)specs\//.test(relPath) ? 'spec' : 'plan';
  const file = relPath.split('/').pop();
  const base = file.replace(/\.md$/, '');
  const m = base.match(DATE_RE);
  return { type, date: m ? m[1] : null, base, archived };
}

export function deriveTopicGuess(relPath) {
  const { type, base } = docParts(relPath);
  let s = base.replace(DATE_RE, '');
  // spec のみ、末尾がちょうど -design / -plan のときだけ除去（redesign を守る）
  if (type === 'spec') s = s.replace(/-(design|plan)$/, '');
  return s;
}

export function extractTitle(mdText) {
  const line = mdText.split('\n').find((l) => l.startsWith('# '));
  if (!line) return '';
  let t = line.slice(2).trim();
  t = t.replace(/\s*(—\s*設計書|実装計画|設計書)\s*$/, '').trim();
  return t;
}

export function hasSupersededBy(v) {
  return typeof v === 'string' && v.trim() !== '';
}

export function scaffoldEntryObject(relPath, mdText) {
  const { type, archived } = docParts(relPath);
  const topic = deriveTopicGuess(relPath);
  const title = extractTitle(mdText) || topic;
  return { path: relPath, title, type, topic, status: archived ? 'shipped' : 'unknown', pr: [], superseded_by: null };
}

export function findUnregistered(existingRelPaths, manifestPaths) {
  return existingRelPaths.filter((p) => !manifestPaths.has(p));
}

const REQUIRED = ['path', 'title', 'type', 'topic', 'status'];

export function validateManifest(entries, existingRelPaths) {
  const errors = [];
  const warnings = [];
  const manifestPaths = new Set(entries.map((e) => e.path));
  const existing = new Set(existingRelPaths);

  for (const e of entries) {
    for (const k of REQUIRED) {
      if (e[k] === undefined || e[k] === null || e[k] === '') errors.push(`${e.path ?? '(no path)'}: missing required field '${k}'`);
    }
    if (e.status && !STATUSES.includes(e.status)) errors.push(`${e.path}: invalid status '${e.status}'`);
    if (e.type && e.type !== 'spec' && e.type !== 'plan') errors.push(`${e.path}: invalid type '${e.type}'`);
    if (e.path && !existing.has(e.path)) errors.push(`${e.path}: manifest entry does not exist on disk (欠落)`);
    if (hasSupersededBy(e.superseded_by) && !manifestPaths.has(e.superseded_by)) errors.push(`${e.path}: superseded_by '${e.superseded_by}' not in manifest`);
    if (hasSupersededBy(e.superseded_by) && e.status !== 'superseded') warnings.push(`${e.path}: has superseded_by but status is '${e.status}' (expected superseded)`);
    if (e.status === 'superseded' && !hasSupersededBy(e.superseded_by)) warnings.push(`${e.path}: status superseded but superseded_by empty`);
  }
  for (const p of existingRelPaths) {
    if (!manifestPaths.has(p)) errors.push(`${p}: file exists but is not registered in manifest (孤児)`);
  }
  const unknownCount = entries.filter((e) => e.status === 'unknown').length;
  if (unknownCount > 0) warnings.push(`${unknownCount} entries still have status 'unknown' (curation pending)`);
  return { errors, warnings };
}

// ─── renderIndex and helpers ──────────────────────────────────────────────────

export const PR_BASE = 'https://gitea.example.com/your-org/maestro/pulls/';

export const BADGE = {
  design: '🔵',
  'in-progress': '🟡',
  shipped: '🟢',
  superseded: '⚪',
  abandoned: '⚫',
  unknown: '❔',
};

export function escapeCell(s) {
  return String(s ?? '')
    .replace(/\r?\n/g, ' ')
    .replace(/\|/g, '\\|')
    .replace(/\[/g, '［')
    .replace(/\]/g, '］');
}

export function prUrl(n) {
  return PR_BASE + n;
}

function row(e) {
  const { date } = docParts(e.path);
  const link = `[${escapeCell(e.title)}](./${e.path})`;
  const prs = (e.pr ?? []).map((n) => `[#${n}](${prUrl(n)})`).join(' ');
  const succ = hasSupersededBy(e.superseded_by) ? `[→](./${e.superseded_by})` : '';
  return `| ${BADGE[e.status] ?? '❔'} | ${e.type} | ${link} | ${date ?? ''} | ${prs} | ${succ} |`;
}

function groupSection(title, entries) {
  const byTopic = new Map();
  for (const e of entries) {
    const k = e.topic && e.topic.trim() ? e.topic : '(未分類)';
    if (!byTopic.has(k)) byTopic.set(k, []);
    byTopic.get(k).push(e);
  }
  const topics = [...byTopic.keys()].sort();
  let out = `\n## ${title}\n`;
  for (const t of topics) {
    const rows = byTopic
      .get(t)
      .slice()
      .sort((a, b) => {
        const d = (docParts(a.path).date ?? '').localeCompare(docParts(b.path).date ?? '');
        return d !== 0 ? d : a.path.localeCompare(b.path);
      });
    out += `\n### ${escapeCell(t)}\n\n| 段階 | 種別 | 資料 | 日付 | PR | 後継 |\n|---|---|---|---|---|---|\n`;
    out += rows.map(row).join('\n') + '\n';
  }
  return out;
}

export function renderIndex(entries) {
  const active = entries.filter((e) => !docParts(e.path).archived);
  const archived = entries.filter((e) => docParts(e.path).archived);
  const counts = STATUSES.map((s) => `${BADGE[s]} ${entries.filter((e) => e.status === s).length}`).join(' ・ ');
  let out = `# 設計資料インデックス\n\n> 生成物。編集は \`docs/superpowers/manifest.yaml\` を直し \`node scripts/gen-design-index.mjs\` で再生成。\n\n${counts}（active ${active.length} / archived ${archived.length}）\n`;
  out += groupSection('Active', active);
  out += groupSection('Archived', archived);
  return out;
}
