/**
 * Individual signal detectors for the Wander Detector.
 * Each function analyzes the message history and returns detected signals.
 */

import { createHash } from 'crypto'
import type { Message } from '../../types/message.js'
import type { WanderSignal, WanderState } from './types.js'

const LOOKBACK_TURNS = 6
const REREAD_LOOKBACK = 4

/** Hash a tool call's name + input for dedup detection */
function hashToolCall(toolName: string, input: unknown): string {
  const raw = `${toolName}:${JSON.stringify(input)}`
  return createHash('md5').update(raw).digest('hex').slice(0, 12)
}

/** Extract tool uses from an assistant message */
function extractToolUses(
  msg: Message,
): Array<{ name: string; input: unknown }> {
  if (msg.type !== 'assistant') return []
  const content = msg.message?.content
  if (!Array.isArray(content)) return []
  return content
    .filter((block: { type: string }) => block.type === 'tool_use')
    .map((block: { name: string; input: unknown }) => ({
      name: block.name,
      input: block.input,
    }))
}

/** Extract tool result errors from a user message */
function extractToolErrors(msg: Message): string[] {
  if (msg.type !== 'user') return []
  const content = msg.message?.content
  if (!Array.isArray(content)) return []
  return content
    .filter(
      (block: { type: string; is_error?: boolean }) =>
        block.type === 'tool_result' && block.is_error,
    )
    .map((block: { content?: string | Array<{ text?: string }> }) => {
      if (typeof block.content === 'string') return block.content
      if (Array.isArray(block.content)) {
        return block.content
          .map((c: { text?: string }) => c.text ?? '')
          .join('')
      }
      return ''
    })
    .filter((s: string) => s.length > 0)
}

/** Extract file paths from FileReadTool uses */
function extractFileReads(
  msg: Message,
): string[] {
  if (msg.type !== 'assistant') return []
  const content = msg.message?.content
  if (!Array.isArray(content)) return []
  return content
    .filter(
      (block: { type: string; name: string }) =>
        block.type === 'tool_use' &&
        (block.name === 'Read' || block.name === 'FileReadTool'),
    )
    .map(
      (block: { input: { file_path?: string } }) =>
        block.input?.file_path ?? '',
    )
    .filter((p: string) => p.length > 0)
}

/**
 * Detect repeated tool calls — same (tool_name, input_hash) seen 2+ times
 * in the last LOOKBACK_TURNS assistant messages.
 */
export function detectRepeatedToolCalls(
  messages: Message[],
  state: WanderState,
): WanderSignal[] {
  const signals: WanderSignal[] = []
  const recentAssistant = messages
    .filter((m) => m.type === 'assistant')
    .slice(-LOOKBACK_TURNS)

  const callCounts = new Map<string, number>()

  for (const msg of recentAssistant) {
    for (const tool of extractToolUses(msg)) {
      const hash = hashToolCall(tool.name, tool.input)
      const key = `${tool.name}:${hash}`
      const count = (callCounts.get(key) ?? 0) + 1
      callCounts.set(key, count)

      if (count >= 2) {
        signals.push({
          type: 'repeated_tool_call',
          weight: 2,
          description: `${tool.name} called ${count}x with identical input`,
          turnIndex: state.turnCount,
        })
      }
    }
  }

  // Update state for cross-turn tracking
  state.recentToolCalls = callCounts
  return signals
}

/**
 * Detect same error appearing in consecutive tool results.
 */
export function detectSameError(
  messages: Message[],
  state: WanderState,
): WanderSignal[] {
  const signals: WanderSignal[] = []
  const recentUser = messages
    .filter((m) => m.type === 'user')
    .slice(-LOOKBACK_TURNS)

  const currentErrors: string[] = []
  for (const msg of recentUser) {
    currentErrors.push(...extractToolErrors(msg))
  }

  // Check for duplicates between current and previous errors
  for (const err of currentErrors) {
    // Normalize error: trim, take first 200 chars
    const normalized = err.trim().slice(0, 200)
    if (state.recentErrors.includes(normalized)) {
      signals.push({
        type: 'same_error',
        weight: 2,
        description: `Same error repeated: "${normalized.slice(0, 60)}..."`,
        turnIndex: state.turnCount,
      })
    }
  }

  // Update state — keep last 10 errors
  state.recentErrors = [
    ...state.recentErrors,
    ...currentErrors.map((e) => e.trim().slice(0, 200)),
  ].slice(-10)

  return signals
}

/**
 * Detect oscillation: pattern A→B→A in recent tool call sequences.
 * Looks for edit→revert→edit or similar back-and-forth patterns.
 */
export function detectOscillation(
  messages: Message[],
  _state: WanderState,
): WanderSignal[] {
  const signals: WanderSignal[] = []
  const recentAssistant = messages
    .filter((m) => m.type === 'assistant')
    .slice(-LOOKBACK_TURNS)

  // Build sequence of tool call hashes
  const sequence: string[] = []
  for (const msg of recentAssistant) {
    for (const tool of extractToolUses(msg)) {
      sequence.push(hashToolCall(tool.name, tool.input))
    }
  }

  // Check for A-B-A pattern
  for (let i = 2; i < sequence.length; i++) {
    if (sequence[i] === sequence[i - 2] && sequence[i] !== sequence[i - 1]) {
      signals.push({
        type: 'oscillation',
        weight: 3,
        description: 'A→B→A oscillation pattern detected in tool calls',
        turnIndex: _state.turnCount,
      })
      break // One oscillation signal per check is enough
    }
  }

  return signals
}

/**
 * Detect filler turns — assistant turns with no tool use (just narration/summary).
 */
export function detectFillerTurn(
  messages: Message[],
  state: WanderState,
): WanderSignal[] {
  const signals: WanderSignal[] = []

  // Check only the most recent assistant message
  const lastAssistant = messages
    .filter((m) => m.type === 'assistant')
    .at(-1)

  if (!lastAssistant) return signals

  const toolUses = extractToolUses(lastAssistant)
  if (toolUses.length === 0) {
    signals.push({
      type: 'filler_turn',
      weight: 1,
      description: 'Assistant turn with no tool use (summary/narration only)',
      turnIndex: state.turnCount,
    })
  }

  return signals
}

/**
 * Detect file re-reads — reading a file that was already read in the last 4 turns.
 */
export function detectFileReread(
  messages: Message[],
  state: WanderState,
): WanderSignal[] {
  const signals: WanderSignal[] = []

  // Get file reads from the most recent assistant message
  const lastAssistant = messages
    .filter((m) => m.type === 'assistant')
    .at(-1)

  if (!lastAssistant) return signals

  const currentReads = extractFileReads(lastAssistant)

  for (const filePath of currentReads) {
    if (state.recentFileReads.includes(filePath)) {
      signals.push({
        type: 'file_reread',
        weight: 1,
        description: `Re-reading already-read file: ${filePath.split('/').pop()}`,
        turnIndex: state.turnCount,
      })
    }
  }

  // Update state — keep last REREAD_LOOKBACK * 3 reads (multiple reads per turn)
  state.recentFileReads = [
    ...state.recentFileReads,
    ...currentReads,
  ].slice(-(REREAD_LOOKBACK * 3))

  return signals
}
