import React, { useState, useEffect, useRef } from "react";
import { render, Text, Box, useApp, Static } from "ink";
import Spinner from "ink-spinner";
import * as fs from "fs";
import * as path from "path";
import { parseConfig } from "@specific/config";
import { PortAllocator, startDatabase, startService, startHttpProxy, InstanceStateManager, } from "../lib/dev/index.js";
import { prepareSecrets } from "../lib/secrets/index.js";
// Colors for service prefixes
const COLORS = ["cyan", "yellow", "green", "magenta", "blue"];
function DevUI() {
    const { exit } = useApp();
    const [state, setState] = useState({
        status: "loading",
        databases: new Map(),
        services: [],
        output: [],
        colorMap: new Map(),
    });
    // Track if shutdown is in progress
    const shuttingDown = useRef(false);
    const stateManagerRef = useRef(null);
    const shutdown = async () => {
        if (shuttingDown.current)
            return;
        shuttingDown.current = true;
        setState((s) => ({ ...s, status: "stopping" }));
        // Stop proxy first (stop accepting new requests)
        if (state.proxy) {
            await state.proxy.stop();
        }
        // Stop services
        for (const service of state.services) {
            await service.stop();
        }
        // Then stop databases
        for (const db of state.databases.values()) {
            await db.stop();
        }
        // Release ownership
        if (stateManagerRef.current) {
            await stateManagerRef.current.releaseOwnership();
        }
        exit();
    };
    // Handle SIGINT/SIGTERM for graceful shutdown
    useEffect(() => {
        const handleSignal = () => {
            shutdown();
        };
        process.on("SIGINT", handleSignal);
        process.on("SIGTERM", handleSignal);
        return () => {
            process.off("SIGINT", handleSignal);
            process.off("SIGTERM", handleSignal);
        };
    }, [state.services, state.databases]);
    const addLog = (line, colorMap) => {
        const color = colorMap.get(`svc:${line.service}`) || "cyan";
        setState((s) => ({
            ...s,
            output: [
                ...s.output,
                { type: "log", service: line.service, text: line.text, color },
            ],
        }));
    };
    useEffect(() => {
        let cancelled = false;
        const startedDatabases = [];
        const startedServices = [];
        let startedProxy = null;
        const stateManager = new InstanceStateManager(process.cwd());
        stateManagerRef.current = stateManager;
        async function start() {
            // Phase 0: Check for existing instances
            await stateManager.cleanStaleState();
            const existing = await stateManager.getExistingInstances();
            if (existing) {
                setState((s) => ({
                    ...s,
                    status: "error",
                    error: `Development environment already running (owned by PID ${existing.owner.pid})`,
                }));
                return;
            }
            try {
                await stateManager.claimOwnership("dev");
            }
            catch (err) {
                setState((s) => ({
                    ...s,
                    status: "error",
                    error: err instanceof Error ? err.message : String(err),
                }));
                return;
            }
            // Phase 1: Load config
            const configPath = path.join(process.cwd(), "specific.hcl");
            if (!fs.existsSync(configPath)) {
                setState((s) => ({
                    ...s,
                    status: "error",
                    error: "No specific.hcl found in current directory",
                }));
                return;
            }
            let config;
            try {
                const hcl = fs.readFileSync(configPath, "utf-8");
                config = await parseConfig(hcl);
            }
            catch (err) {
                setState((s) => ({
                    ...s,
                    status: "error",
                    error: err instanceof Error ? err.message : String(err),
                }));
                return;
            }
            if (cancelled)
                return;
            // Assign colors to databases and services
            const colorMap = new Map();
            let colorIndex = 0;
            for (const db of config.databases) {
                colorMap.set(`db:${db.name}`, COLORS[colorIndex % COLORS.length]);
                colorIndex++;
            }
            for (const service of config.services) {
                colorMap.set(`svc:${service.name}`, COLORS[colorIndex % COLORS.length]);
                colorIndex++;
            }
            setState((s) => ({ ...s, status: "starting", config, colorMap }));
            // Phase 2: Start databases
            const portAllocator = new PortAllocator();
            const databases = new Map();
            for (const db of config.databases) {
                if (cancelled)
                    break;
                try {
                    const port = portAllocator.allocate();
                    const instance = await startDatabase(db, port);
                    databases.set(db.name, instance);
                    startedDatabases.push(instance);
                    // Update state immediately so UI shows this database as ready
                    setState((s) => ({ ...s, databases: new Map(databases) }));
                    const dbState = {
                        engine: instance.engine,
                        port: instance.port,
                        host: instance.host,
                        user: instance.user,
                        password: instance.password,
                        dbName: instance.dbName,
                        url: instance.url,
                    };
                    if (instance.endpoint)
                        dbState.endpoint = instance.endpoint;
                    if (instance.accessKey)
                        dbState.accessKey = instance.accessKey;
                    if (instance.secretKey)
                        dbState.secretKey = instance.secretKey;
                    await stateManager.registerDatabase(db.name, dbState);
                }
                catch (err) {
                    setState((s) => ({
                        ...s,
                        status: "error",
                        error: `Failed to start database "${db.name}": ${err instanceof Error ? err.message : String(err)}`,
                    }));
                    return;
                }
            }
            if (cancelled)
                return;
            // Phase 3: Prepare secrets (loads user secrets + handles generated ones)
            const secrets = await prepareSecrets(config.secrets);
            // Phase 4: Start services
            const services = [];
            for (const service of config.services) {
                if (cancelled)
                    break;
                // Skip static services for now
                if (service.serve) {
                    continue;
                }
                // Skip services without a command
                if (!service.command && !service.dev?.command) {
                    continue;
                }
                try {
                    // Allocate port for services with expose
                    const port = service.expose ? portAllocator.allocate() : undefined;
                    const running = startService(service, databases, secrets, port, (line) => addLog(line, colorMap));
                    services.push(running);
                    startedServices.push(running);
                    // Update state immediately so UI shows this service as ready
                    setState((s) => ({ ...s, services: [...services] }));
                    if (running.process.pid) {
                        const serviceState = {
                            pid: running.process.pid,
                        };
                        if (running.port !== undefined) {
                            serviceState.port = running.port;
                        }
                        await stateManager.registerService(service.name, serviceState);
                    }
                }
                catch (err) {
                    setState((s) => ({
                        ...s,
                        status: "error",
                        error: `Failed to start service "${service.name}": ${err instanceof Error ? err.message : String(err)}`,
                    }));
                    return;
                }
            }
            if (cancelled)
                return;
            // Phase 5: Start HTTP proxy for exposed services
            const exposedServices = services
                .filter((s) => s.port !== undefined)
                .map((s) => ({ name: s.name, port: s.port }));
            if (exposedServices.length > 0) {
                try {
                    const proxy = await startHttpProxy(exposedServices);
                    startedProxy = proxy;
                    setState((s) => ({ ...s, proxy }));
                }
                catch (err) {
                    setState((s) => ({
                        ...s,
                        status: "error",
                        error: `Failed to start HTTP proxy: ${err instanceof Error ? err.message : String(err)}`,
                    }));
                    return;
                }
            }
            if (cancelled)
                return;
            setState((s) => ({ ...s, status: "running", services }));
        }
        start();
        return () => {
            cancelled = true;
            // Cleanup on unmount
            if (startedProxy) {
                startedProxy.stop().catch(() => { });
            }
            for (const service of startedServices) {
                service.stop().catch(() => { });
            }
            for (const db of startedDatabases) {
                db.stop().catch(() => { });
            }
            stateManager.releaseOwnership().catch(() => { });
        };
    }, []);
    if (state.status === "loading") {
        return (React.createElement(Box, null,
            React.createElement(Text, { color: "blue" },
                React.createElement(Spinner, { type: "dots" }))));
    }
    if (state.status === "error") {
        return (React.createElement(Box, { flexDirection: "column" },
            React.createElement(Text, { color: "red" },
                "Error: ",
                state.error)));
    }
    if (state.status === "stopping") {
        return (React.createElement(Box, null,
            React.createElement(Text, { color: "yellow" },
                React.createElement(Spinner, { type: "dots" })),
            React.createElement(Text, null, " Shutting down...")));
    }
    const { config, databases, services, proxy, output } = state;
    // Show spinner while starting if no config yet
    if (!config) {
        return (React.createElement(Box, null,
            React.createElement(Text, { color: "blue" },
                React.createElement(Spinner, { type: "dots" }))));
    }
    // During starting phase, show dynamic view (no logs yet)
    if (state.status === "starting") {
        return (React.createElement(Box, { flexDirection: "column" },
            React.createElement(Text, null,
                React.createElement(Text, { bold: true, color: "cyan" }, "Specific dev server"),
                React.createElement(Text, { dimColor: true }, " (Ctrl+C to stop)")),
            React.createElement(Text, null, " "),
            config.databases.length > 0 && (React.createElement(React.Fragment, null,
                React.createElement(Text, { bold: true }, "Databases:"),
                config.databases.map((db) => {
                    const instance = databases.get(db.name);
                    const isReady = !!instance;
                    return (React.createElement(Text, { key: db.name },
                        React.createElement(Text, { color: isReady ? "green" : "gray" }, " \u25CF "),
                        React.createElement(Text, null,
                            db.name,
                            " (",
                            db.engine,
                            ")"),
                        instance && React.createElement(Text, null,
                            " \u2192 localhost:",
                            instance.port)));
                }),
                React.createElement(Text, null, " "))),
            config.services.length > 0 && (React.createElement(React.Fragment, null,
                React.createElement(Text, { bold: true }, "Services:"),
                config.services.map((svc) => {
                    const running = services.find((s) => s.name === svc.name);
                    const isReady = !!running;
                    const portInfo = running?.port
                        ? ` → localhost:${running.port}`
                        : "";
                    return (React.createElement(Text, { key: svc.name },
                        React.createElement(Text, { color: isReady ? "green" : "gray" }, " \u25CF "),
                        React.createElement(Text, null,
                            svc.name,
                            portInfo)));
                }),
                React.createElement(Text, null, " "))),
            React.createElement(Box, null,
                React.createElement(Text, { color: "blue" },
                    React.createElement(Spinner, { type: "dots" })),
                React.createElement(Text, null, " Starting..."))));
    }
    const staticItems = [
        {
            key: "title",
            content: (React.createElement(Text, null,
                React.createElement(Text, { bold: true, color: "cyan" }, "Specific dev server"),
                React.createElement(Text, { dimColor: true }, " (Ctrl+C to stop)"))),
        },
        { key: "space1", content: React.createElement(Text, null, " ") },
        ...(config.databases.length > 0
            ? [
                { key: "db-header", content: React.createElement(Text, { bold: true }, "Databases:") },
                ...config.databases.map((db) => {
                    const instance = databases.get(db.name);
                    return {
                        key: `db-${db.name}`,
                        content: (React.createElement(Text, null,
                            React.createElement(Text, { color: "green" }, " \u25CF "),
                            React.createElement(Text, null,
                                db.name,
                                " (",
                                db.engine,
                                ")"),
                            instance && React.createElement(Text, null,
                                " \u2192 localhost:",
                                instance.port))),
                    };
                }),
                { key: "space2", content: React.createElement(Text, null, " ") },
            ]
            : []),
        ...(services.length > 0
            ? [
                { key: "svc-header", content: React.createElement(Text, { bold: true }, "Services:") },
                ...services.map((svc) => ({
                    key: `svc-${svc.name}`,
                    content: (React.createElement(Text, null,
                        React.createElement(Text, { color: "green" }, " \u25CF "),
                        React.createElement(Text, null,
                            svc.name,
                            svc.port ? (proxy ? (React.createElement(React.Fragment, null,
                                " → ",
                                React.createElement(Text, { bold: true },
                                    "https://",
                                    svc.name,
                                    ".local.spcf.app"),
                                React.createElement(Text, { dimColor: true },
                                    " (localhost:",
                                    svc.port,
                                    ")"))) : (` → localhost:${svc.port}`)) : ("")))),
                })),
                { key: "space3", content: React.createElement(Text, null, " ") },
            ]
            : []),
        { key: "separator", content: React.createElement(Text, { dimColor: true }, "─".repeat(50)) },
        ...output.map((line, i) => ({
            key: `log-${i}`,
            content: (React.createElement(Text, null,
                React.createElement(Text, { color: line.color },
                    "[",
                    line.service,
                    "]"),
                " ",
                line.text)),
        })),
    ];
    return (React.createElement(Box, { flexDirection: "column" },
        React.createElement(Static, { items: staticItems }, (item) => React.createElement(Box, { key: item.key }, item.content))));
}
export function devCommand() {
    render(React.createElement(DevUI, null));
}
//# sourceMappingURL=dev.js.map