// Copyright (c) 2026 defconxt. All rights reserved.
// Licensed under AGPL-3.0 — see LICENSE file for details.

/**
 * agent-repl.js — Full agentic REPL for NERF CLI.
 *
 * Replaces the raw Gateway chat with a pi-coding-agent session that has:
 * - All built-in pi tools (read, bash, edit, write, lsp, browser, etc.)
 * - NERF security tools (scan, review, compliance, osint, memory)
 * - NERF system prompt with mode detection and RAG
 * - Session persistence, context management, auto-compaction
 * - Image/file reading, code execution, tool calling
 *
 * This makes `nerf` a full security-first agentic coding agent.
 */

import { readFileSync } from 'node:fs';
import { resolve, dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createInterface } from 'node:readline';
import {
  parseSlashCommand,
  loadCommandTemplate,
  substituteArgs,
  COMMAND_REGISTRY,
  wireHandlers,
} from './command-router.js';
import { handleStatus, handleQuick, handleAuto, handleStop, handleDiscuss, handleSteer, handleQueue, handleSetupHooks, handleUpdate, handleKeys, handleLogs, handleForensicsDebug, handleDoctorCmd, handleNerfBare, handleExport } from './command-handlers.js';

const __dirname = dirname(fileURLToPath(import.meta.url));

/**
 * Launch the full agent REPL.
 * @param {object} pkg - package.json contents
 */
