import { type LonLatPoint } from '../../utils/projection'
import { DEFAULT_GAME_AUDIO_CONFIG } from '../audio/audioConfig'
import { type GameControl, type GameDefinition } from '../core/gameDefinition'
import { type GameEvent } from '../core/gameEvent'
import { type GameEffect, type GameResult } from '../core/gameResult'
import { type GameSessionState } from '../core/gameSessionState'
import { EMPTY_GAME_PRESENTATION_STATE, type GamePresentationState } from '../presentation/presentationState'
import { type HudPresentationState } from '../presentation/hudPresentationState'
import { type MapPresentationState } from '../presentation/mapPresentationState'
import { type RulePlugin } from './rulePlugin'

type ClassicSequentialModeState = {
  mode: 'classic-sequential'
  phase: 'start' | 'course' | 'finish' | 'done'
}

function getApproxDistanceMeters(a: LonLatPoint, b: LonLatPoint): number {
  const avgLatRad = ((a.lat + b.lat) / 2) * Math.PI / 180
  const dx = (b.lon - a.lon) * 111320 * Math.cos(avgLatRad)
  const dy = (b.lat - a.lat) * 110540
  return Math.sqrt(dx * dx + dy * dy)
}

function getScoringControls(definition: GameDefinition): GameControl[] {
  return definition.controls.filter((control) => control.kind === 'control')
}

function getSequentialTargets(definition: GameDefinition): GameControl[] {
  return definition.controls
}

function getCompletedControlSequences(definition: GameDefinition, state: GameSessionState): number[] {
  return getScoringControls(definition)
    .filter((control) => state.completedControlIds.includes(control.id) && typeof control.sequence === 'number')
    .map((control) => control.sequence as number)
}

function getSkippedControlSequences(definition: GameDefinition, state: GameSessionState): number[] {
  return getScoringControls(definition)
    .filter((control) => state.skippedControlIds.includes(control.id) && typeof control.sequence === 'number')
    .map((control) => control.sequence as number)
}

function getCurrentTarget(definition: GameDefinition, state: GameSessionState): GameControl | null {
  return getSequentialTargets(definition).find((control) => control.id === state.currentTargetControlId) || null
}

function getNextTarget(definition: GameDefinition, currentTarget: GameControl): GameControl | null {
  const targets = getSequentialTargets(definition)
  const currentIndex = targets.findIndex((control) => control.id === currentTarget.id)
  return currentIndex >= 0 && currentIndex < targets.length - 1
    ? targets[currentIndex + 1]
    : null
}

function getCompletedLegIndices(definition: GameDefinition, state: GameSessionState): number[] {
  const targets = getSequentialTargets(definition)
  const completedLegIndices: number[] = []

  for (let index = 1; index < targets.length; index += 1) {
    if (state.completedControlIds.includes(targets[index].id)) {
      completedLegIndices.push(index - 1)
    }
  }

  return completedLegIndices
}

function getTargetText(control: GameControl): string {
  if (control.kind === 'start') {
    return '开始点'
  }

  if (control.kind === 'finish') {
    return '终点'
  }

  return '目标圈'
}

function getGuidanceState(definition: GameDefinition, distanceMeters: number): GameSessionState['guidanceState'] {
  const audioConfig = definition.audioConfig || DEFAULT_GAME_AUDIO_CONFIG
  const readyDistanceMeters = Math.max(definition.punchRadiusMeters, audioConfig.readyDistanceMeters)
  const approachDistanceMeters = Math.max(readyDistanceMeters, audioConfig.approachDistanceMeters)
  const distantDistanceMeters = Math.max(approachDistanceMeters, audioConfig.distantDistanceMeters)

  if (distanceMeters <= readyDistanceMeters) {
    return 'ready'
  }

  if (distanceMeters <= approachDistanceMeters) {
    return 'approaching'
  }

  if (distanceMeters <= distantDistanceMeters) {
    return 'distant'
  }

  return 'searching'
}

function getGuidanceEffects(
  previousState: GameSessionState['guidanceState'],
  nextState: GameSessionState['guidanceState'],
  controlId: string | null,
): GameEffect[] {
  if (previousState === nextState) {
    return []
  }

  return [{ type: 'guidance_state_changed', guidanceState: nextState, controlId }]
}


