import { readdir, copyFile, mkdir, readFile, writeFile, mkdtemp, rename, rm } from "fs/promises";
import { join, dirname, basename } from "path";
import { fileURLToPath } from "node:url";
import { getProjectRoot, pathExists } from "../util/fs.js";
import { style, header, status, label } from "../util/theme.js";
const PACKS = [
    {
        id: "omk-priority",
        name: "OMK Top Priority",
        description: "Top 12 OMK skills for context, planning, quality gates, review, security, typing, and worktree teamwork",
        skills: [
            "omk-context-broker",
            "omk-repo-explorer",
            "omk-industrial-control-loop",
            "omk-plan-first",
            "omk-quality-gate",
            "omk-test-debug-loop",
            "omk-code-review",
            "omk-security-review",
            "omk-secret-guard",
            "omk-typescript-strict",
            "omk-python-typing",
            "omk-worktree-team",
        ],
    },
    {
        id: "omk-agentic-ops",
        name: "OMK Agentic Ops",
        description: "Custom skills for DAG orchestration review, evidence contracts, and control-loop debugging",
        skills: [
            "omk-adaptorch-orchestration-review",
            "omk-evidence-contract",
            "omk-control-loop-debugger",
        ],
    },
    {
        id: "omk-core",
        name: "OMK Core",
        description: "Essential OMK orchestration skills plus localhost design entrypoints",
        skills: [
            "open-design",
            "awesome-design-md",
            "omk-global-rules",
            "omk-project-rules",
            "omk-kimi-runtime",
            "omk-quality-gate",
            "omk-plan-first",
            "omk-task-router",
            "graph-view",
            "provider",
            "think",
            "deepseek-api",
            "deepseek-enable",
            "deepseek-disable",
            "deepseekset",
            "agentmemory",
            "andrej-karpathy-skills",
            "multica",
        ],
    },
    {
        id: "omk-spec-driven",
        name: "Spec-Driven",
        description: "spec-kit / specify workflow skills",
        skills: [
            "speckit-specify",
            "speckit-plan",
            "speckit-tasks",
            "speckit-implement",
            "speckit-checklist",
            "speckit-clarify",
            "speckit-analyze",
            "speckit-constitution",
            "speckit-taskstoissues",
        ],
    },
    {
        id: "omk-typescript",
        name: "TypeScript",
        description: "TypeScript strictness and design-to-code skills",
        skills: [
            "omk-design-md",
            "omk-flow-design-to-code",
            "matt-pocock-skills",
            "react-doctor",
        ],
    },
    {
        id: "omk-security",
        name: "Security",
        description: "Security review and audit skills",
        skills: [
            "omk-code-review",
            "omk-flow-bugfix",
        ],
    },
    {
        id: "omk-review",
        name: "Review",
        description: "Code review and PR review skills",
        skills: [
            "omk-code-review",
            "omk-multimodal-ui-review",
            "omk-flow-pr-review",
            "andrej-karpathy-skills",
            "react-doctor",
        ],
    },
    {
        id: "omk-release",
        name: "Release",
        description: "Release flow and team-run skills",
        skills: [
            "omk-flow-release",
            "omk-flow-team-run",
            "omk-flow-refactor",
            "omk-flow-feature-dev",
        ],
    },
];
const INSTALLED_PACKS_FILE = ".omk/installed-skill-packs.json";
const SLASH_COMMAND_SKILLS = new Set([
    "deepseek-api",
    "deepseek-disable",
    "deepseek-enable",
    "deepseekset",
    "graph-view",
    "provider",
    "think",
    "open-design",
    "awesome-design-md",
]);
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
async function getTemplatesDir() {
    const kimiSkills = join(packageRoot, "templates", "skills", "kimi");
    if (await pathExists(kimiSkills))
        return kimiSkills;
    return null;
}
async function getInstalledPacks(root) {
    const file = join(root, INSTALLED_PACKS_FILE);
    if (!(await pathExists(file)))
        return [];
    try {
        const data = JSON.parse(await readFile(file, "utf-8"));
        return Array.isArray(data.packs) ? data.packs : [];
    }
    catch {
        return [];
    }
}
async function listDirectoryNames(dir) {
    try {
        const entries = await readdir(dir, { withFileTypes: true });
        return new Set(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name));
    }
    catch {
        return new Set();
    }
}
export async function getSkillCatalog(root = getProjectRoot()) {
    const [installedPacks, templatesDir] = await Promise.all([getInstalledPacks(root), getTemplatesDir()]);
    const templateNames = templatesDir ? await listDirectoryNames(templatesDir) : new Set();
    const installedSkillNames = new Set([
        ...(await listDirectoryNames(join(root, ".kimi", "skills"))),
        ...(await listDirectoryNames(join(root, ".agents", "skills"))),
    ]);
    const packIdsBySkill = new Map();
    for (const pack of PACKS) {
        for (const skillName of pack.skills) {
            const packIds = packIdsBySkill.get(skillName) ?? [];
            packIds.push(pack.id);
            packIdsBySkill.set(skillName, packIds);
        }
    }
    const skillNames = [...new Set([...templateNames, ...packIdsBySkill.keys(), ...installedSkillNames])].sort();
    return {
        templateDir: templatesDir,
        packs: PACKS.map((pack) => ({
            id: pack.id,
            name: pack.name,
            description: pack.description,
            lifecycle: "active",
            installed: installedPacks.includes(pack.id),
            skills: [...pack.skills],
        })),
        skills: skillNames.map((skillName) => ({
            name: skillName,
            lifecycle: "active",
            slashCommand: SLASH_COMMAND_SKILLS.has(skillName),
            templateAvailable: templateNames.has(skillName),
            installed: installedSkillNames.has(skillName),
            packs: packIdsBySkill.get(skillName) ?? [],
        })),
    };
}
async function saveInstalledPacks(root, packs) {
    const file = join(root, INSTALLED_PACKS_FILE);
    await mkdir(dirname(file), { recursive: true });
    await writeJsonAtomic(file, { packs, updatedAt: new Date().toISOString() });
}
async function copySkillDir(src, dest) {
    const entries = await readdir(src, { withFileTypes: true });
    await mkdir(dest, { recursive: true });
    for (const entry of entries) {
        const srcPath = join(src, entry.name);
        const destPath = join(dest, entry.name);
        if (entry.isDirectory()) {
            await copySkillDir(srcPath, destPath);
        }
        else {
            await copyFile(srcPath, destPath);
        }
    }
}
async function copySkillDirAtomic(src, dest, expectedName) {
    await validateSkillTemplate(src, expectedName);
    await mkdir(dirname(dest), { recursive: true });
    const stage = await mkdtemp(join(dirname(dest), `.${basename(dest)}.tmp-`));
    try {
        await copySkillDir(src, stage);
        await validateSkillTemplate(stage, expectedName);
        await replaceDirectory(stage, dest);
    }
    catch (err) {
        await rm(stage, { recursive: true, force: true }).catch(() => undefined);
        throw err;
    }
}
async function replaceDirectory(stage, dest) {
    const backup = join(dirname(dest), `.${basename(dest)}.bak-${process.pid}-${Date.now()}`);
    const hadDest = await pathExists(dest);
    if (hadDest) {
        await rename(dest, backup);
    }
    try {
        await rename(stage, dest);
        if (hadDest) {
            await rm(backup, { recursive: true, force: true }).catch(() => undefined);
        }
    }
    catch (err) {
        if (hadDest && !(await pathExists(dest))) {
            await rename(backup, dest).catch(() => undefined);
        }
        throw err;
    }
}
async function validateSkillTemplate(dir, expectedName) {
    const skillPath = join(dir, "SKILL.md");
    let content;
    try {
        content = await readFile(skillPath, "utf-8");
    }
    catch {
        throw new Error(`Skill template "${expectedName}" is missing SKILL.md`);
    }
    if (!SLASH_COMMAND_SKILLS.has(expectedName)) {
        return;
    }
    const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
    const name = frontmatter?.[1]?.match(/^name:\s*([^\r\n]+)$/m)?.[1]?.trim();
    if (name !== expectedName) {
        throw new Error(`Skill template "${expectedName}" has invalid frontmatter name "${name ?? "<missing>"}"`);
    }
    if (!new RegExp(`^#\\s+/${escapeRegExp(expectedName)}\\b`, "m").test(content)) {
        throw new Error(`Slash command template "${expectedName}" must include heading "# /${expectedName}"`);
    }
}
async function writeJsonAtomic(path, payload) {
    const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
    await writeFile(tmp, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
    await rename(tmp, path);
}
function escapeRegExp(value) {
    return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
export async function skillPackCommand() {
    const root = await getProjectRoot();
    const installed = await getInstalledPacks(root);
    const templatesDir = await getTemplatesDir();
    console.log(header("Skill Packs"));
    console.log("");
    for (const pack of PACKS) {
        const isInstalled = installed.includes(pack.id);
        const marker = isInstalled ? style.mint("✓") : style.gray("○");
        const title = isInstalled ? style.mintBold(pack.name) : style.cream(pack.name);
        console.log(`${marker} ${title} ${style.gray(`(${pack.id})`)}`);
        console.log(`  ${style.gray(pack.description)}`);
        console.log(`  ${style.gray("Skills:")} ${pack.skills.join(", ")}`);
        console.log("");
    }
    if (!templatesDir) {
        console.log(status.warn("Template directory not found. Is OMK installed globally?"));
    }
}
export async function skillCatalogCommand(options = {}) {
    const catalog = await getSkillCatalog();
    const missingTemplates = catalog.skills.filter((skill) => skill.packs.length > 0 && !skill.templateAvailable);
    const output = {
        ok: missingTemplates.length === 0,
        command: "skill catalog",
        checkedAt: new Date().toISOString(),
        data: catalog,
        warnings: missingTemplates.map((skill) => ({
            name: skill.name,
            message: `Skill is referenced by ${skill.packs.join(", ")} but template is missing`,
        })),
        errors: [],
    };
    if (options.json) {
        console.log(JSON.stringify(output, null, 2));
        return;
    }
    console.log(header("Skill Catalog"));
    console.log("");
    console.log(label("Packs", String(catalog.packs.length)));
    console.log(label("Skills", String(catalog.skills.length)));
    console.log(label("Templates", catalog.templateDir ?? "not found"));
    console.log("");
    for (const pack of catalog.packs) {
        const marker = pack.installed ? style.mint("✓") : style.gray("○");
        console.log(`${marker} ${style.cream(pack.id)} ${style.gray(`[${pack.lifecycle}]`)} — ${pack.skills.length} skills`);
    }
    if (output.warnings.length > 0) {
        console.log("");
        for (const warning of output.warnings) {
            console.log(status.warn(`${warning.name}: ${warning.message}`));
        }
    }
}
export async function skillInstallCommand(packId) {
    const root = await getProjectRoot();
    const pack = PACKS.find((p) => p.id === packId);
    if (!pack) {
        console.error(status.error(`Unknown pack "${packId}".`));
        console.error(style.gray(`  Run "omk skill pack" to see available packs.`));
        process.exit(1);
    }
    const templatesDir = await getTemplatesDir();
    if (!templatesDir) {
        console.error(status.error("Skill templates not found."));
        process.exit(1);
    }
    const destDir = join(root, ".kimi", "skills");
    let copied = 0;
    let skipped = 0;
    for (const skill of pack.skills) {
        const src = join(templatesDir, skill);
        const dest = join(destDir, skill);
        if (await pathExists(src)) {
            await copySkillDirAtomic(src, dest, skill);
            copied++;
        }
        else {
            skipped++;
            console.log(status.warn(`Skill template not found: ${skill}`));
        }
    }
    const installed = await getInstalledPacks(root);
    if (!installed.includes(packId)) {
        installed.push(packId);
        await saveInstalledPacks(root, installed);
    }
    console.log(status.success(`Pack "${pack.name}" installed.`));
    console.log(label("Copied", String(copied)));
    if (skipped > 0)
        console.log(label("Skipped", String(skipped)));
}
export async function skillSyncCommand() {
    const root = await getProjectRoot();
    const installed = await getInstalledPacks(root);
    if (installed.length === 0) {
        console.log(status.warn("No packs installed. Run 'omk skill install <pack>' first."));
        return;
    }
    for (const packId of installed) {
        console.log(style.gray(`Syncing ${packId}...`));
        await skillInstallCommand(packId);
    }
    console.log(status.success("All packs synced."));
}
