import { YIAN_KUT_KU_BOSS_METADATA, type Iwt2BossId } from '../content/bosses'
import type {
  Iwt2ArenaEvent,
  Iwt2ArenaIndicator,
  Iwt2ArenaState,
  Iwt2BossEntityState,
  Iwt2GroundHazardState,
  Iwt2HostileAddState,
  Iwt2PartyEntityState,
  Iwt2ProjectileEntityState,
  Iwt2Vec2,
} from './types'
import {
  applyPartyDamageInShape,
  createCircleIndicator,
  createLaneIndicator,
  indicatorPhaseFromAttack,
  partyMemberIntersectsShape,
} from './mechanics'
import { addFirePuddle } from './hazards'
import {
  clamp,
  clampVec2ToArena,
  distanceVec2,
  moveToward,
  normalizeVec2,
  scaleVec2,
  subtractVec2,
  withFallbackFacing,
} from './vector'

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

const BIRD_COUNT = 3
const BIRD_MELEE_RANGE = 24
const BIRD_MELEE_COOLDOWN = 1.15
const BIRD_FLIGHT_DAMAGE = 10
const YIAN_FIREBALL_BOUNCES = 8
const YIAN_SAFE_WALL_MARGIN = 118
const YIAN_CENTER_CAST_DISTANCE = 36
const YIAN_CENTER_CHARGE_SPEED = 430

