'use strict';

// Per-session PTY host process. This owns node-pty for exactly one CTM
// terminal session and embeds the shared headless terminal service for the same
// session. The primary CTM process stays as the control plane; this process owns
// the native PTY resource so node-pty never crosses worker_threads boundaries.

process.env.CTM_PROCESS_ROLE = process.env.CTM_PROCESS_ROLE || 'ctm-session-host';

const pty = require('node-pty');
const { bindHeadlessTerminalService } = require('../lib/headless-term-service');

let sessionId = '';
let ptyProcess = null;
let ptyClosed = false;
let postExitShutdownTimer = null;
const POST_EXIT_HEADLESS_TTL_MS = 60000;

function send(msg) {
  if (typeof process.send !== 'function') return;
  try { process.send(msg); } catch {}
}

const headless = bindHeadlessTerminalService({
  postMessage: (message) => send({ type: 'headless-message', sessionId: message.sessionId || sessionId, message }),
});

function hostError(message, details = {}) {
  send({
    type: 'host-error',
    sessionId,
    message: message && message.message ? message.message : String(message || 'session host error'),
    ...details,
  });
}

function schedulePostExitShutdown() {
  if (postExitShutdownTimer) return;
  postExitShutdownTimer = setTimeout(() => {
    headless.close();
    process.exit(0);
  }, POST_EXIT_HEADLESS_TTL_MS);
  postExitShutdownTimer.unref?.();
}

function finishHostProcess() {
  if (postExitShutdownTimer) {
    clearTimeout(postExitShutdownTimer);
    postExitShutdownTimer = null;
  }
  headless.close();
  setTimeout(() => process.exit(0), 0);
}

function handleHeadlessMessage(message) {
  const payload = message || {};
  try {
    headless.handleMessage(payload);
  } catch (err) {
    hostError(err, { code: 'headless_message_failed' });
  }
  if (ptyClosed && payload.type === 'destroy') {
    finishHostProcess();
  }
}

function startSession(msg) {
  if (ptyProcess) return;
  sessionId = String(msg.sessionId || '');
  const cols = Number(msg.cols || 120) || 120;
  const rows = Number(msg.rows || 30) || 30;
  try {
    headless.handleMessage({ type: 'create', sessionId, cols, rows });
    ptyProcess = pty.spawn(msg.cmd, Array.isArray(msg.args) ? msg.args : [], {
      name: msg.name || 'xterm-256color',
      cols,
      rows,
      cwd: msg.cwd || process.cwd(),
      env: msg.env || process.env,
    });
    send({ type: 'started', sessionId, pid: ptyProcess.pid, cols, rows });
    ptyProcess.onData((data) => {
      const text = String(data || '');
      Promise.resolve(headless.handleMessage({ type: 'feed', sessionId, data: text }))
        .catch((err) => hostError(err, { code: 'headless_feed_failed' }));
      send({ type: 'data', sessionId, data: text });
    });
    ptyProcess.onExit(({ exitCode, signal }) => {
      ptyClosed = true;
      send({ type: 'exit', sessionId, exitCode, signal });
      // Keep the embedded headless terminal alive after PTY exit. The parent
      // process still needs one final serialize pass to persist/restore the
      // complete terminal screen before it sends the destroy message.
      schedulePostExitShutdown();
    });
  } catch (err) {
    hostError(err, { code: 'spawn_failed' });
    send({ type: 'exit', sessionId, exitCode: 1, signal: null, spawnFailed: true });
    headless.close();
    setTimeout(() => process.exit(1), 0);
  }
}

function handleMessage(msg) {
  if (!msg || typeof msg !== 'object') return;
  switch (msg.type) {
    case 'start':
      startSession(msg);
      break;
    case 'input':
      if (ptyProcess && !ptyClosed) ptyProcess.write(String(msg.data || ''));
      break;
    case 'resize': {
      if (ptyProcess && !ptyClosed) {
        const cols = Number(msg.cols || 0);
        const rows = Number(msg.rows || 0);
        if (cols > 0 && rows > 0) ptyProcess.resize(cols, rows);
      }
      break;
    }
    case 'kill':
      if (ptyProcess && !ptyClosed) ptyProcess.kill();
      else finishHostProcess();
      break;
    case 'headless':
      handleHeadlessMessage(msg.message || {});
      break;
    case 'shutdown':
      try { if (ptyProcess && !ptyClosed) ptyProcess.kill(); } catch {}
      finishHostProcess();
      break;
  }
}

process.on('message', handleMessage);
process.on('disconnect', finishHostProcess);
process.on('uncaughtException', (err) => {
  hostError(err, { code: 'uncaught_exception' });
  process.exit(1);
});
process.on('unhandledRejection', (err) => {
  hostError(err, { code: 'unhandled_rejection' });
  process.exit(1);
});

send({ type: 'ready' });
