import { cli, Strategy } from '@jackwener/opencli/registry';
import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
import {
    CLAUDE_DOMAIN, CLAUDE_URL, COMPOSER_SELECTOR, MESSAGE_SELECTOR,
    ensureOnClaude, selectModel, setAdaptiveThinking,
    sendMessage, sendWithFile, getBubbleCount, waitForResponse, parseBoolFlag, withRetry,
    ensureClaudeComposer, requireNonEmptyPrompt, requirePositiveInt,
} from './utils.js';

export const askCommand = cli({
    site: 'claude',
    name: 'ask',
    access: 'write',
    description: 'Send a prompt to Claude and get the response',
    domain: CLAUDE_DOMAIN,
    strategy: Strategy.COOKIE,
    browser: true,
    siteSession: 'persistent',
    navigateBefore: false,
    args: [
        { name: 'prompt', positional: true, required: true, help: 'Prompt to send' },
        { name: 'timeout', type: 'int', default: 120, help: 'Max seconds to wait for response' },
        { name: 'new', type: 'boolean', default: false, help: 'Start a new chat before sending' },
        { name: 'model', default: 'sonnet', choices: ['sonnet', 'opus', 'haiku'], help: 'Model to use: sonnet, opus, or haiku' },
        { name: 'think', type: 'boolean', default: false, help: 'Enable Adaptive thinking' },
        { name: 'file', help: 'Attach a file (image, PDF, text) with the prompt' },
    ],
    columns: ['response'],

    func: async (page, kwargs) => {
        const prompt = requireNonEmptyPrompt(kwargs.prompt, 'claude ask');
        const timeoutSeconds = requirePositiveInt(
            Number(kwargs.timeout ?? 120),
            'claude ask --timeout',
            'Example: opencli claude ask "hello" --timeout 120',
        );
        const timeoutMs = timeoutSeconds * 1000;
        const wantThink = parseBoolFlag(kwargs.think);

        if (parseBoolFlag(kwargs.new)) {
            await page.goto(CLAUDE_URL);
            try {
                await page.wait({ selector: COMPOSER_SELECTOR, timeout: 8 });
            } catch {
                // Composer didn't mount; ensureClaudeComposer below surfaces a typed error.
            }
        } else {
            const navigated = await ensureOnClaude(page);
            if (navigated) {
                // Workspace was recycled; try to resume the most recent
                // conversation instead of starting a new one.
                await page.evaluate(`(() => {
                    var link = document.querySelector('a[href*="/chat/"]');
                    if (link) link.click();
                })()`);
                // Wait for the resumed conversation to render messages, or
                // fall through if the link click had no effect (no recents).
                try {
                    await page.wait({ selector: MESSAGE_SELECTOR, timeout: 5 });
                } catch {
                    // No prior conversation; ensureClaudeComposer still requires composer below.
                }
            }
        }

        // ensureClaudeComposer reads composer presence directly via getPageState,
        // so the previous standalone 2 s settle is redundant.
        await withRetry(() => ensureClaudeComposer(page, 'Claude ask requires a visible composer on the current page.'));

        // Model selector is only available on the new-chat page, not inside
        // an existing conversation. Skip it when we resumed a prior thread.
        const currentUrl = await page.evaluate('window.location.href') || '';
        const inConversation = currentUrl.includes('/chat/');
        const modelExplicit = kwargs.__opencliOptionSources?.model === 'cli';

        const wantModel = kwargs.model || 'sonnet';
        if (inConversation && modelExplicit) {
            throw new ArgumentError(
                `Cannot switch to ${wantModel} model inside an existing conversation.`,
                'Re-run with --new to start a fresh chat before selecting a model.',
            );
        }

        if (!inConversation) {
            const modelResult = await withRetry(() => selectModel(page, wantModel));
            if (!modelResult?.ok) {
                if (modelResult?.upgrade) {
                    throw new ArgumentError(
                        `${wantModel} model requires a paid Claude plan.`,
                        'Pick --model sonnet or --model haiku, or upgrade your account.',
                    );
                }
                throw new CommandExecutionError(`Could not switch to ${wantModel} model`);
            }
            // Post-toggle settle dropped — the next CDP eval (setAdaptiveThinking) gives
            // React enough time to flush aria-checked updates between rountrips.
        }

        const thinkResult = await withRetry(() => setAdaptiveThinking(page, wantThink));
        if (!thinkResult?.ok && wantThink) {
            throw new CommandExecutionError('Could not enable Adaptive thinking');
        }
        // Post-toggle settle dropped — the next CDP eval (sendMessage / sendWithFile)
        // gives React enough time to flush aria-checked updates.

        if (kwargs.file) {
            const baseline = await withRetry(() => getBubbleCount(page));
            try {
                const fileResult = await sendWithFile(page, kwargs.file, prompt);
                if (fileResult && !fileResult.ok) {
                    throw new CommandExecutionError(fileResult.reason || 'Failed to attach file');
                }
            } catch (err) {
                // SPA navigates after send; "Promise was collected" means send succeeded
                if (!String(err?.message || err).includes('Promise was collected')) throw err;
            }
            // Pre-waitForResponse settle dropped — waitForResponse's first 3 s polling
            // tick covers the same window without an unconditional sleep.
            const result = await waitForResponse(page, baseline, prompt, timeoutMs);
            if (!result) {
                throw new EmptyResultError(
                    'claude ask',
                    `No Claude response appeared within ${timeoutSeconds}s. Re-run with a higher --timeout if the model is still generating.`,
                );
            }
            return [{ response: result }];
        }

        const baseline = await withRetry(() => getBubbleCount(page));
        const sendResult = await withRetry(() => sendMessage(page, prompt));
        if (!sendResult?.ok) {
            throw new CommandExecutionError(sendResult?.reason || 'Failed to send message');
        }

        const result = await waitForResponse(page, baseline, prompt, timeoutMs);
        if (!result) {
            throw new EmptyResultError(
                'claude ask',
                `No Claude response appeared within ${timeoutSeconds}s. Re-run with a higher --timeout if the model is still generating.`,
            );
        }
        return [{ response: result }];
    },
});
