'use strict';

const crypto = require('node:crypto');
const { normalizeAgentMode, normalizeAgentRunResult } = require('./contract');
const {
  sanitizeEnv,
  runCommand,
  getChangedFiles,
  getDiff,
} = require('./utils');

const CLAUDE_ENV_KEYS = [
  'CLAUDECODE',
  'CLAUDE_CODE',
  'CLAUDE_CODE_ENTRYPOINT',
  'CLAUDE_CODE_ENABLE_TELEMETRY',
  'ANTHROPIC_API_KEY',
  'ANTHROPIC_AUTH_TOKEN',
];

// Per-mode turn budgets. The previous global default of 50 was too low
// for non-trivial build subtasks (e.g., "build a whole website from
// photos" needs to: list files, copy 5-10 images, resize each, write
// HTML, write CSS, write JS, screenshot, self-critique, fix issues —
// easily 100+ turns). Lower budgets are still right for plan/review
// where the agent shouldn't be doing long tool sequences.
const DEFAULT_MAX_TURNS_BY_MODE = {
  plan: 30,
  review: 30,
  analysis: 30,
  build: 200,
};

function defaultMaxTurns(mode) {
  return DEFAULT_MAX_TURNS_BY_MODE[mode] || 50;
}

function buildClaudeArgs(prompt, opts = {}) {
  // Prompt is sent via stdin to avoid E2BIG on large prompts (macOS
  // ARG_MAX is ~260KB; planning prompts with file-tree + CLAUDE.md +
  // git-log context easily exceed this). The -p flag without a prompt
  // arg makes claude read the prompt from stdin in print mode.
  const maxTurns = opts.maxTurns || defaultMaxTurns(opts.mode);
  const args = [
    '-p',
    '--output-format', opts.outputFormat || 'json',
    '--max-turns', String(maxTurns),
    '--session-id', opts.sessionId,
  ];
  if (opts.budgetUsd) args.push('--max-budget-usd', String(opts.budgetUsd));
  if (opts.model) args.push('--model', opts.model);
  // permissionMode='bypassPermissions' is the explicit signal to opt
  // into autonomous tool use (Write/Bash without prompts). Without it,
  // non-interactive `claude -p` sits idle waiting for approval. Callers
  // should set this only when running in an isolated/disposable
  // workspace.
  if (opts.permissionMode === 'bypassPermissions') {
    // `--allow-dangerously-skip-permissions` only opts in to the capability;
    // it does NOT bypass on its own — Claude Code still asks Write/Bash for
    // approval and `claude -p` hangs in non-interactive mode. Pair it with
    // `--permission-mode bypassPermissions` to actually bypass.
    args.push('--allow-dangerously-skip-permissions');
    args.push('--permission-mode', 'bypassPermissions');
  } else if (opts.permissionMode) {
    args.push('--permission-mode', opts.permissionMode);
  }
  void prompt;
  return args;
}

async function runTask(options = {}) {
  const prompt = options.prompt || '';
  const cwd = options.cwd || process.cwd();
  const mode = normalizeAgentMode(options.mode);
  const sessionId = options.sessionId || crypto.randomUUID();
  const args = buildClaudeArgs(prompt, { ...options, sessionId, mode });
  const env = sanitizeEnv(process.env, { deleteKeys: CLAUDE_ENV_KEYS });

  const result = await runCommand('claude', args, {
    cwd,
    env,
    timeoutMs: options.timeoutMs || 300000,
    maxOutputBytes: options.maxOutputBytes,
    stdin: prompt,
  });
  const changedFiles = getChangedFiles(cwd);

  const outputFormat = options.outputFormat || 'json';
  const { text, raw, runnerError } = extractClaudeOutput(result.output, outputFormat);
  const stderr = result.timedOut
    ? `${result.stderr}${result.stderr ? '\n' : ''}Timed out after ${options.timeoutMs || 300000}ms`
    : runnerError
      ? `${result.stderr}${result.stderr ? '\n' : ''}${runnerError}`
      : result.stderr;

  return normalizeAgentRunResult({
    runnerId: 'claude-code',
    providerType: 'anthropic',
    mode,
    success: result.success && !runnerError,
    output: text,
    outputRaw: raw,
    stderr,
    sessionId,
    resumeHandle: sessionId,
    exitCode: result.exitCode,
    timedOut: result.timedOut,
    signal: result.signal,
    latencyMs: result.latencyMs,
    changedFiles,
    diff: getDiff(cwd, changedFiles),
    events: [],
    artifacts: {
      command: 'claude',
      outputFormat,
    },
  });
}

// Extract the model's text response from `claude -p --output-format json` output.
// The wrapper has shape {"type":"result","subtype":"success|error_*","result":"<text>",
// "is_error":bool, "usage":{...}, ...}. Plain text output passes through unchanged.
// Errors surfaced via subtype (e.g., "error_max_budget_usd", "error_max_turns") are
// propagated as runnerError so the orchestrator can react.
function extractClaudeOutput(rawOutput, outputFormat) {
  const raw = rawOutput || '';
  if (outputFormat !== 'json' || !raw.trim().startsWith('{')) {
    return { text: raw, raw, runnerError: null };
  }
  try {
    const wrapper = JSON.parse(raw);
    if (!wrapper || typeof wrapper !== 'object') {
      return { text: raw, raw, runnerError: null };
    }
    const text = typeof wrapper.result === 'string' ? wrapper.result : '';
    let runnerError = null;
    if (wrapper.is_error === true) {
      const subtype = wrapper.subtype || wrapper.terminal_reason || 'error';
      const detail = Array.isArray(wrapper.errors) && wrapper.errors.length > 0
        ? `: ${wrapper.errors.join('; ')}`
        : (typeof wrapper.result === 'string' && wrapper.result.length > 0 && wrapper.result.length < 200
          ? `: ${wrapper.result}`
          : '');
      runnerError = `claude CLI returned ${subtype}${detail}`;
    }
    return { text, raw, runnerError };
  } catch {
    return { text: raw, raw, runnerError: null };
  }
}

module.exports = {
  id: 'claude-code',
  providerType: 'anthropic',
  kind: 'cli-agent',
  capabilities: {
    modes: ['plan', 'build', 'review', 'analysis'],
    jsonOutput: true,
    streamJson: true,
    resume: true,
    worktree: true,
  },
  buildClaudeArgs,
  runTask,
  extractClaudeOutput,
  defaultMaxTurns,
  DEFAULT_MAX_TURNS_BY_MODE,
};