export async function runAgentRepl(pkg) {
  const { importPiCodingAgent, importPiAi, isPiCodingAgentAvailable } = await import('./gateway/pi-imports.js');

  if (!await isPiCodingAgentAvailable()) {
    console.error('Agent mode requires pi-coding-agent. Falling back to chat mode.');
    return null; // caller falls back to Gateway chat
  }

  const {
    createAgentSession,
    AuthStorage,
    ModelRegistry,
    runPrintMode,
  } = await importPiCodingAgent();

  const { banner } = await import('./brand.js');
  banner(pkg.version);

  // Load config for model selection
  let provider = 'anthropic';
  let modelName = 'claude-sonnet-4-6';
  try {
    const { loadConfig, configExists } = await import('./config.js');
    const { validateConfig } = await import('./gateway/config-validate.js');
    if (configExists()) {
      const config = validateConfig(loadConfig());
      if (config.backend === 'pi-ai' || config.backend === 'pi') {
        provider = config.pi_provider || 'anthropic';
        modelName = config.pi_model || modelName;
      } else if (config.backend === 'ollama') {
        provider = 'openai-completions';
        modelName = config.ollama_model || 'qwen2.5:32b';
      } else if (config.backend === 'openai-compat') {
        provider = 'openai-completions';
        modelName = config.compat_model || modelName;
      }
    }
  } catch { /* use defaults */ }

  // Create agent session
  const authStorage = AuthStorage.create();
  const modelRegistry = new ModelRegistry(authStorage);
  await modelRegistry.refresh();

  // Resolve model — check registry, fall back to provider lookup
  const piAi = await importPiAi();
  let model = piAi.getModel(provider, modelName);

  if (model && !model.apiKey) {
    // Try OAuth token
    const oauthKey = await authStorage.getApiKey(provider);
    if (oauthKey) {
      model = { ...model, apiKey: oauthKey };
    }
  }

  if (!model) {
    console.error(`Model ${provider}/${modelName} not found. Run: nerf setup`);
    return null;
  }

  const { session } = await createAgentSession({
    model,
    authStorage,
    modelRegistry,
  });

  // Bind extensions (loads NERF tools if the extension is registered)
  try {
    await session.bindExtensions({
      commandContextActions: {
        waitForIdle: () => session.agent?.waitForIdle(),
        newSession: async () => {
          const success = await session.newSession();
          return { cancelled: !success };
        },
        reload: async () => { await session.reload(); },
      },
      onError: (err) => {
        process.stderr.write(`\x1b[31mExtension error: ${err.error}\x1b[0m\n`);
      },
    });
  } catch (err) {
    // Extensions are best-effort
    if (process.env.NERF_DEBUG === '1') {
      process.stderr.write(`[agent-repl] Extension bind error: ${err.message}\n`);
    }
  }

  // Inject NERF system prompt if not already present (e.g. when running
  // from a directory without CLAUDE.md, such as after global npm install)
  if (session.systemPrompt && !session.systemPrompt.includes('Claude Integrated Privacy')) {
    const nerfRoot = resolve(__dirname, '..', '..');
    const claudeMdPath = resolve(nerfRoot, 'CLAUDE.md');
    try {
      const claudeMd = readFileSync(claudeMdPath, 'utf8');
      const rl = session.resourceLoader;
      if (rl) {
        rl.appendSystemPromptSource = claudeMd;
        await rl.reload();
        if (typeof session._rebuildSystemPrompt === 'function') {
          const toolNames = session.getActiveToolNames?.() || [];
          const newPrompt = session._rebuildSystemPrompt(toolNames);
          session._baseSystemPrompt = newPrompt;
          session.agent?.setSystemPrompt?.(newPrompt);
        }
      }
    } catch (err) {
      if (process.env.NERF_DEBUG === '1') {
        process.stderr.write(`[agent-repl] System prompt injection failed: ${err.message}\n`);
      }
    }
  }

  // Subscribe to events for streaming output
  session.subscribe((event) => {
    if (event.type === 'message_update' && event.assistantMessageEvent) {
      const ae = event.assistantMessageEvent;
      if (ae.type === 'text_delta' && ae.delta) {
        process.stdout.write(ae.delta);
      }
    } else if (event.type === 'message_update' && event.assistantMessageEvent?.type === 'tool_use_start') {
      const tool = event.assistantMessageEvent;
      process.stderr.write(`\x1b[2m⚡ ${tool.toolName || 'tool'}\x1b[0m\n`);
    } else if (event.type === 'error') {
      process.stderr.write(`\x1b[31mError: ${event.message || event.error}\x1b[0m\n`);
    }
  });

  console.log('');
  console.log(`  \x1b[36mNERF Agent\x1b[0m — ${provider}/${modelName}`);
  console.log('  Full agent mode: file access, tool calling, code execution enabled.');
  console.log('  Press Ctrl+C to exit.\n');

  // Wire direct handler commands into the registry
  wireHandlers({
    status: (args) => handleStatus(args),
    quick: (args) => handleQuick(args, session),
    export: (args) => handleExport(args, session),
    auto: (args) => handleAuto(args, session),
    stop: (args) => handleStop(args),
    discuss: (args) => handleDiscuss(args, session),
    steer: (args) => handleSteer(args),
    queue: (args) => handleQueue(args),
    'setup-hooks': (args) => handleSetupHooks(args),
    update: (args) => handleUpdate(args),
    keys: (args) => handleKeys(args),
    logs: (args) => handleLogs(args),
    'forensics-debug': (args) => handleForensicsDebug(args),
    doctor: (args) => handleDoctorCmd(args),
  });

  // Interactive REPL
  const rl = createInterface({
    input: process.stdin,
    output: process.stdout,
    prompt: '\x1b[36mnerf>\x1b[0m ',
  });

  rl.prompt();

  let processing = false;

  rl.on('line', async (line) => {
    const query = line.trim();
    if (!query) { rl.prompt(); return; }
    if (query === 'exit' || query === 'quit') { rl.close(); return; }

    if (processing) return; // don't queue during agent execution
    processing = true;
    rl.pause();
    process.stdout.write('\n');

    try {
      // --- Slash command interception ---
      const parsed = parseSlashCommand(query);

      if (parsed) {
        const debug = process.env.NERF_DEBUG === '1';
        if (debug) {
          process.stderr.write(`[command-router] parsed: command=${parsed.command} args="${parsed.args}"\n`);
        }

        if (parsed.command === 'nerf') {
          // Bare /nerf: show status summary
          if (debug) process.stderr.write('[command-router] bare /nerf, showing status\n');
          await handleNerfBare(parsed.args, session);
        } else {
          const entry = COMMAND_REGISTRY.get(parsed.command);

          if (!entry) {
            // Unknown command: print error with available commands
            process.stderr.write(`\x1b[31mUnknown command: /nerf:${parsed.command}\x1b[0m\n`);
            process.stderr.write('Available commands:\n');
            for (const [name, meta] of COMMAND_REGISTRY) {
              process.stderr.write(`  /nerf:${name.padEnd(16)} ${meta.desc}\n`);
            }
          } else if (entry.type === 'mode' || entry.type === 'prompt') {
            // Mode or prompt-template command: load template, substitute args, inject as prompt
            if (debug) process.stderr.write(`[command-router] ${entry.type} command: ${parsed.command}${entry.mode ? ' -> ' + entry.mode : ''}\n`);
            try {
              const template = loadCommandTemplate(parsed.command);
              const injectedPrompt = substituteArgs(template, parsed.args);
              await session.prompt(injectedPrompt);
            } catch (templateErr) {
              process.stderr.write(`\x1b[31mFailed to load template for ${parsed.command}: ${templateErr.message}\x1b[0m\n`);
            }
          } else if (entry.type === 'handler') {
            // Handler command: call handler if wired, otherwise inform user
            if (debug) process.stderr.write(`[command-router] handler command: ${parsed.command}\n`);
            if (typeof entry.handler === 'function') {
              await entry.handler(parsed.args, session);
            } else {
              process.stderr.write(`\x1b[33m/nerf:${parsed.command} handler not yet wired. Passing as prompt.\x1b[0m\n`);
              await session.prompt(query);
            }
          }
        }
      } else {
        // Not a slash command: pass through unchanged
        await session.prompt(query);
      }
    } catch (err) {
      process.stderr.write(`\x1b[31mError: ${err.message}\x1b[0m\n`);
    }

    process.stdout.write('\n');
    processing = false;
    rl.resume();
    rl.prompt();
  });

  rl.on('close', () => {
    console.log('');
    session.dispose?.();
    process.exit(0);
  });

  // Keep alive
  await new Promise(() => {});
}
