"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.devBrowser = devBrowser;
const node_fs_1 = require("node:fs");
const node_os_1 = __importDefault(require("node:os"));
const node_path_1 = require("node:path");
const types_1 = require("@playdrop/types");
const commandContext_1 = require("../commandContext");
const http_1 = require("../http");
const messages_1 = require("../messages");
const catalogue_1 = require("../catalogue");
const catalogue_utils_1 = require("../catalogue-utils");
const devShared_1 = require("./devShared");
const appUrls_1 = require("../appUrls");
const devServer_1 = require("./devServer");
const devRuntimeAssets_1 = require("./devRuntimeAssets");
const devAuth_1 = require("../devAuth");
const dev_1 = require("./dev");
const playwright_1 = require("../playwright");
function resolveDevBrowserAuthSelection(options) {
    const normalizedMode = options.devAuth?.trim().toLowerCase() || 'prompt';
    if (options.player !== undefined && normalizedMode !== 'player') {
        throw new Error('dev_auth_player_requires_player_mode');
    }
    return (0, devAuth_1.parseHostedDevAuthSelection)({
        devAuth: options.devAuth,
        player: options.player,
        defaultMode: 'prompt',
        allowPrompt: true,
    });
}
function waitForContextClose(context) {
    return new Promise((resolve) => {
        context.once?.('close', () => {
            resolve();
        });
    });
}
async function devBrowser(targetArg, options = {}) {
    let resolvedTarget;
    let devAuthSelection;
    try {
        resolvedTarget = (0, devShared_1.resolveDevTarget)(targetArg, options.app);
        devAuthSelection = resolveDevBrowserAuthSelection(options);
    }
    catch (error) {
        const message = error?.message || 'Unable to start the dev browser.';
        if (message === 'dev_auth_player_requires_player_mode') {
            (0, messages_1.printErrorWithHelp)('Use --player only with --dev-auth player.', [], { command: 'project dev-browser' });
            process.exitCode = 1;
            return;
        }
        if (message === 'dev_auth_player_slot_required') {
            (0, messages_1.printErrorWithHelp)('Use --player 1, 2, 3, or 4 when --dev-auth player is selected.', [], { command: 'project dev-browser' });
            process.exitCode = 1;
            return;
        }
        const code = error?.code;
        let suggestions = [];
        switch (code) {
            case 'app_required':
                suggestions = ['Use "--app <name>" to select which app to run.'];
                break;
            case 'app_not_found':
                suggestions = ['Check the app name in catalogue.json or run "playdrop project create app" to register a new app.'];
                break;
            case 'catalogue_missing':
                suggestions = ['Run "playdrop project dev-browser" from a workspace with catalogue.json or provide the path to an HTML file.'];
                break;
            case 'invalid_target':
                suggestions = ['Provide a path to an HTML file or directory containing a catalogue.json.'];
                break;
            case 'empty_catalogue':
                suggestions = ['Add apps to catalogue.json before running "playdrop project dev-browser".'];
                break;
            case 'invalid_catalogue':
                suggestions = ['Fix the catalogue.json file and rerun "playdrop project dev-browser".'];
                break;
            default:
                suggestions = ['Use --dev-auth prompt, anonymous, viewer, or player.', 'If you choose --dev-auth player, also pass --player 1, 2, 3, or 4.'];
                break;
        }
        (0, messages_1.printErrorWithHelp)(message, suggestions, { command: 'project dev-browser' });
        process.exitCode = 1;
        return;
    }
    let appName = resolvedTarget.appName;
    const filePath = resolvedTarget.htmlPath;
    if (!(0, node_fs_1.existsSync)(filePath) || !(0, node_fs_1.statSync)(filePath).isFile()) {
        (0, messages_1.printErrorWithHelp)(`App HTML was not found at ${filePath}.`, ['Ensure the catalogue entry points to a compiled HTML file or provide the correct file path.'], { command: 'project dev-browser' });
        process.exitCode = 1;
        return;
    }
    let appType = null;
    let appTypeSlug = (0, appUrls_1.getAppTypeSlug)(appType);
    try {
        const match = (0, catalogue_utils_1.findAppDefinition)(filePath);
        appName = match.name;
        appType = match.type;
        appTypeSlug = (0, appUrls_1.getAppTypeSlug)(appType);
    }
    catch (error) {
        if (resolvedTarget.cataloguePath) {
            (0, messages_1.printErrorWithHelp)(error?.message || 'Catalogue lookup failed.', [
                'Ensure the HTML file is listed exactly once in catalogue.json with a valid type.',
                'Run "playdrop project create app <name>" to scaffold a new entry if needed.',
            ], { command: 'project dev-browser' });
            process.exitCode = 1;
            return;
        }
    }
    const workspacePath = resolvedTarget.cataloguePath ?? (0, node_path_1.dirname)(filePath);
    await (0, commandContext_1.withEnvironment)('project dev-browser', 'Launching the Playdrop dev browser', async ({ client, env, envConfig, account }) => {
        let currentUsername = account?.username?.trim() ?? '';
        if (!currentUsername) {
            try {
                currentUsername = await (0, devShared_1.fetchDevUsername)(client);
            }
            catch (error) {
                if (error instanceof http_1.CLIUnsupportedClientError) {
                    return;
                }
                if (error instanceof types_1.UnsupportedClientError) {
                    (0, http_1.handleUnsupportedError)(error, 'Authentication');
                    process.exitCode = 1;
                    return;
                }
                if (error instanceof types_1.ApiError) {
                    (0, messages_1.printErrorWithHelp)(`Could not fetch your account (status ${error.status}).`, [
                        'Run "playdrop auth login" to refresh your session and ensure the API is reachable.',
                        'Use "playdrop auth whoami" afterwards to confirm your status.',
                    ], { command: 'project dev-browser' });
                    process.exitCode = 1;
                    return;
                }
                if ((0, devShared_1.isNetworkError)(error)) {
                    (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to resolve your account.', 'project dev-browser');
                    process.exitCode = 1;
                    return;
                }
                throw error;
            }
        }
        if (resolvedTarget.cataloguePath) {
            try {
                await (0, devShared_1.assertAppRegistered)(client, currentUsername, appName);
            }
            catch (error) {
                if (error instanceof http_1.CLIUnsupportedClientError) {
                    return;
                }
                if (error instanceof types_1.UnsupportedClientError) {
                    (0, http_1.handleUnsupportedError)(error, 'Dev browser');
                    process.exitCode = 1;
                    return;
                }
                if (error instanceof types_1.ApiError) {
                    if (error.status === 404) {
                        (0, messages_1.printErrorWithHelp)(`App ${currentUsername}/${appName} is not registered on ${env}.`, [
                            `Run "playdrop project create app ${appName}" to register the app before opening the dev browser.`,
                            'If you expected it to exist, ensure you are logged into the correct environment.',
                        ], { command: 'project dev-browser' });
                    }
                    else {
                        (0, messages_1.printErrorWithHelp)(`Failed to verify app registration (status ${error.status}).`, ['Retry in a moment.', 'If the issue persists, contact the Playdrop team.'], { command: 'project dev-browser' });
                    }
                    process.exitCode = 1;
                    return;
                }
                if ((0, devShared_1.isNetworkError)(error)) {
                    (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to verify the app registration.', 'project dev-browser');
                    process.exitCode = 1;
                    return;
                }
                throw error;
            }
        }
        const projectInfo = (0, devShared_1.findProjectInfo)(filePath);
        const devScriptAvailable = Boolean(projectInfo.projectDir
            && projectInfo.packageJson
            && typeof projectInfo.packageJson.scripts?.dev === 'string');
        const taskLookup = (0, catalogue_1.findAppTaskByFile)(filePath);
        if (taskLookup.errors.length > 0) {
            (0, messages_1.printErrorWithHelp)(taskLookup.errors[0] || 'Failed to resolve the app task from catalogue.json.', taskLookup.errors.slice(1), { command: 'project dev-browser' });
            process.exitCode = 1;
            return;
        }
        const localDevAppUrl = (0, devServer_1.buildLocalDevAppUrl)({
            creatorUsername: currentUsername,
            appType: appTypeSlug,
            appName,
            port: devServer_1.DEV_ROUTER_PORT,
        });
        let runtimeAssetManifest = (0, devRuntimeAssets_1.createEmptyDevRuntimeAssetManifest)();
        if (taskLookup.task) {
            try {
                runtimeAssetManifest = await (0, devRuntimeAssets_1.buildDevRuntimeAssetManifest)({
                    client,
                    apiBase: envConfig.apiBase,
                    task: taskLookup.task,
                    creatorUsername: currentUsername,
                    appBaseUrl: new URL('.', localDevAppUrl).toString(),
                });
            }
            catch (error) {
                const formatted = (0, dev_1.formatDevRuntimeAssetManifestFailure)(error);
                (0, messages_1.printErrorWithHelp)(formatted.message, formatted.suggestions, { command: 'project dev-browser' });
                process.exitCode = 1;
                return;
            }
        }
        let handle;
        try {
            handle = await (0, devServer_1.startDevServer)({
                appName,
                appType: appTypeSlug,
                creatorUsername: currentUsername,
                htmlPath: filePath,
                port: devServer_1.DEV_ROUTER_PORT,
                projectInfo,
                runtimeAssetManifest,
            });
        }
        catch (error) {
            const message = typeof error?.message === 'string' ? error.message : '';
            const mountConflict = (0, devServer_1.parseMountConflictError)(message);
            if (mountConflict) {
                (0, messages_1.printErrorWithHelp)(`A different dev session already owns ${mountConflict.ref}.`, [
                    `Active owner pid: ${mountConflict.ownerPid}.`,
                    `Mounted repo root: ${mountConflict.repoRoot}.`,
                    `Mounted HTML path: ${mountConflict.htmlPath}.`,
                ], { command: 'project dev-browser' });
            }
            else if (message.startsWith('dev_router_incompatible:')) {
                (0, messages_1.printErrorWithHelp)(`An incompatible shared dev router is already running on port ${devServer_1.DEV_ROUTER_PORT}.`, [
                    'Stop the existing dev router process, then retry "playdrop project dev-browser".',
                    'If needed, rebuild the local CLI first with "npm run build --workspace @playdrop/playdrop-cli".',
                ], { command: 'project dev-browser' });
            }
            else {
                (0, messages_1.printErrorWithHelp)(error?.message || `Failed to start the shared dev router on port ${devServer_1.DEV_ROUTER_PORT}.`, [
                    'Close the conflicting process or wait for the stale mount to exit.',
                    'Ensure the app HTML file exists and is readable.',
                ], { command: 'project dev-browser' });
            }
            process.exitCode = 1;
            return;
        }
        const devUrl = (0, appUrls_1.buildPlatformDevUrl)(envConfig.webBase, {
            creatorUsername: currentUsername,
            appName,
            appType,
            devAuth: devAuthSelection.devAuth,
            player: devAuthSelection.player ? String(devAuthSelection.player) : null,
            launchCheck: devAuthSelection.devAuth !== 'prompt',
        });
        let launchUrl = devUrl;
        if (devAuthSelection.devAuth !== 'anonymous') {
            try {
                const target = new URL(devUrl);
                const launch = await client.startCliWebLaunch({
                    redirectPath: `${target.pathname}${target.search}`,
                });
                launchUrl = launch.launchUrl;
            }
            catch (error) {
                await handle.close().catch(() => { });
                if (error instanceof http_1.CLIUnsupportedClientError) {
                    return;
                }
                if (error instanceof types_1.UnsupportedClientError) {
                    (0, http_1.handleUnsupportedError)(error, 'Dev browser launch');
                    process.exitCode = 1;
                    return;
                }
                if (error instanceof types_1.ApiError) {
                    (0, messages_1.printErrorWithHelp)(`Could not prepare the Playdrop browser session (status ${error.status}).`, ['Run "playdrop auth login" and retry the dev browser command.'], { command: 'project dev-browser' });
                    process.exitCode = 1;
                    return;
                }
                if ((0, devShared_1.isNetworkError)(error)) {
                    (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to prepare the browser session.', 'project dev-browser');
                    process.exitCode = 1;
                    return;
                }
                throw error;
            }
        }
        const entryLabel = (0, node_path_1.relative)(process.cwd(), filePath) || filePath;
        const runtimeRootLabel = (0, node_path_1.relative)(process.cwd(), (0, node_path_1.dirname)(filePath)) || (0, node_path_1.dirname)(filePath);
        const profileDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)(node_os_1.default.tmpdir(), 'playdrop-dev-browser-'));
        console.log(`App dev router mounted at ${handle.appUrl}`);
        console.log(`Serving ${entryLabel}`);
        console.log(`Static root ${runtimeRootLabel}`);
        if (projectInfo.projectDir && !devScriptAvailable && projectInfo.packageJsonPath) {
            const projectLabel = (0, devShared_1.formatProjectLabel)(projectInfo);
            if (projectLabel) {
                console.log(`package.json detected at ${projectLabel}, but no "dev" script was found. Run your build/watch scripts manually if needed.`);
            }
        }
        console.log(`Dev URL: ${devUrl}`);
        if (launchUrl !== devUrl) {
            console.log(`Launch URL: ${launchUrl}`);
        }
        console.log('Opening an isolated Chromium dev window.');
        console.log('Close the browser window or press Ctrl+C to stop.');
        let context = null;
        let cleanedUp = false;
        const cleanup = async () => {
            if (cleanedUp) {
                return;
            }
            cleanedUp = true;
            process.off('SIGINT', handleSignal);
            process.off('SIGTERM', handleSignal);
            if (context) {
                try {
                    await context.close();
                }
                catch {
                    // Ignore browser shutdown noise during cleanup.
                }
            }
            try {
                await handle.close();
            }
            finally {
                (0, node_fs_1.rmSync)(profileDir, { recursive: true, force: true });
            }
        };
        const handleSignal = () => {
            void cleanup();
        };
        process.on('SIGINT', handleSignal);
        process.on('SIGTERM', handleSignal);
        try {
            context = await (0, playwright_1.launchPersistentChromiumContext)(profileDir, {
                automationOrigin: devAuthSelection.devAuth === 'prompt' ? null : new URL(devUrl).origin,
                viewport: { width: 1440, height: 960 },
            });
        }
        catch (error) {
            await cleanup();
            (0, messages_1.printErrorWithHelp)(error?.message || 'Failed to launch the Playdrop dev browser.', ['Install Playwright Chromium with "npx playwright install chromium" and retry.'], { command: 'project dev-browser' });
            process.exitCode = 1;
            return;
        }
        try {
            const page = context.pages()[0] ?? await context.newPage();
            await page.goto(launchUrl, { waitUntil: 'domcontentloaded' });
            await waitForContextClose(context);
        }
        catch (error) {
            await cleanup();
            (0, messages_1.printErrorWithHelp)(error?.message || 'Failed to open the Playdrop dev browser window.', [], { command: 'project dev-browser' });
            process.exitCode = 1;
            return;
        }
        await cleanup();
    }, { workspacePath });
}
