// packages/plugin-awf/src/commands/install.ts
// AWF Install Command v1.0.0 - Complete install with skills, workflows, rules, personas
// Refactored: core logic extracted to install-core.ts, install-helpers.ts, install-hsa.ts
import * as fs from "node:fs";
import * as path from "node:path";
import pc from "picocolors";
import * as prompts from "@clack/prompts";
import { getIDEOptionsWithTiers, resolveIDESelection, getIDEByValue, getIDEValues, TIER_SELECTION, IDE_REGISTRY, } from "../constants/ides.js";
import { getIDEComponentSummary } from "../constants/ide-install-specs.js";
import { AWF_VERSION } from "../constants.js";
import { showCompactBanner, showCompletionStats } from "../utils/branding.js";
import { countInstalledSkills, countInstalledWorkflows, countInstalledRules } from "../utils/counts.js";
import { installMCPConfig } from "./mcp.js";
import { findSourceDirs, hasInstallContent, hasErrors } from "./install-helpers.js";
import { verifyHSAPrereqs } from "./install-hsa.js";
import { installAWF } from "./install-core.js";
import { detectAllAgents } from "./init.js";
// ══════════════════════════════════════════════════════════════
// Tiered Install Profiles (v4 Optimization)
// ══════════════════════════════════════════════════════════════
// Tier 0: Safety-critical files — ALWAYS copied regardless of profile/MCP
// Tier 1: Profile-dependent — modules, workflows (count varies)
// Tier 2: MCP-conditional — skip for MCP IDEs (served via HSA on-demand)
//
// NOTE: Skills are always copied in full. Each skill's META.yaml is ~100 tokens,
// so copying all 103 skills = ~10,300 tokens — negligible overhead.
// The AI's runtime token budget (config.yaml) controls how many skills it loads,
// not the install profile. Users can configure skill preferences via config.yaml.
//
// Issue #4: Token budget constants reference
// These values should align with TOKEN_BUDGETS.PROFILES in hsa-engine-ts/src/config/constants.ts
// Cannot import directly due to cross-package dependency, but values are kept in sync.
export const PROFILES = {
    minimal: {
        tokenBudget: 2000, // Matches TOKEN_BUDGETS.PROFILES.MVP
        skills: "all",
        modules: ["quality", "edit-verification", "stop-conditions"],
        workflows: "essential",
        tier2: false, // Always skip tier2 files
        description: "Fastest loading — essential modules only",
    },
    standard: {
        tokenBudget: 5000, // Matches TOKEN_BUDGETS.PROFILES.PRODUCTION
        skills: "all",
        modules: "all",
        workflows: "all",
        tier2: "mcp-conditional", // Skip if MCP IDE
        description: "Balanced — all modules, skip MCP-redundant",
    },
    full: {
        tokenBudget: 10000, // Matches TOKEN_BUDGETS.PROFILES.ENTERPRISE
        skills: "all",
        modules: "all",
        workflows: "all",
        tier2: true, // Always copy everything
        description: "Full power — everything local",
    },
};
export const installFlags = {
    ide: {
        type: "string",
        description: "Target IDE (cursor, windsurf, vscode, gemini, claude, all)",
        alias: "i",
        choices: [...getIDEValues(), "all"],
    },
    profile: {
        type: "string",
        description: "Install profile (minimal, standard, full)",
        alias: "p",
        choices: ["minimal", "standard", "full"],
        default: "standard",
    },
    scope: {
        type: "string",
        description: "Install scope (project, global, both)",
        alias: "s",
        choices: ["project", "global", "both"],
        default: "project",
    },
    force: {
        type: "boolean",
        description: "Overwrite existing configurations",
        alias: "f",
        default: false,
    },
    dryRun: {
        type: "boolean",
        description: "Show what would be installed without writing",
        default: false,
    },
    validate: {
        type: "boolean",
        description: "Run validation after installation",
        default: true,
    },
    language: {
        type: "string",
        description: "Language for workflow descriptions (en/vi)",
        alias: "l",
        choices: ["en", "vi"],
    },
};
export async function installCommand(args, flags) {
    // BRAND-01: NOCK gradient compact banner
    await showCompactBanner(AWF_VERSION, "Install Configuration");
    const cwd = process.cwd();
    const force = flags.force;
    const dryRun = flags.dryRun;
    const runValidation = flags.validate !== false;
    const langFlag = flags.language;
    // ══════════════════════════════════════════════════════════════
    // BRAND-02: Step 1/5 — Language selection
    // ══════════════════════════════════════════════════════════════
    console.log(pc.dim("  [1/5]") + " Language");
    let language;
    if (langFlag && ["en", "vi"].includes(langFlag)) {
        language = langFlag;
    }
    else if (force) {
        // F1 FIX: --force always re-prompts language selection
        const langSelection = await prompts.select({
            message: "Select language / Chọn ngôn ngữ",
            options: [
                { value: "en", label: "English" },
                { value: "vi", label: "Tiếng Việt" },
            ],
        });
        if (prompts.isCancel(langSelection)) {
            prompts.cancel("Installation cancelled");
            return;
        }
        language = langSelection;
    }
    else {
        // Check state.json first (set by init wizard)
        const stateFile = path.join(cwd, ".agent", "memory", "state.json");
        let stateLanguage;
        if (fs.existsSync(stateFile)) {
            try {
                const state = JSON.parse(fs.readFileSync(stateFile, "utf-8"));
                stateLanguage = state?.preferences?.language;
            }
            catch { /* ignore parse errors */ }
        }
        if (stateLanguage && ["en", "vi"].includes(stateLanguage)) {
            language = stateLanguage;
            console.log(pc.dim(`  Language: ${language} (from state.json)`));
        }
        else {
            const langSelection = await prompts.select({
                message: "Select language / Chọn ngôn ngữ",
                options: [
                    { value: "en", label: "English" },
                    { value: "vi", label: "Tiếng Việt" },
                ],
            });
            if (prompts.isCancel(langSelection)) {
                prompts.cancel("Installation cancelled");
                return;
            }
            language = langSelection;
        }
    }
    // F2 FIX: Save resolved language to state.json — create dir if needed
    try {
        const memDir = path.join(cwd, ".agent", "memory");
        fs.mkdirSync(memDir, { recursive: true });
        {
            const sf = path.join(memDir, "state.json");
            const st = fs.existsSync(sf) ? JSON.parse(fs.readFileSync(sf, "utf-8")) : {};
            st.preferences = { ...st.preferences, language };
            fs.writeFileSync(sf, JSON.stringify(st, null, 2));
        }
    }
    catch { /* ignore state save errors */ }
    // GLOBAL-LANG: Save language to ~/.nockdev/hsa/user-preferences.json
    // So ALL future projects inherit this language choice
    try {
        const os = await import("node:os");
        const globalPrefsDir = path.join(os.homedir(), ".nockdev", "hsa");
        fs.mkdirSync(globalPrefsDir, { recursive: true });
        const globalPrefsFile = path.join(globalPrefsDir, "user-preferences.json");
        fs.writeFileSync(globalPrefsFile, JSON.stringify({
            language,
            updated_at: new Date().toISOString(),
            source: "cli-install",
        }, null, 2));
    }
    catch { /* ignore global prefs save errors */ }
    // ══════════════════════════════════════════════════════════════
    // BRAND-02: Step 2/5 — Select IDE(s)
    // ══════════════════════════════════════════════════════════════
    console.log(pc.dim("  [2/5]") + " Select IDE(s)");
    let selectedIDEs;
    const ideArg = (args[0] || flags.ide);
    if (ideArg === "all") {
        selectedIDEs = resolveIDESelection([TIER_SELECTION.ALL]);
    }
    else if (ideArg) {
        // Support comma-separated IDEs: --ide cursor,claude,gemini
        const ideValues = ideArg.split(",").map(v => v.trim());
        selectedIDEs = ideValues.flatMap(v => {
            const ide = getIDEByValue(v);
            if (!ide) {
                console.log(pc.yellow(`  ⚠ Unknown IDE: ${v} (skipped)`));
            }
            return ide ? [ide] : [];
        });
    }
    else {
        // Auto-detect existing IDEs + merge with Tier 1 popular defaults
        const detectedAgents = detectAllAgents(cwd);
        const popularIDEs = ["cursor", "claude", "gemini", "antigravity", "vscode", "windsurf"];
        const defaultSelection = [...new Set([...detectedAgents, ...popularIDEs])];
        if (detectedAgents.length > 0) {
            console.log(pc.dim(`  Detected ${detectedAgents.length} IDE(s): ${detectedAgents.join(", ")}`));
        }
        const selection = await prompts.multiselect({
            message: "Select IDE(s) to configure (detected + Tier 1 pre-selected)",
            options: getIDEOptionsWithTiers(),
            required: true,
            initialValues: defaultSelection,
        });
        if (prompts.isCancel(selection)) {
            prompts.cancel("Installation cancelled");
            return;
        }
        selectedIDEs = resolveIDESelection(selection);
    }
    if (selectedIDEs.length === 0) {
        prompts.cancel("No valid IDEs selected");
        return;
    }
    // ══════════════════════════════════════════════════════════════
    // Step 3: Select scope (project / global / both)
    // ══════════════════════════════════════════════════════════════
    let scope;
    const scopeArg = flags.scope;
    if (scopeArg && ["project", "global", "both"].includes(scopeArg)) {
        scope = scopeArg;
    }
    else {
        const globalSupported = selectedIDEs.filter((ide) => ide.globalPath);
        if (globalSupported.length > 0) {
            const scopeSelection = await prompts.select({
                message: "Installation scope",
                options: [
                    { value: "project", label: "📁 Project only", hint: "Current directory" },
                    { value: "global", label: "🌐 Global only", hint: `${globalSupported.length} IDEs support global` },
                    { value: "both", label: "✨ Both (Recommended)", hint: "Project + Global" },
                ],
            });
            if (prompts.isCancel(scopeSelection)) {
                prompts.cancel("Installation cancelled");
                return;
            }
            scope = scopeSelection;
        }
        else {
            scope = "project";
        }
    }
    // ══════════════════════════════════════════════════════════════
    // Step 4: Select profile
    // ══════════════════════════════════════════════════════════════
    const profileArg = flags.profile;
    let profile;
    if (profileArg && profileArg in PROFILES) {
        profile = profileArg;
    }
    else {
        const selection = await prompts.select({
            message: "Select installation profile",
            options: Object.entries(PROFILES).map(([key, val]) => ({
                value: key,
                label: `${key} (${val.description}, ~${val.tokenBudget}t)`,
            })),
        });
        if (prompts.isCancel(selection)) {
            prompts.cancel("Installation cancelled");
            return;
        }
        profile = selection;
    }
    const profileConfig = PROFILES[profile];
    // ══════════════════════════════════════════════════════════════
    // Confirmation
    // ══════════════════════════════════════════════════════════════
    console.log();
    console.log(pc.bold("📋 Installation Summary"));
    console.log(`   IDEs: ${pc.cyan(selectedIDEs.length.toString())} selected`);
    console.log(`   Scope: ${pc.yellow(scope)}`);
    console.log(`   Profile: ${pc.green(profile)} (~${profileConfig.tokenBudget} tokens)`);
    console.log();
    // Show component summary
    if (selectedIDEs.length > 0) {
        if (selectedIDEs.length === 1) {
            const summary = getIDEComponentSummary(selectedIDEs[0].value);
            console.log(pc.dim("   Components:"));
            console.log(`      Skills: ${summary.skills ? pc.green("✔") : pc.dim("—")}`);
            console.log(`      Workflows: ${summary.workflows ? pc.green("✔") : pc.dim("—")}`);
            console.log(`      Rules: ${summary.rules ? pc.green("✔") : pc.dim("—")}`);
            console.log(`      Personas: ${summary.personas ? pc.green("✔") : pc.dim("—")}`);
            const mcpSupported = selectedIDEs[0].mcp?.supported ?? false;
            console.log(`      HSA/MCP: ${mcpSupported ? pc.green("✔") : pc.dim("—")}`);
        }
        else {
            console.log(pc.dim(`   Components: ${pc.cyan("varies per IDE")} (${selectedIDEs.length} targets)`));
        }
        console.log();
    }
    if (dryRun) {
        console.log(pc.yellow("🔍 DRY RUN - no files will be written"));
        console.log();
    }
    // ══════════════════════════════════════════════════════════════
    // Install for each IDE
    // ══════════════════════════════════════════════════════════════
    const spinner = prompts.spinner();
    let installedProject = 0;
    let installedGlobal = 0;
    let totalErrors = 0;
    const allResults = [];
    // Find source directories
    const { configsDir, sourceDir } = findSourceDirs();
    // B-02: Install IDEs with per-IDE progress feedback
    console.log(pc.dim(`\n  [3/5]`) + ` Installing ${selectedIDEs.length} IDE(s)...\n`);
    // GAP-C03: Ensure spinner stops even on unexpected errors
    try {
        for (const ide of selectedIDEs) {
            const targets = [];
            if (scope === "project" || scope === "both") {
                targets.push({ scope: "project", rootDir: cwd });
            }
            if ((scope === "global" || scope === "both") && ide.globalPath) {
                targets.push({ scope: "global", rootDir: ide.globalPath });
            }
            for (const target of targets) {
                const mcpSupported = !!ide.mcp?.supported;
                const result = await installAWF(ide, target.scope, target.rootDir, sourceDir, configsDir, force, dryRun, language, profile, mcpSupported);
                allResults.push(result);
                if (hasInstallContent(result)) {
                    if (target.scope === "project")
                        installedProject++;
                    else
                        installedGlobal++;
                }
                if (hasErrors(result))
                    totalErrors++;
            }
        }
        console.log(pc.green(`\n  \u2714 Installed ${installedProject + installedGlobal} configurations`));
    }
    catch (error) {
        console.log(pc.red(`\n  \u2716 Installation failed`));
        throw error;
    }
    // ══════════════════════════════════════════════════════════════
    // Validation
    // ══════════════════════════════════════════════════════════════
    if (runValidation && !dryRun && allResults.length > 0) {
        console.log();
        console.log(pc.bold("🔍 Running validation..."));
        let validCount = 0;
        let warningCount = 0;
        for (const result of allResults) {
            if (result.validation) {
                if (result.validation.valid) {
                    validCount++;
                }
                warningCount += result.validation.warnings.length;
                // Show errors if any
                if (!result.validation.valid) {
                    console.log(pc.red(`\n❌ ${result.ide} (${result.scope}):`));
                    for (const err of result.validation.errors) {
                        console.log(pc.red(`   ${err}`));
                    }
                }
            }
        }
        console.log();
        if (validCount === allResults.length) {
            console.log(pc.green(`✅ All ${validCount} installations validated`));
        }
        else {
            console.log(pc.yellow(`⚠️ ${validCount}/${allResults.length} passed validation`));
        }
        if (warningCount > 0) {
            console.log(pc.dim(`   (${warningCount} warnings)`));
        }
    }
    // ══════════════════════════════════════════════════════════════
    // MCP Configuration (Optional)
    // ══════════════════════════════════════════════════════════════
    const total = installedProject + installedGlobal;
    const MCP_SUPPORTED_IDES = IDE_REGISTRY
        .filter(ide => ide.mcp?.supported)
        .map(ide => ide.value);
    const mcpEligibleIDEs = selectedIDEs.filter(ide => MCP_SUPPORTED_IDES.includes(ide.value));
    if (mcpEligibleIDEs.length > 0 && !dryRun && total > 0) {
        console.log();
        const mcpInstall = await prompts.confirm({
            message: `Configure MCP (Model Context Protocol) for ${mcpEligibleIDEs.length} IDE(s)?`,
            initialValue: true,
        });
        if (!prompts.isCancel(mcpInstall) && mcpInstall) {
            console.log();
            let mcpSuccess = 0;
            let mcpFailed = 0;
            const { isProjectLocalConfig, MCP_CONFIG_REGISTRY: mcpRegistry } = await import("./mcp-registry.js");
            // Map of IDE values to their global MCP variant keys
            const GLOBAL_MCP_VARIANTS = { roo: "roo-global", trae: "trae-global", blackbox: "blackbox-global", opencode: "opencode-global", openclaw: "openclaw-global" };
            for (const ide of mcpEligibleIDEs) {
                // Global IDEs: use ${workspaceFolder} (IDE resolves per-window)
                // Project-local IDEs: use absolute CWD (config lives in project)
                const projectPath = isProjectLocalConfig(ide.value) ? cwd : undefined;
                const result = await installMCPConfig(ide.value, projectPath, force);
                if (result.success) {
                    console.log(pc.green(`  ✔ MCP configured for ${ide.displayName}`));
                    mcpSuccess++;
                }
                else {
                    console.log(pc.yellow(`  ⚠ MCP failed for ${ide.displayName}: ${result.error}`));
                    mcpFailed++;
                }
                // Also install global MCP variant if exists (e.g., roo → roo-global)
                const globalVariant = GLOBAL_MCP_VARIANTS[ide.value];
                if (globalVariant && mcpRegistry[globalVariant]) {
                    const gResult = await installMCPConfig(globalVariant, undefined, force);
                    if (gResult.success) {
                        console.log(pc.green(`  ✔ MCP configured for ${ide.displayName} (global)`));
                        mcpSuccess++;
                    }
                }
            }
            console.log();
            if (mcpSuccess > 0) {
                console.log(pc.green(`✔ MCP configured: ${mcpSuccess} IDE(s)`));
                if (mcpFailed > 0) {
                    console.log(pc.dim(`  (${mcpFailed} failed - run 'nock awf mcp install' to retry)`));
                }
            }
            // Verify HSA prerequisites (Node.js 18+)
            await verifyHSAPrereqs(cwd, sourceDir);
        }
    }
    // ══════════════════════════════════════════════════════════════
    // BRAND-03: Install Summary Table
    // ══════════════════════════════════════════════════════════════
    console.log();
    if (total > 0) {
        showCompletionStats({
            skills: countInstalledSkills(path.join(cwd, ".agent")),
            workflows: countInstalledWorkflows(path.join(cwd, ".agent")),
            rules: countInstalledRules(path.join(cwd, ".agent")),
            ides: selectedIDEs.map(ide => ide.displayName),
            mcp: mcpEligibleIDEs.length > 0,
            scope: `${installedProject} project, ${installedGlobal} global`,
        });
        if (totalErrors > 0) {
            console.log(pc.yellow(`  ⚠ ${totalErrors} error(s) — run ${pc.cyan("nock awf doctor")} to diagnose`));
        }
        if (!dryRun) {
            console.log(pc.dim("  Quick start:"));
            console.log(`    ${pc.cyan("nock awf doctor")}      Verify configuration`);
            console.log(`    ${pc.cyan("nock awf list")}        Browse skills & workflows`);
            console.log(`    ${pc.cyan("nock awf hsa start")}   Start HSA context engine`);
            console.log();
        }
    }
    else {
        prompts.outro(pc.yellow("No configurations installed"));
    }
}
