'use strict';

const path = require('path');
const { fork, spawn } = require('child_process');

function createCodexAppWorkerClient(options = {}) {
  const workerPath = options.workerPath || path.join(__dirname, 'codex-app-worker.js');
  const workerCommand = String(options.workerCommand || '').trim();
  const workerArgs = Array.isArray(options.workerArgs) ? options.workerArgs : [];
  const onNotification = typeof options.onNotification === 'function' ? options.onNotification : () => {};
  const onServerRequest = typeof options.onServerRequest === 'function' ? options.onServerRequest : null;
  const onExit = typeof options.onExit === 'function' ? options.onExit : () => {};
  const onLog = typeof options.onLog === 'function' ? options.onLog : () => {};

  let worker = null;
  let nextId = 1;
  let configured = false;
  let workerExited = false;
  let appServerRunning = false;
  const pending = new Map();

  function rejectAllPending(err) {
    for (const [, item] of pending) {
      clearTimeout(item.timer);
      item.reject(err);
    }
    pending.clear();
  }

  function specPayload() {
    return {
      command: options.command,
      args: Array.isArray(options.args) ? options.args : [],
      env: options.env || process.env,
      cwd: options.cwd || process.cwd(),
      clientInfo: options.clientInfo || null,
    };
  }

  function ensureWorker() {
    if (worker && !workerExited) return worker;
    workerExited = false;
    configured = false;
    appServerRunning = false;
    const spawnOptions = {
      cwd: options.cwd || process.cwd(),
      env: options.env || process.env,
      stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
    };
    worker = workerCommand
      ? spawn(workerCommand, workerArgs, spawnOptions)
      : fork(workerPath, [], spawnOptions);

    worker.on('message', (message = {}) => {
      if (Object.prototype.hasOwnProperty.call(message, 'id')) {
        const item = pending.get(message.id);
        if (!item) return;
        pending.delete(message.id);
        clearTimeout(item.timer);
        if (message.error) {
          const err = new Error(message.error.message || 'Codex App worker 请求失败。');
          err.code = message.error.code;
          err.data = message.error.data;
          item.reject(err);
        } else {
          item.resolve(message.result || {});
        }
        return;
      }

      if (message.type === 'notification') {
        onNotification(message.notification);
        return;
      }
      if (message.type === 'serverRequest') {
        handleServerRequest(message);
        return;
      }
      if (message.type === 'exit') {
        appServerRunning = false;
        onExit(message.info || {});
        return;
      }
      if (message.type === 'log') {
        onLog(message.level || 'INFO', message.event || 'codex_app_worker_log', message.data || {});
      }
    });

    worker.on('exit', (code, signal) => {
      workerExited = true;
      configured = false;
      appServerRunning = false;
      rejectAllPending(new Error(`Codex App worker 已退出: code=${code ?? 'null'} signal=${signal || 'null'}`));
      onExit({ code, signal, stderr: 'Codex App worker process exited' });
    });

    worker.on('error', (err) => {
      workerExited = true;
      configured = false;
      appServerRunning = false;
      rejectAllPending(err);
      onExit({ code: null, signal: null, stderr: err.message });
    });

    return worker;
  }

  function sendWorker(type, payload = {}, timeoutMs = 300000) {
    const proc = ensureWorker();
    const id = nextId++;
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        pending.delete(id);
        reject(new Error(`Codex App worker 请求超时: ${type}`));
      }, timeoutMs);
      pending.set(id, { resolve, reject, timer, type });
      try {
        proc.send({ id, type, ...payload });
      } catch (err) {
        clearTimeout(timer);
        pending.delete(id);
        reject(err);
      }
    });
  }

  function sendWorkerNotification(type, payload = {}) {
    const proc = ensureWorker();
    proc.send({ type, ...payload });
  }

  function handleServerRequest(message) {
    const requestId = message.requestId;
    if (!requestId) return;
    if (!onServerRequest) {
      sendWorkerNotification('serverRequestResult', {
        requestId,
        error: { code: -32601, message: 'cc-web 暂不支持 Codex app-server 请求。' },
      });
      return;
    }
    Promise.resolve()
      .then(() => onServerRequest(message.request || {}))
      .then((result) => {
        sendWorkerNotification('serverRequestResult', { requestId, result: result || {} });
      })
      .catch((err) => {
        sendWorkerNotification('serverRequestResult', {
          requestId,
          error: { code: -32603, message: err?.message || 'cc-web 处理 Codex App worker 请求失败。' },
        });
      });
  }

  async function configureIfNeeded() {
    if (configured) return;
    await sendWorker('configure', { spec: specPayload() }, 30000);
    configured = true;
  }

  async function start() {
    await configureIfNeeded();
    const result = await sendWorker('start', {}, 30000);
    appServerRunning = true;
    return result;
  }

  async function request(method, params = {}, timeoutMs = 300000) {
    await configureIfNeeded();
    return sendWorker('request', { method, params, timeoutMs }, timeoutMs + 1000);
  }

  function notification(method, params = {}) {
    sendWorkerNotification('notification', { method, params });
  }

  async function reloadMcpServers() {
    await configureIfNeeded();
    return sendWorker('reloadMcpServers', {}, 30000);
  }

  function stop() {
    appServerRunning = false;
    configured = false;
    if (worker && !workerExited) {
      try { worker.send({ type: 'stop' }); } catch {}
      setTimeout(() => {
        try {
          if (worker && !worker.killed) worker.kill('SIGKILL');
        } catch {}
      }, 3000);
    }
    rejectAllPending(new Error('Codex App worker 已停止。'));
  }

  function isRunning() {
    return !!worker && !workerExited && appServerRunning;
  }

  return {
    start,
    stop,
    request,
    notification,
    reloadMcpServers,
    isRunning,
    pid: () => worker?.pid || null,
  };
}

module.exports = { createCodexAppWorkerClient };
