/**
 * Playwright Chromium bootstrap for server deploys.
 *
 * `inspect_artifact` and the artifact PDF/PNG export render through Playwright's
 * Chromium. A bare Node host (e.g. the Railway `node:20` image) ships neither the
 * Chromium browser nor the OS libraries it needs, so those tools fail with
 * "Install the Playwright Chromium runtime with: npx playwright install chromium".
 *
 * This ensures the runtime is present WITHOUT blocking server startup:
 *   - Opt-in via `STUDIOGRAPH_INSTALL_PLAYWRIGHT=1` (the deploy sets it; local
 *     `studiograph serve` stays lean and never downloads ~150MB unprompted).
 *   - PROBE-FIRST: boot runs a real `chromium.launch()` probe. When the browser
 *     binary AND its OS libraries are already present (persistent
 *     `PLAYWRIGHT_BROWSERS_PATH` volume + an image with the deps baked in, e.g.
 *     `mcr.microsoft.com/playwright`), the install is skipped entirely — no apt
 *     churn, no downloads, ready in seconds. The probe is the only honest check
 *     for the OS deps: they live in the ephemeral rootfs, so "did the install
 *     run before" stamps can't be trusted across restarts.
 *   - The install runs through the BUNDLED playwright CLI
 *     (`node node_modules/playwright/cli.js`), not `npx playwright` — npx
 *     re-downloaded the standalone playwright package on every boot and could
 *     drift to a different version (and browser build) than the one
 *     `import('playwright')` resolves. npx remains as a fallback only.
 *   - Runs in the background after the server is already listening, at reduced
 *     CPU priority (best-effort `os.setPriority`) so apt/dpkg churn can't
 *     starve the event loop of live requests.
 *   - Readiness is observable via {@link playwrightReadiness}: render entry
 *     points return a clear "still installing" state instead of racing the
 *     background install on fresh rollouts.
 *   - Set `STUDIOGRAPH_PLAYWRIGHT_BROWSER_ONLY=1` to drop `--with-deps` when
 *     the OS libs are baked into the image. Point `PLAYWRIGHT_BROWSERS_PATH`
 *     at a persistent volume so the browser downloads once, not per boot.
 *
 * Best-effort throughout: any failure is logged and swallowed — the worst case is
 * the pre-existing "inspect unavailable" behavior, never a crashed boot.
 */
import { spawn } from 'node:child_process';
import { existsSync } from 'node:fs';
import { createRequire } from 'node:module';
import os from 'node:os';
import path from 'node:path';
let readiness = { state: 'idle' };
let kicked = false;
export function playwrightReadiness() {
    return readiness;
}
export function __resetPlaywrightBootstrapForTests() {
    kicked = false;
    readiness = { state: 'idle' };
}
function errorMessage(err) {
    return err instanceof Error ? err.message : String(err);
}
/** ONE set of headless-launch options shared by the boot probe and the render
 *  path (artifact-render-export's launchChromium) — they must agree, or the
 *  probe could pass while renders fail (or vice versa). */
export const CHROMIUM_LAUNCH_OPTIONS = {
    headless: true,
    args: ['--no-sandbox', '--disable-dev-shm-usage'],
};
/** Launch failures that mean "the playwright-managed browser isn't usable
 *  here" (vs a transient/other error). Shared by the probe and the render
 *  path's launchChromium: both fall back to the system `chrome` channel on
 *  exactly this predicate, so the probe's verdict matches what a render
 *  would actually do. */
export function isMissingBrowserError(err) {
    const message = err instanceof Error ? err.message : String(err ?? '');
    return /Executable doesn't exist|browserType\.launch|Host system is missing|Chromium distribution/i.test(message);
}
/** The honest readiness check: the executable exists AND a headless launch
 *  succeeds (which exercises the OS shared libraries). Mirrors the render
 *  path's launcher — shared options plus the same system-`chrome` channel
 *  fallback — so a passing probe means renders will pass too, and a probe
 *  failure means they wouldn't have. (Deliberate asymmetry: a MISSING
 *  chromium executable goes straight to install even where system chrome
 *  might render — installing chromium is this bootstrap's job.) */