export function tickYianKutKu(state: Iwt2ArenaState, dt: number): Iwt2YianTickResult {
  const events: Iwt2ArenaEvent[] = []
  const projectiles: Iwt2ProjectileEntityState[] = []
  let party = state.party
  let hazards = state.hazards
  let nextAddId = state.nextAddId
  let nextHazardId = state.nextHazardId
  let nextProjectileId = state.nextProjectileId
  let boss = {
    ...state.boss,
    meleeCooldownRemaining: Math.max(0, state.boss.meleeCooldownRemaining - dt),
    fireballCooldownRemaining: Math.max(0, state.boss.fireballCooldownRemaining - dt),
    phaseSecondsRemaining: Math.max(0, state.boss.phaseSecondsRemaining - dt),
    velocity: { x: 0, y: 0 },
  }

  const birdResult = tickYianBirds({
    dt,
    hazards,
    nextHazardId,
    party,
    state,
    time: state.time + dt,
  })
  party = birdResult.party
  hazards = birdResult.hazards
  nextHazardId = birdResult.nextHazardId
  events.push(...birdResult.events)
  let hostileAdds = birdResult.hostileAdds

  const target = getBossTarget(party)
  if (boss.health <= 0 || !target) {
    return withYianIndicators({
      boss,
      events,
      hazards,
      hostileAdds,
      nextAddId,
      nextHazardId,
      nextProjectileId,
      party,
      projectiles,
    })
  }

  if (boss.attackPhase === 'relocating') {
    const nextPosition = clampVec2ToArena(
      moveToward(boss.position, boss.relocateTarget, YIAN_CENTER_CHARGE_SPEED * dt),
      boss.radius,
      state.bounds,
    )
    boss = {
      ...boss,
      position: nextPosition,
      velocity: scaleVec2(subtractVec2(nextPosition, boss.position), dt > 0 ? 1 / dt : 0),
      facing: withFallbackFacing(subtractVec2(target.position, nextPosition), boss.facing),
    }
    if (distanceVec2(nextPosition, boss.relocateTarget) <= 4 || boss.phaseSecondsRemaining <= 0) {
      boss = {
        ...boss,
        attackPhase: 'idle',
        phaseSecondsRemaining: 0,
        velocity: { x: 0, y: 0 },
      }
    }
    return withYianIndicators({
      boss,
      events,
      hazards,
      hostileAdds,
      nextAddId,
      nextHazardId,
      nextProjectileId,
      party,
      projectiles,
    })
  }

  if (boss.attackPhase === 'fireballWindup') {
    boss = {
      ...boss,
      facing: withFallbackFacing(subtractVec2(boss.fireballTarget, boss.position), boss.facing),
    }
    if (boss.phaseSecondsRemaining <= 0) {
      const direction = withFallbackFacing(subtractVec2(boss.fireballTarget, boss.position), boss.facing)
      projectiles.push({
        id: `boss-projectile-${nextProjectileId}`,
        sourceId: boss.id,
        owner: 'boss',
        projectileKind: 'fireball',
        color: '#ff8b2b',
        position: {
          x: boss.position.x + direction.x * (boss.radius + YIAN_KUT_KU_BOSS_METADATA.fireballRadius! + 2),
          y: boss.position.y + direction.y * (boss.radius + YIAN_KUT_KU_BOSS_METADATA.fireballRadius! + 2),
        },
        velocity: scaleVec2(direction, YIAN_KUT_KU_BOSS_METADATA.fireballSpeed!),
        radius: YIAN_KUT_KU_BOSS_METADATA.fireballRadius!,
        damage: YIAN_KUT_KU_BOSS_METADATA.fireballDamage!,
        remainingSeconds: 7,
        bouncesRemaining: YIAN_FIREBALL_BOUNCES,
      })
      nextProjectileId += 1
      boss = {
        ...boss,
        attackPhase: 'fireballRecover',
        phaseSecondsRemaining: 0.35,
      }
    }
    return withYianIndicators({
      boss,
      events,
      hazards,
      hostileAdds,
      nextAddId,
      nextHazardId,
      nextProjectileId,
      party,
      projectiles,
    })
  }

  if (boss.attackPhase === 'fireballRecover' || boss.attackPhase === 'birdSummonRecover') {
    if (boss.phaseSecondsRemaining <= 0) {
      boss = { ...boss, attackPhase: 'idle', phaseSecondsRemaining: 0 }
    }
    return withYianIndicators({
      boss,
      events,
      hazards,
      hostileAdds,
      nextAddId,
      nextHazardId,
      nextProjectileId,
      party,
      projectiles,
    })
  }

  if (shouldYianRelocate(boss, state)) {
    const center = yianCenterTarget(state)
    boss = {
      ...boss,
      attackPhase: 'relocating',
      phaseSecondsRemaining: 1.4,
      relocateTarget: center,
      chargeStart: { ...boss.position },
      chargeEnd: center,
      velocity: { x: 0, y: 0 },
    }
    return withYianIndicators({
      boss,
      events,
      hazards,
      hostileAdds,
      nextAddId,
      nextHazardId,
      nextProjectileId,
      party,
      projectiles,
    })
  }

  const nextThreshold = nextBirdWaveThreshold(boss)
  if (nextThreshold !== undefined && boss.health / boss.maxHealth <= nextThreshold) {
    const spawn = spawnBirdWave(state, nextAddId, boss.id, nextThreshold)
    hostileAdds = [...hostileAdds, ...spawn.hostileAdds]
    nextAddId = spawn.nextAddId
    boss = {
      ...boss,
      attackPhase: 'birdSummonRecover',
      birdWaveThresholdsTriggered: [...boss.birdWaveThresholdsTriggered, nextThreshold],
      phaseSecondsRemaining: 0.55,
    }
    return withYianIndicators({
      boss,
      events,
      hazards,
      hostileAdds,
      nextAddId,
      nextHazardId,
      nextProjectileId,
      party,
      projectiles,
    })
  }

  if (boss.fireballCooldownRemaining <= 0) {
    if (!isYianAtCenter(boss, state)) {
      const center = yianCenterTarget(state)
      boss = {
        ...boss,
        attackPhase: 'relocating',
        phaseSecondsRemaining: 1.4,
        relocateTarget: center,
        chargeStart: { ...boss.position },
        chargeEnd: center,
        velocity: { x: 0, y: 0 },
      }
      return withYianIndicators({
        boss,
        events,
        hazards,
        hostileAdds,
        nextAddId,
        nextHazardId,
        nextProjectileId,
        party,
        projectiles,
      })
    }

    boss = {
      ...boss,
      attackPhase: 'fireballWindup',
      fireballCooldownRemaining: YIAN_KUT_KU_BOSS_METADATA.fireballCooldown!,
      fireballTarget: { ...target.position },
      phaseSecondsRemaining: YIAN_KUT_KU_BOSS_METADATA.fireballWindup!,
      velocity: { x: 0, y: 0 },
    }
    return withYianIndicators({
      boss,
      events,
      hazards,
      hostileAdds,
      nextAddId,
      nextHazardId,
      nextProjectileId,
      party,
      projectiles,
    })
  }

  const meleeResult = maybeApplyMelee(party, boss, target, state.time + dt)
  party = meleeResult.party
  boss = meleeResult.boss
  events.push(...meleeResult.events)
  if (meleeResult.events.length > 0) {
    return withYianIndicators({
      boss,
      events,
      hazards,
      hostileAdds,
      nextAddId,
      nextHazardId,
      nextProjectileId,
      party,
      projectiles,
    })
  }

  const nextPosition = clampVec2ToArena(
    moveToward(boss.position, yianApproachTarget(target, state), YIAN_KUT_KU_BOSS_METADATA.moveSpeed * dt),
    boss.radius,
    state.bounds,
  )
  boss = {
    ...boss,
    position: nextPosition,
    velocity: scaleVec2(subtractVec2(nextPosition, boss.position), dt > 0 ? 1 / dt : 0),
    facing: withFallbackFacing(subtractVec2(target.position, nextPosition), boss.facing),
  }
  return withYianIndicators({
    boss,
    events,
    hazards,
    hostileAdds,
    nextAddId,
    nextHazardId,
    nextProjectileId,
    party,
    projectiles,
  })
}

