import { spawn } from 'node:child_process';
import { type Config } from './gen/types.gen.js';

export type ServerOptions = {
  hostname?: string;
  port?: number;
  signal?: AbortSignal;
  timeout?: number;
  config?: Config;
  /** Absolute path to the opencode binary (avoids PATH lookup issues in Electron). */
  binaryPath?: string;
};

export type TuiOptions = {
  project?: string;
  model?: string;
  session?: string;
  agent?: string;
  signal?: AbortSignal;
  config?: Config;
};

export async function createOpencodeServer(options?: ServerOptions) {
  options = Object.assign(
    {
      hostname: '127.0.0.1',
      port: 4096,
      timeout: 5000,
    },
    options ?? {},
  );

  const args = [`serve`, `--hostname=${options.hostname}`, `--port=${options.port}`];
  if (options.config?.logLevel) args.push(`--log-level=${options.config.logLevel}`);

  const cmd = options.binaryPath ?? 'opencode';
  const proc = spawn(cmd, args, {
    signal: options.signal,
    shell: process.platform === 'win32',
    env: {
      ...process.env,
      OPENCODE_CONFIG_CONTENT: JSON.stringify(options.config ?? {}),
    },
  });

  const url = await new Promise<string>((resolve, reject) => {
    const id = setTimeout(() => {
      reject(new Error(`Timeout waiting for server to start after ${options.timeout}ms`));
    }, options.timeout);
    let output = '';
    proc.stdout?.on('data', (chunk) => {
      output += chunk.toString();
      const lines = output.split('\n');
      for (const line of lines) {
        if (line.startsWith('opencode server listening')) {
          const match = line.match(/on\s+(https?:\/\/[^\s]+)/);
          if (!match) {
            throw new Error(`Failed to parse server url from output: ${line}`);
          }
          clearTimeout(id);
          resolve(match[1]!);
          return;
        }
      }
    });
    proc.stderr?.on('data', (chunk) => {
      output += chunk.toString();
    });
    proc.on('exit', (code) => {
      clearTimeout(id);
      let msg = `Server exited with code ${code}`;
      if (output.trim()) {
        msg += `\nServer output: ${output}`;
      }
      reject(new Error(msg));
    });
    proc.on('error', (error) => {
      clearTimeout(id);
      reject(error);
    });
    if (options.signal) {
      options.signal.addEventListener('abort', () => {
        clearTimeout(id);
        reject(new Error('Aborted'));
      });
    }
  });

  return {
    url,
    close() {
      proc.kill();
    },
  };
}

export function createOpencodeTui(options?: TuiOptions) {
  const args = [];

  if (options?.project) {
    args.push(`--project=${options.project}`);
  }
  if (options?.model) {
    args.push(`--model=${options.model}`);
  }
  if (options?.session) {
    args.push(`--session=${options.session}`);
  }
  if (options?.agent) {
    args.push(`--agent=${options.agent}`);
  }

  const proc = spawn(`opencode`, args, {
    signal: options?.signal,
    stdio: 'inherit',
    shell: process.platform === 'win32',
    env: {
      ...process.env,
      OPENCODE_CONFIG_CONTENT: JSON.stringify(options?.config ?? {}),
    },
  });

  return {
    close() {
      proc.kill();
    },
  };
}
