import fs from "node:fs";
import path from "node:path";
import { findUserSkill, listUserSkills, type UserSkillEntry } from "@/lib/skills/registry";

/**
 * File-based skill registry, modelled on nexu-io/open-design's daemon layout
 * (see `apps/daemon/src/skills.ts`). Each template is a folder:
 *
 *   src/lib/templates/skills/<id>/
 *     SKILL.md      — frontmatter (id, scenario, tags, …) + prompt body
 *     example.md    — (optional) sample input content
 *     example.html  — (optional) pre-rendered preview, shown inline in the picker
 *
 * Adding a template = adding a folder. No TS code change required; the API
 * routes rescan disk and the client refetches `/api/templates`.
 *
 * Skills installed via the marketplace ({@link listUserSkills}) live under
 * `~/.html-anything/skills/` and are merged into the same registry with
 * namespaced ids (`pkg-<owner>-<repo>--<originalId>`).
 */

const SKILLS_DIR = path.join(process.cwd(), "src/lib/templates/skills");

export type SkillFrontmatter = {
  name?: string;
  zh_name?: string;
  en_name?: string;
  emoji?: string;
  description?: string;
  category?: string;
  scenario?: string;
  aspect_hint?: string;
  featured?: number;
  recommended?: number;
  tags?: string[];
  // sample / preview metadata — only present when example.{md,html} ships alongside
  example_id?: string;
  example_name?: string;
  example_format?: string;
  example_tagline?: string;
  example_desc?: string;
  example_source_url?: string;
  example_source_label?: string;
};

export type SkillExampleMeta = {
  id: string;
  name: string;
  format: string;
  tagline: string;
  desc: string;
  source?: { url: string; label: string };
  /** true iff `example.html` exists on disk */
  hasHtml: boolean;
  /** true iff `example.md` exists on disk */
  hasMd: boolean;
};

/**
 * Skill metadata used by the picker. Body is excluded so we can return the
 * entire registry in one cheap fetch without shipping prompt text to the
 * browser.
 */
export type SkillMeta = {
  id: string;
  zhName: string;
  enName: string;
  emoji: string;
  description: string;
  category: string;
  scenario: string;
  aspectHint: string;
  featured?: number;
  /**
   * If set, the skill is included in the "推荐 / Featured" group at the top of
   * the picker. Lower number = higher rank in that group.
   */
  recommended?: number;
  tags: string[];
  example?: SkillExampleMeta;
};

export type LoadedSkill = SkillMeta & {
  body: string;
  exampleMd?: string;
  exampleHtml?: string;
};

// ─── frontmatter parser ──────────────────────────────────────────────
// Tiny, dependency-free. Handles the flat schema written by
// `scripts/migrate-skills.mts`: strings (optionally quoted), integers, and
// one-line array literals (`tags: ["a", "b"]`).

