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_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_DEPLOY_CRON = /\bcrontab\s+-?[ei]\b|systemctl\s+enable\s|launchctl\s+bootstrap\s/
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 Codex (OpenAI CLI) model_call action.
 *
 * Codex's canonical tool surface per its CLI documentation:
 *   - shell:        run a shell command
 *   - apply_patch:  apply a unified-diff patch to file(s)
 *   - read_file:    read file contents (no outcome)
 *   - write_file:   create a new file
 *
 * Codex traces have not yet been observed in the author's local data, so
 * this detector is "blind" (built from documentation, not captured traces).
 * Refine when real traces are available.
 */
export function detectCodex(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): OutcomeEvent => ({
    kind,
    valueUsd: outcomeValueUsd(kind),
    fingerprint: hash(`codex:${kind}:${fingerprintSource}`),
    detectedAt: action.ts,
    actionId: action.id,
    ...(toolUseId ? { toolUseId } : {}),
  })

  if (name === 'apply_patch') {
    const loc = estimatePatchLoc(input)
    if (loc < MIN_EDIT_LOC) return null
    return make('file_edited', `${input.path ?? input.file ?? ''}|${loc}`)
  }

  if (name === 'write_file') {
    const content: string = input.content ?? input.text ?? ''
    const loc = content.split('\n').length
    if (loc < MIN_NEW_FILE_LOC) {
      if (loc >= MIN_EDIT_LOC) return make('file_edited', `${input.path ?? ''}|${loc}`)
      return null
    }
    return make('file_created', `${input.path ?? ''}|${loc}`)
  }

  if (name === 'shell') {
    const cmd: string = input.command ?? input.cmd ?? ''
    if (!cmd) return null
    if (RE_GH_PR_CREATE.test(cmd)) return make('pr_opened', cmd)
    if (RE_GH_PR_MERGE.test(cmd)) return make('pr_merged', cmd)
    if (RE_GH_RUN_OK.test(cmd)) return make('ci_passes', cmd)
    if (RE_TEST_RUN.test(cmd)) return make('test_passes', cmd)
    if (RE_GIT_COMMIT.test(cmd)) return make('commit_made', cmd)
    if (RE_NPM_PUBLISH.test(cmd)) return make('automation_deployed', cmd)
    if (RE_DEPLOY_CRON.test(cmd)) return make('automation_deployed', cmd)
    return null
  }

  if (name === 'read_file') return null

  return null
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function estimatePatchLoc(input: any): number {
  if (typeof input.patch === 'string') return input.patch.split('\n').length
  if (typeof input.diff === 'string') return input.diff.split('\n').length
  const old: string = input.old_text ?? input.old_string ?? ''
  const next: string = input.new_text ?? input.new_string ?? ''
  return Math.max(old.split('\n').length, next.split('\n').length)
}

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