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 Hermes model_call action.
 *
 * Hermes tools per references/hermes-agent/{model_tools,toolsets}.py:
 *   file_tools:  read_file, write_file, patch, search_files
 *   web_tools:   web_search, web_extract
 *   terminal_tools: terminal, process
 *   image_tools: image_generate
 *   skills_tools: skills_list, skill_view, skill_manage
 *   browser_tools: browser_navigate, browser_snapshot, browser_click, ...
 *   cronjob_tools: cronjob
 *   delegate_tool: delegate_task
 *   memory_tool: memory
 *   discord/feishu/etc — communication tools
 *   tts_tools, vision_tools — multimedia
 *   transcription_tools — voice
 *   send_message_tool — comms
 *   text_to_speech — multimedia
 */
export function detectHermes(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(`hermes:${kind}:${fingerprintSource}`),
    detectedAt: action.ts,
    actionId: action.id,
    ...(toolUseId ? { toolUseId } : {}),
  })

  // ── file operations ─────────────────────────────────────────────
  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 === 'patch') {
    const loc = estimateEditLoc(input)
    if (loc < MIN_EDIT_LOC) return null
    return make('file_edited', `${input.path ?? ''}|${loc}`)
  }

  // ── shell / terminal ────────────────────────────────────────────
  if (name === 'terminal' || name === 'execute_code') {
    const cmd: string = input.command ?? input.code ?? input.script ?? ''
    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
  }

  // ── personal-assistant outcomes (v1.1) ──────────────────────────
  // Tools matching booking / calendar / order / reminder patterns
  if (/calendar(_event)?_create|create_event|calendar\.create|google_calendar_create/i.test(name)) {
    return make('calendar_event_confirmed', `${input.event_id ?? input.title ?? ''}`)
  }
  if (/^(book_|booking_)|_booking$|flights?\.book|hotel\.book/i.test(name)) {
    return make('booking_confirmed', `${input.confirmation ?? input.reference ?? ''}`)
  }
  if (/^order_|place_order|order\.create/i.test(name)) {
    return make('order_placed', `${input.order_id ?? input.reference ?? ''}`)
  }
  if (/reminder_create|^todo_create|task_create|set_reminder/i.test(name)) {
    return make('reminder_set', `${(input.title ?? '').slice(0, 60)}`)
  }

  // ── web ─────────────────────────────────────────────────────────
  if (name === 'web_search' || name === 'web_extract') {
    return make('search_synthesized', input.query ?? input.url ?? '')
  }

  // ── communications ──────────────────────────────────────────────
  // discord / discord_admin / feishu_doc / feishu_drive / send_message
  if (/^discord/.test(name)) {
    // discord posts / admin actions
    const channel = input.channel ?? input.channel_id ?? ''
    return make('message_posted', `${name}|${channel}|${(input.content ?? '').slice(0, 40)}`)
  }

  if (name === 'send_message' || name === 'send_message_tool') {
    const to = input.to ?? input.recipient ?? input.channel ?? ''
    return make('customer_reply', `${to}|${(input.content ?? input.body ?? '').slice(0, 40)}`)
  }

  if (name === 'feishu_doc_tool' || /^feishu_doc/.test(name)) {
    const op = input.action ?? input.op ?? 'edit'
    if (op === 'create' || op === 'create_doc') {
      return make('document_written', `${input.title ?? ''}|create`)
    }
    if (op === 'edit' || op === 'update' || op === 'patch') {
      return make('document_written', `${input.doc_id ?? ''}|edit`)
    }
    return null
  }

  // ── delegated work ──────────────────────────────────────────────
  if (name === 'delegate_task' || name === 'delegate') {
    return make(
      'sub_task_delegated',
      `${input.agent ?? input.target ?? ''}|${(input.task ?? input.prompt ?? '').slice(0, 80)}`,
    )
  }

  // ── multimedia ──────────────────────────────────────────────────
  if (name === 'image_generate') {
    return make('image_generated', `${(input.prompt ?? '').slice(0, 80)}`)
  }

  if (name === 'text_to_speech' || name === 'tts_tool') {
    // The audio asset is delivered; count as voice_transcribed? No — that's
    // the inverse direction. Skip as no outcome registry slot for TTS itself.
    return null
  }

  if (
    name === 'transcription_tools' ||
    name === 'transcribe' ||
    /^transcribe_/.test(name)
  ) {
    const seconds = Number(input.duration_seconds ?? 0)
    const minutes = Math.max(1, Math.round(seconds / 60))
    // One outcome per minute (dedup by fingerprint keeps short clips honest)
    return make('voice_transcribed', `${input.audio_id ?? input.path ?? ''}|m${minutes}`)
  }

  // ── automation / skills ─────────────────────────────────────────
  if (name === 'cronjob' || name === 'cronjob_tools') {
    const op = input.action ?? input.op ?? ''
    if (op === 'create' || op === 'install' || op === 'enable') {
      return make('automation_deployed', `${input.name ?? input.cron ?? ''}`)
    }
    if (op === 'run' || op === 'execute' || op === 'invoke') {
      return make('cron_task', `${input.name ?? input.cron ?? ''}`)
    }
    return null
  }

  if (name === 'skill_manage' || name === 'skills_manage') {
    const op = input.action ?? input.op ?? ''
    if (op === 'create' || op === 'add' || op === 'patch' || op === 'update') {
      return make('skill_committed', `${input.name ?? ''}|${op}`)
    }
    return null
  }

  // ── read-only / admin / planning — never outcomes ──────────────
  if (
    name === 'read_file' ||
    name === 'search_files' ||
    name === 'skills_list' ||
    name === 'skill_view' ||
    name === 'memory' ||
    name === 'memory_tool' ||
    name === 'todo' ||
    name === 'todo_tool' ||
    name === 'clarify' ||
    name === 'process' ||
    name === 'session_search' ||
    /^browser_/.test(name) ||
    /^rl_/.test(name) ||
    name === 'vision_analyze' ||
    name === 'mixture_of_agents'
  ) {
    return null
  }

  return null
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function estimateEditLoc(input: any): number {
  // patch tool: usually { path, old_str/new_str } or { diff }
  if (typeof input.diff === 'string') return input.diff.split('\n').length
  const old: string = input.old_str ?? input.old_string ?? input.old_text ?? ''
  const next: string = input.new_str ?? input.new_string ?? input.new_text ?? ''
  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)
}
