'use strict';

const { EventEmitter } = require('node:events');
const { fork } = require('node:child_process');
const path = require('node:path');

const DEFAULT_START_TIMEOUT_MS = 5000;
const DEFAULT_KILL_TIMEOUT_MS = 3000;

function _durationMs(value, fallback) {
  const n = Number(value);
  return Number.isFinite(n) ? Math.max(0, Math.trunc(n)) : fallback;
}

class HostedPtyAdapter {
  constructor(options = {}) {
    if (!options.sessionId) throw new Error('HostedPtyAdapter requires sessionId');
    this.sessionId = options.sessionId;
    this.cmd = options.cmd;
    this.args = Array.isArray(options.args) ? options.args : [];
    this.cwd = options.cwd;
    this.env = options.env;
    this.cols = Number(options.cols || 120) || 120;
    this.rows = Number(options.rows || 30) || 30;
    this.pid = 0;
    this.hostPid = 0;
    this.started = false;
    this.closed = false;
    this._events = new EventEmitter();
    this._manager = options.manager;
    this._logger = options.logger || console;
    this._workerPath = options.workerPath || path.resolve(__dirname, '..', 'workers', 'session-host-process.js');
    this._startTimeoutMs = _durationMs(options.startTimeoutMs, DEFAULT_START_TIMEOUT_MS);
    this._killTimeoutMs = _durationMs(options.killTimeoutMs, DEFAULT_KILL_TIMEOUT_MS);
    this._child = null;
    this._start();
  }

  _start() {
    const child = fork(this._workerPath, [], {
      cwd: path.resolve(__dirname, '..'),
      env: {
        ...process.env,
        CTM_PROCESS_ROLE: 'ctm-session-host',
      },
      execArgv: [],
      serialization: 'advanced',
      stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
    });
    this._child = child;
    this.hostPid = child.pid || 0;
    this.pid = this.hostPid;

    child.stdout?.on('data', (buf) => {
      const text = String(buf || '').trimEnd();
      if (text) this._logger.log?.(`[session-host:${this.sessionId.slice(0, 8)}] ${text}`);
    });
    child.stderr?.on('data', (buf) => {
      const text = String(buf || '').trimEnd();
      if (text) this._logger.warn?.(`[session-host:${this.sessionId.slice(0, 8)}] ${text}`);
    });
    child.on('message', (msg) => this._handleChildMessage(msg));
    child.on('error', (err) => {
      this._logger.warn?.(`[session-host:${this.sessionId.slice(0, 8)}] process error: ${err.message}`);
      this._events.emit('host-error', err);
    });
    child.on('exit', (code, signal) => {
      if (this.closed) return;
      this.closed = true;
      this._manager?._unregister(this.sessionId, this);
      if (!this._sawPtyExit) {
        this._events.emit('exit', {
          exitCode: Number.isFinite(code) ? code : 1,
          signal: signal || null,
        });
      }
    });

    const startTimer = setTimeout(() => {
      if (this.started || this.closed) return;
      this._logger.warn?.(`[session-host:${this.sessionId.slice(0, 8)}] start timed out after ${this._startTimeoutMs}ms`);
    }, this._startTimeoutMs);
    startTimer.unref?.();

    this._post({
      type: 'start',
      sessionId: this.sessionId,
      cmd: this.cmd,
      args: this.args,
      cwd: this.cwd,
      env: this.env,
      cols: this.cols,
      rows: this.rows,
    });
  }

  _post(message) {
    if (!this._child || this.closed || !this._child.connected) return false;
    try {
      this._child.send(message);
      return true;
    } catch (err) {
      this._events.emit('host-error', err);
      return false;
    }
  }

  _handleChildMessage(msg) {
    if (!msg || typeof msg !== 'object') return;
    switch (msg.type) {
      case 'ready':
        return;
      case 'started':
        this.started = true;
        this.pid = msg.pid || this.pid || this.hostPid;
        this.cols = msg.cols || this.cols;
        this.rows = msg.rows || this.rows;
        this._events.emit('started', msg);
        return;
      case 'data':
        this._events.emit('data', String(msg.data || ''));
        return;
      case 'exit':
        this._sawPtyExit = true;
        this.ptyClosed = true;
        this._events.emit('exit', {
          exitCode: Number.isFinite(msg.exitCode) ? msg.exitCode : 0,
          signal: msg.signal || null,
          spawnFailed: !!msg.spawnFailed,
        });
        return;
      case 'headless-message':
        this._manager?._emitHeadlessMessage(msg.message || {});
        return;
      case 'host-error': {
        const err = new Error(msg.message || 'session host error');
        err.code = msg.code || 'SESSION_HOST_ERROR';
        this._events.emit('host-error', err);
        this._manager?._emitHostError(this.sessionId, err);
        return;
      }
    }
  }

