'use strict';
// Status-hook executor — runs a user-configured shell command when a session
// transitions between states (idle / busy / waiting_input).
//
// Contract:
//   runHook({ command, env, timeoutMs, cwd }) -> Promise<{ ok, stdout, stderr, durationMs, timedOut, error }>
//
// Rules:
// - 10s default timeout (killed with SIGTERM then SIGKILL)
// - Stdout/stderr buffered up to 64KB each, truncated if larger
// - Shell invocation uses `/bin/sh -c <command>` on unix, `cmd.exe /c` on win32
// - env is merged on top of process.env (hook vars override)
// - Non-zero exit code is NOT treated as a script error — surfaced as { ok: false, exitCode }
// - All errors are caught; this function never throws to its caller

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

const DEFAULT_TIMEOUT_MS = 10_000;
const MAX_BUF = 64 * 1024;

function _clip(buf) {
  const s = Buffer.concat(buf).toString('utf8');
  return s.length > MAX_BUF ? s.slice(0, MAX_BUF) + `\n... [truncated ${s.length - MAX_BUF} bytes]` : s;
}

function runHook({ command, env, timeoutMs, cwd } = {}) {
  return new Promise((resolve) => {
    if (!command || typeof command !== 'string') {
      resolve({ ok: false, error: 'empty command', stdout: '', stderr: '', durationMs: 0, timedOut: false });
      return;
    }
    const timeout = timeoutMs || DEFAULT_TIMEOUT_MS;
    const mergedEnv = { ...process.env, ...(env || {}) };

    const isWin = process.platform === 'win32';
    const shell = isWin ? 'cmd.exe' : '/bin/sh';
    const args = isWin ? ['/c', command] : ['-c', command];

    const started = Date.now();
    let timedOut = false;
    const outBuf = [];
    const errBuf = [];
    let child;
    try {
      child = spawn(shell, args, { env: mergedEnv, cwd: cwd || process.cwd(), stdio: ['ignore', 'pipe', 'pipe'] });
    } catch (e) {
      resolve({ ok: false, error: e.message, stdout: '', stderr: '', durationMs: 0, timedOut: false });
      return;
    }

    const killTimer = setTimeout(() => {
      timedOut = true;
      try { child.kill('SIGTERM'); } catch {}
      setTimeout(() => { try { child.kill('SIGKILL'); } catch {} }, 2000).unref();
    }, timeout);

    child.stdout.on('data', chunk => { if (outBuf.length < 100) outBuf.push(chunk); });
    child.stderr.on('data', chunk => { if (errBuf.length < 100) errBuf.push(chunk); });

    child.on('error', (err) => {
      clearTimeout(killTimer);
      resolve({
        ok: false,
        error: err.message,
        stdout: _clip(outBuf),
        stderr: _clip(errBuf),
        durationMs: Date.now() - started,
        timedOut,
      });
    });

    child.on('close', (code, signal) => {
      clearTimeout(killTimer);
      const ok = code === 0 && !timedOut;
      resolve({
        ok,
        exitCode: code,
        signal,
        timedOut,
        error: timedOut ? `timeout after ${timeout}ms` : (code !== 0 ? `exit ${code}` : null),
        stdout: _clip(outBuf),
        stderr: _clip(errBuf),
        durationMs: Date.now() - started,
      });
    });
  });
}

// Build env-var object for a status transition. Emits CTM_* as the primary
// contract and CCMANAGER_* as aliases so scripts written against ccmanager
// continue to work without edits.
function buildHookEnv({ oldState, newState, sessionId, providerId, worktreePath, worktreeBranch, tabTitle }) {
  const vars = {
    CTM_OLD_STATE: oldState || '',
    CTM_NEW_STATE: newState || '',
    CTM_SESSION_ID: sessionId || '',
    CTM_PROVIDER: providerId || '',
    CTM_WORKTREE_PATH: worktreePath || '',
    CTM_WORKTREE_BRANCH: worktreeBranch || '',
    CTM_TAB_TITLE: tabTitle || '',
    // ccmanager-compatible aliases (same values, different names).
    CCMANAGER_OLD_STATE: oldState || '',
    CCMANAGER_NEW_STATE: newState || '',
    CCMANAGER_SESSION_ID: sessionId || '',
    CCMANAGER_WORKTREE_PATH: worktreePath || '',
    CCMANAGER_WORKTREE_BRANCH: worktreeBranch || '',
  };
  return vars;
}

module.exports = { runHook, buildHookEnv, DEFAULT_TIMEOUT_MS };
