import type { Iwt2HealerAbility } from '../content/healerAbilities'
import type {
  Iwt2ArenaEvent,
  Iwt2ArenaState,
  Iwt2EntityId,
  Iwt2PartyEntityState,
} from './types'
import { createArenaEvent } from './mechanics'

const HEALER_ID: Iwt2EntityId = 'player-healer'
const MAX_EVENTS = 80
const DEFAULT_HOT_SECONDS = 8
const DEFAULT_HOT_TICK_SECONDS = 1

export type Iwt2HealerCastResult<TState extends Iwt2ArenaState = Iwt2ArenaState> = {
  cast: boolean
  state: TState
}

export function castIwt2HealerAbility<TState extends Iwt2ArenaState>(
  state: TState,
  ability: Iwt2HealerAbility,
  selectedTargetId: Iwt2EntityId,
): Iwt2HealerCastResult<TState> {
  if (state.boss.health <= 0) return { cast: false, state }
  const healer = state.party.find((member) => member.id === HEALER_ID)
  if (!healer || healer.health <= 0 || healer.mana < ability.manaCost) return { cast: false, state }

  const targetIds = targetIdsForAbility(state.party, ability, selectedTargetId)
  if (targetIds.length === 0) return { cast: false, state }

  const events: Iwt2ArenaEvent[] = []
  let eventId = state.nextEventId
  const targetIdSet = new Set(targetIds)
  const nextParty = state.party.map((member) => {
    const mana = member.id === HEALER_ID ? Math.max(0, member.mana - ability.manaCost) : member.mana
    if (!targetIdSet.has(member.id)) return { ...member, mana }

    const nextMember = applyAbilityToMember(member, ability)
    const healed = Math.max(0, nextMember.health - member.health)
    if (healed > 0) {
      events.push(createArenaEvent(eventId, state.time, 'partyHealed', HEALER_ID, member.id, healed))
      eventId += 1
    }
    return { ...nextMember, mana }
  })

  return {
    cast: true,
    state: {
      ...state,
      party: nextParty,
      nextEventId: eventId,
      events: [...state.events, ...events].slice(-MAX_EVENTS),
    } as TState,
  }
}

function targetIdsForAbility(
  party: Iwt2PartyEntityState[],
  ability: Iwt2HealerAbility,
  selectedTargetId: Iwt2EntityId,
): Iwt2EntityId[] {
  const living = party.filter((member) => member.health > 0)
  if (living.length === 0) return []
  const extraTargets = Math.max(0, Math.floor(ability.extraTargets ?? 0))
  if (ability.kind === 'group') {
    return [...living]
      .sort((a, b) => healthRatio(a) - healthRatio(b))
      .slice(0, 4 + extraTargets)
      .map((member) => member.id)
  }
  const selected = living.find((member) => member.id === selectedTargetId)
  const primaryId = selected?.id ?? living[0].id
  if (extraTargets === 0) return [primaryId]
  const additionalTargets = living
    .filter((member) => member.id !== primaryId)
    .sort((a, b) => healthRatio(a) - healthRatio(b))
    .slice(0, extraTargets)
    .map((member) => member.id)
  return [primaryId, ...additionalTargets]
}

function applyAbilityToMember(member: Iwt2PartyEntityState, ability: Iwt2HealerAbility): Iwt2PartyEntityState {
  if (ability.kind === 'hot') {
    return {
      ...healMember(member, ability.power),
      hotEffects: [
        ...member.hotEffects.filter((effect) => effect.id !== ability.id),
        {
          id: ability.id,
          label: ability.name,
          power: Math.max(1, Math.round(ability.power * 0.5)),
          remainingSeconds: DEFAULT_HOT_SECONDS,
          tickIntervalSeconds: DEFAULT_HOT_TICK_SECONDS,
          nextTickInSeconds: DEFAULT_HOT_TICK_SECONDS,
        },
      ],
    }
  }

  if (ability.kind === 'shield') {
    return {
      ...member,
      shield: Math.max(member.shield, ability.power),
    }
  }

  if (ability.kind === 'cleanse') {
    return {
      ...healMember(member, ability.power),
      status: {
        ...member.status,
        knockedDownSeconds: 0,
        stunnedSeconds: 0,
      },
    }
  }

  return healMember(member, ability.power)
}

function healMember(member: Iwt2PartyEntityState, amount: number): Iwt2PartyEntityState {
  return {
    ...member,
    health: Math.min(member.maxHealth, member.health + amount),
  }
}

function healthRatio(member: Iwt2PartyEntityState) {
  return member.health / member.maxHealth
}
