'use strict';

const readline = require('readline');
const { spawn } = require('child_process');

function createCodexAppServerClient(options = {}) {
  const command = options.command || 'codex';
  const args = Array.isArray(options.args) && options.args.length > 0
    ? options.args.slice()
    : ['app-server', '--stdio'];
  const env = options.env || process.env;
  const cwd = options.cwd || process.cwd();
  const clientInfo = options.clientInfo || {
    name: 'ccweb_codexapp',
    title: 'CC-Web Codex App',
    version: '0.1.0',
  };
  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 : () => {};
  const postInitialize = typeof options.postInitialize === 'function' ? options.postInitialize : null;

  let proc = null;
  let rl = null;
  let nextId = 1;
  let initPromise = null;
  let exited = false;
  const pending = new Map();

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

  function sendRaw(message) {
    if (!proc || !proc.stdin || proc.stdin.destroyed) {
      throw new Error('Codex app-server 未启动。');
    }
    proc.stdin.write(`${JSON.stringify(message)}\n`);
  }

  function respondToServerRequest(id, result, error) {
    try {
      if (error) {
        sendRaw({ id, error });
      } else {
        sendRaw({ id, result: result || {} });
      }
    } catch (err) {
      onLog('WARN', 'codex_app_server_response_failed', { error: err.message });
    }
  }

  function handleServerRequest(message) {
    const id = message.id;
    const method = message.method;
    const params = message.params || {};
    if (onServerRequest) {
      Promise.resolve()
        .then(() => onServerRequest({ method, params, id }))
        .then((result) => respondToServerRequest(id, result || {}))
        .catch((err) => respondToServerRequest(id, null, {
          code: -32603,
          message: err?.message || 'cc-web 无法处理 Codex app-server 请求。',
        }));
      return;
    }
    respondToServerRequest(id, null, {
      code: -32601,
      message: `cc-web 暂不支持 Codex app-server 请求: ${method}`,
    });
  }

  function handleMessage(line) {
    let message;
    try {
      message = JSON.parse(line);
    } catch {
      onLog('WARN', 'codex_app_server_invalid_json', { line: String(line || '').slice(0, 200) });
      return;
    }

    if (Object.prototype.hasOwnProperty.call(message, 'id')) {
      const pendingRequest = pending.get(message.id);
      if (pendingRequest) {
        pending.delete(message.id);
        clearTimeout(pendingRequest.timer);
        if (message.error) {
          const err = new Error(message.error.message || 'Codex app-server 请求失败。');
          err.code = message.error.code;
          err.data = message.error.data;
          pendingRequest.reject(err);
        } else {
          pendingRequest.resolve(message.result || {});
        }
        return;
      }
      if (message.method) {
        handleServerRequest(message);
        return;
      }
    }

    if (message.method) {
      onNotification(message);
    }
  }

  function request(method, params = {}, timeoutMs = 300000) {
    const id = nextId++;
    const message = { id, method, params };
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        pending.delete(id);
        reject(new Error(`Codex app-server 请求超时: ${method}`));
      }, timeoutMs);
      pending.set(id, { resolve, reject, timer, method });
      try {
        sendRaw(message);
      } catch (err) {
        clearTimeout(timer);
        pending.delete(id);
        reject(err);
      }
    });
  }

  function notification(method, params = {}) {
    sendRaw({ method, params });
  }

  function reloadMcpServers() {
    return request('config/mcpServer/reload', {}, 30000);
  }

  function start() {
    if (initPromise) return initPromise;
    exited = false;
    proc = spawn(command, args, {
      env,
      cwd,
      stdio: ['pipe', 'pipe', 'pipe'],
      windowsHide: true,
    });

    let stderr = '';
    proc.stderr.on('data', (chunk) => {
      stderr += chunk.toString();
      if (stderr.length > 4000) stderr = stderr.slice(-4000);
    });

    rl = readline.createInterface({ input: proc.stdout });
    rl.on('line', handleMessage);

    proc.on('exit', (code, signal) => {
      exited = true;
      if (rl) rl.close();
      const err = new Error(`Codex app-server 已退出: code=${code ?? 'null'} signal=${signal || 'null'}`);
      err.exitCode = code;
      err.signal = signal;
      err.stderr = stderr;
      rejectAllPending(err);
      onExit({ code, signal, stderr });
    });

    proc.on('error', (err) => {
      rejectAllPending(err);
      onExit({ code: null, signal: null, stderr: err.message });
    });

    initPromise = request('initialize', {
      clientInfo,
      capabilities: { experimentalApi: true },
    }, 30000)
      .then(async (result) => {
        notification('initialized', {});
        if (postInitialize) await postInitialize({ request, notification, onLog });
        return result;
      })
      .catch((err) => {
        stop();
        throw err;
      });

    return initPromise;
  }

  function stop() {
    initPromise = null;
    if (rl) {
      try { rl.close(); } catch {}
      rl = null;
    }
    if (proc && !exited) {
      try { proc.kill('SIGTERM'); } catch {}
      setTimeout(() => {
        try {
          if (proc && !proc.killed) proc.kill('SIGKILL');
        } catch {}
      }, 3000);
    }
    proc = null;
    rejectAllPending(new Error('Codex app-server 已停止。'));
  }

  function isRunning() {
    return !!proc && !exited;
  }

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

module.exports = { createCodexAppServerClient };
