import { cli, Strategy } from '@jackwener/opencli/registry';
import { CliError, CommandExecutionError, EXIT_CODES, TimeoutError } from '@jackwener/opencli/errors';
import {
    DEEPSEEK_DOMAIN, DEEPSEEK_URL, ensureOnDeepSeek, selectModel, setFeature,
    sendMessage, sendWithFile, getBubbleCount, waitForResponse, parseBoolFlag, withRetry,
    pickResumeUrl, TEXTAREA_SELECTOR,
} from './utils.js';

export const askCommand = cli({
    site: 'deepseek',
    name: 'ask',
    access: 'write',
    description: 'Send a prompt to DeepSeek and get the response',
    domain: DEEPSEEK_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: 'instant', choices: ['instant', 'expert', 'vision'], help: 'Model to use: instant, expert, or vision' },
        { name: 'think', type: 'boolean', default: false, help: 'Enable DeepThink mode' },
        { name: 'search', type: 'boolean', default: false, help: 'Enable web search' },
        { name: 'file', help: 'Attach a file (PDF, image, text) with the prompt' },
    ],
    // columns omitted: derived from row keys so non-think output shows only 'response'

    func: async (page, kwargs) => {
        const prompt = kwargs.prompt;
        const timeoutMs = (kwargs.timeout || 120) * 1000;
        const wantThink = parseBoolFlag(kwargs.think);
        const wantSearch = parseBoolFlag(kwargs.search);
        const wantModel = kwargs.model || 'instant';

        if ((wantModel === 'vision' || wantModel === 'expert') && wantSearch) {
            throw new CliError(
                'ARGUMENT',
                `DeepSeek ${wantModel} mode does not support --search.`,
                'Run without --search, or use --model instant for web search.',
                EXIT_CODES.USAGE_ERROR,
            );
        }

        if (parseBoolFlag(kwargs.new)) {
            await page.goto(DEEPSEEK_URL);
            // Wait for the composer to mount instead of a fixed 3 s sleep.
            try {
                await page.wait({ selector: TEXTAREA_SELECTOR, timeout: 8 });
            } catch {
                // Selector still missing → downstream selectModel/sendMessage
                // will surface the failure with a typed error.
            }
        } else {
            const navigated = await ensureOnDeepSeek(page);
            if (navigated) {
                // Pinned conversations sit in their own DOM section and are
                // skipped so the resume never lands on a topped chat.
                const resumeUrl = await pickResumeUrl(page);
                if (!resumeUrl) {
                    throw new CommandExecutionError(
                        'Workspace was recycled but no prior conversation could be loaded',
                        'Pass --new to start a fresh chat, or wait for the sidebar to populate before retrying.',
                    );
                }
                await page.goto(resumeUrl);
                try {
                    await page.wait({ selector: TEXTAREA_SELECTOR, timeout: 5 });
                } catch {
                    // Conversation page may still be loading; subsequent steps
                    // will retry or report.
                }
            }
        }

        // 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('/a/chat/s/');
        const modelExplicit = kwargs.__opencliOptionSources?.model === 'cli';

        if (inConversation && modelExplicit) {
            throw new CliError(
                'ARGUMENT',
                `Cannot switch to ${wantModel} model inside an existing conversation.`,
                'Re-run with --new to start a fresh chat before selecting a model.',
                EXIT_CODES.USAGE_ERROR,
            );
        }

        if (!inConversation) {
            const modelResult = await withRetry(() => selectModel(page, wantModel));
            if (!modelResult?.ok) {
                throw new CommandExecutionError(`Could not switch to ${wantModel} model`);
            }
            // The 0.5 s settle previously here was redundant: each subsequent
            // step (setFeature, sendMessage) issues a fresh CDP eval, giving
            // React more than enough time to flush the toggle's state update.
        }

        const thinkResult = await withRetry(() => setFeature(page, 'DeepThink', wantThink));
        if (!thinkResult?.ok && wantThink) {
            throw new CommandExecutionError('Could not enable DeepThink');
        }

        // Only instant mode has the Search toggle in the DeepSeek UI.
        let searchResult;
        if (wantModel !== 'vision' && wantModel !== 'expert') {
            searchResult = await withRetry(() => setFeature(page, 'Search', wantSearch));
            if (!searchResult?.ok && wantSearch) {
                throw new CommandExecutionError('Could not enable Search');
            }
        }

        // No settle wait after toggles: the next CDP eval below already gives
        // React time to flush the aria-checked state.

        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;
            }
            // waitForResponse polls every 3 s for new bubbles, so the previous
            // 3 s settle here was a redundant sleep on top of the first poll.
            const result = await waitForResponse(page, baseline, prompt, timeoutMs, wantThink);
            if (!result) {
                throw new TimeoutError('deepseek ask', kwargs.timeout, 'No DeepSeek reply observed before timeout. Retry with --timeout increased.');
            }
            if (wantThink && typeof result === 'object' && result.response !== undefined) {
                return [result];
            }
            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, wantThink);
        if (!result) {
            throw new TimeoutError('deepseek ask', kwargs.timeout, 'No DeepSeek reply observed before timeout. Retry with --timeout increased.');
        }

        if (wantThink && typeof result === 'object' && result.response !== undefined) {
            return [result];
        }
        return [{ response: result }];
    },
});
