import { existsSync, promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";
import { spawn } from "node:child_process";
import { isSourceProductCheckout, resolveGlobalHomeRoot, resolveShreyJuniorProductRoot } from "../runtime.js";
const RUNTIME_METADATA_FILE = "daemon.json";
const RUNTIME_HEALTH_TIMEOUT_MS = 20_000;
const RUNTIME_START_TIMEOUT_MS = 20_000;
const RUNTIME_START_POLL_MS = 200;
export async function openRuntimeTarget(request) {
    return runtimeRequest("/open-target", {
        method: "POST",
        body: JSON.stringify(request)
    });
}
export async function getRuntimeSnapshot(targetRoot) {
    return runtimeRequest(`/runtime-snapshot?targetRoot=${encodeURIComponent(path.resolve(targetRoot))}`);
}
export async function listRuntimeExecutionEvents(targetRoot, afterRevision) {
    const search = new URLSearchParams({ targetRoot: path.resolve(targetRoot) });
    if (typeof afterRevision === "number") {
        search.set("afterRevision", String(afterRevision));
    }
    return runtimeRequest(`/execution-events?${search.toString()}`);
}
export async function transitionRuntimeExecutionState(request) {
    return runtimeRequest("/transition-execution-state", {
        method: "POST",
        body: JSON.stringify(request)
    });
}
export async function materializeRuntimeDerivedOutputs(request) {
    return runtimeRequest("/materialize-derived-outputs", {
        method: "POST",
        body: JSON.stringify(request)
    });
}
export async function getRuntimeIdentity() {
    return runtimeRequest("/runtime-identity");
}
export async function persistRuntimeDerivedOutput(request) {
    return runtimeRequest("/persist-derived-output", {
        method: "POST",
        body: JSON.stringify(request)
    });
}
export async function refreshRuntimeDerivedOutputs(request) {
    return runtimeRequest("/refresh-derived-outputs", {
        method: "POST",
        body: JSON.stringify(request)
    });
}
export async function persistRuntimeRepoGraph(request) {
    return runtimeRequest("/repo-graph", {
        method: "POST",
        body: JSON.stringify(request)
    });
}
export async function getRuntimeRepoGraph(targetRoot) {
    return runtimeRequest(`/repo-graph?targetRoot=${encodeURIComponent(path.resolve(targetRoot))}`);
}
export async function getRuntimeRepoGraphStatus(targetRoot) {
    return runtimeRequest(`/repo-graph-status?targetRoot=${encodeURIComponent(path.resolve(targetRoot))}`);
}
export async function queryRuntimeRepoGraph(targetRoot, kind, query) {
    const search = new URLSearchParams({ targetRoot: path.resolve(targetRoot) });
    if (query.nodeId)
        search.set("nodeId", query.nodeId);
    if (query.path)
        search.set("path", query.path);
    if (query.moduleId)
        search.set("moduleId", query.moduleId);
    if (query.symbol)
        search.set("symbol", query.symbol);
    return runtimeRequest(`/repo-graph/${kind}?${search.toString()}`);
}
export async function getRuntimePackSelectionStatus(targetRoot) {
    return runtimeRequest(`/repo-pack/status?targetRoot=${encodeURIComponent(path.resolve(targetRoot))}`);
}
export async function setRuntimePackSelection(request) {
    return runtimeRequest("/repo-pack/set", {
        method: "POST",
        body: JSON.stringify(request)
    });
}
export async function clearRuntimePackSelection(request) {
    return runtimeRequest("/repo-pack/clear", {
        method: "POST",
        body: JSON.stringify(request)
    });
}
export async function ensureRuntimeDaemon() {
    const existing = await readRuntimeMetadata();
    if (existing && (await runtimeDaemonHealthy(existing))) {
        return existing;
    }
    await fs.mkdir(runtimeMetadataDir(), { recursive: true });
    const launch = resolveRuntimeLaunch();
    const child = spawn(launch.command, launch.args, {
        cwd: resolveShreyJuniorProductRoot(),
        detached: true,
        stdio: "ignore",
        env: process.env
    });
    child.unref();
    const startedAt = Date.now();
    while (Date.now() - startedAt < RUNTIME_START_TIMEOUT_MS) {
        const metadata = await readRuntimeMetadata();
        if (metadata && (await runtimeDaemonHealthy(metadata))) {
            return metadata;
        }
        await new Promise((resolve) => setTimeout(resolve, RUNTIME_START_POLL_MS));
    }
    throw new Error("Kiwi runtime daemon did not become healthy in time.");
}
async function runtimeRequest(pathname, init) {
    return runtimeRequestWithMetadata(pathname, init);
}
async function runtimeRequestWithMetadata(pathname, init, metadataOverride, allowRestart = true) {
    const metadata = metadataOverride ?? await ensureRuntimeDaemon();
    const url = `${metadata.baseUrl}${pathname}`;
    const headers = new Headers(init?.headers);
    const requestInit = {
        method: init?.method ?? "GET",
        headers,
        signal: AbortSignal.timeout(RUNTIME_HEALTH_TIMEOUT_MS)
    };
    if (init?.body !== undefined) {
        headers.set("content-type", "application/json");
        requestInit.body = init.body;
    }
    const response = await fetch(url, requestInit);
    if (!response.ok) {
        const detail = await response.text();
        if (allowRestart && response.status === 404) {
            const restarted = await restartRuntimeDaemon(metadata);
            return runtimeRequestWithMetadata(pathname, init, restarted, false);
        }
        throw new Error(detail || `Kiwi runtime request failed: ${response.status}`);
    }
    const payload = await response.json();
    if (allowRestart && isRuntimeSnapshotPayload(payload) && !payload.decision) {
        const restarted = await restartRuntimeDaemon(metadata);
        return runtimeRequestWithMetadata(pathname, init, restarted, false);
    }
    return payload;
}
async function runtimeDaemonHealthy(metadata) {
    try {
        const response = await fetch(`${metadata.baseUrl}/health`, {
            signal: AbortSignal.timeout(RUNTIME_HEALTH_TIMEOUT_MS)
        });
        return response.ok;
    }
    catch {
        return false;
    }
}
async function readRuntimeMetadata() {
    const metadataPath = runtimeMetadataPath();
    if (!existsSync(metadataPath)) {
        return null;
    }
    try {
        const payload = JSON.parse(await fs.readFile(metadataPath, "utf8"));
        if (!payload?.baseUrl || typeof payload.port !== "number") {
            return null;
        }
        return payload;
    }
    catch {
        return null;
    }
}
function resolveRuntimeLaunch() {
    const directBinary = resolveRuntimeBinaryPath();
    const metadataPath = runtimeMetadataPath();
    const callerSurface = resolveRuntimeCallerSurface();
    const packagingSourceCategory = directBinary
        ? resolveRuntimePackagingSourceCategory(directBinary)
        : "dev-cargo-fallback";
    const launchMode = directBinary
        ? resolveRuntimeLaunchMode(directBinary, packagingSourceCategory)
        : "dev-cargo-fallback";
    const targetTriple = currentRustTargetTriple();
    if (directBinary) {
        return {
            command: directBinary,
            args: [
                "daemon",
                "--metadata-file",
                metadataPath,
                "--launch-mode",
                launchMode,
                "--caller-surface",
                callerSurface,
                "--packaging-source-category",
                packagingSourceCategory,
                "--binary-path",
                directBinary,
                "--target-triple",
                targetTriple
            ]
        };
    }
    if (!allowDevRuntimeFallback()) {
        throw new Error("Kiwi runtime binary is not staged. Run `node scripts/prepare-runtime-sidecar.mjs` first, or set KIWI_CONTROL_ALLOW_DEV_RUNTIME_FALLBACK=1 for an explicit cargo fallback.");
    }
    const cargoCommand = process.platform === "win32" ? "cargo.exe" : "cargo";
    return {
        command: cargoCommand,
        args: [
            "run",
            "--manifest-path",
            path.join(resolveShreyJuniorProductRoot(), "crates", "kiwi-runtime", "Cargo.toml"),
            "--quiet",
            "--bin",
            "kiwi-control-runtime",
            "--",
            "daemon",
            "--metadata-file",
            metadataPath,
            "--launch-mode",
            launchMode,
            "--caller-surface",
            callerSurface,
            "--packaging-source-category",
            packagingSourceCategory,
            "--binary-path",
            path.join(resolveShreyJuniorProductRoot(), "crates", "kiwi-runtime", "Cargo.toml"),
            "--target-triple",
            targetTriple
        ]
    };
}
async function restartRuntimeDaemon(existing) {
    if (existing?.pid) {
        try {
            process.kill(existing.pid, "SIGTERM");
        }
        catch {
            // Best-effort only. A replacement daemon can still be launched.
        }
    }
    await fs.rm(runtimeMetadataPath(), { force: true }).catch(() => null);
    return ensureRuntimeDaemon();
}
function resolveRuntimeBinaryPath() {
    const envOverride = process.env.KIWI_CONTROL_RUNTIME_BIN?.trim()
        || process.env.SHREY_JUNIOR_RUNTIME_BIN?.trim();
    if (envOverride) {
        return path.resolve(envOverride);
    }
    const productRoot = resolveShreyJuniorProductRoot();
    const stagedBinary = resolveStagedRuntimeBinaryPath(productRoot);
    if (stagedBinary) {
        return stagedBinary;
    }
    if (isSourceProductCheckout(productRoot)) {
        return null;
    }
    return null;
}
function runtimeMetadataDir() {
    const metadataFileOverride = process.env.KIWI_CONTROL_RUNTIME_METADATA_FILE?.trim()
        || process.env.SHREY_JUNIOR_RUNTIME_METADATA_FILE?.trim();
    if (metadataFileOverride) {
        return path.dirname(path.resolve(metadataFileOverride));
    }
    const override = process.env.KIWI_CONTROL_RUNTIME_DIR?.trim()
        || process.env.SHREY_JUNIOR_RUNTIME_DIR?.trim();
    if (override) {
        return path.resolve(override);
    }
    const globalHomeRoot = resolveGlobalHomeRoot();
    if (globalHomeRoot && globalHomeRoot !== ".") {
        return path.join(globalHomeRoot, "runtime");
    }
    return path.join(os.tmpdir(), "kiwi-control-runtime");
}
function runtimeMetadataPath() {
    const metadataFileOverride = process.env.KIWI_CONTROL_RUNTIME_METADATA_FILE?.trim()
        || process.env.SHREY_JUNIOR_RUNTIME_METADATA_FILE?.trim();
    if (metadataFileOverride) {
        return path.resolve(metadataFileOverride);
    }
    return path.join(runtimeMetadataDir(), RUNTIME_METADATA_FILE);
}
function isRuntimeSnapshotPayload(value) {
    return typeof value === "object"
        && value !== null
        && "targetRoot" in value
        && "revision" in value
        && "lifecycle" in value;
}
function resolveRuntimeLaunchMode(binaryPath, packagingSourceCategory) {
    if (packagingSourceCategory === "dev-cargo-fallback") {
        return "dev-cargo-fallback";
    }
    if (packagingSourceCategory === "bundled-sidecar" || packagingSourceCategory === "source-bundle-sibling") {
        return "sidecar";
    }
    void binaryPath;
    return "direct-binary";
}
function resolveStagedRuntimeBinaryPath(productRoot) {
    const executableName = currentRuntimeExecutableName();
    const sidecarTriple = currentRustTargetTriple();
    const candidates = isSourceProductCheckout(productRoot)
        ? [
            path.join(productRoot, "apps", "sj-ui", "src-tauri", "binaries", `kiwi-control-runtime-${sidecarTriple}${process.platform === "win32" ? ".exe" : ""}`),
            path.join(productRoot, "packages", "sj-core", "dist", "runtime", "bin", executableName)
        ]
        : [
            path.join(productRoot, "bin", executableName)
        ];
    return candidates.find((candidate) => existsSync(candidate)) ?? null;
}
function resolveRuntimeCallerSurface() {
    return process.env.KIWI_CONTROL_RUNTIME_CALLER_SURFACE?.trim()
        || process.env.SHREY_JUNIOR_RUNTIME_CALLER_SURFACE?.trim()
        || "cli";
}
function resolveRuntimePackagingSourceCategory(binaryPath) {
    const override = process.env.KIWI_CONTROL_RUNTIME_PACKAGING_SOURCE_CATEGORY?.trim()
        || process.env.SHREY_JUNIOR_RUNTIME_PACKAGING_SOURCE_CATEGORY?.trim();
    if (override) {
        return override;
    }
    const normalizedBinaryPath = path.resolve(binaryPath);
    const productRoot = resolveShreyJuniorProductRoot();
    if (isSourceProductCheckout(productRoot)) {
        if (normalizedBinaryPath.includes(`${path.sep}.app${path.sep}Contents${path.sep}MacOS${path.sep}`)) {
            return "source-bundle-sibling";
        }
        return "local-staged";
    }
    if (normalizedBinaryPath.includes(`${path.sep}runtime${path.sep}bin${path.sep}`)) {
        return "runtime-bundle";
    }
    return "env-override";
}
function currentRuntimeExecutableName() {
    return process.platform === "win32" ? "kiwi-control-runtime.exe" : "kiwi-control-runtime";
}
function currentRustTargetTriple() {
    if (process.platform === "darwin" && process.arch === "arm64") {
        return "aarch64-apple-darwin";
    }
    if (process.platform === "darwin" && process.arch === "x64") {
        return "x86_64-apple-darwin";
    }
    if (process.platform === "linux" && process.arch === "arm64") {
        return "aarch64-unknown-linux-gnu";
    }
    if (process.platform === "linux" && process.arch === "x64") {
        return "x86_64-unknown-linux-gnu";
    }
    if (process.platform === "win32" && process.arch === "arm64") {
        return "aarch64-pc-windows-msvc";
    }
    if (process.platform === "win32" && process.arch === "x64") {
        return "x86_64-pc-windows-msvc";
    }
    throw new Error(`Unsupported runtime target: ${process.platform}/${process.arch}`);
}
function allowDevRuntimeFallback() {
    const value = process.env.KIWI_CONTROL_ALLOW_DEV_RUNTIME_FALLBACK?.trim().toLowerCase()
        || process.env.SHREY_JUNIOR_ALLOW_DEV_RUNTIME_FALLBACK?.trim().toLowerCase();
    return value === "1" || value === "true" || value === "yes";
}
