import { BULLDROME_BOSS_METADATA } from '../content/bosses'
import type {
  Iwt2ArenaEvent,
  Iwt2ArenaIndicator,
  Iwt2ArenaState,
  Iwt2BossEntityState,
  Iwt2EntityId,
  Iwt2GroundHazardState,
  Iwt2HostileAddState,
  Iwt2PartyEntityState,
  Iwt2ProjectileEntityState,
  Iwt2Vec2,
} from './types'
import { tickGreatJaggi } from './greatJaggiAi'
import { tickKhezu } from './khezuAi'
import { tickYianKutKu } from './yianKutKuAi'
import {
  applyPartyDamageInShape,
  createArenaEvent,
  createCircleIndicator,
  createLaneIndicator,
  createLinearMovementPlan,
  indicatorPhaseFromAttack,
} from './mechanics'
import {
  clampVec2ToArena,
  distanceVec2,
  moveToward,
  scaleVec2,
  subtractVec2,
  withFallbackFacing,
} from './vector'

export type Iwt2BossTickResult = {
  boss: Iwt2BossEntityState
  party: Iwt2PartyEntityState[]
  hostileAdds?: Iwt2HostileAddState[]
  hazards?: Iwt2GroundHazardState[]
  projectiles?: Iwt2ProjectileEntityState[]
  events: Iwt2ArenaEvent[]
  indicators: Iwt2ArenaIndicator[]
  nextAddId?: number
  nextHazardId?: number
  nextProjectileId?: number
}

export function tickBoss(state: Iwt2ArenaState, dt: number): Iwt2BossTickResult {
  if (state.boss.bossId === 'yian-kut-ku') return tickYianKutKu(state, dt)
  if (state.boss.bossId === 'great-jaggi') return tickGreatJaggi(state, dt)
  if (state.boss.bossId === 'khezu') return tickKhezu(state, dt)
  return tickBulldrome(state, dt)
}

