import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback, useEffect, useRef, useState } from 'react';
import { Box, Text, useApp, useInput } from 'ink';
import TextInput from 'ink-text-input';
import { listModels } from '../providers/openrouter.js';
import { runAgent } from '../agent/loop.js';
import { buildSystemPrompt } from '../agent/prompt.js';
import { compactMessages, estimateTokens } from '../agent/compact.js';
import { assembleTools } from '../tools/assemble.js';
import { PermissionGate } from '../permissions.js';
import { discoverSkills } from '../context/skills.js';
import { discoverCommands, expandCommand } from '../context/commands.js';
import { saveSession } from '../sessions.js';
import { Markdown } from './markdown.js';
const HELP = `Commands:
  /model <id>   switch model (any OpenRouter id); /model alone lists suggestions
  /compact      summarize older conversation to free context
  /skills       list discovered skills; /<skill-name> [args] invokes one
  /mcp          list connected MCP servers and their tools
  /clear        reset the conversation
  /help         show this help
  /exit         quit
Custom commands: .cheapcode/commands/<name>.md becomes /<name> ($ARGUMENTS expands).
Keys: Esc cancels a running turn. Approvals: y once, a always (session), n deny.`;
export function App({ apiKey, provider, initialModel, cwd, yolo, mcpTools, mcpServerNames, hooks, initialMessages, }) {
    const { exit } = useApp();
    const [items, setItems] = useState([
        {
            kind: 'info',
            text: `CheapCode v0.3.0 · ${cwd} · type /help for commands${initialMessages ? ` · resumed session (${initialMessages.length} messages)` : ''}`,
        },
    ]);
    const [streamText, setStreamText] = useState('');
    const [input, setInput] = useState('');
    const [busy, setBusy] = useState(false);
    const [model, setModel] = useState(initialModel);
    const [approval, setApproval] = useState(null);
    const [totals, setTotals] = useState({ tokens: 0, cost: 0 });
    const messagesRef = useRef(initialMessages ?? [{ role: 'system', content: buildSystemPrompt(cwd) }]);
    const abortRef = useRef(null);
    const modelRef = useRef(model);
    modelRef.current = model;
    // Per-model context window, for automatic compaction.
    const [contextLength, setContextLength] = useState(null);
    const contextLengthRef = useRef(null);
    contextLengthRef.current = contextLength;
    useEffect(() => {
        let stale = false;
        listModels(apiKey)
            .then((models) => {
            if (stale)
                return;
            const info = models.find((m) => m.id === model);
            setContextLength(info?.contextLength ?? null);
        })
            .catch(() => setContextLength(null));
        return () => {
            stale = true;
        };
    }, [apiKey, model]);
    const gateRef = useRef(new PermissionGate((tool, summary) => new Promise((resolve) => {
        setApproval({ toolName: tool.name, summary, resolve });
    }), yolo));
    const toolsRef = useRef(null);
    if (toolsRef.current === null) {
        toolsRef.current = assembleTools({
            apiKey,
            provider,
            getModel: () => modelRef.current,
            gate: gateRef.current,
            mcpTools,
            cwd,
        });
    }
    const skillsRef = useRef(null);
    if (skillsRef.current === null)
        skillsRef.current = discoverSkills(cwd);
    const commandsRef = useRef(null);
    if (commandsRef.current === null)
        commandsRef.current = discoverCommands(cwd);
    const push = useCallback((item) => {
        setItems((prev) => [...prev, item]);
    }, []);
    useInput((_input, key) => {
        if (key.escape && busy) {
            abortRef.current?.abort();
        }
    });
    const runTurn = useCallback(async (userText) => {
        messagesRef.current.push({ role: 'user', content: userText });
        const controller = new AbortController();
        abortRef.current = controller;
        setBusy(true);
        setStreamText('');
        try {
            const events = runAgent({
                apiKey,
                model: modelRef.current,
                provider,
                messages: messagesRef.current,
                tools: toolsRef.current,
                gate: gateRef.current,
                ctx: { cwd },
                signal: controller.signal,
                hooks,
                contextLength: contextLengthRef.current,
            });
            for await (const ev of events) {
                switch (ev.type) {
                    case 'assistant-delta':
                        setStreamText((prev) => prev + ev.text);
                        break;
                    case 'assistant-message':
                        setStreamText('');
                        push({ kind: 'assistant', text: ev.text });
                        break;
                    case 'tool-start':
                        push({ kind: 'tool', name: ev.name, summary: ev.summary, isError: false });
                        break;
                    case 'tool-result':
                        if (ev.isError) {
                            push({ kind: 'tool', name: ev.name, summary: ev.result.slice(0, 120), isError: true });
                        }
                        break;
                    case 'usage':
                        setTotals((t) => ({
                            tokens: t.tokens + ev.usage.promptTokens + ev.usage.completionTokens,
                            cost: t.cost + (ev.usage.cost ?? 0),
                        }));
                        break;
                    case 'compacted':
                        push({ kind: 'info', text: `auto-compacted ${ev.summarizedCount} older messages` });
                        break;
                    case 'error':
                        push({ kind: 'error', text: ev.message });
                        break;
                    case 'done':
                        if (ev.reason === 'cancelled')
                            push({ kind: 'info', text: 'cancelled' });
                        break;
                }
            }
        }
        catch (err) {
            push({ kind: 'error', text: err.message });
        }
        finally {
            setStreamText('');
            setBusy(false);
            setApproval(null);
            abortRef.current = null;
            saveSession(cwd, modelRef.current, messagesRef.current);
        }
    }, [apiKey, cwd, push]);
    const handleCommand = useCallback(async (raw) => {
        const [cmd, ...rest] = raw.slice(1).split(/\s+/);
        const arg = rest.join(' ').trim();
        switch (cmd) {
            case 'exit':
            case 'quit':
                exit();
                break;
            case 'help':
                push({ kind: 'info', text: HELP });
                break;
            case 'clear':
                messagesRef.current = [{ role: 'system', content: buildSystemPrompt(cwd) }];
                setItems([{ kind: 'info', text: 'conversation cleared' }]);
                setTotals({ tokens: 0, cost: 0 });
                break;
            case 'model':
                if (arg) {
                    setModel(arg);
                    push({ kind: 'info', text: `model set to ${arg}` });
                }
                else {
                    try {
                        const models = await listModels(apiKey);
                        const suggestions = models
                            .filter((m) => /claude|gpt|gemini|glm|qwen|deepseek|grok/i.test(m.id))
                            .slice(0, 20)
                            .map((m) => `  ${m.id}  (${Math.round(m.contextLength / 1000)}k ctx)`)
                            .join('\n');
                        push({
                            kind: 'info',
                            text: `current model: ${modelRef.current}\nusage: /model <id>\n\nsuggestions:\n${suggestions}`,
                        });
                    }
                    catch (err) {
                        push({ kind: 'error', text: `could not list models: ${err.message}` });
                    }
                }
                break;
            case 'compact': {
                setBusy(true);
                try {
                    const { messages, summarizedCount } = await compactMessages({
                        apiKey,
                        model: modelRef.current,
                        provider,
                        messages: messagesRef.current,
                    });
                    messagesRef.current = messages;
                    push({
                        kind: 'info',
                        text: summarizedCount > 0
                            ? `compacted ${summarizedCount} messages (~${estimateTokens(messages)} tokens in context now)`
                            : 'nothing to compact yet',
                    });
                }
                catch (err) {
                    push({ kind: 'error', text: `compaction failed: ${err.message}` });
                }
                finally {
                    setBusy(false);
                }
                break;
            }
            case 'skills': {
                const skills = skillsRef.current;
                push({
                    kind: 'info',
                    text: skills.length === 0
                        ? 'no skills found (.cheapcode/skills, .claude/skills, .cursor/skills, ~/.config/cheapcode/skills)'
                        : skills.map((s) => `  /${s.name} - ${s.description}`).join('\n'),
                });
                break;
            }
            case 'mcp': {
                const names = mcpServerNames;
                const tools = mcpTools.map((t) => `  ${t.name}`).join('\n');
                push({
                    kind: 'info',
                    text: names.length === 0
                        ? 'no MCP servers configured (add "mcpServers" to ~/.config/cheapcode/config.json or .cheapcode/settings.json)'
                        : `connected servers: ${names.join(', ')}\n${tools}`,
                });
                break;
            }
            default: {
                const custom = commandsRef.current.find((c) => c.name === cmd);
                if (custom) {
                    const prompt = expandCommand(custom, arg);
                    push({ kind: 'user', text: `/${cmd}${arg ? ` ${arg}` : ''}` });
                    void runTurn(prompt);
                    break;
                }
                const skill = skillsRef.current.find((s) => s.name === cmd);
                if (skill) {
                    push({ kind: 'user', text: `/${cmd}${arg ? ` ${arg}` : ''}` });
                    void runTurn(`Read the skill file at ${skill.file} with read_file and follow it for this request${arg ? `: ${arg}` : '.'}`);
                    break;
                }
                push({ kind: 'error', text: `unknown command: /${cmd} (try /help)` });
            }
        }
    }, [apiKey, cwd, exit, push, mcpTools, mcpServerNames, runTurn]);
    const onSubmit = useCallback((value) => {
        const text = value.trim();
        if (!text || busy)
            return;
        setInput('');
        if (text.startsWith('/')) {
            void handleCommand(text);
        }
        else {
            push({ kind: 'user', text });
            void runTurn(text);
        }
    }, [busy, handleCommand, push, runTurn]);
    return (_jsxs(Box, { flexDirection: "column", children: [items.map((item, i) => (_jsx(Item, { item: item }, i))), streamText.length > 0 && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: streamText }) })), approval && (_jsx(ApprovalPrompt, { approval: approval, onDecision: (d) => {
                    approval.resolve(d);
                    setApproval(null);
                } })), !approval && (_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: busy ? '· ' : '> ' }), busy ? (_jsx(Text, { dimColor: true, children: "working... (Esc to cancel)" })) : (_jsx(TextInput, { value: input, onChange: setInput, onSubmit: onSubmit }))] })), _jsx(Box, { children: _jsxs(Text, { dimColor: true, children: [model, " \u00B7 ~", estimateTokens(messagesRef.current), " tok in context \u00B7 ", totals.tokens, " tok used", totals.cost > 0 ? ` · $${totals.cost.toFixed(4)}` : ''] }) })] }));
}
function Item({ item }) {
    switch (item.kind) {
        case 'user':
            return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: '> ' }), _jsx(Text, { color: "cyan", children: item.text })] }));
        case 'assistant':
            return (_jsx(Box, { marginTop: 1, children: _jsx(Markdown, { children: item.text }) }));
        case 'tool':
            return (_jsxs(Text, { color: item.isError ? 'red' : 'magenta', dimColor: !item.isError, children: ['  ⚙ ', item.name, item.summary ? `: ${item.summary}` : ''] }));
        case 'info':
            return _jsx(Text, { dimColor: true, children: item.text });
        case 'error':
            return _jsx(Text, { color: "red", children: `error: ${item.text}` });
    }
}
function ApprovalPrompt({ approval, onDecision, }) {
    useInput((input) => {
        const c = input.toLowerCase();
        if (c === 'y')
            onDecision('yes');
        else if (c === 'a')
            onDecision('always');
        else if (c === 'n')
            onDecision('no');
    });
    return (_jsxs(Box, { borderStyle: "round", borderColor: "yellow", paddingX: 1, flexDirection: "column", children: [_jsxs(Text, { color: "yellow", bold: true, children: ["approve ", approval.toolName, "?"] }), _jsx(Text, { children: approval.summary }), _jsx(Text, { dimColor: true, children: "[y] yes once \u00B7 [a] always this session \u00B7 [n] deny" })] }));
}
