// Hatchway CLI - Built with Rollup
import { r as reactExports, j as jsxRuntimeExports, B as Box, c as colors, T as Text, s as symbols } from './theme-NAQBkisB.js';
import 'node:stream';
import 'node:process';
import 'chalk';
import 'node:events';
import { b as getLogger, g as getLogBuffer } from './runner-logger-instance-Dj_JMznn.js';

function BuildPanel({ build, width, height }) {
    const [elapsedTime, setElapsedTime] = reactExports.useState(0);
    // Update elapsed time every second
    reactExports.useEffect(() => {
        if (!build || build.status !== 'running') {
            setElapsedTime(0);
            return;
        }
        const startTime = build.startTime;
        const updateElapsed = () => {
            const now = Date.now();
            setElapsedTime(Math.floor((now - startTime) / 1000));
        };
        updateElapsed();
        const interval = setInterval(updateElapsed, 1000);
        return () => clearInterval(interval);
    }, [build?.id, build?.status, build?.startTime]);
    // Don't render if no build
    if (!build) {
        return null;
    }
    const truncate = (str, maxLen) => {
        if (str.length <= maxLen)
            return str;
        return str.substring(0, maxLen - 3) + '...';
    };
    const formatDuration = (seconds) => {
        if (seconds < 60)
            return `${seconds}s`;
        const mins = Math.floor(seconds / 60);
        const secs = seconds % 60;
        return `${mins}m ${secs}s`;
    };
    // Determine elapsed time to show
    const displayElapsed = build.status === 'running'
        ? elapsedTime
        : build.endTime
            ? Math.floor((build.endTime - build.startTime) / 1000)
            : 0;
    return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", width: width, height: height, borderStyle: "single", borderColor: colors.darkGray, paddingX: 1, children: [jsxRuntimeExports.jsx(Box, { marginBottom: 1, children: jsxRuntimeExports.jsx(Text, { color: colors.cyan, bold: true, children: "BUILD" }) }), jsxRuntimeExports.jsx(Box, { children: jsxRuntimeExports.jsx(Text, { color: colors.white, children: truncate(build.projectSlug, width - 4) }) }), build.template && (jsxRuntimeExports.jsx(Box, { children: jsxRuntimeExports.jsx(Text, { color: colors.gray, children: truncate(build.template, width - 4) }) })), jsxRuntimeExports.jsx(Box, { children: jsxRuntimeExports.jsx(Text, { color: colors.gray, children: build.agent === 'claude-code' ? build.model : build.agent }) }), jsxRuntimeExports.jsxs(Box, { marginTop: 1, children: [build.status === 'running' && (jsxRuntimeExports.jsxs(Text, { color: colors.cyan, children: [symbols.spinnerFrames[Math.floor(Date.now() / 120) % symbols.spinnerFrames.length], " ", formatDuration(displayElapsed)] })), build.status === 'completed' && (jsxRuntimeExports.jsxs(Text, { color: colors.success, children: [symbols.check, " ", formatDuration(displayElapsed)] })), build.status === 'failed' && (jsxRuntimeExports.jsxs(Text, { color: colors.error, children: [symbols.cross, " ", formatDuration(displayElapsed)] })), build.status === 'pending' && (jsxRuntimeExports.jsx(Text, { color: colors.gray, children: "Pending..." }))] }), build.todos.length > 0 && (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", marginTop: 1, children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, bold: true, children: "TASKS" }), jsxRuntimeExports.jsx(TodoList, { todos: build.todos, maxWidth: width - 6, maxVisible: 10 })] })), build.status === 'failed' && build.error && (jsxRuntimeExports.jsx(Box, { marginTop: 1, children: jsxRuntimeExports.jsx(Text, { color: colors.error, wrap: "truncate", children: truncate(build.error, width - 4) }) }))] }));
}
/**
 * TodoList - Smart display of todo items
 * - Shows up to maxVisible items (default 10)
 * - Prioritizes in_progress and pending tasks
 * - Hides completed tasks when space is needed
 * - Shows count of hidden completed tasks
 */