export function tickBulldrome(state: Iwt2ArenaState, dt: number): Iwt2BossTickResult {
  const events: Iwt2ArenaEvent[] = []
  const target = getBossTarget(state.party)
  let party = state.party
  let boss = {
    ...state.boss,
    meleeCooldownRemaining: Math.max(0, state.boss.meleeCooldownRemaining - dt),
    chargeCooldownRemaining: Math.max(0, state.boss.chargeCooldownRemaining - dt),
    phaseSecondsRemaining: Math.max(0, state.boss.phaseSecondsRemaining - dt),
  }
  const wallContactSeconds = isBossNearWall(boss, state)
    ? boss.wallContactSeconds + dt
    : Math.max(0, boss.wallContactSeconds - dt * 2)
  boss = { ...boss, wallContactSeconds }

  if (boss.health <= 0 || !target) {
    return withBulldromeIndicators({ boss: { ...boss, velocity: { x: 0, y: 0 } }, party, events })
  }

  if (boss.attackPhase === 'relocating') {
    const nextPosition = clampVec2ToArena(
      moveToward(boss.position, boss.relocateTarget, BULLDROME_BOSS_METADATA.moveSpeed * 1.45 * dt),
      boss.radius,
      state.bounds,
    )
    const velocity = scaleVec2(subtractVec2(nextPosition, boss.position), dt > 0 ? 1 / dt : 0)
    boss = {
      ...boss,
      position: nextPosition,
      velocity,
      facing: withFallbackFacing(subtractVec2(target.position, nextPosition), boss.facing),
      wallContactSeconds: isBossNearWall({ ...boss, position: nextPosition }, state) ? boss.wallContactSeconds : 0,
    }
    if (boss.phaseSecondsRemaining <= 0 || distanceVec2(nextPosition, boss.relocateTarget) <= 8) {
      boss = {
        ...boss,
        attackPhase: 'idle',
        phaseSecondsRemaining: 0,
        chargeCooldownRemaining: Math.max(boss.chargeCooldownRemaining, 1.2),
        velocity: { x: 0, y: 0 },
      }
    }
    return withBulldromeIndicators({ boss, party, events })
  }

  if (boss.attackPhase === 'chargeWindup') {
    boss = {
      ...boss,
      velocity: { x: 0, y: 0 },
      facing: withFallbackFacing(subtractVec2(boss.chargeEnd, boss.position), boss.facing),
    }
    if (boss.phaseSecondsRemaining <= 0) boss = { ...boss, attackPhase: 'charging' }
    return withBulldromeIndicators({ boss, party, events })
  }

  if (boss.attackPhase === 'charging') {
    const nextPosition = clampVec2ToArena(
      moveToward(boss.position, boss.chargeEnd, BULLDROME_BOSS_METADATA.chargeSpeed * dt),
      boss.radius,
      state.bounds,
    )
    const hitResult = applyChargeHits(party, boss, boss.position, nextPosition, state.time + dt)
    party = hitResult.party
    events.push(...hitResult.events)
    const velocity = scaleVec2(subtractVec2(nextPosition, boss.position), dt > 0 ? 1 / dt : 0)
    boss = {
      ...boss,
      position: nextPosition,
      velocity,
      facing: withFallbackFacing(velocity, boss.facing),
      chargeHitEntityIds: hitResult.hitEntityIds,
    }
    if (distanceVec2(nextPosition, boss.chargeEnd) <= 1) {
      const needsSlam = boss.chargeCount % 3 === 0
      boss = {
        ...boss,
        attackPhase: needsSlam ? 'slamWindup' : 'chargeRecover',
        phaseSecondsRemaining: needsSlam ? BULLDROME_BOSS_METADATA.slamWindup : 0.45,
        slamApplied: false,
        velocity: { x: 0, y: 0 },
      }
    }
    return withBulldromeIndicators({ boss, party, events })
  }

  if (boss.attackPhase === 'slamWindup') {
    boss = { ...boss, velocity: { x: 0, y: 0 } }
    if (boss.phaseSecondsRemaining <= 0) {
      const slamResult = applySlam(party, boss, state.time + dt)
      party = slamResult.party
      events.push(...slamResult.events)
      boss = {
        ...boss,
        attackPhase: 'slamRecover',
        phaseSecondsRemaining: 0.5,
        slamApplied: true,
      }
    }
    return withBulldromeIndicators({ boss, party, events })
  }

  if (boss.attackPhase === 'chargeRecover' || boss.attackPhase === 'slamRecover') {
    boss = { ...boss, velocity: { x: 0, y: 0 } }
    if (boss.phaseSecondsRemaining <= 0) {
      boss = { ...boss, attackPhase: 'idle', phaseSecondsRemaining: 0 }
    }
    return withBulldromeIndicators({ boss, party, events })
  }

  if (boss.attackPhase === 'idle' && boss.wallContactSeconds >= 1.1) {
    boss = {
      ...boss,
      attackPhase: 'relocating',
      phaseSecondsRemaining: 1.8,
      relocateTarget: relocationTarget(boss, state),
      velocity: { x: 0, y: 0 },
    }
    return withBulldromeIndicators({ boss, party, events })
  }

  if (boss.chargeCooldownRemaining <= 0) {
    const charge = createLinearMovementPlan({
      bounds: state.bounds,
      from: boss.position,
      length: BULLDROME_BOSS_METADATA.chargeLength,
      radius: boss.radius,
      target: target.position,
    })
    boss = {
      ...boss,
      attackPhase: 'chargeWindup',
      phaseSecondsRemaining: BULLDROME_BOSS_METADATA.chargeWindup,
      chargeCooldownRemaining: BULLDROME_BOSS_METADATA.chargeCooldown,
      chargeCount: boss.chargeCount + 1,
      chargeStart: { ...boss.position },
      chargeEnd: charge.end,
      chargeHitEntityIds: [],
      slamApplied: false,
      facing: charge.direction,
      velocity: { x: 0, y: 0 },
    }
    events.push(createArenaEvent(state.nextEventId + events.length, state.time + dt, 'bossChargeStart', boss.id, target.id))
    return withBulldromeIndicators({ boss, party, events })
  }

  const meleeResult = maybeApplyMelee(party, boss, target, state.time + dt, state.nextEventId + events.length)
  party = meleeResult.party
  events.push(...meleeResult.events)
  boss = meleeResult.boss

  if (meleeResult.events.length > 0) return withBulldromeIndicators({ boss, party, events })

  const nextPosition = clampVec2ToArena(
    moveToward(boss.position, target.position, BULLDROME_BOSS_METADATA.moveSpeed * dt),
    boss.radius,
    state.bounds,
  )
  const velocity = scaleVec2(subtractVec2(nextPosition, boss.position), dt > 0 ? 1 / dt : 0)
  boss = {
    ...boss,
    position: nextPosition,
    velocity,
    facing: withFallbackFacing(subtractVec2(target.position, nextPosition), boss.facing),
  }
  return withBulldromeIndicators({ boss, party, events })
}

function isBossNearWall(boss: Iwt2BossEntityState, state: Iwt2ArenaState) {
  const margin = boss.radius + 10
  return (
    boss.position.x <= margin
    || boss.position.x >= state.bounds.width - margin
    || boss.position.y <= margin
    || boss.position.y >= state.bounds.height - margin
  )
}

function relocationTarget(boss: Iwt2BossEntityState, state: Iwt2ArenaState): Iwt2Vec2 {
  const center = { x: state.bounds.width * 0.58, y: state.bounds.height * 0.5 }
  const awayFromWall = {
    x: boss.position.x < state.bounds.width * 0.5 ? 1 : -1,
    y: boss.position.y < state.bounds.height * 0.5 ? 1 : -1,
  }
  return {
    x: Math.min(state.bounds.width - boss.radius, Math.max(boss.radius, center.x + awayFromWall.x * 70)),
    y: Math.min(state.bounds.height - boss.radius, Math.max(boss.radius, center.y + awayFromWall.y * 54)),
  }
}

function getBossTarget(party: Iwt2PartyEntityState[]): Iwt2PartyEntityState | undefined {
  const livingTank = party.find((member) => member.classId === 'paladin' && member.health > 0)
  if (livingTank) return livingTank
  return party.find((member) => member.health > 0)
}