function parseFrontmatter(raw: string): { fm: SkillFrontmatter; body: string } {
  const m = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n?([\s\S]*)$/m.exec(raw);
  if (!m) return { fm: {}, body: raw };
  const block = m[1];
  const body = m[2] ?? "";
  const fm: SkillFrontmatter = {};
  for (const line of block.split(/\r?\n/)) {
    const mm = /^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.*)$/.exec(line);
    if (!mm) continue;
    const key = mm[1];
    let val: string = mm[2].trim();
    if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
      val = val.slice(1, -1).replace(/\\"/g, '"');
    }
    switch (key) {
      case "featured": {
        const n = Number(val);
        if (Number.isFinite(n)) fm.featured = n;
        break;
      }
      case "recommended": {
        const n = Number(val);
        if (Number.isFinite(n)) fm.recommended = n;
        break;
      }
      case "tags": {
        const arr = /^\[(.*)\]$/.exec(val);
        if (arr) {
          fm.tags = arr[1]
            .split(",")
            .map((s) => s.trim().replace(/^["']|["']$/g, ""))
            .map((s) => s.replace(/\\"/g, '"'))
            .filter(Boolean);
        }
        break;
      }
      case "name":
      case "zh_name":
      case "en_name":
      case "emoji":
      case "description":
      case "category":
      case "scenario":
      case "aspect_hint":
      case "example_id":
      case "example_name":
      case "example_format":
      case "example_tagline":
      case "example_desc":
      case "example_source_url":
      case "example_source_label":
        (fm as Record<string, string>)[key] = val;
        break;
    }
  }
  return { fm, body: body.trim() };
}

function safeRead(p: string): string | undefined {
  try {
    return fs.readFileSync(p, "utf8");
  } catch {
    return undefined;
  }
}

function fmToMeta(id: string, fm: SkillFrontmatter, hasHtml: boolean, hasMd: boolean): SkillMeta {
  const meta: SkillMeta = {
    id,
    zhName: fm.zh_name ?? fm.name ?? id,
    enName: fm.en_name ?? id,
    emoji: fm.emoji ?? "✨",
    description: fm.description ?? "",
    category: fm.category ?? "other",
    scenario: fm.scenario ?? "marketing",
    aspectHint: fm.aspect_hint ?? "",
    tags: Array.isArray(fm.tags) ? fm.tags : [],
  };
  if (typeof fm.featured === "number") meta.featured = fm.featured;
  if (typeof fm.recommended === "number") meta.recommended = fm.recommended;
  if (fm.example_id || hasMd || hasHtml) {
    meta.example = {
      id: fm.example_id ?? `example-${id}`,
      name: fm.example_name ?? `${meta.zhName} 示例`,
      format: fm.example_format ?? "markdown",
      tagline: fm.example_tagline ?? "",
      desc: fm.example_desc ?? "",
      hasHtml,
      hasMd,
      ...(fm.example_source_url
        ? {
            source: {
              url: fm.example_source_url,
              label: fm.example_source_label ?? fm.example_source_url,
            },
          }
        : {}),
    };
  }
  return meta;
}

// ─── public surface ──────────────────────────────────────────────────

// Cache the metadata listing in production; bypass in dev so new skill
// folders show up without restarting `next dev`.
let metaCache: SkillMeta[] | null = null;
const isDev = process.env.NODE_ENV !== "production";

/** Drop the cached metadata listing. Call after install / uninstall. */
export function invalidateSkillsCache(): void {
  metaCache = null;
}

function isValidBundledId(id: string): boolean {
  return /^[a-z0-9][a-z0-9-]*$/i.test(id);
}

function isValidSkillId(id: string): boolean {
  // Bundled ids stay strictly kebab-case. Marketplace ids are namespaced as
  // `pkg-<owner>__<repo>--<originalId>` — `__` shows up inside the package id
  // segment, so we allow it (plus `.` for tag-like refs) before the `--`.
  return (
    isValidBundledId(id) ||
    /^pkg-[a-z0-9][a-z0-9._-]*__[a-z0-9][a-z0-9._-]*--[a-z0-9][a-z0-9._-]*$/i.test(id)
  );
}

function loadSkillFromDir(id: string, dir: string): LoadedSkill | null {
  const raw = safeRead(path.join(dir, "SKILL.md"));
  if (!raw) return null;
  const { fm, body } = parseFrontmatter(raw);
  const exampleMd = safeRead(path.join(dir, "example.md"));
  const exampleHtml = safeRead(path.join(dir, "example.html"));
  const meta = fmToMeta(id, fm, !!exampleHtml, !!exampleMd);
  return { ...meta, body, exampleMd, exampleHtml };
}

function metaFromDir(id: string, dir: string): SkillMeta | null {
  const raw = safeRead(path.join(dir, "SKILL.md"));
  if (!raw) return null;
  const { fm } = parseFrontmatter(raw);
  const hasHtml = fs.existsSync(path.join(dir, "example.html"));
  const hasMd = fs.existsSync(path.join(dir, "example.md"));
  return fmToMeta(id, fm, hasHtml, hasMd);
}

/** Return picker-ready metadata for every bundled + user-installed skill. */
export function listSkills(): SkillMeta[] {
  if (!isDev && metaCache) return metaCache;
  const out: SkillMeta[] = [];
  let dirents: fs.Dirent[] = [];
  try {
    dirents = fs.readdirSync(SKILLS_DIR, { withFileTypes: true });
  } catch {
    dirents = [];
  }
  for (const ent of dirents) {
    if (!ent.isDirectory()) continue;
    const id = ent.name;
    if (!isValidBundledId(id)) continue;
    const meta = metaFromDir(id, path.join(SKILLS_DIR, id));
    if (meta) out.push(meta);
  }
  let userSkills: UserSkillEntry[] = [];
  try {
    userSkills = listUserSkills();
  } catch {
    userSkills = [];
  }
  for (const entry of userSkills) {
    const meta = metaFromDir(entry.id, entry.dir);
    if (meta) out.push(meta);
  }
  metaCache = out;
  return out;
}

/** Load one skill including its prompt body and example contents. */
export function loadSkill(id: string): LoadedSkill | null {
  if (!isValidSkillId(id)) return null;
  if (id.includes("--")) {
    const entry = findUserSkill(id);
    if (!entry) return null;
    return loadSkillFromDir(id, entry.dir);
  }
  return loadSkillFromDir(id, path.join(SKILLS_DIR, id));
}

/** Lightweight check for the picker — true iff `example.html` exists. */
export function skillHasPreview(id: string): boolean {
  if (!isValidSkillId(id)) return false;
  if (id.includes("--")) {
    const entry = findUserSkill(id);
    if (!entry) return false;
    return fs.existsSync(path.join(entry.dir, "example.html"));
  }
  return fs.existsSync(path.join(SKILLS_DIR, id, "example.html"));
}
