"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatDevRuntimeAssetManifestFailure = formatDevRuntimeAssetManifestFailure;
exports.dev = dev;
const node_fs_1 = require("node:fs");
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 DEFAULT_ROOM_BASE = 'http://localhost:3001';
function formatDevRuntimeAssetManifestFailure(error) {
    const rawMessage = error instanceof Error ? error.message : String(error ?? 'unknown_error');
    if (rawMessage.startsWith('dev_runtime_asset_ref_invalid:')) {
        const ref = rawMessage.slice('dev_runtime_asset_ref_invalid:'.length);
        return {
            message: `Invalid uses.assets ref "${ref}" in catalogue.json.`,
            suggestions: [
                'Use an exact canonical asset ref like asset:creator/name@r1.',
            ],
        };
    }
    if (rawMessage.startsWith('dev_runtime_asset_version_not_found:')) {
        const ref = rawMessage.slice('dev_runtime_asset_version_not_found:'.length);
        return {
            message: `Exact direct asset dependency "${ref}" does not exist on the server.`,
            suggestions: [
                'Publish that exact asset revision first or update uses.assets to a valid exact ref.',
            ],
        };
    }
    if (rawMessage.startsWith('dev_runtime_asset_not_public:')) {
        const ref = rawMessage.slice('dev_runtime_asset_not_public:'.length);
        return {
            message: `Direct asset dependency "${ref}" is not publicly readable for project dev.`,
            suggestions: [
                'Make the asset public or remove it from uses.assets for this first-pass dev flow.',
            ],
        };
    }
    if (rawMessage.startsWith('dev_runtime_pack_ref_invalid:')) {
        const ref = rawMessage.slice('dev_runtime_pack_ref_invalid:'.length);
        return {
            message: `Invalid uses.packs ref "${ref}" in catalogue.json.`,
            suggestions: [
                'Use an exact canonical pack ref like pack:creator/name@1.2.3.',
            ],
        };
    }
    if (rawMessage.startsWith('dev_runtime_pack_version_not_found:')) {
        const ref = rawMessage.slice('dev_runtime_pack_version_not_found:'.length);
        return {
            message: `Exact pack dependency "${ref}" does not exist on the server.`,
            suggestions: [
                'Publish that exact pack version first or update uses.packs to a valid exact ref.',
            ],
        };
    }
    if (rawMessage.startsWith('dev_runtime_pack_not_public:')) {
        const ref = rawMessage.slice('dev_runtime_pack_not_public:'.length);
        return {
            message: `Pack dependency "${ref}" is not publicly readable for project dev.`,
            suggestions: [
                'Make the pack public or remove it from uses.packs for this first-pass dev flow.',
            ],
        };
    }
    if (rawMessage.startsWith('dev_runtime_key_duplicate:')) {
        const runtimeKey = rawMessage.split(':')[1] ?? 'unknown';
        return {
            message: `Duplicate runtimeKey "${runtimeKey}" detected while building dev runtime assets.`,
            suggestions: [
                'Give every owned asset and direct dependency a unique runtimeKey in catalogue.json.',
            ],
        };
    }
    if (rawMessage.startsWith('dev_runtime_owned_asset_duplicate:')) {
        const ownedAssetName = rawMessage.slice('dev_runtime_owned_asset_duplicate:'.length);
        return {
            message: `Duplicate owned asset name "${ownedAssetName}" detected in catalogue.json.`,
            suggestions: [
                'Owned asset names must be unique inside the app catalogue entry.',
            ],
        };
    }
    if (rawMessage.startsWith('dev_runtime_asset_files_missing:')) {
        const ref = rawMessage.slice('dev_runtime_asset_files_missing:'.length);
        return {
            message: `Dependency "${ref}" has no runtime file manifest on the server.`,
            suggestions: [
                'Republish the dependency so it has declared file roles before using it in project dev.',
            ],
        };
    }
    if (rawMessage.startsWith('dev_runtime_asset_spec_invalid:')) {
        const [, ref = 'unknown', assetSpecVersion = 'unknown'] = rawMessage.split(':');
        return {
            message: `Dependency "${ref}" returned an invalid asset spec version ref "${assetSpecVersion}".`,
            suggestions: [
                'Republish the dependency with a valid assetSpecVersionRef or remove it from uses.assets.',
            ],
        };
    }
    if (rawMessage.startsWith('dev_runtime_asset_spec_unsupported:')) {
        const [, ref = 'unknown', assetSpecVersion = 'unknown'] = rawMessage.split(':');
        return {
            message: `Dependency "${ref}" uses unsupported custom asset version "${assetSpecVersion}" for this app.`,
            suggestions: [
                'Update assetSpecSupport in catalogue.json or pin uses.assets to a supported custom asset revision.',
            ],
        };
    }
    if (rawMessage.startsWith('dev_runtime_app_type_missing:')) {
        const appName = rawMessage.slice('dev_runtime_app_type_missing:'.length);
        return {
            message: `App "${appName}" is missing a valid type in catalogue.json.`,
            suggestions: [
                'Set "type" in the app catalogue entry before running "playdrop project dev".',
            ],
        };
    }
    return {
        message: 'Failed to build the local runtime asset manifest for project dev.',
        suggestions: [
            rawMessage,
        ],
    };
}
async function dev(targetArg, _port, appOption, devOptions = {}) {
    let resolvedTarget;
    try {
        resolvedTarget = (0, devShared_1.resolveDevTarget)(targetArg, appOption);
    }
    catch (error) {
        const code = error?.code;
        const message = error?.message || 'Unable to resolve dev target.';
        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" 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".'];
                break;
            case 'invalid_catalogue':
                suggestions = ['Fix the catalogue.json file and rerun "playdrop project dev".'];
                break;
            default:
                suggestions = ['Provide an app name, HTML file path, or run inside a Playdrop workspace.'];
                break;
        }
        (0, messages_1.printErrorWithHelp)(message, suggestions, { command: 'project dev' });
        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' });
        process.exitCode = 1;
        return;
    }
    let appTypeSlug = 'game';
    try {
        const match = (0, catalogue_utils_1.findAppDefinition)(filePath);
        appName = match.name;
        appTypeSlug = (0, appUrls_1.getAppTypeSlug)(match.type);
    }
    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' });
            process.exitCode = 1;
            return;
        }
    }
    let devAuthSelection;
    try {
        devAuthSelection = (0, devAuth_1.parseHostedDevAuthSelection)({
            devAuth: devOptions.devAuth,
            player: devOptions.player,
            defaultMode: 'prompt',
            allowPrompt: true,
        });
    }
    catch (error) {
        const message = error instanceof Error ? error.message : 'dev_auth_invalid';
        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' });
        }
        else {
            (0, messages_1.printErrorWithHelp)('Use --dev-auth prompt, anonymous, viewer, or player.', ['If you choose --dev-auth player, also pass --player 1, 2, 3, or 4.'], { command: 'project dev' });
        }
        process.exitCode = 1;
        return;
    }
    const workspacePath = resolvedTarget.cataloguePath ?? (0, node_path_1.dirname)(filePath);
    await (0, commandContext_1.withEnvironment)('project dev', 'Starting the dev server', 
    // eslint-disable-next-line complexity
    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' });
                    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');
                    process.exitCode = 1;
                    return;
                }
                throw error;
            }
        }
        let appResponse = null;
        if (resolvedTarget.cataloguePath) {
            try {
                appResponse = 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');
                    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 starting the dev server.`,
                            'If you expected it to exist, ensure you are logged into the correct environment.'
                        ], { command: 'project dev' });
                    }
                    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' });
                    }
                    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');
                    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' });
            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 = formatDevRuntimeAssetManifestFailure(error);
                (0, messages_1.printErrorWithHelp)(formatted.message, formatted.suggestions, { command: 'project dev' });
                process.exitCode = 1;
                return;
            }
        }
        if (resolvedTarget.ecsPath && resolvedTarget.serverScriptPath && appResponse?.id) {
            try {
                if (!(0, node_fs_1.existsSync)(resolvedTarget.ecsPath) || !(0, node_fs_1.existsSync)(resolvedTarget.serverScriptPath)) {
                    throw new Error('ecs_or_server_missing');
                }
                const ecsRaw = (0, node_fs_1.readFileSync)(resolvedTarget.ecsPath, 'utf8');
                const ecsJson = JSON.parse(ecsRaw);
                const serverJs = (0, node_fs_1.readFileSync)(resolvedTarget.serverScriptPath, 'utf8');
                const roomBase = (process.env.PLAYDROP_ROOM_BASE || DEFAULT_ROOM_BASE).replace(/\/$/, '');
                const response = await fetch(`${roomBase}/rooms/dev_config`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        app_id: appResponse.id,
                        ecs: ecsJson,
                        server_js: serverJs,
                    }),
                });
                if (!response.ok) {
                    let reason = `${response.status}`;
                    try {
                        const payload = await response.json();
                        if (payload?.error) {
                            reason = payload.error;
                        }
                    }
                    catch {
                        // ignore parse errors
                    }
                    throw new Error(`room_dev_config_failed:${reason}`);
                }
                console.log(`Uploaded dev ECS config to engine-server for app ${appName}.`);
            }
            catch (error) {
                (0, messages_1.printErrorWithHelp)(error?.message || 'Failed to upload dev ECS config to engine-server.', [
                    'Ensure catalogue entry includes valid "ecs" and "server" paths.',
                    'Check PLAYDROP_ROOM_BASE or the default http://localhost:3001 is reachable.',
                ], { command: 'project dev' });
                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' });
            }
            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".',
                    `If needed, rebuild the local CLI first with "npm run build --workspace @playdrop/playdrop-cli" from /Users/oliviermichon/Documents/playdrop.`,
                ], { command: 'project dev' });
            }
            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' });
            }
            process.exitCode = 1;
            return;
        }
        let shuttingDown = false;
        const handleShutdown = () => {
            if (shuttingDown)
                return;
            shuttingDown = true;
            process.off('SIGINT', handleShutdown);
            process.off('SIGTERM', handleShutdown);
            handle
                .close()
                .catch(closeError => {
                console.error(closeError instanceof Error ? closeError.message : closeError);
            });
        };
        process.on('SIGINT', handleShutdown);
        process.on('SIGTERM', handleShutdown);
        const webUrl = envConfig.webBase;
        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);
        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.`);
            }
        }
        if (webUrl) {
            const iframeUrl = (0, devAuth_1.applyHostedDevAuthSelectionToUrl)(`${webUrl}/creators/${encodeURIComponent(currentUsername)}/apps/${appTypeSlug}/${encodeURIComponent(appName)}/dev`, devAuthSelection);
            console.log('\nTest your app at:');
            console.log(`  ${iframeUrl}`);
            console.log('\nMake something fun then share it with');
            console.log(`playdrop project publish ${appName}`);
        }
        else {
            console.log(`\nOpen ${handle.appUrl} in your browser to test locally.`);
        }
    }, { workspacePath });
}