function maybeApplyMelee(
  party: Iwt2PartyEntityState[],
  boss: Iwt2BossEntityState,
  target: Iwt2PartyEntityState,
  time: number,
  firstEventId: number,
): Omit<Iwt2BossTickResult, 'indicators'> {
  if (boss.meleeCooldownRemaining > 0) return { boss, party, events: [] }
  if (distanceVec2(boss.position, target.position) > BULLDROME_BOSS_METADATA.meleeRange + target.radius) {
    return { boss, party, events: [] }
  }
  const nextParty = party.map((member) => {
    if (member.id !== target.id) return member
    return { ...member, health: Math.max(0, member.health - BULLDROME_BOSS_METADATA.meleeDamage) }
  })
  const events = [
    createArenaEvent(firstEventId, time, 'partyDamaged', boss.id, target.id, BULLDROME_BOSS_METADATA.meleeDamage),
  ]
  if (target.health > 0 && target.health - BULLDROME_BOSS_METADATA.meleeDamage <= 0) {
    events.push(createArenaEvent(firstEventId + 1, time, 'entityDefeated', boss.id, target.id))
  }
  return {
    boss: { ...boss, meleeCooldownRemaining: BULLDROME_BOSS_METADATA.meleeCooldown, velocity: { x: 0, y: 0 } },
    party: nextParty,
    events,
  }
}

function applyChargeHits(
  party: Iwt2PartyEntityState[],
  boss: Iwt2BossEntityState,
  start: Iwt2Vec2,
  end: Iwt2Vec2,
  time: number,
): { party: Iwt2PartyEntityState[], hitEntityIds: Iwt2EntityId[], events: Iwt2ArenaEvent[] } {
  const result = applyPartyDamageInShape(
    party,
    {
      kind: 'lane',
      start,
      end,
      width: boss.radius,
    },
    {
      damage: BULLDROME_BOSS_METADATA.chargeDamage,
      damageEventType: 'bossChargeHit',
      excludedEntityIds: boss.chargeHitEntityIds,
      knockdownSeconds: BULLDROME_BOSS_METADATA.chargeStunSeconds,
      sourceId: boss.id,
      stunSeconds: BULLDROME_BOSS_METADATA.chargeStunSeconds,
      time,
    },
  )
  return {
    ...result,
    hitEntityIds: [...boss.chargeHitEntityIds, ...result.hitEntityIds],
  }
}

function applySlam(
  party: Iwt2PartyEntityState[],
  boss: Iwt2BossEntityState,
  time: number,
): { party: Iwt2PartyEntityState[], events: Iwt2ArenaEvent[] } {
  const result = applyPartyDamageInShape(
    party,
    {
      kind: 'circle',
      position: boss.position,
      radius: BULLDROME_BOSS_METADATA.slamRadius,
    },
    {
      damage: BULLDROME_BOSS_METADATA.slamDamage,
      knockdownSeconds: BULLDROME_BOSS_METADATA.slamStunSeconds,
      sourceId: boss.id,
      stunSeconds: BULLDROME_BOSS_METADATA.slamStunSeconds,
      time,
    },
  )
  return {
    party: result.party,
    events: [
      createArenaEvent(0, time, 'bossSlam', boss.id, undefined, BULLDROME_BOSS_METADATA.slamRadius),
      ...result.events,
    ],
  }
}

function withBulldromeIndicators(
  result: Omit<Iwt2BossTickResult, 'indicators'>,
): Iwt2BossTickResult {
  return {
    ...result,
    indicators: createBulldromeIndicators(result.boss),
  }
}

function createBulldromeIndicators(boss: Iwt2BossEntityState): Iwt2ArenaIndicator[] {
  const indicators: Iwt2ArenaIndicator[] = []
  if (
    boss.attackPhase === 'chargeWindup'
    || boss.attackPhase === 'charging'
    || boss.attackPhase === 'chargeRecover'
  ) {
    indicators.push(createLaneIndicator({
      color: boss.attackPhase === 'charging' ? '#f05b4f' : '#f1c663',
      end: boss.chargeEnd,
      id: `${boss.id}:charge-lane`,
      mechanicId: 'bulldrome-charge',
      phase: indicatorPhaseFromAttack(
        boss.attackPhase === 'chargeWindup',
        boss.attackPhase === 'charging',
      ),
      sourceId: boss.id,
      start: boss.chargeStart,
      width: boss.radius,
    }))
  }
  if (boss.attackPhase === 'slamWindup' || boss.attackPhase === 'slamRecover') {
    indicators.push(createCircleIndicator({
      color: '#ff7f50',
      id: `${boss.id}:slam-circle`,
      mechanicId: 'bulldrome-slam',
      phase: indicatorPhaseFromAttack(
        boss.attackPhase === 'slamWindup',
        boss.attackPhase === 'slamRecover',
      ),
      position: boss.position,
      radius: BULLDROME_BOSS_METADATA.slamRadius,
      sourceId: boss.id,
    }))
  }
  return indicators
}
