import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
import chalk from "chalk";
import { createReadStream, unwatchFile, watchFile } from "fs";
import { Box, render, Text, useInput, useStdout } from "ink";
import Spinner from "ink-spinner";
import { memo, useEffect, useRef, useState } from "react";
import { LOG_COLORS } from "./constants/log-colors.js";
const NEXTJS_MCP_404_REGEX = /(?:\[POST\]|POST)\s+\/_next\/mcp\b[^\n]*\b404\b/i;
// Compact ASCII logo for very small terminals
const COMPACT_LOGO = "d3k";
// Full ASCII logo lines as array for easier rendering
const FULL_LOGO = ["   ▐▌▄▄▄▄ █  ▄ ", "   ▐▌   █ █▄▀  ", "▗▞▀▜▌▀▀▀█ █ ▀▄ ", "▝▚▄▟▌▄▄▄█ █  █ "];
// Type colors map - defined outside component to avoid recreation
const TYPE_COLORS = {
    NETWORK: LOG_COLORS.NETWORK,
    ERROR: LOG_COLORS.ERROR,
    WARNING: LOG_COLORS.WARNING,
    INFO: LOG_COLORS.INFO,
    LOG: LOG_COLORS.LOG,
    DEBUG: LOG_COLORS.DEBUG,
    SCREENSHOT: LOG_COLORS.SCREENSHOT,
    DOM: LOG_COLORS.DOM,
    CDP: LOG_COLORS.CDP,
    CHROME: LOG_COLORS.CHROME,
    CRASH: LOG_COLORS.CRASH,
    REPLAY: LOG_COLORS.REPLAY,
    NAVIGATION: LOG_COLORS.NAVIGATION,
    INTERACTION: LOG_COLORS.INTERACTION,
    GET: LOG_COLORS.SERVER,
    POST: LOG_COLORS.SERVER,
    PUT: LOG_COLORS.SERVER,
    DELETE: LOG_COLORS.SERVER,
    PATCH: LOG_COLORS.SERVER,
    HEAD: LOG_COLORS.SERVER,
    OPTIONS: LOG_COLORS.SERVER
};
// Memoized log line component to prevent re-parsing on every render
const LogLine = memo(({ log, isCompact, isVeryCompact }) => {
    // Parse log line to colorize different parts
    const parts = log.content.match(/^\[(.*?)\] \[(.*?)\] (?:\[(.*?)\] )?(.*)$/);
    if (parts) {
        let [, timestamp, source, type, message] = parts;
        // Extract HTTP method from SERVER logs as a secondary tag
        if (source === "SERVER" && !type && message) {
            const methodMatch = message.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s/);
            if (methodMatch) {
                type = methodMatch[1];
                message = message.slice(type.length + 1); // Remove method from message
            }
        }
        // Replace warning emoji in ERROR/WARNING messages for consistent terminal rendering
        if (message && (type === "ERROR" || type === "WARNING")) {
            message = message.replace(/⚠/g, "[!]");
        }
        // In very compact mode, simplify the output
        if (isVeryCompact) {
            const shortSource = source === "BROWSER" ? "B" : "S";
            const shortType = type ? type.split(".")[0].charAt(0) : "";
            return (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { dimColor: true, children: ["[", shortSource, "]"] }), shortType && _jsxs(Text, { dimColor: true, children: ["[", shortType, "]"] }), _jsxs(Text, { children: [" ", message] })] }));
        }
        // Use shared color constants
        const sourceColor = source === "BROWSER" ? LOG_COLORS.BROWSER : LOG_COLORS.SERVER;
        // In compact mode, skip padding
        if (isCompact) {
            return (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { dimColor: true, children: ["[", timestamp, "]"] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: sourceColor, bold: true, children: ["[", source.charAt(0), "]"] }), type && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: TYPE_COLORS[type] || "#A0A0A0", children: ["[", type, "]"] })] })), _jsxs(Text, { children: [" ", message] })] }));
        }
        // Normal mode with minimal padding
        return (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { dimColor: true, children: ["[", timestamp, "]"] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: sourceColor, bold: true, children: ["[", source, "]"] }), type ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: TYPE_COLORS[type] || "#A0A0A0", children: ["[", type, "]"] }), _jsx(Text, { children: " " })] })) : (_jsx(Text, { children: " " })), _jsx(Text, { children: message })] }));
    }
    // Fallback for unparsed lines
    return _jsx(Text, { wrap: "truncate-end", children: log.content });
});
const TUIApp = ({ appPort: initialAppPort, mcpPort, logFile, commandName, serversOnly, version, projectName, updateAvailable, onStatusUpdate, onAppPortUpdate, onUpdateAvailableUpdate }) => {
    const [logs, setLogs] = useState([]);
    const [scrollOffset, setScrollOffset] = useState(0);
    const [initStatus, setInitStatus] = useState(null);
    const [appPort, setAppPort] = useState(initialAppPort);
    const [updateInfo, setUpdateInfo] = useState(updateAvailable || null);
    const [portConfirmed, setPortConfirmed] = useState(false);
    const logIdCounter = useRef(0);
    const [clearFromLogId, setClearFromLogId] = useState(0); // Track log ID to clear from
    const { stdout } = useStdout();
    const ctrlCMessageDefault = "^L clear ^C quit";
    const [ctrlCMessage, setCtrlCMessage] = useState(ctrlCMessageDefault);
    const maxScrollOffsetRef = useRef(0);
    const [terminalSize, setTerminalSize] = useState(() => ({
        width: stdout?.columns || 80,
        height: stdout?.rows || 24
    }));
    useEffect(() => {
        if (!stdout) {
            return;
        }
        const handleResize = () => {
            setTerminalSize({
                width: stdout.columns || 80,
                height: stdout.rows || 24
            });
        };
        stdout.on("resize", handleResize);
        return () => {
            if (typeof stdout.off === "function") {
                stdout.off("resize", handleResize);
            }
            else {
                stdout.removeListener("resize", handleResize);
            }
        };
    }, [stdout]);
    // Get terminal dimensions with fallbacks
    const termWidth = terminalSize.width;
    const termHeight = terminalSize.height;
    // Determine if we should use compact mode
    const isCompact = termWidth < 80 || termHeight < 20;
    const isVeryCompact = termWidth < 60 || termHeight < 15;
    // Provide status update function to parent
    useEffect(() => {
        onStatusUpdate((status) => {
            // Check if this is the "Press Ctrl+C again" warning
            if (status?.includes("Press Ctrl+C again")) {
                // Update the bottom Ctrl+C message with warning emoji
                setCtrlCMessage("⚠️ ^C again to quit");
                // Clear the init status since we don't want it in the header anymore
                setInitStatus(null);
                // Reset after 3 seconds
                setTimeout(() => {
                    setCtrlCMessage(ctrlCMessageDefault);
                }, 3000);
            }
            else {
                setInitStatus(status);
            }
        });
    }, [onStatusUpdate]);
    // Provide app port update function to parent
    useEffect(() => {
        onAppPortUpdate((port) => {
            setAppPort(port);
            setPortConfirmed(true);
        });
    }, [onAppPortUpdate]);
    // Provide update available function to parent
    useEffect(() => {
        onUpdateAvailableUpdate((info) => {
            setUpdateInfo(info);
        });
    }, [onUpdateAvailableUpdate]);
    // Calculate available lines for logs dynamically based on terminal height and mode
    const calculateMaxVisibleLogs = () => {
        if (isVeryCompact) {
            // In very compact mode, use most of the screen for logs, account for bottom status line
            return Math.max(3, termHeight - 8);
        }
        else if (isCompact) {
            // In compact mode, reduce header size, account for bottom status line
            return Math.max(3, termHeight - 10);
        }
        else {
            // Normal mode calculation - account for all UI elements
            const headerBorderLines = 2; // Top border (with title) + bottom border
            const headerContentLines = 5; // Logo is 4 lines tall, +1 for padding
            const logBoxBorderLines = 2; // Top and bottom border of log box
            const logBoxHeaderLines = 1; // "Logs (X total)" text
            const logBoxFooterLines = 1; // Always reserve space for "(X lines below)" to keep layout stable
            const bottomStatusLine = 1; // Log path and quit message
            const safetyBuffer = 0; // No buffer needed with explicit heights
            const totalReservedLines = headerBorderLines +
                headerContentLines +
                logBoxBorderLines +
                logBoxHeaderLines +
                logBoxFooterLines +
                bottomStatusLine +
                safetyBuffer;
            return Math.max(3, termHeight - totalReservedLines);
        }
    };
    const maxLogs = 1000;
    const maxVisibleLogs = calculateMaxVisibleLogs();
    const maxScrollOffset = Math.max(0, maxLogs - maxVisibleLogs);
    maxScrollOffsetRef.current = maxScrollOffset;
    useEffect(() => {
        let logStream;
        let buffer = "";
        let pendingLogs = [];
        let flushTimeout = null;
        // Batch log updates to prevent excessive renders
        const flushPendingLogs = () => {
            if (pendingLogs.length === 0)
                return;
            const logsToAdd = pendingLogs;
            pendingLogs = [];
            flushTimeout = null;
            setLogs((prevLogs) => {
                const updated = [...prevLogs, ...logsToAdd];
                // Keep only last N logs to prevent memory issues
                if (updated.length > maxLogs) {
                    return updated.slice(-maxLogs);
                }
                return updated;
            });
            // Auto-scroll to bottom only if user is already at the bottom
            // Otherwise, increment scroll offset by count of new logs
            setScrollOffset((currentOffset) => {
                return currentOffset === 0 ? 0 : Math.min(maxScrollOffsetRef.current, currentOffset + logsToAdd.length);
            });
        };
        const appendLog = (line) => {
            if (NEXTJS_MCP_404_REGEX.test(line)) {
                return;
            }
            const newLog = {
                id: logIdCounter.current++,
                content: line
            };
            pendingLogs.push(newLog);
            // Debounce: flush after 50ms of no new logs
            // Terminal synchronized updates prevent flicker, so we can be more responsive
            if (flushTimeout) {
                clearTimeout(flushTimeout);
            }
            flushTimeout = setTimeout(flushPendingLogs, 50);
        };
        // Create a read stream for the log file
        logStream = createReadStream(logFile, {
            encoding: "utf8",
            start: 0
        });
        logStream.on("data", (chunk) => {
            buffer += chunk.toString();
            const lines = buffer.split("\n");
            // Keep the last incomplete line in buffer
            buffer = lines.pop() || "";
            // Add complete lines to logs
            for (const line of lines) {
                if (line.trim()) {
                    appendLog(line);
                }
            }
        });
        logStream.on("error", (error) => {
            appendLog(chalk.red(`Error reading log file: ${error.message}`));
        });
        // Watch for new content
        watchFile(logFile, { interval: 100 }, (curr, prev) => {
            if (curr.size > prev.size) {
                // File has grown, read new content
                const stream = createReadStream(logFile, {
                    encoding: "utf8",
                    start: prev.size
                });
                let watchBuffer = "";
                stream.on("data", (chunk) => {
                    watchBuffer += chunk.toString();
                    const lines = watchBuffer.split("\n");
                    watchBuffer = lines.pop() || "";
                    for (const line of lines) {
                        if (line.trim()) {
                            appendLog(line);
                        }
                    }
                });
            }
        });
        // Cleanup
        return () => {
            if (logStream) {
                logStream.destroy();
            }
            if (flushTimeout) {
                clearTimeout(flushTimeout);
            }
            unwatchFile(logFile);
        };
    }, [logFile]);
    // Handle keyboard input
    useInput((input, key) => {
        if (key.ctrl && input === "c") {
            // Send SIGINT to trigger main process shutdown handler
            process.kill(process.pid, "SIGINT");
        }
        else if (key.ctrl && input === "l") {
            // Ctrl-L: Clear logs box - set clear point to last log ID
            const lastLogId = logs.length > 0 ? logs[logs.length - 1].id : logIdCounter.current;
            setClearFromLogId(lastLogId);
            setScrollOffset(0); // Reset scroll to bottom
        }
        else if (key.upArrow) {
            const filteredCount = logs.filter((log) => log.id > clearFromLogId).length;
            setScrollOffset((prev) => Math.min(prev + 1, Math.max(0, filteredCount - maxVisibleLogs)));
        }
        else if (key.downArrow) {
            setScrollOffset((prev) => Math.max(0, prev - 1));
        }
        else if (key.pageUp) {
            const filteredCount = logs.filter((log) => log.id > clearFromLogId).length;
            setScrollOffset((prev) => Math.min(prev + maxVisibleLogs, Math.max(0, filteredCount - maxVisibleLogs)));
        }
        else if (key.pageDown) {
            setScrollOffset((prev) => Math.max(0, prev - maxVisibleLogs));
        }
        else if (input === "G" && key.shift) {
            // Shift+G to go to end
            setScrollOffset(0);
        }
        else if (input === "g" && !key.shift) {
            // g to go to beginning
            const filteredCount = logs.filter((log) => log.id > clearFromLogId).length;
            setScrollOffset(Math.max(0, filteredCount - maxVisibleLogs));
        }
    });
    // Calculate visible logs - filter to only show logs after the clear point
    const filteredLogs = logs.filter((log) => log.id > clearFromLogId);
    const visibleLogs = filteredLogs.slice(Math.max(0, filteredLogs.length - maxVisibleLogs - scrollOffset), filteredLogs.length - scrollOffset);
    // Render compact header for small terminals
    const renderCompactHeader = () => (_jsx(Box, { borderStyle: "single", borderColor: "#A18CE5", paddingX: 1, marginBottom: 1, children: _jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsxs(Box, { children: [_jsx(Text, { color: "#A18CE5", bold: true, children: COMPACT_LOGO }), _jsxs(Text, { children: [" v", version, " "] }), initStatus && _jsxs(Text, { dimColor: true, children: ["- ", initStatus] })] }), !isVeryCompact && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["App: localhost:", appPort, " | MCP: localhost:", mcpPort] }), _jsxs(Text, { dimColor: true, children: ["\uD83D\uDCF8 http://localhost:", mcpPort, "/logs", projectName ? `?project=${encodeURIComponent(projectName)}` : ""] })] }))] }) }));
    // Render normal header
    const renderNormalHeader = () => {
        // Create custom top border with title embedded (like Claude Code)
        const title = ` ${commandName} v${version} ${initStatus ? `- ${initStatus} ` : ""}`;
        const borderChar = "─";
        const leftPadding = 2;
        // Account for border characters and padding
        const availableWidth = termWidth - 2; // -2 for corner characters
        const titleLength = title.length;
        const rightBorderLength = Math.max(0, availableWidth - titleLength - leftPadding);
        const topBorderLine = `╭${borderChar.repeat(leftPadding)}${title}${borderChar.repeat(rightBorderLength)}╮`;
        return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#A18CE5", children: topBorderLine }), _jsx(Box, { borderStyle: "round", borderColor: "#A18CE5", borderTop: false, paddingX: 1, paddingY: 1, children: _jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Box, { flexDirection: "column", alignItems: "flex-start", children: FULL_LOGO.map((line) => (_jsx(Text, { color: "#A18CE5", bold: true, children: line }, line))) }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: "cyan", children: ["\uD83C\uDF10 App: http://localhost:", appPort, " "] }), !portConfirmed && _jsx(Spinner, { type: "dots" })] }), _jsxs(Text, { color: "cyan", children: ["\uD83E\uDD16 MCP: http://localhost:", mcpPort] }), _jsxs(Text, { color: "cyan", children: ["\uD83D\uDCF8 Logs: http://localhost:", mcpPort, "/logs", projectName ? `?project=${encodeURIComponent(projectName)}` : ""] }), serversOnly && (_jsx(Text, { color: "cyan", children: "\uD83D\uDDA5\uFE0F Servers-only mode - use Chrome extension for browser monitoring" }))] })] }) })] }));
    };
    // Calculate the height for the logs box to ensure stable layout from first render
    // This prevents the layout from shifting as logs fill in
    const logsBoxHeight = maxVisibleLogs + 3; // +3 for header line, borders
    return (_jsxs(Box, { flexDirection: "column", height: termHeight, children: [isCompact ? renderCompactHeader() : renderNormalHeader(), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, height: logsBoxHeight, children: [!isVeryCompact && (_jsxs(Text, { color: "gray", dimColor: true, children: ["Logs (", filteredLogs.length, " total", scrollOffset > 0 && `, scrolled up ${scrollOffset} lines`, ")"] })), _jsx(Box, { flexDirection: "column", children: visibleLogs.length === 0 ? (_jsx(Text, { dimColor: true, children: "Waiting for logs..." })) : (visibleLogs.map((log) => (_jsx(LogLine, { log: log, isCompact: isCompact, isVeryCompact: isVeryCompact }, log.id)))) }), !isVeryCompact && logs.length > maxVisibleLogs && scrollOffset > 0 && (_jsxs(Text, { dimColor: true, children: ["(", scrollOffset, " lines below)"] }))] }), _jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsx(Box, { children: _jsxs(Text, { color: "#A18CE5", children: ["\u23F5\u23F5", " ", isVeryCompact
                                    ? logFile.split("/").slice(-2, -1)[0] || "logs" // Just show directory name
                                    : logFile.replace(process.env.HOME || "", "~")] }) }), _jsxs(Box, { gap: 2, children: [updateInfo && _jsxs(Text, { color: "yellow", children: ["\u2191 v", updateInfo.latestVersion, " available (d3k upgrade)"] }), _jsx(Text, { color: "#A18CE5", children: ctrlCMessage })] })] })] }));
};
export async function runTUI(options) {
    return new Promise((resolve, reject) => {
        try {
            let statusUpdater = null;
            let appPortUpdater = null;
            let updateAvailableUpdater = null;
            // Wrap stdout.write to add synchronized update escape sequences
            // This tells the terminal to buffer all output until the end marker
            // Supported by iTerm2, Kitty, WezTerm, and other modern terminals
            const originalWrite = process.stdout.write.bind(process.stdout);
            const syncStart = "\x1b[?2026h"; // Begin synchronized update (DECSM 2026)
            const syncEnd = "\x1b[?2026l"; // End synchronized update (DECRM 2026)
            process.stdout.write = ((chunk, encodingOrCb, cb) => {
                if (typeof chunk === "string" && chunk.length > 0) {
                    // Wrap output in synchronized update markers to prevent partial renders
                    const wrapped = syncStart + chunk + syncEnd;
                    if (typeof encodingOrCb === "function") {
                        return originalWrite(wrapped, encodingOrCb);
                    }
                    return originalWrite(wrapped, encodingOrCb, cb);
                }
                if (typeof encodingOrCb === "function") {
                    return originalWrite(chunk, encodingOrCb);
                }
                return originalWrite(chunk, encodingOrCb, cb);
            });
            const app = render(_jsx(TUIApp, { ...options, onStatusUpdate: (fn) => {
                    statusUpdater = fn;
                }, onAppPortUpdate: (fn) => {
                    appPortUpdater = fn;
                }, onUpdateAvailableUpdate: (fn) => {
                    updateAvailableUpdater = fn;
                } }), { exitOnCtrlC: false });
            // Give React one tick to set up the updaters
            setImmediate(() => {
                resolve({
                    app,
                    updateStatus: (status) => {
                        if (statusUpdater) {
                            statusUpdater(status);
                        }
                    },
                    updateAppPort: (port) => {
                        if (appPortUpdater) {
                            appPortUpdater(port);
                        }
                    },
                    updateUpdateAvailable: (info) => {
                        if (updateAvailableUpdater) {
                            updateAvailableUpdater(info);
                        }
                    }
                });
            });
        }
        catch (error) {
            console.error("Error in runTUI render:", error);
            reject(error);
        }
    });
}
//# sourceMappingURL=tui-interface-impl.js.map