"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatHostedLaunchCheckFailure = formatHostedLaunchCheckFailure;
exports.runLocalHostedLaunchCheck = runLocalHostedLaunchCheck;
exports.runUploadedHostedLaunchCheck = runUploadedHostedLaunchCheck;
const appUrls_1 = require("../appUrls");
const captureRuntime_1 = require("../captureRuntime");
const devShared_1 = require("../commands/devShared");
const dev_1 = require("../commands/dev");
const devAuth_1 = require("../devAuth");
const devServer_1 = require("../commands/devServer");
const devRuntimeAssets_1 = require("../commands/devRuntimeAssets");
const registration_1 = require("./registration");
const DEFAULT_HOSTED_LAUNCH_TIMEOUT_MS = 15000;
const POST_READY_SETTLE_MS = 750;
const CAPTURE_SURFACE = 'DESKTOP';
function resolvePostAuthHostedLaunchState(controllerMode) {
    return controllerMode === 'REQUIRED' ? 'controller_required' : 'ready';
}
function resolveAnonymousHostedLaunchState(input) {
    const surfaceTargets = Array.isArray(input.surfaceTargets) ? input.surfaceTargets : [];
    if (!surfaceTargets.includes(CAPTURE_SURFACE)) {
        return 'surface_unsupported';
    }
    if (input.authMode === 'REQUIRED') {
        return 'login_required';
    }
    return resolvePostAuthHostedLaunchState(input.controllerMode);
}
function normalizeLaunchCheckMessage(message) {
    const normalized = typeof message === 'string' ? message.trim() : '';
    return normalized || 'Hosted app failed to launch.';
}
function buildPassedLaunchCheckResult(artifactFingerprint) {
    return {
        status: 'PASSED',
        errorCode: null,
        message: null,
        checkedAt: new Date().toISOString(),
        artifactFingerprint,
    };
}
function buildFailedLaunchCheckResult(errorCode, message, artifactFingerprint) {
    return {
        status: 'FAILED',
        errorCode,
        message,
        checkedAt: new Date().toISOString(),
        artifactFingerprint,
    };
}
function formatHostedLaunchCheckFailure(taskName, result, scope = 'local') {
    const scopeLabel = scope === 'staged' ? 'staged upload preview' : 'local hosted preview';
    const errorCode = result.errorCode?.trim() || 'startup_failure';
    const message = normalizeLaunchCheckMessage(result.message);
    return `hosted_launch_check_failed: ${taskName} failed the ${scopeLabel} launch-check (${errorCode}). ${message}`;
}
function normalizeLaunchCheckFailure(error, artifactFingerprint) {
    const rawMessage = error instanceof Error ? error.message : String(error);
    const message = normalizeLaunchCheckMessage(rawMessage);
    const errorCodeMatch = message.match(/\berrorCode=([a-z0-9_]+)\b/i);
    if (errorCodeMatch?.[1]) {
        return buildFailedLaunchCheckResult(errorCodeMatch[1], message, artifactFingerprint);
    }
    const prefixedCodeMatch = message.match(/^([a-z0-9_]+):/i);
    if (prefixedCodeMatch?.[1]
        && (prefixedCodeMatch[1].startsWith('hosted_app_')
            || prefixedCodeMatch[1].startsWith('local_dev_')
            || prefixedCodeMatch[1].startsWith('dev_auth_'))) {
        return buildFailedLaunchCheckResult(prefixedCodeMatch[1], message, artifactFingerprint);
    }
    if (/errorCode=missing_init\b/i.test(message) || /hosted_app_missing_client_init:/i.test(message)) {
        return buildFailedLaunchCheckResult('missing_init', message, artifactFingerprint);
    }
    if (/errorCode=missing_ready\b/i.test(message) || /hosted_app_missing_ready:/i.test(message)) {
        return buildFailedLaunchCheckResult('missing_ready', message, artifactFingerprint);
    }
    if (/errorCode=sdk_mismatch\b/i.test(message) || /hosted_app_sdk_version_mismatch:/i.test(message)) {
        return buildFailedLaunchCheckResult('sdk_mismatch', message, artifactFingerprint);
    }
    return buildFailedLaunchCheckResult('startup_failure', message, artifactFingerprint);
}
function shouldRethrowHostedLaunchCheckError(error) {
    const message = error instanceof Error ? error.message : String(error);
    return /Failed to launch Playwright Chromium/i.test(message) || /Required setup:/i.test(message);
}
function buildRegistrationRequiredLaunchCheckFailure(taskName) {
    return buildFailedLaunchCheckResult('app_registration_required_for_auth_validation', `Auth-required hosted app "${taskName}" must be registered on PlayDrop before viewer launch validation can run. Run "playdrop project create app ${taskName}" and try again.`, null);
}
function appendLaunchCheckParams(targetUrl, devAuth) {
    const withAuth = (0, devAuth_1.applyHostedDevAuthSelectionToUrl)(targetUrl, {
        devAuth,
        player: null,
    });
    const nextUrl = new URL(withAuth);
    nextUrl.searchParams.set('launchCheck', '1');
    return nextUrl.toString();
}
async function runHostedLaunchCheckWithCapture(options) {
    const expectedHostedLaunchState = options.expectedHostedLaunchState ?? 'ready';
    try {
        const result = await (0, captureRuntime_1.runCapture)({
            targetUrl: options.targetUrl,
            expectedUrl: options.targetUrl,
            timeoutMs: options.timeoutMs ?? DEFAULT_HOSTED_LAUNCH_TIMEOUT_MS,
            settleAfterReadyMs: POST_READY_SETTLE_MS,
            minimumLogLevel: 'warn',
            enableCaptureBridge: true,
            requireHostedLaunchReady: true,
            expectedHostedLaunchState,
            token: options.token ?? null,
            user: options.user ?? null,
            savedSessionBootstrap: options.savedSessionBootstrap ?? false,
        });
        if (result.errorCount > 0) {
            return buildFailedLaunchCheckResult('startup_failure', `Hosted app emitted ${result.errorCount} console, page, or network error(s) during startup.`, options.artifactFingerprint ?? null);
        }
        if (result.hostedLaunchState?.state !== expectedHostedLaunchState) {
            return buildFailedLaunchCheckResult('startup_failure', expectedHostedLaunchState === 'login_required'
                ? 'Hosted app did not stop at the PlayDrop sign-in gate.'
                : expectedHostedLaunchState === 'controller_required'
                    ? 'Hosted app did not stop at the PlayDrop controller gate.'
                    : expectedHostedLaunchState === 'surface_unsupported'
                        ? 'Hosted app did not stop at the PlayDrop unsupported-surface gate.'
                        : 'Hosted app never reported the ready state.', options.artifactFingerprint ?? null);
        }
        return buildPassedLaunchCheckResult(options.artifactFingerprint ?? null);
    }
    catch (error) {
        if (shouldRethrowHostedLaunchCheckError(error)) {
            throw error;
        }
        return normalizeLaunchCheckFailure(error, options.artifactFingerprint ?? null);
    }
}
async function startOrReuseMountedDevRuntime(input) {
    const appTypeSlug = (0, appUrls_1.getAppTypeSlug)(input.task.type ?? 'GAME');
    const runtimeAssetManifest = input.task.hostingMode === 'EXTERNAL' || input.task.externalUrl
        ? (0, devRuntimeAssets_1.createEmptyDevRuntimeAssetManifest)()
        : await (0, devRuntimeAssets_1.buildDevRuntimeAssetManifest)({
            client: input.client,
            apiBase: input.apiBase,
            task: input.task,
            creatorUsername: input.creatorUsername,
            appBaseUrl: `http://127.0.0.1:${devServer_1.DEV_ROUTER_PORT}/apps/dev/${encodeURIComponent(input.creatorUsername)}/${encodeURIComponent(appTypeSlug)}/${encodeURIComponent(input.task.name)}/`,
        });
    const serverAlreadyRunning = await (0, devServer_1.isDevServerAvailable)({
        creatorUsername: input.creatorUsername,
        appType: appTypeSlug,
        appName: input.task.name,
        port: devServer_1.DEV_ROUTER_PORT,
    }, 750);
    if (serverAlreadyRunning) {
        await (0, devServer_1.updateMountedDevRuntimeAssetManifest)({
            creatorUsername: input.creatorUsername,
            appName: input.task.name,
            runtimeAssetManifest,
            port: devServer_1.DEV_ROUTER_PORT,
        });
        return { handle: null, runtimeAssetManifest };
    }
    try {
        const handle = await (0, devServer_1.startDevServer)({
            appName: input.task.name,
            appType: appTypeSlug,
            creatorUsername: input.creatorUsername,
            htmlPath: input.task.filePath,
            port: devServer_1.DEV_ROUTER_PORT,
            projectInfo: (0, devShared_1.findProjectInfo)(input.task.filePath),
            runtimeAssetManifest,
        });
        await new Promise((resolve) => setTimeout(resolve, 1000));
        return { handle, runtimeAssetManifest };
    }
    catch (error) {
        const formatted = (0, dev_1.formatDevRuntimeAssetManifestFailure)(error);
        const mountConflict = (0, devServer_1.parseMountConflictError)(error instanceof Error ? error.message : '');
        if (mountConflict) {
            throw new Error(`A different dev session already owns ${mountConflict.ref}. Active owner pid: ${mountConflict.ownerPid}.`);
        }
        throw new Error(formatted.message);
    }
}
async function runLocalHostedLaunchCheck(input) {
    let mountedRuntime = null;
    try {
        mountedRuntime = await startOrReuseMountedDevRuntime({
            client: input.client,
            apiBase: input.apiBase,
            creatorUsername: input.creatorUsername,
            task: input.task,
        });
        const localAppMetadata = mountedRuntime.runtimeAssetManifest.response.localAppMetadata;
        const anonymousExpectedState = resolveAnonymousHostedLaunchState(localAppMetadata);
        if (anonymousExpectedState === 'login_required') {
            const registeredApp = await (0, registration_1.fetchRegisteredAppShell)(input.client, input.creatorUsername, input.task.name);
            if (!registeredApp?.id) {
                return buildRegistrationRequiredLaunchCheckFailure(input.task.name);
            }
            const anonymousGateResult = await runHostedLaunchCheckWithCapture({
                targetUrl: (0, appUrls_1.buildPlatformDevUrl)(input.webBase, {
                    creatorUsername: input.creatorUsername,
                    appName: input.task.name,
                    appType: input.task.type ?? 'GAME',
                    devAuth: 'anonymous',
                    launchCheck: true,
                }),
                timeoutMs: input.timeoutMs,
                expectedHostedLaunchState: anonymousExpectedState,
            });
            if (anonymousGateResult.status !== 'PASSED') {
                return anonymousGateResult;
            }
            return await runHostedLaunchCheckWithCapture({
                targetUrl: (0, appUrls_1.buildPlatformDevUrl)(input.webBase, {
                    creatorUsername: input.creatorUsername,
                    appName: input.task.name,
                    appType: input.task.type ?? 'GAME',
                    devAuth: 'viewer',
                    launchCheck: true,
                }),
                timeoutMs: input.timeoutMs,
                expectedHostedLaunchState: resolvePostAuthHostedLaunchState(localAppMetadata.controllerMode),
                token: input.token ?? null,
                user: input.currentUser ?? null,
                savedSessionBootstrap: true,
            });
        }
        return await runHostedLaunchCheckWithCapture({
            targetUrl: (0, appUrls_1.buildPlatformDevUrl)(input.webBase, {
                creatorUsername: input.creatorUsername,
                appName: input.task.name,
                appType: input.task.type ?? 'GAME',
                devAuth: 'anonymous',
                launchCheck: true,
            }),
            timeoutMs: input.timeoutMs,
            expectedHostedLaunchState: anonymousExpectedState,
        });
    }
    finally {
        if (mountedRuntime?.handle) {
            await mountedRuntime.handle.close().catch(() => { });
        }
    }
}
async function runUploadedHostedLaunchCheck(input) {
    const preview = await input.client.fetchAppUploadLaunchCheckPreview(input.creatorUsername, input.task.name, input.sessionId);
    const anonymousExpectedState = resolveAnonymousHostedLaunchState(preview.localAppMetadata);
    let result;
    if (anonymousExpectedState === 'login_required') {
        if (!preview.app?.id) {
            result = buildRegistrationRequiredLaunchCheckFailure(input.task.name);
        }
        else {
            const anonymousGateResult = await runHostedLaunchCheckWithCapture({
                targetUrl: appendLaunchCheckParams(preview.launchUrl, 'anonymous'),
                artifactFingerprint: preview.session.manifestHash,
                timeoutMs: input.timeoutMs,
                expectedHostedLaunchState: anonymousExpectedState,
            });
            if (anonymousGateResult.status !== 'PASSED') {
                result = anonymousGateResult;
            }
            else {
                result = await runHostedLaunchCheckWithCapture({
                    targetUrl: appendLaunchCheckParams(preview.launchUrl, 'viewer'),
                    artifactFingerprint: preview.session.manifestHash,
                    timeoutMs: input.timeoutMs,
                    expectedHostedLaunchState: resolvePostAuthHostedLaunchState(preview.localAppMetadata.controllerMode),
                    token: input.token ?? null,
                    user: input.currentUser ?? null,
                    savedSessionBootstrap: true,
                });
            }
        }
    }
    else {
        result = await runHostedLaunchCheckWithCapture({
            targetUrl: appendLaunchCheckParams(preview.launchUrl, 'anonymous'),
            artifactFingerprint: preview.session.manifestHash,
            timeoutMs: input.timeoutMs,
            expectedHostedLaunchState: anonymousExpectedState,
        });
    }
    const recorded = await input.client.recordAppUploadLaunchCheck(input.creatorUsername, input.task.name, input.sessionId, {
        status: result.status,
        artifactFingerprint: preview.session.manifestHash,
        errorCode: result.errorCode,
        message: result.message,
    });
    const recordedLaunchCheck = recorded.session.launchCheck;
    if (recordedLaunchCheck.status !== 'PASSED' && recordedLaunchCheck.status !== 'FAILED') {
        return buildFailedLaunchCheckResult('startup_failure', 'Upload launch-check was not recorded on the staged session.', preview.session.manifestHash);
    }
    return {
        status: recordedLaunchCheck.status,
        errorCode: recordedLaunchCheck.errorCode,
        message: recordedLaunchCheck.message,
        checkedAt: recordedLaunchCheck.checkedAt ?? new Date().toISOString(),
        artifactFingerprint: recordedLaunchCheck.artifactFingerprint,
    };
}