function shouldYianRelocate(
  boss: Iwt2BossEntityState,
  state: Iwt2ArenaState,
): boolean {
  return (
    boss.position.x < YIAN_SAFE_WALL_MARGIN
    || boss.position.x > state.bounds.width - YIAN_SAFE_WALL_MARGIN
    || boss.position.y < YIAN_SAFE_WALL_MARGIN
    || boss.position.y > state.bounds.height - YIAN_SAFE_WALL_MARGIN
  )
}

function isYianAtCenter(boss: Iwt2BossEntityState, state: Iwt2ArenaState): boolean {
  return distanceVec2(boss.position, yianCenterTarget(state)) <= YIAN_CENTER_CAST_DISTANCE
}

function yianApproachTarget(target: Iwt2PartyEntityState, state: Iwt2ArenaState): Iwt2Vec2 {
  return clampVec2ToArena({
    x: target.position.x + (target.position.x < state.bounds.width * 0.5 ? 190 : -190),
    y: target.position.y + (target.position.y < state.bounds.height * 0.5 ? 96 : -96),
  }, YIAN_KUT_KU_BOSS_METADATA.radius, state.bounds)
}

function yianCenterTarget(state: Iwt2ArenaState): Iwt2Vec2 {
  return {
    x: state.bounds.width * 0.5,
    y: state.bounds.height * 0.5,
  }
}

