import { app, BrowserWindow, dialog, ipcMain } from 'electron';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { execFileSync } from 'node:child_process';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/**
 * The agent core is the compiled CLI code — one implementation shared by the
 * terminal UI and the desktop app. In development it lives at <repo>/dist;
 * in a packaged app scripts/package-mac.sh copies it to <app>/core-dist.
 */
const CORE_DIR = [path.join(__dirname, '..', 'core-dist'), path.join(__dirname, '..', '..', 'dist')].find((d) => fs.existsSync(path.join(d, 'agent', 'loop.js')));
const core = (p) => import(pathToFileURL(path.join(CORE_DIR, p)).href);
/**
 * No default project: the app starts fresh and the user connects a folder
 * (or, later, an SSH host) explicitly. CHEAPCODE_CWD is a dev override.
 */
let cwd = process.env.CHEAPCODE_CWD || null;
const state = {
    messages: [],
    model: 'openrouter/auto',
    provider: null,
    apiKey: '',
    tools: null,
    gate: null,
    hooks: null,
    controller: null,
    pendingApproval: null,
    contextLength: null,
};
let win = null;
/**
 * Events raised before the renderer finishes loading (or during reloads)
 * are queued and flushed on did-finish-load, so nothing is lost to the race
 * between session init and page load.
 */
