// packages/plugin-awf/src/commands/install-hsa.ts
// HSA Package Manager — download, install, update @nockdev/hsa
import * as fs from "node:fs";
import * as path from "node:path";
import { execSync } from "node:child_process";
import { homedir, tmpdir } from "node:os";
import pc from "picocolors";
import * as prompts from "@clack/prompts";
// ══════════════════════════════════════════════════════════════
// CONSTANTS
// ══════════════════════════════════════════════════════════════
/** Dedicated HSA install directory */
const HSA_INSTALL_DIR = path.join(homedir(), ".nockdev", "hsa");
/** npm package name */
const HSA_PACKAGE = "@nockdev/hsa";
/** Files to extract from the tarball's dist/ directory */
const HSA_DIST_FILES = [
    "hsa-cli.bundle.js",
    "hsa-daemon.bundle.js",
    "hsa-http.bundle.js",
    "hsa-lib.bundle.js",
    "hsa-engine.node", // native addon (optional)
    "integrity.json",
];
/** HTML files (dashboard) */
const HSA_HTML_FILES = [
    "dashboard.html",
    "logs.html",
];
// ══════════════════════════════════════════════════════════════
// PUBLIC API
// ══════════════════════════════════════════════════════════════
/**
 * Get the dedicated HSA install directory path.
 */
export function getHSAInstallDir() {
    return HSA_INSTALL_DIR;
}
/**
 * Get the installed HSA version from manifest.json.
 * Returns null if not installed.
 */
export function getInstalledHSAVersion() {
    const manifestPath = path.join(HSA_INSTALL_DIR, "manifest.json");
    try {
        if (!fs.existsSync(manifestPath))
            return null;
        const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
        return manifest.version || null;
    }
    catch {
        return null;
    }
}
/**
 * Get the path to hsa-cli.bundle.js if installed.
 * Returns null if not installed or bundle missing.
 */
export function getHSABundlePath() {
    const bundlePath = path.join(HSA_INSTALL_DIR, "hsa-cli.bundle.js");
    return fs.existsSync(bundlePath) ? bundlePath : null;
}
/**
 * Download and install HSA from npm registry.
 *
 * Strategy: fetch tarball URL from npm registry, download via `fetch()`,
 * extract with `tar`, then copy dist/* files to ~/.nockdev/hsa/.
 * Works on all platforms without additional dependencies.
 */
export async function downloadHSA(version = "latest") {
    // SEC-07: Validate version to prevent command injection in tar fallback
    if (version !== "latest" && !/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(version)) {
        return { success: false, version: "", path: "", error: `Invalid version: ${version}` };
    }
    const tempDir = path.join(tmpdir(), `hsa-install-${Date.now()}`);
    try {
        // 1. Create temp directory
        fs.mkdirSync(tempDir, { recursive: true });
        // 2. Get tarball URL from npm registry (INST-01 FIX: async fetch instead of execSync)
        const registryUrl = `https://registry.npmjs.org/${HSA_PACKAGE}/${version}`;
        const metaRes = await fetch(registryUrl, { signal: AbortSignal.timeout(10_000) });
        if (!metaRes.ok) {
            return { success: false, version: "", path: "", error: `npm registry returned ${metaRes.status}` };
        }
        const meta = await metaRes.json();
        const installedVersion = meta.version;
        const tarballUrl = meta.dist.tarball;
        // 3. Download tarball (INST-02 FIX: async fetch instead of npm pack)
        const tarRes = await fetch(tarballUrl, { signal: AbortSignal.timeout(30_000) });
        if (!tarRes.ok) {
            return { success: false, version: installedVersion, path: "", error: `Tarball download failed: ${tarRes.status}` };
        }
        const tgzPath = path.join(tempDir, `hsa-${installedVersion}.tgz`);
        const buffer = Buffer.from(await tarRes.arrayBuffer());
        fs.writeFileSync(tgzPath, buffer);
        // 4. Extract tarball (tar available on Windows 10+, macOS, Linux)
        const extractDir = path.join(tempDir, "extracted");
        fs.mkdirSync(extractDir, { recursive: true });
        execSync(`tar xzf "${tgzPath}" -C "${extractDir}"`, {
            timeout: 30_000,
            stdio: ["ignore", "pipe", "pipe"],
        });
        // npm tarball extracts to ./package/ inside the target directory
        const packageDir = path.join(extractDir, "package");
        if (!fs.existsSync(packageDir)) {
            return { success: false, version: installedVersion, path: "", error: "Tarball extraction failed: package/ directory not found" };
        }
        // 5. Ensure install directory exists
        fs.mkdirSync(HSA_INSTALL_DIR, { recursive: true });
        // 6. Copy dist files
        const distDir = path.join(packageDir, "dist");
        let copiedCount = 0;
        for (const file of HSA_DIST_FILES) {
            const src = path.join(distDir, file);
            const dest = path.join(HSA_INSTALL_DIR, file);
            if (fs.existsSync(src)) {
                fs.copyFileSync(src, dest);
                copiedCount++;
            }
        }
        // 7. Copy HTML files (dashboard) from package root
        for (const file of HSA_HTML_FILES) {
            const src = path.join(packageDir, file);
            const dest = path.join(HSA_INSTALL_DIR, file);
            if (fs.existsSync(src)) {
                fs.copyFileSync(src, dest);
                copiedCount++;
            }
        }
        if (copiedCount === 0) {
            return {
                success: false, version: installedVersion, path: HSA_INSTALL_DIR,
                error: "No dist files found in package"
            };
        }
        // 8. Copy node_modules (playwright-core, sharp, @img/*) — required for canvas/design tools
        const pkgNodeModules = path.join(packageDir, "node_modules");
        if (fs.existsSync(pkgNodeModules)) {
            const targetNm = path.join(HSA_INSTALL_DIR, "node_modules");
            fs.mkdirSync(targetNm, { recursive: true });
            // Copy each top-level module from tarball's node_modules
            for (const entry of fs.readdirSync(pkgNodeModules)) {
                const src = path.join(pkgNodeModules, entry);
                const dst = path.join(targetNm, entry);
                try {
                    if (entry.startsWith("@")) {
                        // Scoped packages (@img/sharp-*, etc.)
                        fs.mkdirSync(dst, { recursive: true });
                        for (const sub of fs.readdirSync(src)) {
                            fs.cpSync(path.join(src, sub), path.join(dst, sub), { recursive: true, force: true });
                        }
                    }
                    else {
                        fs.cpSync(src, dst, { recursive: true, force: true });
                    }
                    copiedCount++;
                }
                catch { /* skip locked/failed modules */ }
            }
        }
        // 8. Write manifest.json
        const manifest = {
            version: installedVersion,
            installedAt: new Date().toISOString(),
            files: copiedCount,
            package: HSA_PACKAGE,
        };
        fs.writeFileSync(path.join(HSA_INSTALL_DIR, "manifest.json"), JSON.stringify(manifest, null, 2));
        return {
            success: true,
            version: installedVersion,
            path: HSA_INSTALL_DIR,
        };
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        return { success: false, version: "", path: "", error: msg };
    }
    finally {
        // Cleanup temp directory
        try {
            fs.rmSync(tempDir, { recursive: true, force: true });
        }
        catch { /* ignore cleanup errors */ }
    }
}
/**
 * Check if a newer version is available on npm.
 */