function buildPunchHintText(definition: GameDefinition, state: GameSessionState, currentTarget: GameControl | null): string {
  if (state.status === 'idle') {
    return '先打开始点即可正式开始比赛'
  }

  if (state.status === 'finished') {
    return '本局已完成'
  }

  if (!currentTarget) {
    return '本局已完成'
  }

  const targetText = getTargetText(currentTarget)
  if (state.inRangeControlId !== currentTarget.id) {
    return definition.punchPolicy === 'enter'
      ? `进入${targetText}自动打点`
      : `进入${targetText}后点击打点`
  }

  return definition.punchPolicy === 'enter'
    ? `${targetText}内，自动打点中`
    : `${targetText}内，可点击打点`
}

function buildTargetSummaryText(state: GameSessionState, currentTarget: GameControl | null): string {
  if (state.status === 'finished') {
    return '本局已完成'
  }

  if (!currentTarget) {
    return '等待路线初始化'
  }

  if (currentTarget.kind === 'start') {
    return `${currentTarget.label} / 先打开始点`
  }

  if (currentTarget.kind === 'finish') {
    return `${currentTarget.label} / 前往终点`
  }

  const sequenceText = typeof currentTarget.sequence === 'number'
    ? `第 ${currentTarget.sequence} 点`
    : '当前目标点'
  return `${sequenceText} / ${currentTarget.label}`
}

function buildSkipFeedbackText(currentTarget: GameControl): string {
  if (currentTarget.kind === 'start') {
    return '开始点不可跳过'
  }

  if (currentTarget.kind === 'finish') {
    return '终点不可跳过'
  }

  return `已跳过检查点 ${typeof currentTarget.sequence === 'number' ? currentTarget.sequence : currentTarget.label}`
}

function resolveGuidanceForTarget(
  definition: GameDefinition,
  previousState: GameSessionState,
  target: GameControl | null,
  location: LonLatPoint | null,
): { guidanceState: GameSessionState['guidanceState']; inRangeControlId: string | null; effects: GameEffect[] } {
  if (!target || !location) {
    const guidanceState: GameSessionState['guidanceState'] = 'searching'
    return {
      guidanceState,
      inRangeControlId: null,
      effects: getGuidanceEffects(previousState.guidanceState, guidanceState, target ? target.id : null),
    }
  }

  const distanceMeters = getApproxDistanceMeters(target.point, location)
  const guidanceState = getGuidanceState(definition, distanceMeters)
  const inRangeControlId = distanceMeters <= definition.punchRadiusMeters ? target.id : null
  return {
    guidanceState,
    inRangeControlId,
    effects: getGuidanceEffects(previousState.guidanceState, guidanceState, target.id),
  }
}