let rendererReady = false;
const queuedEvents = [];
function send(ev) {
    if (!rendererReady || !win) {
        queuedEvents.push(ev);
        return;
    }
    win.webContents.send('agent:event', ev);
}
function flushEvents() {
    rendererReady = true;
    while (queuedEvents.length > 0 && win) {
        win.webContents.send('agent:event', queuedEvents.shift());
    }
}
function gitInfo(dir) {
    if (!dir)
        return { branch: null, repo: null };
    let branch = null;
    try {
        branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
            cwd: dir,
            encoding: 'utf8',
            timeout: 3000,
            stdio: ['ignore', 'pipe', 'pipe'],
        }).trim();
    }
    catch {
        /* not a repo */
    }
    return { branch, repo: path.basename(dir) };
}
function currentState() {
    return { cwd, model: state.model, ...gitInfo(cwd) };
}
/** (Re)initialize the agent session for the current cwd. Throws on failure. */
async function initSession() {
    if (!cwd)
        return;
    const [{ loadConfig }, { buildSystemPrompt }, { assembleTools }, { PermissionGate }, { createHookRunner }] = await Promise.all([
        core('config.js'),
        core('agent/prompt.js'),
        core('tools/assemble.js'),
        core('permissions.js'),
        core('hooks.js'),
    ]);
    const config = loadConfig(cwd);
    if (!config.apiKey) {
        throw new Error('No API key found. Set CHEAPCODE_API_KEY (create one at https://cheapcode.ai/account) or add "apiKey" to ~/.config/cheapcode/config.json');
    }
    state.apiKey = config.apiKey;
    state.model = config.model;
    state.provider = config.provider;
    state.hooks = createHookRunner(config.hooks, cwd);
    state.messages = [{ role: 'system', content: buildSystemPrompt(cwd) }];
    state.gate = new PermissionGate((tool, summary) => new Promise((resolve) => {
        state.pendingApproval = resolve;
        send({ type: 'approval-request', toolName: tool.name, summary });
    }), false);
    state.tools = assembleTools({
        apiKey: state.apiKey,
        provider: state.provider,
        getModel: () => state.model,
        gate: state.gate,
        mcpTools: [],
        cwd,
    });
    // Context window for auto-compaction (best-effort).
    core('providers/openrouter.js')
        .then(({ listModels }) => listModels(state.apiKey))
        .then((models) => {
        state.contextLength = models.find((m) => m.id === state.model)?.contextLength ?? null;
    })
        .catch(() => undefined);
}
async function runTurn(userText) {
    if (!cwd || !state.tools) {
        send({ type: 'error', message: 'Connect to a folder first (New Agent > Open a folder).' });
        return;
    }
    const { runAgent } = await core('agent/loop.js');
    state.messages.push({ role: 'user', content: userText });
    state.controller = new AbortController();
    send({ type: 'busy', busy: true });
    try {
        const events = runAgent({
            apiKey: state.apiKey,
            model: state.model,
            provider: state.provider,
            messages: state.messages,
            tools: state.tools,
            gate: state.gate,
            ctx: { cwd },
            signal: state.controller.signal,
            hooks: state.hooks,
            contextLength: state.contextLength,
        });
        for await (const ev of events)
            send(ev);
    }
    catch (err) {
        send({ type: 'error', message: err.message });
    }
    finally {
        state.controller = null;
        state.pendingApproval = null;
        send({ type: 'busy', busy: false });
        if (cwd) {
            const dir = cwd;
            core('sessions.js')
                .then(({ saveSession }) => saveSession(dir, state.model, state.messages))
                .catch(() => undefined);
        }
    }
}
function registerIpc() {
    ipcMain.on('chat:send', (_e, text) => {
        if (state.controller)
            return; // one turn at a time
        void runTurn(text);
    });
    ipcMain.on('chat:cancel', () => {
        state.controller?.abort();
        // Unblock a pending approval so the loop can observe the abort.
        state.pendingApproval?.('no');
        state.pendingApproval = null;
    });
    ipcMain.on('approval:respond', (_e, decision) => {
        state.pendingApproval?.(decision);
        state.pendingApproval = null;
    });
    ipcMain.on('model:set', (_e, model) => {
        state.model = model;
    });
    ipcMain.on('chat:clear', () => {
        if (state.controller || !cwd)
            return;
        const dir = cwd;
        void core('agent/prompt.js').then(({ buildSystemPrompt }) => {
            state.messages = [{ role: 'system', content: buildSystemPrompt(dir) }];
        });
    });
    ipcMain.handle('folder:choose', async () => {
        if (!win || state.controller)
            return null;
        const result = await dialog.showOpenDialog(win, {
            properties: ['openDirectory', 'createDirectory'],
            ...(cwd ? { defaultPath: cwd } : {}),
        });
        const picked = result.filePaths[0];
        if (!picked)
            return null;
        const previous = cwd;
        cwd = picked;
        try {
            await initSession(); // fresh session (tools, system prompt, gate) for the new project
        }
        catch (err) {
            cwd = previous;
            send({ type: 'error', message: err.message });
            return null;
        }
        return currentState();
    });
    ipcMain.handle('state:get', () => currentState());
    ipcMain.handle('models:list', async () => {
        try {
            const { loadConfig } = await core('config.js');
            const apiKey = state.apiKey || loadConfig(cwd ?? process.cwd()).apiKey;
            if (!apiKey)
                return [];
            const { listModels } = await core('providers/openrouter.js');
            const models = await listModels(apiKey);
            return models
                .filter((m) => /deepseek|glm|claude|gpt|gemini|qwen|grok|kimi/i.test(m.id))
                .slice(0, 40)
                .map((m) => ({ id: m.id, contextLength: m.contextLength }));
        }
        catch {
            return [];
        }
    });
}
function createWindow() {
    rendererReady = false;
    win = new BrowserWindow({
        width: 1280,
        height: 820,
        minWidth: 900,
        minHeight: 600,
        title: 'CheapCode',
        titleBarStyle: 'hiddenInset',
        trafficLightPosition: { x: 14, y: 13 },
        backgroundColor: '#131316',
        icon: path.join(__dirname, '..', 'build', 'icon.png'),
        webPreferences: {
            preload: path.join(__dirname, 'preload.cjs'),
            contextIsolation: true,
            nodeIntegration: false,
        },
    });
    win.webContents.on('did-finish-load', flushEvents);
    const devServer = process.env.VITE_DEV_SERVER;
    if (devServer) {
        void win.loadURL(devServer);
    }
    else {
        void win.loadFile(path.join(__dirname, '..', 'dist-renderer', 'index.html'));
    }
}
app.whenReady().then(async () => {
    registerIpc();
    createWindow();
    if (cwd) {
        // Dev override path: initialize eagerly, surfacing failures in the UI.
        try {
            await initSession();
        }
        catch (err) {
            send({ type: 'error', message: err.message });
        }
    }
    app.on('activate', () => {
        if (BrowserWindow.getAllWindows().length === 0)
            createWindow();
    });
});
app.on('window-all-closed', () => {
    if (process.platform !== 'darwin')
        app.quit();
});