function TodoList({ todos, maxWidth, maxVisible = 10 }) {
    // Separate todos by status
    const inProgress = todos.filter(t => t.status === 'in_progress');
    const pending = todos.filter(t => t.status === 'pending');
    const completed = todos.filter(t => t.status === 'completed');
    const cancelled = todos.filter(t => t.status === 'cancelled');
    // Calculate how many slots we have for each category
    // Priority: in_progress > pending > completed > cancelled
    inProgress.length + pending.length;
    let visibleTodos = [];
    let hiddenCompletedCount = 0;
    if (todos.length <= maxVisible) {
        // All todos fit - show them in order
        visibleTodos = todos;
    }
    else {
        // Need to prioritize - always show in_progress and pending first
        visibleTodos = [...inProgress, ...pending];
        // Calculate remaining slots for completed tasks
        const remainingSlots = maxVisible - visibleTodos.length;
        if (remainingSlots > 0) {
            // Show as many completed as we can fit
            visibleTodos = [...visibleTodos, ...completed.slice(0, remainingSlots)];
            hiddenCompletedCount = Math.max(0, completed.length - remainingSlots);
        }
        else {
            hiddenCompletedCount = completed.length;
        }
        // Add cancelled if there's still room
        const slotsAfterCompleted = maxVisible - visibleTodos.length;
        if (slotsAfterCompleted > 0) {
            visibleTodos = [...visibleTodos, ...cancelled.slice(0, slotsAfterCompleted)];
        }
    }
    // Sort visible todos to maintain logical order (by original index)
    visibleTodos.sort((a, b) => {
        const aIndex = todos.findIndex(t => t.id === a.id);
        const bIndex = todos.findIndex(t => t.id === b.id);
        return aIndex - bIndex;
    });
    return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [visibleTodos.map((todo) => (jsxRuntimeExports.jsx(TodoRow, { todo: todo, maxWidth: maxWidth }, todo.id))), hiddenCompletedCount > 0 && (jsxRuntimeExports.jsxs(Text, { color: colors.dimGray, children: ["\u2713 ", hiddenCompletedCount, " completed"] }))] }));
}
// Todo row component
function TodoRow({ todo, maxWidth }) {
    const statusIcon = {
        pending: jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: symbols.hollowDot }),
        in_progress: jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: symbols.spinnerFrames[Math.floor(Date.now() / 120) % symbols.spinnerFrames.length] }),
        completed: jsxRuntimeExports.jsx(Text, { color: colors.success, children: symbols.check }),
        cancelled: jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "\u2298" }),
    }[todo.status];
    const textColor = {
        pending: colors.dimGray,
        in_progress: colors.white,
        completed: colors.gray,
        cancelled: colors.dimGray,
    }[todo.status];
    const truncatedContent = todo.content.length > maxWidth - 3
        ? todo.content.substring(0, maxWidth - 6) + '...'
        : todo.content;
    return (jsxRuntimeExports.jsxs(Box, { children: [statusIcon, jsxRuntimeExports.jsxs(Text, { color: textColor, children: [" ", truncatedContent] })] }));
}

/**
 * useBuildState - Hook to track build state and todos
 *
 * Manages:
 * - Active builds list
 * - Current build selection (for multi-build navigation)
 * - Todo list updates
 * - Build lifecycle events
 *
 * Subscribes to RunnerLogger events to automatically update state.
 *
 * IMPORTANT: Uses refs for event handlers to prevent race conditions
 * where events could be missed during re-subscriptions caused by
 * callback dependency changes.
 */