function buildPresentation(definition: GameDefinition, state: GameSessionState): GamePresentationState {
  const scoringControls = getScoringControls(definition)
  const sequentialTargets = getSequentialTargets(definition)
  const currentTarget = getCurrentTarget(definition, state)
  const currentTargetIndex = currentTarget ? sequentialTargets.findIndex((control) => control.id === currentTarget.id) : -1
  const completedControls = scoringControls.filter((control) => state.completedControlIds.includes(control.id))
  const running = state.status === 'running'
  const activeLegIndices = running && currentTargetIndex > 0
    ? [currentTargetIndex - 1]
    : []
  const completedLegIndices = getCompletedLegIndices(definition, state)
  const punchButtonEnabled = running && !!currentTarget && state.inRangeControlId === currentTarget.id && definition.punchPolicy === 'enter-confirm'
  const activeStart = running && !!currentTarget && currentTarget.kind === 'start'
  const completedStart = definition.controls.some((control) => control.kind === 'start' && state.completedControlIds.includes(control.id))
  const activeFinish = running && !!currentTarget && currentTarget.kind === 'finish'
  const completedFinish = definition.controls.some((control) => control.kind === 'finish' && state.completedControlIds.includes(control.id))
  const punchButtonText = currentTarget
    ? currentTarget.kind === 'start'
      ? '开始打卡'
      : currentTarget.kind === 'finish'
        ? '结束打卡'
        : '打点'
    : '打点'
  const revealFullCourse = completedStart
  const hudPresentation: HudPresentationState = {
    actionTagText: '目标',
    distanceTagText: '点距',
    targetSummaryText: buildTargetSummaryText(state, currentTarget),
    hudTargetControlId: currentTarget ? currentTarget.id : null,
    progressText: '0/0',
    punchButtonText,
    punchableControlId: punchButtonEnabled && currentTarget ? currentTarget.id : null,
    punchButtonEnabled,
    punchHintText: buildPunchHintText(definition, state, currentTarget),
  }
  const targetingPresentation = {
    punchableControlId: punchButtonEnabled && currentTarget ? currentTarget.id : null,
    guidanceControlId: currentTarget ? currentTarget.id : null,
    hudControlId: currentTarget ? currentTarget.id : null,
    highlightedControlId: running && currentTarget ? currentTarget.id : null,
  }

  if (!scoringControls.length) {
    return {
      map: {
        ...EMPTY_GAME_PRESENTATION_STATE.map,
        controlVisualMode: 'single-target',
        showCourseLegs: true,
        guidanceLegAnimationEnabled: true,
        focusableControlIds: [],
        focusedControlId: null,
        focusedControlSequences: [],
        activeStart,
        completedStart,
        activeFinish,
        focusedFinish: false,
        completedFinish,
        revealFullCourse,
        activeLegIndices,
        completedLegIndices,
        skippedControlIds: [],
        skippedControlSequences: [],
      },
      hud: hudPresentation,
      targeting: targetingPresentation,
    }
  }

  const mapPresentation: MapPresentationState = {
    controlVisualMode: 'single-target',
    showCourseLegs: true,
    guidanceLegAnimationEnabled: true,
    focusableControlIds: [],
    focusedControlId: null,
    focusedControlSequences: [],
    activeControlIds: running && currentTarget ? [currentTarget.id] : [],
    activeControlSequences: running && currentTarget && currentTarget.kind === 'control' && typeof currentTarget.sequence === 'number' ? [currentTarget.sequence] : [],
    activeStart,
    completedStart,
    activeFinish,
    focusedFinish: false,
    completedFinish,
    revealFullCourse,
    activeLegIndices,
    completedLegIndices,
    completedControlIds: completedControls.map((control) => control.id),
    completedControlSequences: getCompletedControlSequences(definition, state),
    skippedControlIds: state.skippedControlIds,
    skippedControlSequences: getSkippedControlSequences(definition, state),
  }

  return {
    map: mapPresentation,
    hud: {
      ...hudPresentation,
      progressText: `${completedControls.length}/${scoringControls.length}`,
    },
    targeting: targetingPresentation,
  }
}

function resolveClassicPhase(nextTarget: GameControl | null, currentTarget: GameControl, finished: boolean): ClassicSequentialModeState['phase'] {
  if (finished || currentTarget.kind === 'finish') {
    return 'done'
  }

  if (currentTarget.kind === 'start') {
    return nextTarget && nextTarget.kind === 'finish' ? 'finish' : 'course'
  }

  if (nextTarget && nextTarget.kind === 'finish') {
    return 'finish'
  }

  return 'course'
}

function getInitialTargetId(definition: GameDefinition): string | null {
  const firstTarget = getSequentialTargets(definition)[0]
  return firstTarget ? firstTarget.id : null
}

function buildCompletedEffect(control: GameControl, punchPolicy: GameDefinition['punchPolicy']): GameEffect {
  const allowAutoPopup = punchPolicy === 'enter'
    ? false
    : (control.displayContent ? control.displayContent.autoPopup : true)
  const autoOpenQuiz = control.kind === 'control'
    && !!control.displayContent
    && control.displayContent.ctas.some((item) => item.type === 'quiz')
  if (control.kind === 'start') {
    return {
      type: 'control_completed',
      controlId: control.id,
      controlKind: 'start',
      sequence: null,
      label: control.label,
      displayTitle: control.displayContent ? control.displayContent.title : '比赛开始',
      displayBody: control.displayContent ? control.displayContent.body : '已完成开始点打卡，前往 1 号点。',
      displayAutoPopup: allowAutoPopup,
      displayOnce: control.displayContent ? control.displayContent.once : false,
      displayPriority: control.displayContent ? control.displayContent.priority : 1,
      autoOpenQuiz: false,
    }
  }

  if (control.kind === 'finish') {
    return {
      type: 'control_completed',
      controlId: control.id,
      controlKind: 'finish',
      sequence: null,
      label: control.label,
      displayTitle: control.displayContent ? control.displayContent.title : '比赛结束',
      displayBody: control.displayContent ? control.displayContent.body : '已完成终点打卡，本局结束。',
      displayAutoPopup: allowAutoPopup,
      displayOnce: control.displayContent ? control.displayContent.once : false,
      displayPriority: control.displayContent ? control.displayContent.priority : 2,
      autoOpenQuiz: false,
    }
  }

  const sequenceText = typeof control.sequence === 'number' ? String(control.sequence) : control.label
  const displayTitle = control.displayContent ? control.displayContent.title : `完成 ${sequenceText}`
  const displayBody = control.displayContent ? control.displayContent.body : control.label

  return {
    type: 'control_completed',
    controlId: control.id,
    controlKind: 'control',
    sequence: control.sequence,
    label: control.label,
    displayTitle,
    displayBody,
    displayAutoPopup: allowAutoPopup,
    displayOnce: control.displayContent ? control.displayContent.once : false,
    displayPriority: control.displayContent ? control.displayContent.priority : 1,
    autoOpenQuiz,
  }
}

