/**
 * scripts/generate-docs/lib/skill-parser.ts
 *
 * Walks every `templates/skills/**\/SKILL.md`, parses the YAML frontmatter via
 * gray-matter, and returns a normalised `Skill[]` ready for Handlebars context
 * building. Slugs and phase buckets come from the shared
 * `templates/skills/lib/skill-slug.ts` helpers so that the doc generator
 * cannot drift from the installer.
 */
import { readFileSync } from 'node:fs';
import { join, relative, dirname, basename } from 'node:path';
import { sync as globSync } from 'glob';
import matter from 'gray-matter';
import {
  remapSkillPath,
  phaseFromAuthoringPath,
} from '../../../templates/skills/lib/skill-slug';

const SKILLS_ROOT = join(__dirname, '..', '..', '..', 'templates', 'skills');

export interface Skill {
  /** Slash-command slug (e.g. `ba-audit-actors`, `efcore`, `backend-controller`) */
  slug: string;
  /** Frontmatter `name:` field (canonical command name) */
  name: string;
  /** Frontmatter `description:` (flattened to a single string) */
  description: string;
  /** Phase bucket: `business-analyse`, `development/backend`, `lifecycle`, … */
  phase: string;
  /** Optional `argument-hint:` from frontmatter */
  argumentHint: string | null;
  /** Optional `allowed-tools:` list (mixed string or comma-separated YAML) */
  allowedTools: string[];
  /** Optional `model:` (e.g. `haiku`) */
  model: string | null;
  /** Optional `group:` (e.g. `H` for ba-develop) */
  group: string | null;
  /** Path to the source SKILL.md relative to repo root, for debugging */
  sourcePath: string;
  /** First non-empty body line, useful for hover tooltips */
  firstBodyLine: string;
}

/**
 * Flatten a YAML scalar (which gray-matter may yield as a multi-line string
 * with embedded newlines from `>` block scalars) into a single, trimmed line.
 */
function flatten(value: unknown): string {
  if (value == null) return '';
  if (typeof value !== 'string') return String(value);
  return value.replace(/\s+/g, ' ').trim();
}

/**
 * Coerce `allowed-tools` to a string array. YAML accepts both list and
 * comma-separated forms; we normalise to a single shape.
 */
function toToolList(value: unknown): string[] {
  if (!value) return [];
  if (Array.isArray(value)) return value.map((v) => String(v).trim()).filter(Boolean);
  return String(value)
    .split(',')
    .map((s) => s.trim())
    .filter(Boolean);
}

/**
 * Extract the first non-empty, non-heading line from the markdown body.
 * Used as a short summary on cards when the YAML description is unhelpfully
 * long.
 */
function firstBodyLine(body: string): string {
  for (const raw of body.split('\n')) {
    const line = raw.trim();
    if (!line) continue;
    if (line.startsWith('#')) continue; // skip headings
    if (line.startsWith('>')) continue; // skip blockquotes
    if (line.startsWith('---')) continue; // skip horizontal rules
    return line.length > 200 ? line.slice(0, 197) + '…' : line;
  }
  return '';
}

/**
 * Parse a single SKILL.md given its absolute path. Returns `null` for files
 * that the installer would not deploy (BA `_workflow/` companions, BA index).
 */
export function parseSkillFile(absPath: string): Skill | null {
  const rel = relative(SKILLS_ROOT, absPath).replace(/\\/g, '/');
  const remapped = remapSkillPath(rel);
  if (remapped === null) return null;

  const raw = readFileSync(absPath, 'utf-8');
  const parsed = matter(raw);
  const data = parsed.data ?? {};

  const name = flatten(data.name) || basename(dirname(rel));
  const description = flatten(data.description);
  const phase = typeof data.phase === 'string' && data.phase.trim()
    ? data.phase.trim()
    : phaseFromAuthoringPath(rel);

  // Derive slug: prefer the remapped folder name (matches what the installer
  // deploys); for nested skills (e.g. `development/backend/controller/`), use
  // the YAML `name:` since only the first folder is registered by Claude Code.
  const remappedFolder = dirname(remapped).split('/')[0];
  const slug = name || remappedFolder;

  return {
    slug,
    name,
    description,
    phase,
    argumentHint: flatten(data['argument-hint']) || null,
    allowedTools: toToolList(data['allowed-tools']),
    model: flatten(data.model) || null,
    group: flatten(data.group) || null,
    sourcePath: `templates/skills/${rel}`,
    firstBodyLine: firstBodyLine(parsed.content),
  };
}

/**
 * Walk the entire `templates/skills/` tree, parse every SKILL.md, drop the
 * ones the installer would not deploy, and return them sorted by slug for
 * deterministic output (the snapshot tests rely on this).
 */
export function parseAllSkills(): Skill[] {
  const pattern = join(SKILLS_ROOT, '**', 'SKILL.md').replace(/\\/g, '/');
  const files = globSync(pattern, { absolute: true });

  const skills: Skill[] = [];
  for (const file of files) {
    const parsed = parseSkillFile(file);
    if (parsed) skills.push(parsed);
  }
  skills.sort((a, b) => a.slug.localeCompare(b.slug));
  return skills;
}

/**
 * Group skills by their `phase` field, returning a deterministically-ordered
 * `Record<phase, Skill[]>`. Used by Handlebars helpers downstream.
 */
export function groupByPhase(skills: Skill[]): Record<string, Skill[]> {
  const out: Record<string, Skill[]> = {};
  for (const s of skills) {
    (out[s.phase] ??= []).push(s);
  }
  // Sort keys for stable rendering
  return Object.fromEntries(
    Object.entries(out).sort(([a], [b]) => a.localeCompare(b))
  );
}

// CLI entry: `npx tsx scripts/generate-docs/lib/skill-parser.ts`
// Prints a summary so we can verify the parser sees every expected skill.
if (require.main === module) {
  const skills = parseAllSkills();
  const groups = groupByPhase(skills);
  // eslint-disable-next-line no-console
  console.log(`Parsed ${skills.length} skills from templates/skills/`);
  for (const [phase, list] of Object.entries(groups)) {
    // eslint-disable-next-line no-console
    console.log(`  [${phase}] ${list.length}`);
    for (const s of list) {
      // eslint-disable-next-line no-console
      console.log(`    /${s.slug}  ${s.description.slice(0, 80)}`);
    }
  }
}
