/**
 * Wander Detector — Core scoring logic and escalation protocol.
 *
 * Detects when the agent is looping, oscillating, or producing filler,
 * and escalates through: nudge → compact → halt.
 */

// Message type is generated at build time from types/message.js
// Using the same import pattern as other services (e.g., SessionMemory)
import type { Message } from '../../types/message.js'
import {
  detectRepeatedToolCalls,
  detectSameError,
  detectOscillation,
  detectFillerTurn,
  detectFileReread,
} from './signals.js'
import {
  type EscalationAction,
  type WanderState,
  createFreshWanderState,
} from './types.js'

// Escalation thresholds
const NUDGE_THRESHOLD = 3
const COMPACT_THRESHOLD = 6
const HALT_THRESHOLD = 9

// Singleton session state — resets when the module is re-imported (new session)
let wanderState: WanderState = createFreshWanderState()

/**
 * Get the current wander state (for turn budget display).
 */
export function getWanderState(): Readonly<WanderState> {
  return wanderState
}

/**
 * Reset wander state (for testing or explicit reset).
 */
export function resetWanderState(): void {
  wanderState = createFreshWanderState()
}

/**
 * Extract a short approach description from the most recent assistant turn's tool uses.
 */
function extractApproach(messages: Message[]): string | undefined {
  const lastAssistant = messages
    .filter((m) => m.type === 'assistant')
    .at(-1)

  if (!lastAssistant) return undefined

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const msg = lastAssistant as any
  const content = msg.message?.content
  if (!Array.isArray(content)) return undefined

  const toolUses = content.filter(
    (block: { type: string }) => block.type === 'tool_use',
  )
  if (toolUses.length === 0) return undefined

  // Build a short approach description from tool names + key inputs
  const tools = toolUses.map(
    (t: { name: string; input?: Record<string, unknown> }) => {
      const name = t.name
      // For edit/write tools, include the file path
      const filePath =
        t.input?.file_path ?? t.input?.path ?? t.input?.command
      if (filePath && typeof filePath === 'string') {
        const short = filePath.split(/[/\\]/).pop() ?? filePath
        return `${name}(${short})`
      }
      return name
    },
  )

  return tools.join(' + ')
}

/**
 * Analyze the current message history for wander signals.
 * Returns an escalation action if the score crosses a threshold, or undefined if OK.
 */
export function analyzeForWander(
  messages: Message[],
): EscalationAction | undefined {
  wanderState.turnCount++

  // Run all signal detectors
  const newSignals = [
    ...detectRepeatedToolCalls(messages, wanderState),
    ...detectSameError(messages, wanderState),
    ...detectOscillation(messages, wanderState),
    ...detectFillerTurn(messages, wanderState),
    ...detectFileReread(messages, wanderState),
  ]

  // Add new signals to state
  wanderState.signals.push(...newSignals)

  // Calculate score from new signals only (cumulative)
  const newScore = newSignals.reduce((sum, s) => sum + s.weight, 0)
  wanderState.score += newScore

  // Track approach
  const approach = extractApproach(messages)
  if (
    approach &&
    !wanderState.approachesTried.includes(approach)
  ) {
    wanderState.approachesTried.push(approach)
    // Keep last 10 approaches
    if (wanderState.approachesTried.length > 10) {
      wanderState.approachesTried = wanderState.approachesTried.slice(-10)
    }
  }

  // No new signals this turn — reduce consecutive escalation count
  if (newSignals.length === 0) {
    wanderState.consecutiveEscalations = Math.max(
      0,
      wanderState.consecutiveEscalations - 1,
    )
    return undefined
  }

  // Determine escalation level
  if (wanderState.score >= HALT_THRESHOLD) {
    wanderState.consecutiveEscalations++
    const approachList = wanderState.approachesTried.length > 0
      ? wanderState.approachesTried.join(', ')
      : 'unknown'

    return {
      level: 'halt',
      message: [
        `I've been unable to make progress after ${wanderState.turnCount} turns.`,
        `Approaches tried: ${approachList}`,
        `Wander score: ${wanderState.score} (${wanderState.signals.length} signals detected).`,
        'What should I do differently?',
      ].join('\n'),
    }
  }

  if (wanderState.score >= COMPACT_THRESHOLD) {
    wanderState.consecutiveEscalations++
    return {
      level: 'compact',
      message: [
        'Multiple wander signals detected. Compacting context to preserve progress.',
        `Approaches tried: ${wanderState.approachesTried.join(', ') || 'unknown'}`,
        'Retaining only file state and test results. Trying a fundamentally different strategy.',
      ].join('\n'),
    }
  }

  if (wanderState.score >= NUDGE_THRESHOLD) {
    wanderState.consecutiveEscalations++
    const recentSignals = newSignals
      .map((s) => s.description)
      .join('; ')

    return {
      level: 'nudge',
      message: [
        `Wander detected: ${recentSignals}`,
        `Approaches tried so far: ${wanderState.approachesTried.join(', ') || 'none yet'}`,
        'Try a fundamentally different strategy rather than retrying the same approach.',
      ].join('\n'),
    }
  }

  return undefined
}
