// packages/plugin-awf/src/commands/registry.ts
// P1+P2a: Skill Registry + Upgrade Engine CLI commands
// No hardcoded values — all config from constants.ts and YAML manifests
import pc from "picocolors";
import * as prompts from "@clack/prompts";
import { existsSync, readFileSync, readdirSync, mkdirSync, copyFileSync, writeFileSync } from "node:fs";
import { join, basename } from "node:path";
import { AWF_VERSION, AGENT_DIR } from "../constants.js";
// ── Constants (dynamic, not hardcoded) ─────────────────────
/** Upgrades directory name within .agent/ */
const UPGRADES_DIR = "upgrades";
/** Backup suffix pattern */
const BACKUP_SUFFIX = ".bak";
/** YAML extensions to scan */
const YAML_EXTS = [".yaml", ".yml"];
// ── Flags ──────────────────────────────────────────────────
export const registryFlags = {
    action: {
        type: "string",
        description: "Sub-action: list, check, upgrade, rollback",
        alias: "a",
        choices: ["list", "check", "upgrade", "rollback"],
        default: "list",
    },
    "dry-run": {
        type: "boolean",
        description: "Preview changes without applying",
        alias: "d",
        default: false,
    },
    verbose: {
        type: "boolean",
        description: "Show detailed output",
        alias: "v",
        default: false,
    },
};
// ── Main Command ───────────────────────────────────────────
export async function registryCommand(args, flags) {
    const action = (args[0] ?? flags.action ?? "list");
    const dryRun = flags["dry-run"];
    const verbose = flags.verbose;
    const projectRoot = process.cwd();
    switch (action) {
        case "list":
            return listSkills(projectRoot, verbose);
        case "check":
            return checkUpgrades(projectRoot);
        case "upgrade":
            return applyUpgrade(projectRoot, dryRun, verbose);
        case "rollback":
            return rollbackUpgrade(projectRoot);
        default:
            console.log(pc.yellow(`Unknown action: ${action}`));
            console.log(`  Available: ${pc.cyan("list")}, ${pc.cyan("check")}, ${pc.cyan("upgrade")}, ${pc.cyan("rollback")}`);
    }
}
// ── List Skills ────────────────────────────────────────────
async function listSkills(projectRoot, verbose) {
    const skillsDir = join(projectRoot, AGENT_DIR, "skills");
    if (!existsSync(skillsDir)) {
        console.log(pc.yellow("No skills directory found. Run: nock awf init"));
        return;
    }
    console.log(pc.bold("\n📦 DOMYH Skill Registry\n"));
    console.log(`  AWF Version: ${pc.cyan(AWF_VERSION)}`);
    console.log(`  Skills Dir:  ${pc.dim(skillsDir)}\n`);
    const categories = readdirSync(skillsDir, { withFileTypes: true })
        .filter(d => d.isDirectory())
        .map(d => d.name);
    let totalSkills = 0;
    for (const category of categories) {
        const catDir = join(skillsDir, category);
        const skills = readdirSync(catDir, { withFileTypes: true })
            .filter(d => d.isDirectory());
        if (skills.length === 0)
            continue;
        console.log(`  ${pc.bold(category)} (${skills.length})`);
        for (const skill of skills) {
            const meta = readMeta(join(catDir, skill.name));
            const vLabel = meta?.version ? pc.dim(`v${meta.version}`) : pc.dim("v-");
            const lcLabel = meta?.lifecycle === "deprecated"
                ? pc.red("deprecated")
                : meta?.lifecycle === "experimental"
                    ? pc.yellow("experimental")
                    : pc.green("active");
            const awfLabel = meta?.awf_min ? pc.dim(`≥${meta.awf_min}`) : "";
            console.log(`    ${pc.cyan(skill.name)} ${vLabel} ${lcLabel} ${awfLabel}`);
            if (verbose && meta?.description) {
                console.log(`      ${pc.dim(String(meta.description))}`);
            }
            totalSkills++;
        }
        console.log();
    }
    console.log(pc.dim(`  Total: ${totalSkills} skills in ${categories.length} categories\n`));
}
// ── Check Upgrades ─────────────────────────────────────────
async function checkUpgrades(projectRoot) {
    const upgradesDir = join(projectRoot, AGENT_DIR, UPGRADES_DIR);
    if (!existsSync(upgradesDir)) {
        console.log(pc.green("✔ No upgrades directory. Your setup is up to date."));
        return;
    }
    const manifests = scanUpgradeManifests(upgradesDir);
    if (manifests.length === 0) {
        console.log(pc.green("✔ No pending upgrade manifests found."));
        return;
    }
    console.log(pc.bold(`\n📋 Available Upgrades (${manifests.length})\n`));
    for (const { filename, manifest } of manifests) {
        const applicable = isVersionApplicable(manifest.from, AWF_VERSION);
        const status = applicable ? pc.green("applicable") : pc.dim("skipped");
        console.log(`  ${pc.cyan(filename)}: ${manifest.from} → ${manifest.to} [${status}]`);
        if (manifest.description) {
            console.log(`    ${pc.dim(manifest.description)}`);
        }
        console.log(`    Steps: ${manifest.steps.length}`);
    }
    console.log();
}
// ── Apply Upgrade ──────────────────────────────────────────
async function applyUpgrade(projectRoot, dryRun, verbose) {
    const upgradesDir = join(projectRoot, AGENT_DIR, UPGRADES_DIR);
    if (!existsSync(upgradesDir)) {
        console.log(pc.yellow("No upgrades directory found."));
        return;
    }
    const manifests = scanUpgradeManifests(upgradesDir)
        .filter(m => isVersionApplicable(m.manifest.from, AWF_VERSION));
    if (manifests.length === 0) {
        console.log(pc.green("✔ No applicable upgrades for current version."));
        return;
    }
    console.log(pc.bold(`\n⬆ Applying ${manifests.length} upgrade(s)${dryRun ? " (DRY RUN)" : ""}\n`));
    for (const { filename, manifest } of manifests) {
        console.log(`  ${pc.cyan(filename)}: ${manifest.from} → ${manifest.to}`);
        for (const step of manifest.steps) {
            const label = `    ${step.action}: ${step.target}`;
            if (dryRun) {
                console.log(`${label} ${pc.dim("[preview]")}`);
                continue;
            }
            try {
                executeStep(projectRoot, step, verbose);
                console.log(`${label} ${pc.green("✓")}`);
            }
            catch (err) {
                console.log(`${label} ${pc.red("✗")} ${pc.dim(String(err))}`);
            }
        }
    }
    if (!dryRun) {
        console.log(pc.green("\n✔ Upgrade complete."));
    }
    else {
        console.log(pc.dim("\n  Dry run — no changes made. Remove --dry-run to apply."));
    }
}
// ── Rollback ───────────────────────────────────────────────
async function rollbackUpgrade(projectRoot) {
    const backupDir = join(projectRoot, AGENT_DIR, UPGRADES_DIR, ".backup");
    if (!existsSync(backupDir)) {
        console.log(pc.yellow("No backup found. Nothing to rollback."));
        return;
    }
    const backups = readdirSync(backupDir).filter(f => f.endsWith(BACKUP_SUFFIX));
    if (backups.length === 0) {
        console.log(pc.yellow("Backup directory is empty."));
        return;
    }
    const shouldProceed = await prompts.confirm({
        message: `Rollback ${backups.length} backed-up files?`,
    });
    if (prompts.isCancel(shouldProceed) || !shouldProceed)
        return;
    for (const backup of backups) {
        const originalName = backup.replace(BACKUP_SUFFIX, "");
        const backupPath = join(backupDir, backup);
        const targetPath = join(projectRoot, AGENT_DIR, originalName);
        try {
            copyFileSync(backupPath, targetPath);
            console.log(`  ${pc.green("✓")} Restored ${originalName}`);
        }
        catch (err) {
            console.log(`  ${pc.red("✗")} Failed to restore ${originalName}: ${err}`);
        }
    }
    console.log(pc.green("\n✔ Rollback complete."));
}
// ── Helpers ────────────────────────────────────────────────
function readMeta(skillDir) {
    for (const ext of YAML_EXTS) {
        const metaPath = join(skillDir, `META${ext}`);
        if (existsSync(metaPath)) {
            try {
                const raw = readFileSync(metaPath, "utf-8");
                // Simple YAML parsing for key: value pairs (no dependency on yaml lib)
                const result = {};
                for (const line of raw.split("\n")) {
                    const match = line.match(/^(\w[\w_-]*)\s*:\s*(.+)/);
                    if (match) {
                        result[match[1]] = match[2].trim().replace(/^["']|["']$/g, "");
                    }
                }
                return result;
            }
            catch {
                return null;
            }
        }
    }
    return null;
}
function scanUpgradeManifests(upgradesDir) {
    const results = [];
    try {
        const files = readdirSync(upgradesDir)
            .filter(f => YAML_EXTS.some(ext => f.endsWith(ext)));
        for (const f of files) {
            try {
                const raw = readFileSync(join(upgradesDir, f), "utf-8");
                // Parse simple YAML upgrade manifest
                const manifest = parseUpgradeManifest(raw);
                if (manifest) {
                    results.push({ filename: f, manifest });
                }
            }
            catch {
                // Skip malformed files
            }
        }
    }
    catch {
        // Directory read failed
    }
    return results.sort((a, b) => a.manifest.from.localeCompare(b.manifest.from));
}
function parseUpgradeManifest(raw) {
    // Simple structured YAML parser for upgrade manifests
    const lines = raw.split("\n");
    let from = "";
    let to = "";
    let description = "";
    const steps = [];
    let currentStep = null;
    for (const line of lines) {
        const trimmed = line.trim();
        if (trimmed.startsWith("#") || trimmed === "")
            continue;
        // Top-level fields
        const fromMatch = trimmed.match(/^from:\s*["']?([^"']+)["']?$/);
        if (fromMatch) {
            from = fromMatch[1];
            continue;
        }
        const toMatch = trimmed.match(/^to:\s*["']?([^"']+)["']?$/);
        if (toMatch) {
            to = toMatch[1];
            continue;
        }
        const descMatch = trimmed.match(/^description:\s*["']?(.+?)["']?$/);
        if (descMatch) {
            description = descMatch[1];
            continue;
        }
        // Step entries (- action: ...)
        const stepMatch = trimmed.match(/^-\s*action:\s*(\w+)/);
        if (stepMatch) {
            if (currentStep?.action && currentStep?.target) {
                steps.push(currentStep);
            }
            currentStep = { action: stepMatch[1] };
            continue;
        }
        // Step fields
        if (currentStep) {
            const fieldMatch = trimmed.match(/^(\w+):\s*(.+)/);
            if (fieldMatch) {
                currentStep[fieldMatch[1]] = fieldMatch[2].trim().replace(/^["']|["']$/g, "");
            }
        }
    }
    if (currentStep?.action && currentStep?.target) {
        steps.push(currentStep);
    }
    if (!from || !to)
        return null;
    return { from, to, description, steps };
}
function isVersionApplicable(from, currentVersion) {
    // Simple semver prefix match: "6.4" matches "6.4.9"
    return currentVersion.startsWith(from);
}
function executeStep(projectRoot, step, verbose) {
    const agentDir = join(projectRoot, AGENT_DIR);
    switch (step.action) {
        case "add_skill": {
            const targetDir = join(agentDir, "skills", step.target);
            if (!existsSync(targetDir)) {
                mkdirSync(targetDir, { recursive: true });
                if (verbose)
                    console.log(`      Created: ${step.target}`);
            }
            break;
        }
        case "deprecate":
        case "update_meta": {
            const metaPath = join(agentDir, "skills", step.target, "META.yaml");
            if (existsSync(metaPath) && step.field && step.value !== undefined) {
                // Backup before modify
                backupFile(projectRoot, metaPath);
                const content = readFileSync(metaPath, "utf-8");
                const fieldRegex = new RegExp(`^(${step.field}):\\s*.+$`, "m");
                const newLine = `${step.field}: ${step.value}`;
                const updated = fieldRegex.test(content)
                    ? content.replace(fieldRegex, newLine)
                    : content + `\n${newLine}\n`;
                writeFileSync(metaPath, updated, "utf-8");
                if (verbose)
                    console.log(`      Updated ${step.field} = ${step.value}`);
            }
            break;
        }
        case "remove_skill": {
            if (verbose)
                console.log(`      Would remove: ${step.target} (manual action required)`);
            break;
        }
        case "add_file": {
            if (verbose)
                console.log(`      Would add file: ${step.target} (template required)`);
            break;
        }
    }
}
function backupFile(projectRoot, filePath) {
    const backupDir = join(projectRoot, AGENT_DIR, UPGRADES_DIR, ".backup");
    if (!existsSync(backupDir)) {
        mkdirSync(backupDir, { recursive: true });
    }
    const backupName = basename(filePath) + BACKUP_SUFFIX;
    try {
        copyFileSync(filePath, join(backupDir, backupName));
    }
    catch {
        // Non-critical
    }
}