function useBuildState() {
    const [builds, setBuilds] = reactExports.useState([]);
    const [currentBuildIndex, setCurrentBuildIndex] = reactExports.useState(0);
    const [isConnected, setIsConnected] = reactExports.useState(false);
    const [isVerbose, setIsVerbose] = reactExports.useState(false);
    // Derive current build from index
    const currentBuild = builds.length > 0 ? builds[currentBuildIndex] ?? null : null;
    // Navigation between builds
    const nextBuild = reactExports.useCallback(() => {
        setCurrentBuildIndex(prev => {
            if (builds.length === 0)
                return 0;
            return (prev + 1) % builds.length;
        });
    }, [builds.length]);
    const prevBuild = reactExports.useCallback(() => {
        setCurrentBuildIndex(prev => {
            if (builds.length === 0)
                return 0;
            return prev === 0 ? builds.length - 1 : prev - 1;
        });
    }, [builds.length]);
    // Verbose toggle - updates both UI state and logger
    const toggleVerbose = reactExports.useCallback(() => {
        setIsVerbose(prev => {
            const newValue = !prev;
            // Also update the logger's verbose flag
            try {
                const logger = getLogger();
                logger.setVerbose(newValue);
            }
            catch {
                // Logger not initialized yet
            }
            return newValue;
        });
    }, []);
    // Track when a new build is added to auto-select it
    const [pendingNewBuildId, setPendingNewBuildId] = reactExports.useState(null);
    // Add a new build
    const addBuild = reactExports.useCallback((build) => {
        setBuilds(prev => {
            // Check if build already exists
            const exists = prev.some(b => b.id === build.id);
            if (exists) {
                // Update existing build
                return prev.map(b => b.id === build.id ? { ...b, ...build } : b);
            }
            // Mark this as a new build to auto-select
            setPendingNewBuildId(build.id);
            // Add new build
            return [...prev, build];
        });
    }, []);
    // Auto-select newly added builds
    reactExports.useEffect(() => {
        if (pendingNewBuildId) {
            const index = builds.findIndex(b => b.id === pendingNewBuildId);
            if (index !== -1) {
                setCurrentBuildIndex(index);
            }
            setPendingNewBuildId(null);
        }
    }, [builds, pendingNewBuildId]);
    // Update an existing build
    const updateBuild = reactExports.useCallback((buildId, updates) => {
        setBuilds(prev => prev.map(build => build.id === buildId ? { ...build, ...updates } : build));
    }, []);
    // Update todos for a build
    const updateTodos = reactExports.useCallback((buildId, todos) => {
        setBuilds(prev => prev.map(build => build.id === buildId ? { ...build, todos } : build));
    }, []);
    // Connection status
    const setConnected = reactExports.useCallback((connected) => {
        setIsConnected(connected);
    }, []);
    // Use refs to store the latest callbacks so event handlers always call current versions
    // This prevents race conditions where events could be missed during re-subscriptions
    const addBuildRef = reactExports.useRef(addBuild);
    const updateBuildRef = reactExports.useRef(updateBuild);
    const updateTodosRef = reactExports.useRef(updateTodos);
    // Keep refs up to date
    reactExports.useEffect(() => {
        addBuildRef.current = addBuild;
    }, [addBuild]);
    reactExports.useEffect(() => {
        updateBuildRef.current = updateBuild;
    }, [updateBuild]);
    reactExports.useEffect(() => {
        updateTodosRef.current = updateTodos;
    }, [updateTodos]);
    // Subscribe to RunnerLogger events - only runs ONCE on mount
    // Uses refs for callbacks to avoid re-subscription race conditions
    reactExports.useEffect(() => {
        const logger = getLogger();
        // Sync initial state from logger (in case events already fired)
        setIsConnected(logger.isConnected());
        setIsVerbose(logger.isVerbose());
        // Load any existing builds
        const existingBuilds = logger.getAllBuilds();
        if (existingBuilds.length > 0) {
            for (const build of existingBuilds) {
                addBuildRef.current(build);
            }
        }
        // Handle build start events - use ref to get latest callback
        const handleBuildStart = (build) => {
            addBuildRef.current(build);
        };
        // Handle build update events
        const handleBuildUpdate = (build) => {
            updateBuildRef.current(build.id, build);
        };
        // Handle build complete events
        const handleBuildComplete = (build) => {
            updateBuildRef.current(build.id, { ...build, status: build.status });
        };
        // Handle todo updates
        const handleTodoUpdate = (buildId, todos) => {
            updateTodosRef.current(buildId, todos);
        };
        // Handle connection events
        const handleConnected = () => {
            setIsConnected(true);
        };
        const handleDisconnected = () => {
            setIsConnected(false);
        };
        // Handle verbose change
        const handleVerboseChange = (verbose) => {
            setIsVerbose(verbose);
        };
        // Subscribe to events
        logger.on('buildStart', handleBuildStart);
        logger.on('buildUpdate', handleBuildUpdate);
        logger.on('buildComplete', handleBuildComplete);
        logger.on('todoUpdate', handleTodoUpdate);
        logger.on('connected', handleConnected);
        logger.on('disconnected', handleDisconnected);
        logger.on('verboseChange', handleVerboseChange);
        // Cleanup - only runs on unmount
        return () => {
            logger.off('buildStart', handleBuildStart);
            logger.off('buildUpdate', handleBuildUpdate);
            logger.off('buildComplete', handleBuildComplete);
            logger.off('todoUpdate', handleTodoUpdate);
            logger.off('connected', handleConnected);
            logger.off('disconnected', handleDisconnected);
            logger.off('verboseChange', handleVerboseChange);
        };
    }, []); // Empty deps - only subscribe once on mount
    const state = {
        builds,
        currentBuildIndex,
        currentBuild,
        isConnected,
        isVerbose,
    };
    const actions = {
        nextBuild,
        prevBuild,
        setVerbose: setIsVerbose,
        toggleVerbose,
        addBuild,
        updateBuild,
        updateTodos,
        setConnected,
    };
    return [state, actions];
}
/**
 * Hook to subscribe to log entries from the buffer
 */
function useLogEntries(maxEntries = 100) {
    const [entries, setEntries] = reactExports.useState([]);
    reactExports.useEffect(() => {
        const buffer = getLogBuffer();
        // Initialize with current entries
        setEntries(buffer.getRecent(maxEntries));
        // Subscribe to new entries
        const unsubscribe = buffer.onLog((entry) => {
            setEntries(prev => {
                const newEntries = [...prev, entry];
                // Keep only the most recent entries
                if (newEntries.length > maxEntries) {
                    return newEntries.slice(-maxEntries);
                }
                return newEntries;
            });
        });
        return unsubscribe;
    }, [maxEntries]);
    return entries;
}

export { BuildPanel as B, useLogEntries as a, useBuildState as u };
//# sourceMappingURL=useBuildState-Dy7pRR8Z.js.map