function applyCompletion(definition: GameDefinition, state: GameSessionState, currentTarget: GameControl, at: number): GameResult {
  const completedControlIds = state.completedControlIds.includes(currentTarget.id)
    ? state.completedControlIds
    : [...state.completedControlIds, currentTarget.id]
  const nextTarget = getNextTarget(definition, currentTarget)
  const completedFinish = currentTarget.kind === 'finish'
  const finished = completedFinish || (!nextTarget && definition.autoFinishOnLastControl)
  const nextState: GameSessionState = {
    ...state,
    endReason: finished ? 'completed' : state.endReason,
    startedAt: currentTarget.kind === 'start' && state.startedAt === null ? at : state.startedAt,
    completedControlIds,
    skippedControlIds: currentTarget.id === state.currentTargetControlId
      ? state.skippedControlIds.filter((controlId) => controlId !== currentTarget.id)
      : state.skippedControlIds,
    currentTargetControlId: nextTarget ? nextTarget.id : null,
    inRangeControlId: null,
    score: getScoringControls(definition).filter((control) => completedControlIds.includes(control.id)).length,
    status: finished ? 'finished' : state.status,
    endedAt: finished ? at : state.endedAt,
    guidanceState: nextTarget ? 'searching' : 'searching',
    modeState: {
      mode: 'classic-sequential',
      phase: resolveClassicPhase(nextTarget, currentTarget, finished),
    },
  }
  const effects: GameEffect[] = [buildCompletedEffect(currentTarget, definition.punchPolicy)]

  if (finished) {
    effects.push({ type: 'session_finished' })
  }

  return {
    nextState,
    presentation: buildPresentation(definition, nextState),
    effects,
  }
}

function applySkip(
  definition: GameDefinition,
  state: GameSessionState,
  currentTarget: GameControl,
  location: LonLatPoint | null,
): GameResult {
  const nextTarget = getNextTarget(definition, currentTarget)
  const nextPhase = nextTarget && nextTarget.kind === 'finish' ? 'finish' : 'course'
  const nextGuidance = resolveGuidanceForTarget(definition, state, nextTarget, location)
  const nextState: GameSessionState = {
    ...state,
    skippedControlIds: state.skippedControlIds.includes(currentTarget.id)
      ? state.skippedControlIds
      : [...state.skippedControlIds, currentTarget.id],
    currentTargetControlId: nextTarget ? nextTarget.id : null,
    inRangeControlId: nextGuidance.inRangeControlId,
    guidanceState: nextGuidance.guidanceState,
    modeState: {
      mode: 'classic-sequential',
      phase: nextTarget ? nextPhase : 'done',
    },
  }

  return {
    nextState,
    presentation: buildPresentation(definition, nextState),
    effects: [
      ...nextGuidance.effects,
      { type: 'punch_feedback', text: buildSkipFeedbackText(currentTarget), tone: 'neutral' },
    ],
  }
}

export class ClassicSequentialRule implements RulePlugin {
  get mode(): 'classic-sequential' {
    return 'classic-sequential'
  }

  initialize(definition: GameDefinition): GameSessionState {
    return {
      status: 'idle',
      endReason: null,
      startedAt: null,
      endedAt: null,
      completedControlIds: [],
      skippedControlIds: [],
      currentTargetControlId: getInitialTargetId(definition),
      inRangeControlId: null,
      score: 0,
      guidanceState: 'searching',
      modeState: {
        mode: 'classic-sequential',
        phase: 'start',
      },
    }
  }

  buildPresentation(definition: GameDefinition, state: GameSessionState): GamePresentationState {
    return buildPresentation(definition, state)
  }

