'use strict';

// Claude Code approval prompt provider.
// Extracted from approval-patterns.js and approval-agent.js.

const APPROVAL_RE = /Do you want to (proceed|make this edit to .+|create .+|overwrite .+)\??/;
const BROAD_APPROVAL_RE = /Do you want to .+\?/;
// File-operation prompts render only the diff above them — no "Write"/"Edit"
// tool header. Derive the operation + target file from the prompt line itself so
// the approver treats it as a normal Edit/Write instead of an "Unknown" shell
// command that risk-scores the file's CODE CONTENT (and so never cleanly
// auto-approves / mis-routes to the verifier).
const FILE_OP_RE = /Do you want to (make this edit to|overwrite|create)\s+(.+?)\s*\??$/i;
function deriveFileOpTool(proceedLine) {
  const m = FILE_OP_RE.exec(String(proceedLine || ''));
  if (!m) return null;
  return {
    toolName: /make this edit/i.test(m[1]) ? 'Edit' : 'Write',
    command: m[2].trim(),
  };
}
const YES_RE = /^\D*1\.\s*Yes\b/;
const ESC_CANCEL_RE = /Esc to cancel/;
// MCP tool-use prompts have no "Bash command"-style header; the tool identity
// is the line ending in "(MCP)" (e.g. `plugin:playwright:playwright – Navigate
// to a URL(url: "…") (MCP)`) or a raw `mcp__…` / `plugin:…` tool name.
const MCP_TOOL_LINE_RE = /\(MCP\)\s*$/i;
const MCP_TOOL_NAME_RE = /^(?:[⏺●]\s*)?(?:plugin:|mcp__)/i;