function tickYianBirds({
  dt,
  hazards,
  nextHazardId,
  party,
  state,
  time,
}: {
  dt: number
  hazards: Iwt2GroundHazardState[]
  nextHazardId: number
  party: Iwt2PartyEntityState[]
  state: Iwt2ArenaState
  time: number
}): {
  hostileAdds: Iwt2HostileAddState[]
  party: Iwt2PartyEntityState[]
  hazards: Iwt2GroundHazardState[]
  nextHazardId: number
  events: Iwt2ArenaEvent[]
} {
  const events: Iwt2ArenaEvent[] = []
  const hostileAdds: Iwt2HostileAddState[] = []
  let nextParty = party
  let nextHazards = hazards
  let nextHazardIdValue = nextHazardId

  for (const add of state.hostileAdds) {
    if (add.health <= 0) continue
    let nextAdd = {
      ...add,
      attackCooldownRemaining: Math.max(0, add.attackCooldownRemaining - dt),
      flightCooldownRemaining: Math.max(0, add.flightCooldownRemaining - dt),
      phaseSecondsRemaining: Math.max(0, add.phaseSecondsRemaining - dt),
    }

    if (nextAdd.attackPhase === 'flightWindup') {
      if (nextAdd.phaseSecondsRemaining <= 0) {
        nextAdd = {
          ...nextAdd,
          attackPhase: 'flying',
          position: { ...nextAdd.flightStart },
          velocity: scaleVec2(normalizeVec2(subtractVec2(nextAdd.flightEnd, nextAdd.flightStart)), YIAN_KUT_KU_BOSS_METADATA.birdFlightSpeed!),
        }
      }
      hostileAdds.push(nextAdd)
      continue
    }

    if (nextAdd.attackPhase === 'flying') {
      const previousPosition = nextAdd.position
      const nextPosition = moveToward(
        nextAdd.position,
        nextAdd.flightEnd,
        YIAN_KUT_KU_BOSS_METADATA.birdFlightSpeed! * dt,
      )
      const shape = {
        kind: 'lane' as const,
        start: previousPosition,
        end: nextPosition,
        width: nextAdd.radius,
      }
      const hitMembers = nextParty.filter((member) => (
        !nextAdd.flightHitEntityIds.includes(member.id)
        && partyMemberIntersectsShape(member, shape)
      ))
      const hitResult = applyPartyDamageInShape(nextParty, shape, {
        damage: BIRD_FLIGHT_DAMAGE,
        excludedEntityIds: nextAdd.flightHitEntityIds,
        knockdownSeconds: 0,
        sourceId: nextAdd.id,
        stunSeconds: YIAN_KUT_KU_BOSS_METADATA.birdStunSeconds!,
        time,
      })
      nextParty = hitResult.party
      events.push(...hitResult.events)
      for (const member of hitMembers) {
        const puddle = addFirePuddle({
          damage: YIAN_KUT_KU_BOSS_METADATA.firePuddleDamage!,
          duration: YIAN_KUT_KU_BOSS_METADATA.firePuddleSeconds!,
          hazards: nextHazards,
          nextHazardId: nextHazardIdValue,
          position: member.position,
          radius: YIAN_KUT_KU_BOSS_METADATA.firePuddleRadius!,
          sourceId: nextAdd.id,
          time,
        })
        nextHazards = puddle.hazards
        nextHazardIdValue = puddle.nextHazardId
      }

      nextAdd = {
        ...nextAdd,
        flightHitEntityIds: [...nextAdd.flightHitEntityIds, ...hitResult.hitEntityIds],
        position: nextPosition,
        velocity: scaleVec2(subtractVec2(nextPosition, previousPosition), dt > 0 ? 1 / dt : 0),
        facing: { x: 1, y: 0 },
      }
      if (distanceVec2(nextPosition, nextAdd.flightEnd) <= 1) {
        nextAdd = {
          ...nextAdd,
          attackPhase: 'flightRecover',
          flightCooldownRemaining: YIAN_KUT_KU_BOSS_METADATA.birdFlightCooldown!,
          phaseSecondsRemaining: 0.4,
          velocity: { x: 0, y: 0 },
        }
      }
      hostileAdds.push(nextAdd)
      continue
    }

    if (nextAdd.attackPhase === 'flightRecover') {
      if (nextAdd.phaseSecondsRemaining <= 0) {
        nextAdd = { ...nextAdd, attackPhase: 'idle', phaseSecondsRemaining: 0 }
      }
      hostileAdds.push(nextAdd)
      continue
    }

    if (nextAdd.flightCooldownRemaining <= 0) {
      hostileAdds.push(startBirdFlight(nextAdd, state, time))
      continue
    }

    const target = getNearestPartyMember(nextParty, nextAdd.position)
    if (!target) {
      hostileAdds.push({ ...nextAdd, velocity: { x: 0, y: 0 } })
      continue
    }

    const nextPosition = clampVec2ToArena(
      moveToward(nextAdd.position, target.position, YIAN_KUT_KU_BOSS_METADATA.moveSpeed * 1.35 * dt),
      nextAdd.radius,
      state.bounds,
    )
    nextAdd = {
      ...nextAdd,
      position: nextPosition,
      velocity: scaleVec2(subtractVec2(nextPosition, nextAdd.position), dt > 0 ? 1 / dt : 0),
      facing: withFallbackFacing(subtractVec2(target.position, nextPosition), nextAdd.facing),
    }
    if (
      nextAdd.attackCooldownRemaining <= 0
      && distanceVec2(nextAdd.position, target.position) <= nextAdd.radius + target.radius + BIRD_MELEE_RANGE
    ) {
      const hitResult = applyPartyDamageInShape(nextParty, {
        kind: 'circle',
        position: nextAdd.position,
        radius: nextAdd.radius + BIRD_MELEE_RANGE,
      }, {
        damage: YIAN_KUT_KU_BOSS_METADATA.birdContactDamage!,
        sourceId: nextAdd.id,
        time,
      })
      nextParty = hitResult.party
      events.push(...hitResult.events)
      nextAdd = { ...nextAdd, attackCooldownRemaining: BIRD_MELEE_COOLDOWN }
    }
    hostileAdds.push(nextAdd)
  }

  return {
    events,
    hazards: nextHazards,
    hostileAdds,
    nextHazardId: nextHazardIdValue,
    party: nextParty,
  }
}

