import { jsx as _jsx } from "react/jsx-runtime";
/**
 * @license
 * Copyright 2025 HopCode Team
 * SPDX-License-Identifier: Apache-2.0
 */
/**
 * BackgroundTaskViewContext — React state for the Background tasks
 * dialog. Subscription plumbing (registry callbacks → entries) lives in
 * `useBackgroundTaskView`, invoked once here so it owns the single-slot
 * `setStatusChangeCallback` for the TUI's lifetime.
 */
import { createContext, useContext, useCallback, useEffect, useMemo, useState, } from 'react';
import { createDebugLogger } from '@hoptrendy/hopcode-core';
import { useBackgroundTaskView, } from '../hooks/useBackgroundTaskView.js';
const debugLogger = createDebugLogger('BG_TASK_VIEW');
// ─── Context ────────────────────────────────────────────────
export const BackgroundTaskViewStateContext = createContext(null);
export const BackgroundTaskViewActionsContext = createContext(null);
// ─── Defaults (used when no provider is mounted) ────────────
const DEFAULT_STATE = {
    entries: [],
    selectedIndex: 0,
    dialogMode: 'closed',
    dialogOpen: false,
    pillFocused: false,
};
const noop = () => { };
const noopBool = () => false;
const DEFAULT_ACTIONS = {
    moveSelectionUp: noopBool,
    moveSelectionDown: noopBool,
    openDialog: noop,
    closeDialog: noop,
    enterDetail: noop,
    exitDetail: noop,
    cancelSelected: noop,
    resumeSelected: async () => { },
    setPillFocused: noop,
};
// ─── Hooks ──────────────────────────────────────────────────
export function useBackgroundTaskViewState() {
    return useContext(BackgroundTaskViewStateContext) ?? DEFAULT_STATE;
}
export function useBackgroundTaskViewActions() {
    return useContext(BackgroundTaskViewActionsContext) ?? DEFAULT_ACTIONS;
}
export function BackgroundTaskViewProvider({ config, children, }) {
    const { entries } = useBackgroundTaskView(config ?? null);
    const [rawSelectedIndex, setRawSelectedIndex] = useState(0);
    const [dialogMode, setDialogMode] = useState('closed');
    const [pillFocused, setPillFocused] = useState(false);
    const dialogOpen = dialogMode !== 'closed';
    const hasEntries = entries.length > 0;
    // Drop stale pill focus once the pill itself unmounts — i.e., when the
    // registry is empty. The pill stays rendered while terminal entries
    // exist (so the user can reopen the dialog post-termination), so we
    // intentionally do *not* drop focus on the running → terminal flip.
    useEffect(() => {
        if (pillFocused && !hasEntries)
            setPillFocused(false);
    }, [pillFocused, hasEntries]);
    // rawSelectedIndex can fall out of range when entries shrink; clamp on read.
    const selectedIndex = entries.length === 0
        ? 0
        : Math.min(Math.max(0, rawSelectedIndex), entries.length - 1);
    const moveSelectionUp = useCallback(() => {
        if (selectedIndex <= 0)
            return false;
        setRawSelectedIndex(selectedIndex - 1);
        return true;
    }, [selectedIndex]);
    const moveSelectionDown = useCallback(() => {
        if (entries.length === 0)
            return false;
        if (selectedIndex >= entries.length - 1)
            return false;
        setRawSelectedIndex(selectedIndex + 1);
        return true;
    }, [entries.length, selectedIndex]);
    const openDialog = useCallback(() => {
        setDialogMode('list');
        setPillFocused(false);
    }, []);
    const closeDialog = useCallback(() => {
        setDialogMode('closed');
    }, []);
    const enterDetail = useCallback(() => {
        if (entries.length === 0)
            return;
        setDialogMode('detail');
    }, [entries.length]);
    const exitDetail = useCallback(() => {
        setDialogMode('list');
    }, []);
    const cancelSelected = useCallback(() => {
        if (!config)
            return;
        const target = entries[selectedIndex];
        if (!target)
            return;
        if (target.kind === 'agent' && target.status === 'paused') {
            config.abandonBackgroundAgent(target.agentId);
            return;
        }
        // All three registries' cancel paths are no-ops on non-running
        // entries, so no pre-check here. Shell cancel goes through
        // requestCancel — it triggers the AbortController only and lets the
        // spawn's settle path record the real terminal moment + outcome
        // (mirrors the task_stop tool path in #3687). Monitor cancel is
        // synchronous: settle + abort happen inside the registry's cancel(),
        // matching its own task_stop path.
        switch (target.kind) {
            case 'agent':
                config.getBackgroundTaskRegistry().cancel(target.agentId);
                break;
            case 'shell':
                config.getBackgroundShellRegistry().requestCancel(target.shellId);
                break;
            case 'monitor':
                config.getMonitorRegistry().cancel(target.monitorId);
                break;
            case 'dream': {
                // Aborts the dream fork-agent via MemoryManager.cancelTask;
                // the manager flips status to 'cancelled' before aborting, and
                // the runDream finally block releases the consolidation lock as
                // the agent unwinds. Same one-shot fire-and-forget shape as
                // shell.requestCancel above.
                //
                // cancelTask returns false in the contract-violation path
                // (running record without an AbortController). Today this is
                // unreachable because the controller is registered before
                // storeWith fires the notify, but if a future refactor
                // breaks the invariant a silent ignore here would let the
                // user think the cancel took. Log + leave the dialog open.
                const ok = config.getMemoryManager().cancelTask(target.dreamId);
                if (!ok) {
                    debugLogger.warn(`cancelSelected: dream task ${target.dreamId} could not be cancelled ` +
                        `(internal state inconsistency — see MemoryManager.cancelTask warn).`);
                }
                break;
            }
            default: {
                const _exhaustive = target;
                throw new Error(`cancelSelected: unknown DialogEntry kind: ${JSON.stringify(_exhaustive)}`);
            }
        }
    }, [config, entries, selectedIndex]);
    const resumeSelected = useCallback(async () => {
        if (!config)
            return;
        const target = entries[selectedIndex];
        if (!target ||
            target.kind !== 'agent' ||
            target.status !== 'paused' ||
            target.resumeBlockedReason) {
            return;
        }
        await config.resumeBackgroundAgent(target.agentId);
    }, [config, entries, selectedIndex]);
    const state = useMemo(() => ({
        entries,
        selectedIndex,
        dialogMode,
        dialogOpen,
        pillFocused,
    }), [entries, selectedIndex, dialogMode, dialogOpen, pillFocused]);
    const actions = useMemo(() => ({
        moveSelectionUp,
        moveSelectionDown,
        openDialog,
        closeDialog,
        enterDetail,
        exitDetail,
        cancelSelected,
        resumeSelected,
        setPillFocused,
    }), [
        moveSelectionUp,
        moveSelectionDown,
        openDialog,
        closeDialog,
        enterDetail,
        exitDetail,
        cancelSelected,
        resumeSelected,
        setPillFocused,
    ]);
    return (_jsx(BackgroundTaskViewStateContext.Provider, { value: state, children: _jsx(BackgroundTaskViewActionsContext.Provider, { value: actions, children: children }) }));
}
//# sourceMappingURL=BackgroundTaskViewContext.js.map