  reduce(definition: GameDefinition, state: GameSessionState, event: GameEvent): GameResult {
    if (event.type === 'session_started') {
      const nextState: GameSessionState = {
        ...state,
        status: 'running',
        endReason: null,
        startedAt: null,
        endedAt: null,
        inRangeControlId: null,
        guidanceState: 'searching',
        modeState: {
          mode: 'classic-sequential',
          phase: 'start',
        },
      }
      return {
        nextState,
        presentation: buildPresentation(definition, nextState),
        effects: [{ type: 'session_started' }],
      }
    }

    if (event.type === 'session_ended') {
      const nextState: GameSessionState = {
        ...state,
        status: 'finished',
        endReason: 'completed',
        endedAt: event.at,
        guidanceState: 'searching',
        modeState: {
          mode: 'classic-sequential',
          phase: 'done',
        },
      }
      return {
        nextState,
        presentation: buildPresentation(definition, nextState),
        effects: [{ type: 'session_finished' }],
      }
    }

    if (event.type === 'session_timed_out') {
      const nextState: GameSessionState = {
        ...state,
        status: 'failed',
        endReason: 'timed_out',
        endedAt: event.at,
        guidanceState: 'searching',
        modeState: {
          mode: 'classic-sequential',
          phase: 'done',
        },
      }
      return {
        nextState,
        presentation: buildPresentation(definition, nextState),
        effects: [{ type: 'session_timed_out' }],
      }
    }

    if (state.status !== 'running' || !state.currentTargetControlId) {
      return {
        nextState: state,
        presentation: buildPresentation(definition, state),
        effects: [],
      }
    }

    const currentTarget = getCurrentTarget(definition, state)
    if (!currentTarget) {
      return {
        nextState: state,
        presentation: buildPresentation(definition, state),
        effects: [],
      }
    }

    if (event.type === 'gps_updated') {
      const distanceMeters = getApproxDistanceMeters(currentTarget.point, { lon: event.lon, lat: event.lat })
      const inRangeControlId = distanceMeters <= definition.punchRadiusMeters ? currentTarget.id : null
      const guidanceState = getGuidanceState(definition, distanceMeters)
      const nextState: GameSessionState = {
        ...state,
        inRangeControlId,
        guidanceState,
      }
      const guidanceEffects = getGuidanceEffects(state.guidanceState, guidanceState, currentTarget.id)

      if (definition.punchPolicy === 'enter' && inRangeControlId === currentTarget.id) {
        const completionResult = applyCompletion(definition, nextState, currentTarget, event.at)
        return {
          ...completionResult,
          effects: [...guidanceEffects, ...completionResult.effects],
        }
      }

      return {
        nextState,
        presentation: buildPresentation(definition, nextState),
        effects: guidanceEffects,
      }
    }

    if (event.type === 'punch_requested') {
      if (state.inRangeControlId !== currentTarget.id) {
        return {
          nextState: state,
          presentation: buildPresentation(definition, state),
          effects: [{ type: 'punch_feedback', text: currentTarget.kind === 'start' ? '未进入开始点打卡范围' : currentTarget.kind === 'finish' ? '未进入终点打卡范围' : '未进入目标打点范围', tone: 'warning' }],
        }
      }

      return applyCompletion(definition, state, currentTarget, event.at)
    }

    if (event.type === 'skip_requested') {
      if (!definition.skipEnabled) {
        return {
          nextState: state,
          presentation: buildPresentation(definition, state),
          effects: [{ type: 'punch_feedback', text: '当前配置未开启跳点', tone: 'warning' }],
        }
      }

      if (currentTarget.kind !== 'control') {
        return {
          nextState: state,
          presentation: buildPresentation(definition, state),
          effects: [{ type: 'punch_feedback', text: currentTarget.kind === 'start' ? '开始点不可跳过' : '终点不可跳过', tone: 'warning' }],
        }
      }

      if (event.lon === null || event.lat === null) {
        return {
          nextState: state,
          presentation: buildPresentation(definition, state),
          effects: [{ type: 'punch_feedback', text: '当前无定位，无法跳点', tone: 'warning' }],
        }
      }

      const distanceMeters = getApproxDistanceMeters(currentTarget.point, { lon: event.lon, lat: event.lat })
      if (distanceMeters > definition.skipRadiusMeters) {
        return {
          nextState: state,
          presentation: buildPresentation(definition, state),
          effects: [{ type: 'punch_feedback', text: `未进入跳点范围 (${Math.round(definition.skipRadiusMeters)}m)`, tone: 'warning' }],
        }
      }

      return applySkip(
        definition,
        state,
        currentTarget,
        { lon: event.lon, lat: event.lat },
      )
    }

    return {
      nextState: state,
      presentation: buildPresentation(definition, state),
      effects: [],
    }
  }
}