function spawnBirdWave(
  state: Iwt2ArenaState,
  nextAddId: number,
  sourceBossId: Iwt2BossId,
  threshold: number,
): { hostileAdds: Iwt2HostileAddState[], nextAddId: number } {
  const hostileAdds: Iwt2HostileAddState[] = []
  let nextId = nextAddId
  for (let index = 0; index < BIRD_COUNT; index += 1) {
    const y = birdLaneY(state, index, threshold * 100)
    const radius = YIAN_KUT_KU_BOSS_METADATA.birdRadius!
    const flightStart = { x: -radius - 8, y }
    const flightEnd = { x: state.bounds.width + radius + 8, y }
    hostileAdds.push({
      id: `yian-bird-${nextId}`,
      kind: 'hostileAdd',
      addKind: 'yian-bird',
      sourceBossId,
      position: { ...flightStart },
      velocity: { x: 0, y: 0 },
      facing: { x: 1, y: 0 },
      radius,
      health: YIAN_KUT_KU_BOSS_METADATA.birdHealth!,
      maxHealth: YIAN_KUT_KU_BOSS_METADATA.birdHealth!,
      damageDone: 0,
      attackCooldownRemaining: 0,
      flightCooldownRemaining: YIAN_KUT_KU_BOSS_METADATA.birdFlightCooldown!,
      attackPhase: 'flightWindup',
      phaseSecondsRemaining: YIAN_KUT_KU_BOSS_METADATA.birdFlightWindup!,
      flightStart,
      flightEnd,
      flightHitEntityIds: [],
    })
    nextId += 1
  }
  return { hostileAdds, nextAddId: nextId }
}

function startBirdFlight(add: Iwt2HostileAddState, state: Iwt2ArenaState, time: number): Iwt2HostileAddState {
  const y = birdLaneY(state, Number(add.id.replace(/\D/g, '')) % BIRD_COUNT, time)
  const flightStart = { x: -add.radius - 8, y }
  const flightEnd = { x: state.bounds.width + add.radius + 8, y }
  return {
    ...add,
    attackPhase: 'flightWindup',
    phaseSecondsRemaining: YIAN_KUT_KU_BOSS_METADATA.birdFlightWindup!,
    flightStart,
    flightEnd,
    flightHitEntityIds: [],
    velocity: { x: 0, y: 0 },
  }
}

function birdLaneY(state: Iwt2ArenaState, index: number, seed: number): number {
  const laneHeight = state.bounds.height / (BIRD_COUNT + 1)
  const base = laneHeight * (index + 1)
  const wobble = Math.sin(seed * 8.37 + index * 2.1) * 34
  return clamp(base + wobble, 58, state.bounds.height - 58)
}