  onData(callback) {
    this._events.on('data', callback);
    return { dispose: () => this._events.off('data', callback) };
  }

  onExit(callback) {
    this._events.on('exit', callback);
    return { dispose: () => this._events.off('exit', callback) };
  }

  onStarted(callback) {
    this._events.on('started', callback);
    return { dispose: () => this._events.off('started', callback) };
  }

  write(data) {
    this._post({ type: 'input', sessionId: this.sessionId, data: String(data || '') });
  }

  resize(cols, rows) {
    const nextCols = Number(cols || 0);
    const nextRows = Number(rows || 0);
    if (nextCols > 0) this.cols = nextCols;
    if (nextRows > 0) this.rows = nextRows;
    this._post({ type: 'resize', sessionId: this.sessionId, cols: this.cols, rows: this.rows });
  }

  kill() {
    if (this.closed) return;
    this._post({ type: 'kill', sessionId: this.sessionId });
    const child = this._child;
    const timer = setTimeout(() => {
      if (!this.closed && child && !child.killed) {
        try { child.kill('SIGKILL'); } catch {}
      }
    }, this._killTimeoutMs);
    timer.unref?.();
  }

  postHeadlessMessage(message) {
    this._post({ type: 'headless', sessionId: this.sessionId, message });
  }

  terminate() {
    if (this.closed) return;
    this._post({ type: 'shutdown', sessionId: this.sessionId });
    try { this._child?.kill('SIGTERM'); } catch {}
  }
}

class SessionHostManager extends EventEmitter {
  constructor(options = {}) {
    super();
    this.workerPath = options.workerPath || path.resolve(__dirname, '..', 'workers', 'session-host-process.js');
    this.logger = options.logger || console;
    this.hosts = new Map();
  }

  createPty(options = {}) {
    const host = new HostedPtyAdapter({
      ...options,
      workerPath: this.workerPath,
      manager: this,
      logger: this.logger,
    });
    this.hosts.set(options.sessionId, host);
    return host;
  }

  get(sessionId) {
    return this.hosts.get(sessionId);
  }

  has(sessionId) {
    return this.hosts.has(sessionId);
  }

  postHeadlessMessage(message) {
    const host = this.get(message && message.sessionId);
    if (!host) return false;
    host.postHeadlessMessage(message);
    return true;
  }

  terminateAll() {
    for (const host of this.hosts.values()) host.terminate();
    this.hosts.clear();
  }

  _unregister(sessionId, host) {
    if (this.hosts.get(sessionId) === host) this.hosts.delete(sessionId);
  }

  _emitHeadlessMessage(message) {
    this.emit('headless-message', message);
  }

  _emitHostError(sessionId, err) {
    this.emit('host-error', { sessionId, error: err });
  }
}

class HeadlessTerminalRouter extends EventEmitter {
  constructor(options = {}) {
    super();
    if (!options.legacyWorker) throw new Error('HeadlessTerminalRouter requires legacyWorker');
    if (!options.sessionHostManager) throw new Error('HeadlessTerminalRouter requires sessionHostManager');
    this.legacyWorker = options.legacyWorker;
    this.sessionHostManager = options.sessionHostManager;
    this.legacyWorker.on('message', (msg) => this.emit('message', msg));
    this.legacyWorker.on('error', (err) => this.emit('error', err));
    this.sessionHostManager.on('headless-message', (msg) => this.emit('message', msg));
    this.sessionHostManager.on('host-error', ({ sessionId, error }) => {
      this.emit('host-error', { sessionId, error });
    });
  }

  postMessage(message) {
    if (message?.sessionId && this.sessionHostManager.postHeadlessMessage(message)) return;
    this.legacyWorker.postMessage(message);
  }

  terminate() {
    this.sessionHostManager.terminateAll();
    return this.legacyWorker.terminate();
  }
}

function createSessionHostManager(options = {}) {
  return new SessionHostManager(options);
}

function createHeadlessTerminalRouter(options = {}) {
  return new HeadlessTerminalRouter(options);
}

module.exports = {
  HostedPtyAdapter,
  SessionHostManager,
  HeadlessTerminalRouter,
  createSessionHostManager,
  createHeadlessTerminalRouter,
};
