import * as vscode from 'vscode';
import { logError, logInfo } from '../../utils';

/**
 * Pending-approval coordination for ConnectAI.
 *
 * Why this module exists:
 *   - The agent already has a transaction-based "dry run" approval flow
 *     (see agent.ts:2362, transactionManager.commit/rollback). The decision
 *     UI lives inside the chat as an inline box, which is fine for a single
 *     prompt but misses the broader problem: the user wants a stable place
 *     to see "what is the agent waiting for me to approve?", with a status
 *     bar badge that pulls them in.
 *
 *   - Connect_origin solves this with a queue of pending actions in a
 *     dashboard. ConnectAI today only ever has a single active transaction,
 *     so we model the queue as **0..1 current approval** to keep the surface
 *     small. Extending to N approvals later is a list change inside this
 *     module — no consumer needs to switch shapes.
 *
 * Wiring (read-only summary):
 *   agent.ts (dryRun)  ──enqueue──▶  ApprovalQueue  ──onChange──▶  ApprovalPanelProvider (webview)
 *                                                  ──onChange──▶  ApprovalStatusBar (badge)
 *   webview button     ──approve/reject──▶ ApprovalQueue ──invokes callback──▶ agent.approveTransaction()
 */

export type ApprovalKind = 'transaction' | 'file-write' | 'file-create' | 'file-delete' | 'command';

export interface Approval {
    id: string;
    kind: ApprovalKind;
    title: string;
    summary: string;
    files: string[];
    createdAt: number;
}

export interface ApprovalCallbacks {
    approve: () => Promise<void> | void;
    reject: () => Promise<void> | void;
}

interface ApprovalEntry {
    approval: Approval;
    callbacks: ApprovalCallbacks;
}

export class ApprovalQueue {
    private _current: ApprovalEntry | null = null;
    private readonly _emitter = new vscode.EventEmitter<void>();
    /** Fires whenever the current approval changes (set / approved / rejected / cleared). */
    public readonly onChange = this._emitter.event;

    /**
     * Replace the currently pending approval, if any. The previous approval is
     * silently dropped — its callbacks are NOT invoked. This matches the
     * agent's transaction model: the new dry-run pre-empts whatever was waiting.
     */
    enqueue(approval: Approval, callbacks: ApprovalCallbacks): void {
        if (this._current) {
            logInfo('Approval pre-empted by newer pending approval.', {
                droppedId: this._current.approval.id,
                newId: approval.id,
            });
        }
        this._current = { approval, callbacks };
        logInfo('Approval enqueued.', { id: approval.id, kind: approval.kind, fileCount: approval.files.length });
        this._emitter.fire();
    }

    /** Returns the currently pending approval, or null. */
    current(): Approval | null {
        return this._current?.approval ?? null;
    }

    /** 0 or 1 with the current model — future-proof for a real queue. */
    pendingCount(): number {
        return this._current ? 1 : 0;
    }

    /**
     * Approve the pending entry whose id matches. If `id` is omitted, approve
     * the current entry. Mismatched ids are ignored to avoid stale-button
     * double-fire from the webview.
     */
    async approve(id?: string): Promise<void> {
        const entry = this._take(id);
        if (!entry) return;
        try {
            await entry.callbacks.approve();
        } catch (e: any) {
            logError('Approval approve callback threw.', { id: entry.approval.id, error: e?.message ?? String(e) });
        }
    }

    async reject(id?: string): Promise<void> {
        const entry = this._take(id);
        if (!entry) return;
        try {
            await entry.callbacks.reject();
        } catch (e: any) {
            logError('Approval reject callback threw.', { id: entry.approval.id, error: e?.message ?? String(e) });
        }
    }

    /** Clear without firing callbacks (used by host on shutdown). */
    clear(): void {
        if (!this._current) return;
        this._current = null;
        this._emitter.fire();
    }

    dispose(): void {
        this._current = null;
        this._emitter.dispose();
    }

    private _take(id?: string): ApprovalEntry | null {
        if (!this._current) return null;
        if (id !== undefined && id !== this._current.approval.id) {
            logInfo('Approval id mismatch — ignoring.', { requested: id, current: this._current.approval.id });
            return null;
        }
        const entry = this._current;
        this._current = null;
        this._emitter.fire();
        return entry;
    }
}
