import path from "node:path";
import { getCookies } from "@steipete/sweet-cookie";
import { runProviderDomFlow } from "../browser/providerDomFlow.js";
import { delay } from "../browser/utils.js";
import { runGeminiWebWithFallback, saveFirstGeminiImageFromOutput } from "./client.js";
import { geminiDeepThinkDomProvider } from "../browser/providers/index.js";
import { resolveGeminiWebModel } from "./models.js";
import { openGeminiBrowserSession } from "./browserSessionManager.js";
import { selectGeminiExecutionMode } from "./executionMode.js";
const GEMINI_COOKIE_NAMES = [
    "__Secure-1PSID",
    "__Secure-1PSIDTS",
    "__Secure-1PSIDCC",
    "__Secure-1PAPISID",
    "NID",
    "AEC",
    "SOCS",
    "__Secure-BUCKET",
    "__Secure-ENID",
    "SID",
    "HSID",
    "SSID",
    "APISID",
    "SAPISID",
    "__Secure-3PSID",
    "__Secure-3PSIDTS",
    "__Secure-3PAPISID",
    "SIDCC",
];
const GEMINI_REQUIRED_COOKIES = ["__Secure-1PSID", "__Secure-1PSIDTS"];
function estimateTokenCount(text) {
    return Math.ceil(text.length / 4);
}
function resolveInvocationPath(value) {
    if (!value)
        return undefined;
    const trimmed = value.trim();
    if (!trimmed)
        return undefined;
    return path.isAbsolute(trimmed) ? trimmed : path.resolve(process.cwd(), trimmed);
}
function resolveCookieDomain(cookie) {
    const rawDomain = cookie.domain?.trim();
    if (rawDomain) {
        return rawDomain.startsWith(".") ? rawDomain.slice(1) : rawDomain;
    }
    const rawUrl = cookie.url?.trim();
    if (rawUrl) {
        try {
            return new URL(rawUrl).hostname;
        }
        catch {
            return null;
        }
    }
    return null;
}
function pickCookieValue(cookies, name) {
    const matches = cookies.filter((cookie) => cookie.name === name && typeof cookie.value === "string");
    if (matches.length === 0)
        return undefined;
    const preferredDomain = matches.find((cookie) => {
        const domain = resolveCookieDomain(cookie);
        return domain === "google.com" && (cookie.path ?? "/") === "/";
    });
    const googleDomain = matches.find((cookie) => (resolveCookieDomain(cookie) ?? "").endsWith("google.com"));
    return (preferredDomain ?? googleDomain ?? matches[0])?.value;
}
function buildGeminiCookieMap(cookies) {
    const cookieMap = {};
    for (const name of GEMINI_COOKIE_NAMES) {
        const value = pickCookieValue(cookies, name);
        if (value)
            cookieMap[name] = value;
    }
    return cookieMap;
}
function hasRequiredGeminiCookies(cookieMap) {
    return GEMINI_REQUIRED_COOKIES.every((name) => Boolean(cookieMap[name]));
}
const GEMINI_CDP_COOKIE_URLS = [
    "https://gemini.google.com",
    "https://accounts.google.com",
    "https://www.google.com",
];
async function loadGeminiCookiesFromCDP(browserConfig, log) {
    const session = await openGeminiBrowserSession({
        browserConfig,
        keepBrowserDefault: false,
        purpose: "Gemini manual-login cookie extraction (no keychain)",
        log,
    });
    try {
        const client = session.client;
        const { Network, Page } = client;
        await Network.enable({});
        await Page.enable();
        log?.("[gemini-web] Navigating to gemini.google.com for sign-in/cookie capture...");
        await Page.navigate({ url: "https://gemini.google.com" });
        await delay(2_000);
        const pollTimeoutMs = 5 * 60_000;
        const pollIntervalMs = 2_000;
        const deadline = Date.now() + pollTimeoutMs;
        let lastNotice = 0;
        let cookieMap = {};
        while (Date.now() < deadline) {
            const { cookies } = await Network.getCookies({ urls: GEMINI_CDP_COOKIE_URLS });
            cookieMap = buildGeminiCookieMap(cookies);
            if (hasRequiredGeminiCookies(cookieMap)) {
                log?.(`[gemini-web] Extracted ${Object.keys(cookieMap).length} Gemini cookie(s) via CDP.`);
                return { cookieMap, warnings: [] };
            }
            const now = Date.now();
            if (now - lastNotice > 10_000) {
                log?.("[gemini-web] Waiting for Google sign-in... please sign in in the opened Chrome window.");
                lastNotice = now;
            }
            await delay(pollIntervalMs);
        }
        throw new Error("Timed out waiting for Google sign-in (5 minutes). Please sign in and retry.");
    }
    finally {
        await session.close();
    }
}
async function runGeminiDeepThinkViaBrowser(prompt, browserConfig, log) {
    const session = await openGeminiBrowserSession({
        browserConfig,
        keepBrowserDefault: true,
        purpose: "Gemini Deep Think",
        log,
    });
    try {
        const client = session.client;
        const { Runtime, Page } = client;
        if (!Runtime ||
            typeof Runtime.enable !== "function" ||
            typeof Runtime.evaluate !== "function") {
            throw new Error("Chrome Runtime domain unavailable for Gemini Deep Think DOM automation.");
        }
        if (!Page || typeof Page.enable !== "function" || typeof Page.navigate !== "function") {
            throw new Error("Chrome Page domain unavailable for Gemini Deep Think DOM automation.");
        }
        await Runtime.enable();
        await Page.enable();
        const evaluate = async (expression) => {
            const { result } = await Runtime.evaluate({ expression, returnByValue: true });
            return result?.value;
        };
        log?.("[gemini-web] Navigating to gemini.google.com...");
        await Page.navigate({ url: "https://gemini.google.com/app" });
        await delay(3_000);
        const domResult = await runProviderDomFlow(geminiDeepThinkDomProvider, {
            prompt,
            evaluate,
            delay,
            log,
            state: {
                inputTimeoutMs: browserConfig?.inputTimeoutMs,
                timeoutMs: browserConfig?.timeoutMs,
            },
        });
        log?.(`[gemini-web] Deep Think response received (${domResult.text.length} chars).`);
        return domResult;
    }
    finally {
        await session.close();
    }
}
async function loadGeminiCookiesFromInline(browserConfig, log) {
    const inline = browserConfig?.inlineCookies;
    if (!inline || inline.length === 0)
        return { cookieMap: {}, warnings: [] };
    const cookieMap = buildGeminiCookieMap(inline.filter((cookie) => Boolean(cookie?.name && typeof cookie.value === "string")));
    if (Object.keys(cookieMap).length > 0) {
        const source = browserConfig?.inlineCookiesSource ?? "inline";
        log?.(`[gemini-web] Loaded Gemini cookies from inline payload (${source}): ${Object.keys(cookieMap).length} cookie(s).`);
    }
    else {
        log?.("[gemini-web] Inline cookie payload provided but no Gemini cookies matched.");
    }
    return { cookieMap, warnings: [] };
}
async function loadGeminiCookiesFromChrome(browserConfig, log) {
    try {
        // Learned: Gemini web relies on Google auth cookies in the *browser* profile, not API keys.
        const profileCandidate = browserConfig?.chromeCookiePath ?? browserConfig?.chromeProfile ?? undefined;
        const profile = typeof profileCandidate === "string" && profileCandidate.trim().length > 0
            ? profileCandidate.trim()
            : undefined;
        const sources = [
            "https://gemini.google.com",
            "https://accounts.google.com",
            "https://www.google.com",
        ];
        const { cookies, warnings } = await getCookies({
            url: sources[0],
            origins: sources,
            names: [...GEMINI_COOKIE_NAMES],
            browsers: ["chrome"],
            mode: "merge",
            chromeProfile: profile,
            timeoutMs: 5_000,
        });
        if (warnings.length && log?.verbose) {
            log(`[gemini-web] Cookie warnings:\n- ${warnings.join("\n- ")}`);
        }
        const cookieMap = buildGeminiCookieMap(cookies);
        log?.(`[gemini-web] Loaded Gemini cookies from Chrome (node): ${Object.keys(cookieMap).length} cookie(s).`);
        return { cookieMap, warnings };
    }
    catch (error) {
        log?.(`[gemini-web] Failed to load Chrome cookies via node: ${error instanceof Error ? error.message : String(error ?? "")}`);
        return { cookieMap: {}, warnings: [] };
    }
}
function formatGeminiCookieError(warnings) {
    const base = "Gemini browser mode requires Chrome cookies for google.com (missing __Secure-1PSID/__Secure-1PSIDTS).";
    const guidance = "Try --browser-manual-login or --browser-inline-cookies-file if local cookie extraction is unavailable.";
    if (warnings.length === 0) {
        return `${base} ${guidance}`;
    }
    return `${base}\nCookie read warnings:\n- ${warnings.join("\n- ")}\n${guidance}`;
}
async function loadGeminiCookies(browserConfig, log, options) {
    const inlineResult = await loadGeminiCookiesFromInline(browserConfig, log);
    const hasInlineRequired = hasRequiredGeminiCookies(inlineResult.cookieMap);
    if (hasInlineRequired) {
        return inlineResult;
    }
    const manualNoKeychain = Boolean(browserConfig?.manualLogin) || Boolean(options?.preferManualNoKeychain);
    if (manualNoKeychain) {
        log?.("[gemini-web] Using manual-login cookie extraction path (no keychain cookie read).");
        const cdpResult = await loadGeminiCookiesFromCDP(browserConfig, log);
        return {
            cookieMap: { ...cdpResult.cookieMap, ...inlineResult.cookieMap },
            warnings: [...inlineResult.warnings, ...cdpResult.warnings],
        };
    }
    if (browserConfig?.cookieSync === false && !hasInlineRequired) {
        log?.("[gemini-web] Cookie sync disabled and inline cookies missing Gemini auth tokens.");
        return inlineResult;
    }
    const chromeResult = await loadGeminiCookiesFromChrome(browserConfig, log);
    return {
        cookieMap: { ...chromeResult.cookieMap, ...inlineResult.cookieMap },
        warnings: [...inlineResult.warnings, ...chromeResult.warnings],
    };
}
export function createGeminiWebExecutor(geminiOptions) {
    return async (runOptions) => {
        const startTime = Date.now();
        const log = runOptions.log;
        log?.("[gemini-web] Starting Gemini web executor (TypeScript)");
        const model = resolveGeminiWebModel(runOptions.config?.desiredModel, log);
        const generateImagePath = resolveInvocationPath(geminiOptions.generateImage);
        const editImagePath = resolveInvocationPath(geminiOptions.editImage);
        const outputPath = resolveInvocationPath(geminiOptions.outputPath);
        const attachmentPaths = (runOptions.attachments ?? []).map((attachment) => attachment.path);
        let prompt = runOptions.prompt;
        if (geminiOptions.aspectRatio && (generateImagePath || editImagePath)) {
            prompt = `${prompt} (aspect ratio: ${geminiOptions.aspectRatio})`;
        }
        if (geminiOptions.youtube) {
            prompt = `${prompt}\n\nYouTube video: ${geminiOptions.youtube}`;
        }
        if (generateImagePath && !editImagePath) {
            prompt = `Generate an image: ${prompt}`;
        }
        const modeSelection = selectGeminiExecutionMode({
            model,
            attachmentPaths,
            generateImagePath,
            editImagePath,
        });
        const domClient = {
            mode: "dom",
            execute: async () => {
                log?.("[gemini-web] Using browser DOM automation for Deep Think.");
                const browserResult = await runGeminiDeepThinkViaBrowser(prompt, runOptions.config, log);
                const tookMs = Date.now() - startTime;
                let answerMarkdown = browserResult.text;
                if (geminiOptions.showThoughts && browserResult.thoughts) {
                    answerMarkdown = `## Thinking\n\n${browserResult.thoughts}\n\n## Response\n\n${browserResult.text}`;
                }
                log?.(`[gemini-web] Completed in ${tookMs}ms`);
                return {
                    answerText: browserResult.text,
                    answerMarkdown,
                    tookMs,
                    answerTokens: estimateTokenCount(browserResult.text),
                    answerChars: browserResult.text.length,
                };
            },
        };
        const httpClient = {
            mode: "http",
            execute: async () => {
                const useNoKeychainPath = Boolean(runOptions.config?.manualLogin);
                const cookieResult = await loadGeminiCookies(runOptions.config, log, {
                    preferManualNoKeychain: useNoKeychainPath,
                });
                if (!hasRequiredGeminiCookies(cookieResult.cookieMap)) {
                    throw new Error(formatGeminiCookieError(cookieResult.warnings));
                }
                const configTimeout = typeof runOptions.config?.timeoutMs === "number" &&
                    Number.isFinite(runOptions.config.timeoutMs)
                    ? Math.max(1_000, runOptions.config.timeoutMs)
                    : null;
                const defaultTimeoutMs = geminiOptions.youtube
                    ? 240_000
                    : geminiOptions.generateImage || geminiOptions.editImage
                        ? 300_000
                        : 120_000;
                const timeoutMs = Math.min(configTimeout ?? defaultTimeoutMs, 600_000);
                const controller = new AbortController();
                const timeout = setTimeout(() => controller.abort(), timeoutMs);
                let response;
                try {
                    if (editImagePath) {
                        const intro = await runGeminiWebWithFallback({
                            prompt: "Here is an image to edit",
                            files: [editImagePath],
                            model,
                            cookieMap: cookieResult.cookieMap,
                            chatMetadata: null,
                            signal: controller.signal,
                        });
                        const editPrompt = `Use image generation tool to ${prompt}`;
                        const out = await runGeminiWebWithFallback({
                            prompt: editPrompt,
                            files: attachmentPaths,
                            model,
                            cookieMap: cookieResult.cookieMap,
                            chatMetadata: intro.metadata,
                            signal: controller.signal,
                        });
                        response = {
                            text: out.text ?? null,
                            thoughts: geminiOptions.showThoughts ? out.thoughts : null,
                            has_images: false,
                            image_count: 0,
                        };
                        const resolvedOutputPath = outputPath ?? generateImagePath ?? "generated.png";
                        const imageSave = await saveFirstGeminiImageFromOutput(out, cookieResult.cookieMap, resolvedOutputPath, controller.signal);
                        response.has_images = imageSave.saved;
                        response.image_count = imageSave.imageCount;
                        if (!imageSave.saved) {
                            throw new Error(`No images generated. Response text:\n${out.text || "(empty response)"}`);
                        }
                    }
                    else if (generateImagePath) {
                        const out = await runGeminiWebWithFallback({
                            prompt,
                            files: attachmentPaths,
                            model,
                            cookieMap: cookieResult.cookieMap,
                            chatMetadata: null,
                            signal: controller.signal,
                        });
                        response = {
                            text: out.text ?? null,
                            thoughts: geminiOptions.showThoughts ? out.thoughts : null,
                            has_images: false,
                            image_count: 0,
                        };
                        const imageSave = await saveFirstGeminiImageFromOutput(out, cookieResult.cookieMap, generateImagePath, controller.signal);
                        response.has_images = imageSave.saved;
                        response.image_count = imageSave.imageCount;
                        if (!imageSave.saved) {
                            throw new Error(`No images generated. Response text:\n${out.text || "(empty response)"}`);
                        }
                    }
                    else {
                        const out = await runGeminiWebWithFallback({
                            prompt,
                            files: attachmentPaths,
                            model,
                            cookieMap: cookieResult.cookieMap,
                            chatMetadata: null,
                            signal: controller.signal,
                        });
                        response = {
                            text: out.text ?? null,
                            thoughts: geminiOptions.showThoughts ? out.thoughts : null,
                            has_images: out.images.length > 0,
                            image_count: out.images.length,
                        };
                    }
                }
                finally {
                    clearTimeout(timeout);
                }
                const answerText = response.text ?? "";
                let answerMarkdown = answerText;
                if (geminiOptions.showThoughts && response.thoughts) {
                    answerMarkdown = `## Thinking\n\n${response.thoughts}\n\n## Response\n\n${answerText}`;
                }
                if (response.has_images && response.image_count > 0) {
                    const imagePath = generateImagePath || outputPath || "generated.png";
                    answerMarkdown += `\n\n*Generated ${response.image_count} image(s). Saved to: ${imagePath}*`;
                }
                const tookMs = Date.now() - startTime;
                log?.(`[gemini-web] Completed in ${tookMs}ms`);
                return {
                    answerText,
                    answerMarkdown,
                    tookMs,
                    answerTokens: estimateTokenCount(answerText),
                    answerChars: answerText.length,
                };
            },
        };
        if (model === "gemini-3-pro-deep-think" && modeSelection.mode === "http") {
            log?.(`[gemini-web] Deep Think DOM path skipped (${modeSelection.reasons.join(", ")} requested); using HTTP/header fallback path.`);
        }
        const executionClient = modeSelection.mode === "dom" ? domClient : httpClient;
        return executionClient.execute();
    };
}