function nextBirdWaveThreshold(boss: Iwt2BossEntityState): number | undefined {
  return YIAN_KUT_KU_BOSS_METADATA.birdWaveThresholds
    ?.find((threshold) => !boss.birdWaveThresholdsTriggered.includes(threshold))
}

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 getNearestPartyMember(party: Iwt2PartyEntityState[], position: Iwt2Vec2): Iwt2PartyEntityState | undefined {
  let nearest: Iwt2PartyEntityState | undefined
  let nearestDistance = Number.POSITIVE_INFINITY
  for (const member of party) {
    if (member.health <= 0) continue
    const distance = distanceVec2(member.position, position)
    if (distance < nearestDistance) {
      nearest = member
      nearestDistance = distance
    }
  }
  return nearest
}

function maybeApplyMelee(
  party: Iwt2PartyEntityState[],
  boss: Iwt2BossEntityState,
  target: Iwt2PartyEntityState,
  time: number,
): { boss: Iwt2BossEntityState, party: Iwt2PartyEntityState[], events: Iwt2ArenaEvent[] } {
  if (boss.meleeCooldownRemaining > 0) return { boss, party, events: [] }
  if (distanceVec2(boss.position, target.position) > YIAN_KUT_KU_BOSS_METADATA.meleeRange + target.radius) {
    return { boss, party, events: [] }
  }
  const result = applyPartyDamageInShape(party, {
    kind: 'circle',
    position: boss.position,
    radius: YIAN_KUT_KU_BOSS_METADATA.meleeRange,
  }, {
    damage: YIAN_KUT_KU_BOSS_METADATA.meleeDamage,
    sourceId: boss.id,
    time,
  })
  return {
    boss: { ...boss, meleeCooldownRemaining: YIAN_KUT_KU_BOSS_METADATA.meleeCooldown },
    party: result.party,
    events: result.events,
  }
}

function withYianIndicators(result: Omit<Iwt2YianTickResult, 'indicators'>): Iwt2YianTickResult {
  return {
    ...result,
    indicators: createYianIndicators(result.boss, result.hostileAdds),
  }
}

function createYianIndicators(
  boss: Iwt2BossEntityState,
  hostileAdds: Iwt2HostileAddState[],
): Iwt2ArenaIndicator[] {
  const indicators: Iwt2ArenaIndicator[] = []
  if (boss.attackPhase === 'relocating') {
    indicators.push(createLaneIndicator({
      color: '#ffce5c',
      end: boss.chargeEnd,
      id: `${boss.id}:center-charge`,
      mechanicId: 'yian-center-charge',
      phase: 'active',
      sourceId: boss.id,
      start: boss.chargeStart,
      width: boss.radius,
    }))
  }
  if (boss.attackPhase === 'fireballWindup') {
    indicators.push(createCircleIndicator({
      color: '#ff8b2b',
      id: `${boss.id}:fireball-cast`,
      mechanicId: 'yian-fireball-cast',
      phase: 'windup',
      position: boss.position,
      radius: boss.radius + 18,
      sourceId: boss.id,
    }))
    indicators.push(createLaneIndicator({
      color: '#ffb347',
      end: boss.fireballTarget,
      id: `${boss.id}:fireball-line`,
      mechanicId: 'yian-fireball-line',
      phase: 'windup',
      sourceId: boss.id,
      start: boss.position,
      width: YIAN_KUT_KU_BOSS_METADATA.fireballRadius! + 4,
    }))
  }
  for (const add of hostileAdds) {
    if (
      add.attackPhase !== 'flightWindup'
      && add.attackPhase !== 'flying'
      && add.attackPhase !== 'flightRecover'
    ) {
      continue
    }
    indicators.push(createLaneIndicator({
      color: '#ffce5c',
      end: add.flightEnd,
      id: `${add.id}:flight-lane`,
      mechanicId: 'yian-bird-flight',
      phase: indicatorPhaseFromAttack(
        add.attackPhase === 'flightWindup',
        add.attackPhase === 'flying',
      ),
      sourceId: add.id,
      start: add.flightStart,
      width: add.radius,
    }))
  }
  return indicators
}
