import { createHash } from 'node:crypto'
import { outcomeValueUsd } from '../registry.ts'
import type { ActionForDetection, OutcomeEvent } from './types.ts'

const MIN_EDIT_LOC = 50
const MIN_NEW_FILE_LOC = 100

const RE_GIT_COMMIT = /\bgit\s+commit\b/
const RE_GIT_PUSH = /\bgit\s+push\b/
const RE_GH_PR_CREATE = /\bgh\s+pr\s+create\b/
const RE_GH_PR_MERGE = /\bgh\s+pr\s+merge\b/
const RE_NPM_PUBLISH = /\bnpm\s+publish\b/
const RE_GH_RUN_OK = /\bgh\s+run\s+(watch|view|rerun)\b/
const RE_TEST_RUN =
  /\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?test\b|\bpytest\b|\bcargo\s+test\b|\bgo\s+test\b|\bvitest(?:\s+run)?\b|\bjest\b|\bmocha\b/

/**
 * Detect Thomas outcomes from a Claude Code model_call action.
 *
 * Claude Code emits `tool_use` blocks in its response array. We map each
 * known tool/action pattern to an entry in the Outcome Registry. Unknown
 * tools and read-only actions (Read, Glob, Grep) produce no outcome.
 */
export function detectClaudeCode(action: ActionForDetection): OutcomeEvent[] {
  if (action.kind !== 'model_call') return []
  const response = action.payload?.response
  if (!Array.isArray(response)) return []

  const events: OutcomeEvent[] = []

  for (const block of response) {
    if (block?.type !== 'tool_use') continue
    const out = mapToolUse(block, action)
    if (out) events.push(out)
  }

  return events
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function mapToolUse(block: any, action: ActionForDetection): OutcomeEvent | null {
  const name: string = block.name ?? ''
  const input = block.input ?? {}
  const toolUseId: string | undefined = typeof block.id === 'string' ? block.id : undefined

  const make = (kind: string, fingerprintSource: string, subAgent?: string): OutcomeEvent => ({
    kind,
    valueUsd: outcomeValueUsd(kind),
    fingerprint: hash(`${kind}:${fingerprintSource}`),
    detectedAt: action.ts,
    actionId: action.id,
    ...(subAgent ? { subAgent } : {}),
    ...(toolUseId ? { toolUseId } : {}),
  })

  switch (name) {
    case 'Edit':
    case 'MultiEdit': {
      const diffLoc = estimateEditLoc(input)
      if (diffLoc < MIN_EDIT_LOC) return null
      return make('file_edited', `${input.file_path ?? ''}|${diffLoc}`)
    }

    case 'Write': {
      const content: string = input.content ?? ''
      const loc = content.split('\n').length
      if (loc < MIN_NEW_FILE_LOC) {
        // Smaller writes count as edits, not creates.
        if (loc >= MIN_EDIT_LOC) return make('file_edited', `${input.file_path ?? ''}|${loc}`)
        return null
      }
      return make('file_created', `${input.file_path ?? ''}|${loc}`)
    }

    case 'Bash': {
      const cmd: string = input.command ?? ''
      if (!cmd) return null

      // PR commands first (most specific)
      if (RE_GH_PR_CREATE.test(cmd)) return make('pr_opened', cmd)
      if (RE_GH_PR_MERGE.test(cmd)) return make('pr_merged', cmd)
      // CI watching (strong verification when exit 0)
      if (RE_GH_RUN_OK.test(cmd)) return make('ci_passes', cmd)
      // Test runs (strong verification when exit 0)
      if (RE_TEST_RUN.test(cmd)) return make('test_passes', cmd)
      // Commits
      if (RE_GIT_COMMIT.test(cmd)) return make('commit_made', cmd)
      if (RE_GIT_PUSH.test(cmd)) {
        // git push of new branch is often the start of a PR — but we only
        // credit pr_opened on explicit `gh pr create`. Plain push without
        // PR creation yields no outcome (avoid double-counting commit_made).
        return null
      }
      if (RE_NPM_PUBLISH.test(cmd)) {
        return make('automation_deployed', cmd)
      }
      return null
    }

    case 'Task': {
      // Subagent invocation — Claude Code's Task tool spawns a subagent.
      const subagentType: string = input.subagent_type ?? 'general'
      return make(
        'sub_task_delegated',
        `${subagentType}|${(input.description ?? '').slice(0, 80)}`,
        subagentType,
      )
    }

    case 'WebSearch':
    case 'WebFetch':
      return make('search_synthesized', input.query ?? input.url ?? '')

    case 'NotebookEdit':
      return make('file_edited', `${input.notebook_path ?? ''}|notebook`)

    // Read-only / planning tools — never produce outcomes
    case 'Read':
    case 'Glob':
    case 'Grep':
    case 'TodoWrite':
    case 'ExitPlanMode':
      return null

    default:
      return null
  }
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function estimateEditLoc(input: any): number {
  // Edit: { old_string, new_string }
  // MultiEdit: { edits: [{old_string, new_string}, ...] }
  if (Array.isArray(input.edits)) {
    return input.edits.reduce((sum: number, e: { old_string?: string; new_string?: string }) => {
      const oldLines = (e.old_string ?? '').split('\n').length
      const newLines = (e.new_string ?? '').split('\n').length
      return sum + Math.max(oldLines, newLines)
    }, 0)
  }
  if (typeof input.old_string === 'string' || typeof input.new_string === 'string') {
    const oldLines = (input.old_string ?? '').split('\n').length
    const newLines = (input.new_string ?? '').split('\n').length
    return Math.max(oldLines, newLines)
  }
  return 0
}

function hash(s: string): string {
  return createHash('sha256').update(s).digest('hex').slice(0, 16)
}
