/**
 * script-runner-child.ts
 *
 * Runs as a child process spawned by script-runner.ts.
 * Reads one JSON line from stdin, and branches on the `runtime` field:
 *
 *   runtime === 'playwright':
 *     Launches Chromium via Playwright, calls main({ context, params }).
 *
 *   runtime === 'plain':
 *     No Chromium. Calls main({ params }) directly.
 *
 * Protocol:
 *   stdin:  { scriptPath, params, runtime, storageState?, headless? }
 *   stdout: { type: 'result', value: <return value> }
 *           { type: 'log', text: <line> }
 *   exit 0  on success, exit 1 on error (stack trace on stderr)
 */

import { createRequire } from 'module';

async function readStdinAll(): Promise<string> {
  const chunks: Buffer[] = [];
  for await (const chunk of process.stdin) chunks.push(chunk as Buffer);
  return Buffer.concat(chunks).toString('utf-8');
}

// ── Console interception helpers ──────────────────────────────────────────────

function emitLog(text: string) {
  process.stdout.write(JSON.stringify({ type: 'log', text }) + '\n');
}

function patchConsole() {
  const originalLog = console.log.bind(console);
  const originalInfo = console.info.bind(console);
  const originalWarn = console.warn.bind(console);
  const originalError = console.error.bind(console);

  console.log = (...args: unknown[]) => emitLog(args.map(String).join(' '));
  console.info = (...args: unknown[]) => emitLog(args.map(String).join(' '));
  console.warn = (...args: unknown[]) => emitLog('[warn] ' + args.map(String).join(' '));
  console.error = (...args: unknown[]) => emitLog('[error] ' + args.map(String).join(' '));

  return function restoreConsole() {
    console.log = originalLog;
    console.info = originalInfo;
    console.warn = originalWarn;
    console.error = originalError;
  };
}

// ── Safe result serialization ─────────────────────────────────────────────────

function emitResult(result: unknown): void {
  let resultMessage: string;
  try {
    resultMessage = JSON.stringify({ type: 'result', value: result });
  } catch (e) {
    resultMessage = JSON.stringify({
      type: 'result',
      value: null,
      serializationError: (e instanceof Error ? e.message : String(e)),
    });
  }
  process.stdout.write(resultMessage + '\n');
}

// ── Load user script ──────────────────────────────────────────────────────────

function loadUserFn(scriptPath: string): (...args: unknown[]) => unknown {
  const require = createRequire(import.meta.url);
  const userScript = require(scriptPath) as unknown;

  let fn: ((...args: unknown[]) => unknown) | undefined;
  if (typeof userScript === 'function') {
    fn = userScript as (...args: unknown[]) => unknown;
  } else if (userScript !== null && typeof userScript === 'object') {
    const mod = userScript as Record<string, unknown>;
    if (typeof mod['default'] === 'function') {
      fn = mod['default'] as (...args: unknown[]) => unknown;
    } else if (typeof mod['main'] === 'function') {
      fn = mod['main'] as (...args: unknown[]) => unknown;
    }
  }

  if (typeof fn !== 'function') {
    throw new Error(
      'script must export a function (module.exports = fn, or named exports .default/.main)'
    );
  }
  return fn;
}

// ── Plain runtime ─────────────────────────────────────────────────────────────

async function runPlain(
  scriptPath: string,
  params: Record<string, unknown>,
): Promise<void> {
  const fn = loadUserFn(scriptPath);
  const restore = patchConsole();
  try {
    const result = await fn({ params });
    emitResult(result);
  } finally {
    restore();
  }
}

// ── Playwright runtime ────────────────────────────────────────────────────────

async function runPlaywright(
  scriptPath: string,
  params: Record<string, unknown>,
  storageState: object | undefined,
  headless: boolean,
): Promise<void> {
  // Dynamic import so that the chromium binary is only loaded for playwright runtime.
  const { chromium } = await import('playwright');

  const fn = loadUserFn(scriptPath);
  const restore = patchConsole();

  const browser = await chromium.launch({ headless });
  try {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const contextOptions = storageState ? { storageState: storageState as any } : {};
    const context = await browser.newContext(contextOptions);
    const result = await fn({ context, params });
    emitResult(result);
  } finally {
    restore();
    await browser.close();
  }
}

// ── Entry point ───────────────────────────────────────────────────────────────

async function main() {
  const raw = await readStdinAll();
  const input = JSON.parse(raw) as {
    scriptPath: string;
    params: Record<string, unknown>;
    runtime: 'plain' | 'playwright';
    storageState?: object;
    headless?: boolean;
  };

  const { scriptPath, params, runtime, storageState, headless = true } = input;

  if (runtime === 'plain') {
    await runPlain(scriptPath, params);
  } else {
    await runPlaywright(scriptPath, params, storageState, headless);
  }
}

main().catch((e: unknown) => {
  const err = e instanceof Error ? e : new Error(String(e));
  process.stderr.write((err.stack ?? err.message) + '\n');
  process.exit(1);
});
