const { spawn } = require('child_process');
const logger = require('./logger').child('PROCESS');
const { errorToMeta } = require('../utils/errorMeta');

function streamLines(stream, onLine) {
  let buffer = '';
  stream.on('data', (chunk) => {
    buffer += chunk.toString();
    const parts = buffer.split(/\r\n|\n|\r/);
    buffer = parts.pop() ?? '';

    for (const line of parts) {
      if (line.length > 0) {
        onLine(line);
      }
    }
  });

  stream.on('end', () => {
    if (buffer.length > 0) {
      onLine(buffer);
    }
  });
}

function spawnTrackedProcess({
  cmd,
  args,
  cwd,
  onStdoutLine,
  onStderrLine,
  onStart,
  context = {}
}) {
  logger.info('spawn:start', { cmd, args, cwd, context });

  const child = spawn(cmd, args, {
    cwd,
    env: process.env,
    stdio: ['ignore', 'pipe', 'pipe'],
    detached: true
  });

  if (onStart) {
    onStart(child);
  }

  if (child.stdout && onStdoutLine) {
    streamLines(child.stdout, onStdoutLine);
  }

  if (child.stderr && onStderrLine) {
    streamLines(child.stderr, onStderrLine);
  }

  const promise = new Promise((resolve, reject) => {
    child.on('error', (error) => {
      logger.error('spawn:error', { cmd, args, context, error: errorToMeta(error) });
      reject(error);
    });

    child.on('close', (code, signal) => {
      logger.info('spawn:close', { cmd, args, code, signal, context });
      if (code === 0) {
        resolve({ code, signal });
      } else {
        const error = new Error(`Prozess ${cmd} beendet mit Code ${code ?? 'null'} (Signal ${signal ?? 'none'}).`);
        error.code = code;
        error.signal = signal;
        reject(error);
      }
    });
  });

  let cancelCalled = false;
  const killProcessTree = (signal) => {
    const pid = Number(child.pid);
    if (Number.isFinite(pid) && pid > 0) {
      try {
        process.kill(-pid, signal);
        return true;
      } catch (_error) {
        // fallback below
      }
    }
    try {
      child.kill(signal);
      return true;
    } catch (_error) {
      return false;
    }
  };
  const cancel = () => {
    if (cancelCalled) {
      return;
    }
    cancelCalled = true;

    logger.warn('spawn:cancel:requested', { cmd, args, context, pid: child.pid });
    // Instant cancel by user request.
    killProcessTree('SIGKILL');
  };

  return {
    child,
    promise,
    cancel
  };
}

module.exports = {
  spawnTrackedProcess,
  streamLines
};