async function probeChromiumLaunch() {
    let playwright;
    try {
        playwright = await import('playwright');
    }
    catch (err) {
        return { ok: false, reason: `playwright package unavailable: ${errorMessage(err)}` };
    }
    try {
        const exe = playwright.chromium.executablePath();
        if (!exe || !existsSync(exe)) {
            return { ok: false, reason: `chromium executable missing${exe ? ` at ${exe}` : ''}` };
        }
    }
    catch (err) {
        return { ok: false, reason: `chromium executablePath failed: ${errorMessage(err)}` };
    }
    let browser;
    try {
        browser = await playwright.chromium.launch(CHROMIUM_LAUNCH_OPTIONS);
    }
    catch (err) {
        // Same fallback predicate as launchChromium — a non-missing-browser launch
        // failure (crash, OOM, sandbox) must fail the probe, not sneak through via
        // system chrome the render path would never try.
        if (!isMissingBrowserError(err)) {
            return { ok: false, reason: `chromium launch failed: ${errorMessage(err)}` };
        }
        try {
            browser = await playwright.chromium.launch({ ...CHROMIUM_LAUNCH_OPTIONS, channel: 'chrome' });
        }
        catch {
            return { ok: false, reason: `chromium launch failed: ${errorMessage(err)}` };
        }
    }
    // The launch IS the probe — a failed close must not read as launch failure.
    await browser.close().catch(() => { });
    return { ok: true };
}
/** Prefer the playwright CLI bundled in our own node_modules: same version
 *  (and browser build) as the `import('playwright')` the render path uses,
 *  and no per-boot npx package download. */
function installCommand(browserOnly) {
    const installArgs = ['install', ...(browserOnly ? [] : ['--with-deps']), 'chromium'];
    try {
        const require = createRequire(import.meta.url);
        const pkgPath = require.resolve('playwright/package.json');
        const pkg = require('playwright/package.json');
        const bin = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.playwright;
        if (bin) {
            const cli = path.join(path.dirname(pkgPath), bin);
            if (existsSync(cli)) {
                return { command: process.execPath, args: [cli, ...installArgs], via: `bundled playwright ${pkg.version ?? ''}`.trim() };
            }
        }
    }
    catch {
        // Resolution failure → npx fallback below.
    }
    return { command: 'npx', args: ['--yes', 'playwright', ...installArgs], via: 'npx fallback' };
}
function runInstall(command, args, log) {
    return new Promise((resolve) => {
        const child = spawn(command, args, { stdio: 'inherit', env: process.env });
        // Deprioritize the installer: apt/dpkg churn during the install used to
        // starve the event loop for minutes (the window in which the entities-route
        // double-send race fired). Best-effort — unsupported platforms just skip it.
        try {
            if (typeof child.pid === 'number')
                os.setPriority(child.pid, 10);
        }
        catch {
            // os.setPriority unavailable/denied — proceed at normal priority.
        }
        child.on('exit', (code) => resolve(code));
        child.on('error', (err) => {
            log(`[playwright] chromium install could not start: ${err.message}`);
            resolve(null);
        });
    });
}
export function ensurePlaywrightChromium(log = console.log) {
    if (kicked)
        return; // one attempt per process
    kicked = true;
    if (process.env.STUDIOGRAPH_INSTALL_PLAYWRIGHT !== '1')
        return;
    const browserOnly = process.env.STUDIOGRAPH_PLAYWRIGHT_BROWSER_ONLY === '1';
    readiness = { state: 'checking', startedAt: Date.now() };
    void (async () => {
        try {
            const probe = await probeChromiumLaunch();
            if (probe.ok) {
                readiness = { state: 'ready' };
                log('[playwright] chromium runtime ready — launch probe passed, install skipped');
                return;
            }
            readiness = { state: 'installing', detail: probe.reason, startedAt: Date.now() };
            const { command, args, via } = installCommand(browserOnly);
            log(`[playwright] chromium runtime not ready (${probe.reason}) — installing in the background via ${via}: ${command} ${args.join(' ')}`);
            const code = await runInstall(command, args, log);
            if (code !== 0) {
                readiness = { state: 'failed', detail: `install exited ${code ?? 'without starting'}` };
                log(`[playwright] chromium install exited ${code ?? 'null'} (inspect_artifact/export stay unavailable)`);
                return;
            }
            const recheck = await probeChromiumLaunch();
            if (recheck.ok) {
                readiness = { state: 'ready' };
                log('[playwright] chromium runtime ready — inspect_artifact/export enabled');
            }
            else {
                readiness = { state: 'failed', detail: `install completed but ${recheck.reason}` };
                log(`[playwright] install completed but the launch probe still fails: ${recheck.reason}`);
            }
        }
        catch (err) {
            readiness = { state: 'failed', detail: errorMessage(err) };
            log(`[playwright] chromium bootstrap failed (inspect_artifact stays unavailable): ${errorMessage(err)}`);
        }
    })();
}
//# sourceMappingURL=playwright-bootstrap.js.map