/**
 * Shared approval primitives.
 *
 * UI components:
 *   - ApprovalButtonBar — reusable [Accept] [Accept All] [Reject] button strip
 *   - Border            — horizontal rule
 */
import { APPROVAL } from '../../app/approvals/types.js';
import { Container, getEditorKeybindings, Text } from '../../tui/index.js';
import { hasCustomExtensionUI } from '../extension-ui.js';
/**
 * Create an ApprovalGate that shows a TUI component for approval.
 *
 * The only thing that varies per tool is the Component class — the
 * auto-approve check, accept-all toggle, and ctx.ui.custom ceremony
 * are always the same.
 */
export function createApprovalGate(ComponentClass, autoApproveState) {
    return {
        isAutoApproved: () => autoApproveState.getAutoApprove(),
        requestApproval: async (request, ctx) => {
            if (!hasCustomExtensionUI(ctx))
                return APPROVAL.ACCEPT;
            sharedPromptState.pending++;
            // Queue behind any pending prompt
            return new Promise((resolve) => {
                sharedPromptState.queue = sharedPromptState.queue.then(async () => {
                    sharedPromptState.shown++;
                    // Re-check auto-approve — a previous prompt in the queue
                    // may have been "Accept All"
                    if (autoApproveState.getAutoApprove() && !request.alwaysRequireExplicitApproval) {
                        resetSharedPromptCountersIfDrained();
                        resolve(APPROVAL.ACCEPT);
                        return;
                    }
                    try {
                        const queueInfo = sharedPromptState.pending > 1
                            ? {
                                current: sharedPromptState.shown,
                                total: sharedPromptState.pending
                            }
                            : undefined;
                        const choice = await ctx.ui.custom((_tui, uiTheme, _kb, done) => new ComponentClass(request.payload, uiTheme, done, queueInfo, _tui), { placement: 'conversation' });
                        resetSharedPromptCountersIfDrained();
                        resolve(choice);
                    }
                    catch {
                        resetSharedPromptCountersIfDrained();
                        resolve(undefined);
                    }
                });
            });
        },
        onAcceptAll: () => autoApproveState.setAutoApprove(true)
    };
}
export function createBatchingApprovalGate(ComponentClass) {
    let pendingBatch = [];
    let flushScheduled = false;
    const flushBatch = () => {
        if (flushScheduled || pendingBatch.length === 0)
            return;
        flushScheduled = true;
        queueMicrotask(() => {
            const batch = pendingBatch;
            pendingBatch = [];
            flushScheduled = false;
            if (batch.length === 0)
                return;
            sharedPromptState.queue = sharedPromptState.queue.then(async () => {
                const first = batch[0];
                try {
                    const choice = await first.ctx.ui.custom((_tui, uiTheme, _kb, done) => new ComponentClass(batch.map((entry) => entry.request.payload), uiTheme, done, undefined, _tui), { placement: 'conversation' });
                    for (const entry of batch)
                        entry.resolve(choice);
                }
                catch {
                    for (const entry of batch)
                        entry.resolve(undefined);
                }
            });
        });
    };
    return {
        isAutoApproved: () => false,
        requestApproval: async (request, ctx) => {
            if (!hasCustomExtensionUI(ctx))
                return APPROVAL.ACCEPT;
            return new Promise((resolve) => {
                pendingBatch.push({
                    request,
                    ctx,
                    resolve
                });
                flushBatch();
            });
        },
        onAcceptAll: () => { }
    };
}
export { APPROVAL };
// Serialize approval prompts across the one interactive TUI process. The shell
// can render only one custom approval component at a time, so per-gate queues
// are insufficient when bash/filesystem/excel/task prompts race with each
// other.
const sharedPromptState = {
    queue: Promise.resolve(),
    pending: 0,
    shown: 0
};
function resetSharedPromptCountersIfDrained() {
    if (sharedPromptState.shown >= sharedPromptState.pending) {
        sharedPromptState.pending = 0;
        sharedPromptState.shown = 0;
    }
}
// ---------------------------------------------------------------------------
// Shared UI components
// ---------------------------------------------------------------------------
const BUTTONS = [APPROVAL.ACCEPT, APPROVAL.ACCEPT_ALL, APPROVAL.REJECT];
/** Horizontal rule. Avoids importing from higher-level delivery coordinators. */
export class Border {
    color;
    constructor(color) {
        this.color = color;
    }
    invalidate() { }
    render(width) {
        return [this.color('\u2500'.repeat(Math.max(1, width)))];
    }
}
/**
 * Reusable [Accept] [Accept All] [Reject] button strip with ←/→ keyboard nav.
 *
 * Meant to be composed inside a parent Container — call `getContainer()` once
 * to embed the rendered buttons, then forward keypresses via `handleInput()`.
 */
export class ApprovalButtonBar {
    selectedButton = 0;
    container = new Container();
    theme;
    hint;
    buttons;
    constructor(theme, hint, hideAcceptAllOrButtons) {
        this.theme = theme;
        this.hint = hint ?? '←→ select  enter confirm  esc cancel';
        if (Array.isArray(hideAcceptAllOrButtons)) {
            this.buttons = hideAcceptAllOrButtons;
        }
        else {
            this.buttons = hideAcceptAllOrButtons ? [APPROVAL.ACCEPT, APPROVAL.REJECT] : BUTTONS;
        }
        this.render();
    }
    getContainer() {
        return this.container;
    }
    render() {
        this.container.clear();
        const parts = [];
        for (let i = 0; i < this.buttons.length; i++) {
            const label = this.buttons[i];
            if (i === this.selectedButton) {
                if (label === APPROVAL.ACCEPT || label === APPROVAL.ALLOW_ONCE) {
                    parts.push(this.theme.fg('success', this.theme.bold(`[ ${label} ]`)));
                }
                else if (label === APPROVAL.REJECT) {
                    parts.push(this.theme.fg('error', this.theme.bold(`[ ${label} ]`)));
                }
                else {
                    parts.push(this.theme.bold(`[ ${label} ]`));
                }
            }
            else {
                parts.push(this.theme.fg('dim', `  ${label}  `));
            }
        }
        this.container.addChild(new Text('  ' + parts.join('  '), 0, 0));
        this.container.addChild(new Text(this.theme.fg('dim', '  ' + this.hint), 0, 0));
    }
    /**
     * Handle a keypress. Returns the chosen button, or undefined if the key
     * was navigation / unrecognized.
     */
    handleInput(keyData) {
        const kb = getEditorKeybindings();
        if (kb.matches(keyData, 'cursorLeft') || keyData === 'h') {
            this.selectedButton = Math.max(0, this.selectedButton - 1);
            this.render();
        }
        else if (kb.matches(keyData, 'cursorRight') || keyData === 'l') {
            this.selectedButton = Math.min(this.buttons.length - 1, this.selectedButton + 1);
            this.render();
        }
        else if (kb.matches(keyData, 'selectConfirm') || keyData === '\n' || keyData === '\r') {
            return this.buttons[this.selectedButton];
        }
        else if (kb.matches(keyData, 'selectCancel')) {
            return APPROVAL.REJECT;
        }
        return undefined;
    }
}
//# sourceMappingURL=approval.js.map