"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAX_CAPTURE_TIMEOUT_SECONDS = exports.CAPTURE_LOG_LEVEL_VALUES = void 0;
exports.resolveCaptureLogLevel = resolveCaptureLogLevel;
exports.validateCaptureTimeout = validateCaptureTimeout;
exports.runCapture = runCapture;
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const playwright_1 = require("./playwright");
const sessionCookie_1 = require("./sessionCookie");
const FRAME_SELECTOR = 'iframe[title="Game"]';
exports.CAPTURE_LOG_LEVEL_VALUES = ['debug', 'info', 'warn', 'error'];
exports.MAX_CAPTURE_TIMEOUT_SECONDS = 600;
function formatConsoleValue(value) {
    if (typeof value === 'string')
        return value;
    if (typeof value === 'number' || typeof value === 'boolean' || value === null) {
        return String(value);
    }
    if (typeof value === 'undefined') {
        return 'undefined';
    }
    if (typeof value === 'function') {
        return '[function]';
    }
    if (typeof value === 'object') {
        try {
            return JSON.stringify(value);
        }
        catch {
            return '[object]';
        }
    }
    return String(value);
}
function serializePayload(payload) {
    if (payload === undefined) {
        return '';
    }
    if (typeof payload === 'string') {
        return payload;
    }
    try {
        return JSON.stringify(payload);
    }
    catch {
        return String(payload);
    }
}
function severityOrder(level) {
    switch (level) {
        case 'debug':
            return 0;
        case 'info':
            return 1;
        case 'warn':
            return 2;
        case 'error':
            return 3;
        default:
            return 1;
    }
}
function mapConsoleTypeToLevel(type) {
    const normalized = type.toLowerCase();
    if (normalized === 'debug')
        return 'debug';
    if (normalized === 'warning' || normalized === 'warn')
        return 'warn';
    if (normalized === 'error' || normalized === 'assert' || normalized === 'trace')
        return 'error';
    return 'info';
}
function shouldEmit(level, threshold) {
    return severityOrder(level) >= severityOrder(threshold);
}
function normalizeComparableUrl(rawUrl) {
    const parsed = new URL(rawUrl);
    const pathname = parsed.pathname.replace(/\/+$/, '') || '/';
    return `${parsed.origin}${pathname}`;
}
function resolveAutomationOrigin(targetUrl) {
    const parsed = new URL(targetUrl);
    return parsed.searchParams.get('launchCheck') === '1' ? parsed.origin : null;
}
function normalizeHostedLaunchStatePayload(payload) {
    if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
        return null;
    }
    const state = typeof payload.state === 'string'
        ? payload.state.trim().toLowerCase()
        : '';
    if (state === 'ready') {
        return { state: 'ready' };
    }
    if (state === 'login_required') {
        return { state: 'login_required' };
    }
    if (state === 'controller_required') {
        return { state: 'controller_required' };
    }
    if (state === 'surface_unsupported') {
        return { state: 'surface_unsupported' };
    }
    if (state !== 'error') {
        return null;
    }
    const errorCode = typeof payload.errorCode === 'string'
        ? payload.errorCode.trim() || null
        : null;
    const message = typeof payload.message === 'string'
        ? payload.message.trim() || null
        : null;
    return {
        state: 'error',
        errorCode,
        message,
    };
}
function formatHostedLaunchError(state) {
    const parts = ['Hosted app failed to launch.'];
    if (state.errorCode) {
        parts.push(`errorCode=${state.errorCode}.`);
    }
    if (state.message) {
        parts.push(state.message);
    }
    return parts.join(' ');
}
function formatHostedLaunchStateMismatch(actualState, expectedState) {
    if (expectedState === "login_required") {
        return `Hosted app reported ${actualState} instead of login_required.`;
    }
    if (expectedState === "controller_required") {
        return `Hosted app reported ${actualState} instead of controller_required.`;
    }
    if (expectedState === "surface_unsupported") {
        return `Hosted app reported ${actualState} instead of surface_unsupported.`;
    }
    return `Hosted app reported ${actualState} instead of ready.`;
}
function shouldSettleHostedLaunchWaiter(state, expectedState) {
    if (state.state === "error" || !expectedState) {
        return true;
    }
    return state.state === expectedState;
}
async function writeLogFile(logPath, lines) {
    if (!logPath || logPath.trim().length === 0) {
        return;
    }
    await (0, promises_1.mkdir)((0, node_path_1.dirname)(logPath), { recursive: true });
    await (0, promises_1.writeFile)(logPath, `${lines.join('\n')}\n`, 'utf8');
    console.log(`[capture] Saved logs to ${(0, node_path_1.relative)(process.cwd(), logPath) || logPath}`);
}
function resolveCaptureLogLevel(value) {
    if (!value)
        return 'info';
    const normalized = value.trim().toLowerCase();
    if (exports.CAPTURE_LOG_LEVEL_VALUES.includes(normalized)) {
        return normalized;
    }
    throw new Error(`Unsupported log level "${value}". Choose one of: ${exports.CAPTURE_LOG_LEVEL_VALUES.join(', ')}`);
}
function validateCaptureTimeout(value) {
    if (value === undefined || Number.isNaN(value)) {
        return 5;
    }
    if (!Number.isFinite(value) || value <= 0) {
        throw new Error('Timeout must be a positive number of seconds.');
    }
    if (value > exports.MAX_CAPTURE_TIMEOUT_SECONDS) {
        throw new Error(`Timeout cannot exceed ${exports.MAX_CAPTURE_TIMEOUT_SECONDS} seconds (10 minutes).`);
    }
    return value;
}
async function runCapture(options) {
    const errors = [];
    const outputLines = [];
    const expectedUrl = options.expectedUrl ? normalizeComparableUrl(options.expectedUrl) : null;
    const expectedHostedLaunchState = options.requireHostedLaunchReady
        ? options.expectedHostedLaunchState ?? 'ready'
        : null;
    const hasExplicitLogin = Boolean(options.login?.username);
    const shouldBootstrapSavedSession = options.savedSessionBootstrap !== false && !hasExplicitLogin;
    const bootstrapToken = shouldBootstrapSavedSession ? options.token?.trim() || null : null;
    const bootstrapUser = shouldBootstrapSavedSession ? options.user ?? null : null;
    let finalUrl = options.targetUrl;
    let hostedLaunchState = null;
    const record = (level, message) => {
        const line = `[capture][${level}] ${message}`;
        outputLines.push(line);
        if (level === 'error') {
            console.error(line);
            return;
        }
        if (level === 'warn') {
            console.warn(line);
            return;
        }
        if (level === 'debug') {
            console.debug(line);
            return;
        }
        console.log(line);
    };
    const recordError = (message) => {
        errors.push(message);
        record('error', message);
    };
    try {
        await (0, playwright_1.withChromiumPage)(async ({ context, page }) => {
            const targetOrigin = new URL(options.targetUrl).origin;
            let hostedLaunchWaiterSettled = false;
            let resolveHostedLaunchWaiter = null;
            const hostedLaunchWaiter = options.requireHostedLaunchReady
                ? new Promise((resolve) => {
                    resolveHostedLaunchWaiter = resolve;
                })
                : null;
            const settleHostedLaunchWaiter = (state) => {
                hostedLaunchState = state;
                if (!hostedLaunchWaiter || hostedLaunchWaiterSettled) {
                    return;
                }
                if (!shouldSettleHostedLaunchWaiter(state, expectedHostedLaunchState)) {
                    return;
                }
                hostedLaunchWaiterSettled = true;
                resolveHostedLaunchWaiter?.(state);
            };
            if (bootstrapToken) {
                await context.addCookies((0, sessionCookie_1.buildCaptureAccessTokenCookies)(options.targetUrl, bootstrapToken));
            }
            if (bootstrapToken || bootstrapUser) {
                await page.addInitScript(({ token, user }) => {
                    try {
                        if (token) {
                            window.localStorage.setItem('playdrop.accessToken', token);
                        }
                    }
                    catch {
                        // ignore storage errors
                    }
                    try {
                        if (user) {
                            window.localStorage.setItem('playdrop.user', JSON.stringify(user));
                        }
                    }
                    catch {
                        // ignore storage errors
                    }
                    try {
                        window.sessionStorage.removeItem('playdrop.logoutReason');
                    }
                    catch {
                        // ignore storage errors
                    }
                }, { token: bootstrapToken, user: bootstrapUser });
            }
            if (options.enableCaptureBridge) {
                await page.exposeBinding('__playdropCaptureLog', async (_source, type, payload) => {
                    if (type === 'hosted-launch-state') {
                        const normalizedHostedState = normalizeHostedLaunchStatePayload(payload);
                        if (normalizedHostedState) {
                            settleHostedLaunchWaiter(normalizedHostedState);
                        }
                    }
                    const normalized = typeof type === 'string' ? type.toLowerCase() : 'info';
                    const level = normalized === 'error' || normalized === 'fatal'
                        ? 'error'
                        : normalized === 'warn' || normalized === 'warning'
                            ? 'warn'
                            : normalized === 'debug'
                                ? 'debug'
                                : 'info';
                    const serialized = serializePayload(payload);
                    const line = ['[capture][custom]', normalized, serialized].filter(Boolean).join(' ');
                    if (level === 'error') {
                        errors.push(line);
                        if (shouldEmit(level, options.minimumLogLevel)) {
                            console.error(line);
                        }
                        outputLines.push(line);
                        return;
                    }
                    outputLines.push(line);
                    if (!shouldEmit(level, options.minimumLogLevel)) {
                        return;
                    }
                    if (level === 'warn') {
                        console.warn(line);
                    }
                    else if (level === 'debug') {
                        console.debug(line);
                    }
                    else {
                        console.log(line);
                    }
                });
                await page.addInitScript(() => {
                    window.addEventListener('unhandledrejection', event => {
                        try {
                            console.error('[capture][unhandledrejection]', event.reason);
                        }
                        catch {
                            console.error('[capture][unhandledrejection]');
                        }
                    });
                    const bridgeWindow = window;
                    bridgeWindow.playdrop = bridgeWindow.playdrop || {};
                    bridgeWindow.__playdropCaptureHostedLaunchState = null;
                    const bridge = (type, payload) => {
                        if (type === 'hosted-launch-state') {
                            bridgeWindow.__playdropCaptureHostedLaunchState = payload;
                        }
                        try {
                            const binding = bridgeWindow.__playdropCaptureLog;
                            if (typeof binding === 'function') {
                                binding(type, payload);
                            }
                        }
                        catch (error) {
                            console.error('[capture][bridge-error]', error);
                        }
                    };
                    Object.defineProperty(bridgeWindow.playdrop, 'capture', {
                        value: bridge,
                        configurable: true,
                        writable: true,
                    });
                });
            }
            const handleConsoleMessage = async (message) => {
                const type = message.type();
                const level = mapConsoleTypeToLevel(type);
                const args = await Promise.all(message.args().map(arg => arg.jsonValue().catch(() => arg.toString())));
                const rendered = args.length > 0 ? args.map(formatConsoleValue).join(' ') : message.text();
                const line = `[capture][console:${type}] ${rendered}`;
                outputLines.push(line);
                let effectiveLevel = level;
                if (level === 'warn' && line.includes('GL Driver Message')) {
                    effectiveLevel = 'debug';
                }
                if (type === 'error' || type === 'assert' || type === 'trace') {
                    errors.push(line);
                    if (shouldEmit(effectiveLevel, options.minimumLogLevel)) {
                        console.error(line);
                    }
                    return;
                }
                if (!shouldEmit(effectiveLevel, options.minimumLogLevel)) {
                    return;
                }
                if (effectiveLevel === 'warn') {
                    console.warn(line);
                }
                else if (effectiveLevel === 'debug') {
                    console.debug(line);
                }
                else if (effectiveLevel === 'info') {
                    console.info(line);
                }
                else {
                    console.log(line);
                }
            };
            page.on('console', message => {
                void handleConsoleMessage(message).catch((error) => {
                    const text = error instanceof Error ? error.message : String(error);
                    recordError(`[console-handler] Failed to inspect console message: ${text}`);
                });
            });
            page.on('pageerror', error => {
                const text = error?.message ?? String(error);
                errors.push(text);
                outputLines.push(`[capture][pageerror] ${text}`);
                if (shouldEmit('error', options.minimumLogLevel)) {
                    console.error(`[capture][pageerror] ${text}`);
                }
            });
            page.on('requestfailed', request => {
                const failure = request.failure();
                const errorText = failure?.errorText ?? '';
                const text = `${request.method()} ${request.url()} - ${errorText || 'failed'}`;
                if (/ERR_ABORTED/i.test(errorText) || /ERR_HTTP2_PROTOCOL_ERROR/i.test(errorText)) {
                    const line = `[capture][requestcancelled] ${text}`;
                    outputLines.push(line);
                    if (shouldEmit('debug', options.minimumLogLevel)) {
                        console.debug(line);
                    }
                    return;
                }
                errors.push(text);
                outputLines.push(`[capture][requestfailed] ${text}`);
                if (shouldEmit('warn', options.minimumLogLevel)) {
                    console.error(`[capture][requestfailed] ${text}`);
                }
            });
            page.on('response', response => {
                const status = response.status();
                if (status < 400) {
                    return;
                }
                const text = `${status} ${response.statusText()} ${response.url()}`;
                if (status === 404 && /\\?playdrop_channel=/.test(response.url())) {
                    const line = `[capture][response-reload] ${text}`;
                    outputLines.push(line);
                    if (shouldEmit('debug', options.minimumLogLevel)) {
                        console.debug(line);
                    }
                    return;
                }
                errors.push(text);
                outputLines.push(`[capture][response] ${text}`);
                if (shouldEmit('warn', options.minimumLogLevel)) {
                    console.error(`[capture][response] ${text}`);
                }
            });
            if (options.login?.username) {
                const loginUrl = options.login.loginUrl?.trim() || `${targetOrigin}/login`;
                record('info', `Opening login page ${loginUrl}`);
                await page.goto(loginUrl, { waitUntil: 'domcontentloaded', timeout: 30000 });
                await page.waitForLoadState('networkidle', { timeout: 30000 });
                await page.fill('input[name="username"]', options.login.username);
                await page.fill('input[name="password"]', options.login.password);
                await page.getByRole('button', { name: 'Login' }).click();
                await page.waitForURL((url) => {
                    try {
                        return !new URL(url.toString()).pathname.startsWith('/login');
                    }
                    catch {
                        return false;
                    }
                }, { timeout: 30000 });
                await page.waitForLoadState('networkidle', { timeout: 30000 });
                record('info', 'Login succeeded');
            }
            record('info', `Opening ${options.targetUrl}`);
            const response = await page.goto(options.targetUrl, { waitUntil: 'domcontentloaded', timeout: 30000 });
            if (response && !response.ok()) {
                recordError(`[navigation] ${response.status()} ${response.statusText()} ${options.targetUrl}`);
            }
            const readinessTimeoutMs = Math.min(Math.max(options.timeoutMs, 1000), 30000);
            const readinessHandle = await page.waitForFunction(({ frameSelector, waitForHostedLaunchState }) => {
                const frame = document.querySelector(frameSelector);
                if (frame) {
                    return 'frame';
                }
                if (waitForHostedLaunchState) {
                    const state = window.__playdropCaptureHostedLaunchState;
                    if (state && typeof state.state === 'string' && state.state.trim().length > 0) {
                        return `hosted:${state.state.trim().toLowerCase()}`;
                    }
                }
                return null;
            }, { frameSelector: FRAME_SELECTOR, waitForHostedLaunchState: Boolean(expectedHostedLaunchState) }, { timeout: readinessTimeoutMs });
            await readinessHandle.jsonValue();
            const assertPageState = async () => {
                const currentUrl = page.url();
                if (!currentUrl) {
                    throw new Error('Capture page did not finish loading a URL.');
                }
                const finalComparableUrl = normalizeComparableUrl(currentUrl);
                if (expectedUrl && finalComparableUrl !== expectedUrl) {
                    throw new Error(`Capture landed on ${finalComparableUrl} instead of ${expectedUrl}.`);
                }
                const finalPathname = new URL(currentUrl).pathname;
                if (finalPathname.startsWith('/login')) {
                    throw new Error(`Capture landed on the login page (${currentUrl}).`);
                }
                const frameCount = await page.locator(FRAME_SELECTOR).count();
                if ((expectedHostedLaunchState ?? 'ready') === 'ready' && frameCount === 0) {
                    throw new Error('The Playdrop app frame never appeared.');
                }
                if (expectedHostedLaunchState === 'login_required' && frameCount > 0) {
                    throw new Error('The Playdrop auth gate booted the app frame unexpectedly.');
                }
                if (expectedHostedLaunchState === 'controller_required' && frameCount > 0) {
                    throw new Error('The Playdrop controller gate booted the app frame unexpectedly.');
                }
                if (expectedHostedLaunchState === 'surface_unsupported' && frameCount > 0) {
                    throw new Error('The Playdrop surface gate booted the app frame unexpectedly.');
                }
                return currentUrl;
            };
            if (hostedLaunchWaiter) {
                const launchState = await Promise.race([
                    hostedLaunchWaiter,
                    page.waitForTimeout(options.timeoutMs).then(() => {
                        if (hostedLaunchState && expectedHostedLaunchState && hostedLaunchState.state !== expectedHostedLaunchState) {
                            throw new Error(formatHostedLaunchStateMismatch(hostedLaunchState.state, expectedHostedLaunchState));
                        }
                        throw new Error(expectedHostedLaunchState === 'login_required'
                            ? 'Hosted app did not stop at the PlayDrop sign-in gate before the launch-check timeout.'
                            : expectedHostedLaunchState === 'controller_required'
                                ? 'Hosted app did not stop at the PlayDrop controller gate before the launch-check timeout.'
                                : expectedHostedLaunchState === 'surface_unsupported'
                                    ? 'Hosted app did not stop at the PlayDrop unsupported-surface gate before the launch-check timeout.'
                                    : 'Hosted app did not reach the ready state before the launch-check timeout.');
                    }),
                ]);
                if (launchState.state === 'error') {
                    throw new Error(formatHostedLaunchError(launchState));
                }
                if (expectedHostedLaunchState && launchState.state !== expectedHostedLaunchState) {
                    throw new Error(formatHostedLaunchStateMismatch(launchState.state, expectedHostedLaunchState));
                }
            }
            finalUrl = await assertPageState();
            await page.waitForTimeout(options.settleAfterReadyMs ?? options.timeoutMs);
            finalUrl = await assertPageState();
            if (options.screenshotPath) {
                const targetDir = (0, node_path_1.dirname)(options.screenshotPath);
                if (targetDir && targetDir !== '.' && targetDir !== '/') {
                    await (0, promises_1.mkdir)(targetDir, { recursive: true });
                }
                await page.screenshot({ path: options.screenshotPath, fullPage: true });
                console.log(`[capture] Saved screenshot to ${(0, node_path_1.relative)(process.cwd(), options.screenshotPath) || options.screenshotPath}`);
            }
        }, {
            ...(options.contextOptions ?? {}),
            automationOrigin: resolveAutomationOrigin(options.targetUrl),
        });
    }
    finally {
        await writeLogFile(options.logPath, outputLines);
    }
    return {
        errorCount: errors.length,
        finalUrl,
        hostedLaunchState,
    };
}