export async function checkHSAUpdate() {
    const installed = getInstalledHSAVersion();
    try {
        // INST-01 FIX: Use async fetch instead of blocking execSync("npm view")
        const res = await fetch(`https://registry.npmjs.org/${HSA_PACKAGE}/latest`, { signal: AbortSignal.timeout(5_000) });
        if (!res.ok)
            throw new Error(`Registry returned ${res.status}`);
        const data = await res.json();
        const latestVersion = data.version;
        return {
            installed,
            latest: latestVersion,
            needsUpdate: installed !== latestVersion,
        };
    }
    catch (err) {
        return {
            installed,
            latest: "",
            needsUpdate: false,
            error: err instanceof Error ? err.message : "Failed to check npm registry",
        };
    }
}
// ══════════════════════════════════════════════════════════════
// BACKWARD COMPATIBILITY
// ══════════════════════════════════════════════════════════════
/**
 * Register HSA MCP server availability.
 * Previously a no-op (npx handled everything).
 * Now returns success if HSA is installed in dedicated dir.
 */
export function copyHSA(_rootDir, _sourceDir) {
    const version = getInstalledHSAVersion();
    if (version) {
        return {
            success: true,
            count: 0,
            error: null,
            skipped: [`HSA v${version} installed at ${HSA_INSTALL_DIR}`],
        };
    }
    return {
        success: true,
        count: 0,
        error: null,
        skipped: ["HSA not yet installed — will download during MCP install"],
    };
}
// ══════════════════════════════════════════════════════════════
// HSA Dependencies Check
// ══════════════════════════════════════════════════════════════
/**
 * Verify HSA MCP server prerequisites and optionally download.
 */
export async function verifyHSAPrereqs(_cwd, _sourceDir) {
    const hsaInstall = await prompts.confirm({
        message: "Install HSA MCP server? (requires Node.js 18+)",
        initialValue: true,
    });
    if (prompts.isCancel(hsaInstall) || !hsaInstall) {
        return;
    }
    const hsaSpinner = prompts.spinner();
    // Check Node.js version
    try {
        const nodeVersion = execSync("node --version", { encoding: "utf-8", timeout: 10000 }).trim();
        const major = parseInt(nodeVersion.replace("v", ""), 10);
        if (major < 18) {
            hsaSpinner.stop(pc.yellow(`⚠ Node.js ${nodeVersion} too old (need >=18)`));
            return;
        }
    }
    catch {
        hsaSpinner.stop(pc.yellow("⚠ Node.js not available — HSA install skipped"));
        return;
    }
    // Download HSA
    const existing = getInstalledHSAVersion();
    if (existing) {
        hsaSpinner.start("Checking for HSA updates...");
        const update = await checkHSAUpdate();
        if (update.needsUpdate) {
            hsaSpinner.stop(pc.cyan(`HSA update: ${existing} → ${update.latest}`));
            hsaSpinner.start(`Downloading HSA v${update.latest}...`);
            const result = await downloadHSA(update.latest);
            hsaSpinner.stop(result.success
                ? pc.green(`✔ HSA updated to v${result.version}`)
                : pc.yellow(`⚠ Update failed: ${result.error}`));
        }
        else {
            hsaSpinner.stop(pc.green(`✔ HSA v${existing} is up to date`));
        }
    }
    else {
        hsaSpinner.start("Downloading HSA MCP server...");
        const result = await downloadHSA();
        hsaSpinner.stop(result.success
            ? pc.green(`✔ HSA v${result.version} installed at ${result.path}`)
            : pc.yellow(`⚠ Download failed: ${result.error}`));
    }
}