// Reduce an MCP tool line to a stable tool name for rule-matching / display:
// drop the leading bullet, the trailing "(MCP)", and the volatile "(args…)".
function cleanMcpToolName(line) {
  let s = String(line || '').trim().replace(/^[⏺●]\s*/, '');
  s = s.replace(/\s*\(MCP\)\s*$/i, '');
  const argIdx = s.indexOf('(');
  if (argIdx > 0) s = s.slice(0, argIdx).trim();
  return s || 'MCP tool';
}
const DURABLE_YES_OPTION_RE = /^\s*(?:[❯›▶▸>]\s*)?2\.\s*Yes,\s*(allow all|allow\b.*\b(?:for|from) this project\b|(?:and\s+)?allow\s+access\s+to\b.*\b(?:and\s+similar\s+commands|for\s+this\s+session)\b|and\s+allow\s+Claude\b.*\bthis\s+session\b|and don't ask again|and always allow)/i;

module.exports = {
  id: 'claude-code',
  name: 'Claude Code',
  deriveFileOpTool,

  detect(text) {
    const allLines = text.split('\n');
    const nonEmpty = allLines.filter(l => l.trim());
    if (nonEmpty.length < 2) return false;

    const tail = nonEmpty.slice(-3);
    const hasEscAnchor = tail.some(l => ESC_CANCEL_RE.test(l));

    const lastYesIdx = nonEmpty.findLastIndex(l => YES_RE.test(l));
    if (lastYesIdx < 0) return false;

    if (!hasEscAnchor && (nonEmpty.length - lastYesIdx) > 4) return false;
    if (hasEscAnchor && (nonEmpty.length - lastYesIdx) > 30) return false;

    const nearbyText = nonEmpty.slice(Math.max(0, lastYesIdx - 5), lastYesIdx + 1).join('\n');
    if (APPROVAL_RE.test(nearbyText)) return true;
    if (BROAD_APPROVAL_RE.test(nearbyText)) return true;
    return false;
  },

  parse(text) {
    const lines = text.split('\n').map(l => l.trim()).filter(Boolean);

    let proceedIdx = -1;
    for (let i = lines.length - 1; i >= 0; i--) {
      if (APPROVAL_RE.test(lines[i])) { proceedIdx = i; break; }
    }
    if (proceedIdx < 0) {
      for (let i = lines.length - 1; i >= 0; i--) {
        if (BROAD_APPROVAL_RE.test(lines[i])) { proceedIdx = i; break; }
      }
    }
    if (proceedIdx < 0) return null;

    let hasYesNo = false;
    for (let i = proceedIdx + 1; i < Math.min(proceedIdx + 6, lines.length); i++) {
      if (/^\D*1\.\s*Yes\b/.test(lines[i])) { hasYesNo = true; break; }
    }
    if (!hasYesNo) return null;

    let warning = '';
    for (let i = proceedIdx - 1; i >= Math.max(proceedIdx - 3, 0); i--) {
      const line = lines[i];
      if (!line) continue;
      if (/command contains|could write|could modify|could delete|could overwrite|which can|permission|dangerous|destructive|overwrite|will modify|will delete|will overwrite|execute arbitrary|executes commands|modifies files|cannot be auto-allowed|shell command substitution/i.test(line)) {
        warning = line;
        break;
      }
    }

    let warningIdx = -1;
    if (warning) {
      for (let i = proceedIdx - 1; i >= Math.max(proceedIdx - 3, 0); i--) {
        if (lines[i] === warning) { warningIdx = i; break; }
      }
    }

    let toolName = '';
    const contextLines = [];
    const endIdx = warningIdx > 0 ? warningIdx : proceedIdx;

    for (let i = endIdx - 1; i >= Math.max(0, endIdx - 30); i--) {
      const line = lines[i];
      if (/^[⏺●]?\s*(Bash command|Bash|Edit|Write|Read|Glob|Grep|Fetch|WebFetch|NotebookEdit|TodoWrite|Agent)\b/.test(line)) {
        toolName = line.trim();
        break;
      }
      // MCP tool-use prompt: capture the real tool name (no longer "Unknown") so
      // the heuristic, learned rules, and "don't ask again" can target it. Keep
      // the full descriptor (with args/URL) in the command for the reviewer.
      if (MCP_TOOL_LINE_RE.test(line) || MCP_TOOL_NAME_RE.test(line)) {
        toolName = cleanMcpToolName(line);
        contextLines.unshift(line.trim());
        break;
      }
      contextLines.unshift(line);
    }

    // No tool header above the diff → this is one of Claude's file-operation
    // prompts ("overwrite/create/make this edit to <file>?"). Use the operation
    // and target file as tool/command so it auto-approves as a normal Edit/Write.
    let fileOpCommand = '';
    if (!toolName) {
      const fileOp = deriveFileOpTool(lines[proceedIdx]);
      if (fileOp) { toolName = fileOp.toolName; fileOpCommand = fileOp.command; }
    }

    const command = (fileOpCommand || contextLines.join('\n')).trim();

    const ctxStart = Math.max(0, endIdx - (contextLines.length + 1));
    const ctxEnd = Math.min(lines.length, proceedIdx + 5);
    const fullContext = lines.slice(ctxStart, ctxEnd).join('\n');

    let hasAllowAll = false;
    for (let i = proceedIdx + 1; i < Math.min(proceedIdx + 8, lines.length); i++) {
      if (DURABLE_YES_OPTION_RE.test(lines[i])) { hasAllowAll = true; break; }
    }

    return {
      toolName: toolName || 'Unknown',
      command: command.slice(0, 2000),
      warning: warning || '',
      fullContext: fullContext.slice(0, 2000),
      hasAllowAll,
      providerId: 'claude-code',
    };
  },

  approveKeystroke(ctx) {
    return ctx.hasAllowAll ? '2' : '1';
  },

  approveAllKeystroke(ctx) {
    return ctx.hasAllowAll ? '2' : null;
  },

  requiresEnter: true,
  // Claude Code does not reliably redraw the approval widget after a bare
  // numeric selection. The selection is committed by Enter, so CTM must send
  // Enter after the preflight prompt validation instead of waiting for a
  // pre-Enter transition that may never arrive.
  commitApprovalBeforeVerify: true,
};
