import { spawn } from 'child_process';
import { readFile } from 'fs/promises';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { modelMap as MODEL_MAP } from './adapters/claude.mjs';

const __dirname = dirname(fileURLToPath(import.meta.url));
const WORKSPACE_DIR = join(__dirname, 'workspace');

function resolveModel(model) {
  const key = String(model || 'balanced').trim().toLowerCase();
  if (key === 'fast' || key === 'balanced' || key === 'powerful') {
    return MODEL_MAP[key];
  }
  return String(model || MODEL_MAP.balanced);
}

/**
 * Spawn Claude CLI using stream-json output format.
 * Returns the final result text, and optionally calls onStream
 * with partial text as it arrives for real-time UI updates.
 *
 * @param {string} prompt
 * @param {object} opts
 * @param {string} opts.model - fast | balanced | powerful or provider model name
 * @param {number} opts.timeout - ms before killing
 * @param {boolean} opts.sandboxed - if true (default), run in workspace/ with hooks
 * @param {function} opts.onStream - callback(partialText) for streaming updates
 * @param {string[]} opts.extraArgs - additional CLI args
 */
export async function askClaude(prompt, { model = 'balanced', timeout = 120_000, sandboxed = true, onStream, extraArgs = [] } = {}) {
  return new Promise((resolve, reject) => {
    const selectedModel = resolveModel(model);
    const args = [
      '-p', prompt,
      '--model', selectedModel,
      '--output-format', 'stream-json',
      '--verbose',
      ...extraArgs
    ];

    const proc = spawn('claude', args, {
      cwd: sandboxed ? WORKSPACE_DIR : __dirname,
      env: { ...process.env },
      stdio: ['pipe', 'pipe', 'pipe']
    });

    let fullResult = '';
    let buffer = '';
    let killed = false;

    const timer = setTimeout(() => {
      killed = true;
      proc.kill('SIGTERM');
      setTimeout(() => proc.kill('SIGKILL'), 5000);
    }, timeout);

    proc.stdout.on('data', (chunk) => {
      buffer += chunk.toString();
      // Process complete JSON lines
      const lines = buffer.split('\n');
      buffer = lines.pop(); // keep incomplete line in buffer

      for (const line of lines) {
        if (!line.trim()) continue;
        try {
          const event = JSON.parse(line);

          if (event.type === 'assistant' && event.message?.content) {
            // Extract text from content blocks
            for (const block of event.message.content) {
              if (block.type === 'text' && block.text) {
                if (onStream) onStream(block.text);
              }
            }
          } else if (event.type === 'result') {
            fullResult = event.result || '';
          }
        } catch {
          // Ignore malformed lines
        }
      }
    });

    let stderr = '';
    proc.stderr.on('data', (chunk) => { stderr += chunk.toString(); });

    proc.on('close', (code) => {
      clearTimeout(timer);
      if (killed) {
        reject(new Error(`Claude CLI timed out after ${timeout}ms`));
      } else if (code !== 0 && !fullResult) {
        reject(new Error(`Claude CLI exited with code ${code}\n${stderr}`));
      } else {
        resolve(fullResult.trim());
      }
    });

    proc.on('error', (err) => {
      clearTimeout(timer);
      reject(new Error(`Claude CLI spawn error: ${err.message}`));
    });

    // Close stdin so Claude reads the prompt from args
    proc.stdin.end();
  });
}

/**
 * Ask Claude with file context prepended to the prompt.
 * Context files are read by the parent agent (not the subprocess),
 * so they don't need to be inside the sandbox.
 */
export async function askClaudeWithContext(prompt, contextFiles = [], opts = {}) {
  let context = '';
  for (const filePath of contextFiles) {
    try {
      const content = await readFile(filePath, 'utf-8');
      context += `--- ${filePath} ---\n${content}\n\n`;
    } catch {
      context += `--- ${filePath} --- (could not read)\n\n`;
    }
  }
  const fullPrompt = context ? `${context}\n---\n\n${prompt}` : prompt;
  return askClaude(fullPrompt, opts);
}

/**
 * Pick the appropriate model for a task type.
 */
export function pickModel(taskType) {
  switch (taskType) {
    case 'health':
    case 'format':
    case 'classify':
      return 'fast';
    case 'chat':
    case 'journal':
    case 'analyze':
    case 'summarize':
      return 'balanced';
    case 'self-improve':
    case 'complex':
    case 'build':
      return 'powerful';
    default:
      return 'balanced';
  }
}
