import type { Combatant, Encounter } from '@/lib/schemas/encounter';

/**
 * Pure combat-state transitions. Every function returns a NEW encounter and
 * never mutates its input. The turn pointer is tracked by combatant *identity*,
 * so adding/removing/reordering combatants mid-fight never silently changes
 * whose turn it is — the bug cluster (C16–C19) that the old app never fixed.
 */

export function currentCombatant(enc: Encounter): Combatant | undefined {
  return enc.combatants[enc.turnIndex];
}

/** Sort by initiative (desc). JS sort is stable, so ties keep insertion order. */
export function sortByInitiative(combatants: Combatant[]): Combatant[] {
  return [...combatants].sort((a, b) => b.initiative - a.initiative);
}

function indexOfId(combatants: Combatant[], id: string | undefined): number {
  if (!id) return -1;
  return combatants.findIndex((c) => c.id === id);
}

/** Reorder by initiative and start at the top of the order, round 1. */
export function startEncounter(enc: Encounter): Encounter {
  return {
    ...enc,
    status: 'active',
    round: enc.combatants.length > 0 ? 1 : 0,
    turnIndex: 0,
    combatants: sortByInitiative(enc.combatants),
  };
}

export function endEncounter(enc: Encounter): Encounter {
  return { ...enc, status: 'ended' };
}

/** Advance to the next combatant; wrapping past the end increments the round. */
export function nextTurn(enc: Encounter): Encounter {
  if (enc.combatants.length === 0) return { ...enc, turnIndex: 0 };
  const atEnd = enc.turnIndex >= enc.combatants.length - 1;
  return {
    ...enc,
    turnIndex: atEnd ? 0 : enc.turnIndex + 1,
    round: atEnd ? enc.round + 1 : enc.round,
  };
}

/** Step back a turn; wrapping before the start decrements the round (min 1). */
export function previousTurn(enc: Encounter): Encounter {
  if (enc.combatants.length === 0) return { ...enc, turnIndex: 0 };
  const atStart = enc.turnIndex <= 0;
  return {
    ...enc,
    turnIndex: atStart ? enc.combatants.length - 1 : enc.turnIndex - 1,
    round: atStart ? Math.max(1, enc.round - 1) : enc.round,
  };
}

/**
 * Insert a combatant. When the encounter is active, the new combatant is placed
 * by initiative and the turn pointer is corrected so it still points at the
 * same combatant whose turn it is.
 */
export function addCombatant(enc: Encounter, combatant: Combatant): Encounter {
  if (enc.status !== 'active') {
    return { ...enc, combatants: [...enc.combatants, combatant] };
  }
  const currentId = currentCombatant(enc)?.id;
  const combatants = sortByInitiative([...enc.combatants, combatant]);
  const turnIndex = Math.max(0, indexOfId(combatants, currentId));
  return { ...enc, combatants, turnIndex };
}

/** Remove a combatant, keeping the turn on the correct next combatant. */
export function removeCombatant(enc: Encounter, id: string): Encounter {
  const removedIndex = indexOfId(enc.combatants, id);
  if (removedIndex === -1) return enc;

  const combatants = enc.combatants.filter((c) => c.id !== id);
  if (combatants.length === 0) return { ...enc, combatants, turnIndex: 0 };

  let turnIndex = enc.turnIndex;
  let round = enc.round;
  if (removedIndex < enc.turnIndex) {
    // someone before the current turn left: shift pointer left
    turnIndex = enc.turnIndex - 1;
  } else if (removedIndex === enc.turnIndex) {
    // current combatant left: turn passes to whoever now sits at this index
    if (turnIndex >= combatants.length) {
      turnIndex = 0;
      if (enc.status === 'active') round += 1;
    }
  }
  return { ...enc, combatants, turnIndex, round };
}

/** Patch a combatant by id. Re-sorts (and re-anchors the turn) if initiative changed. */
export function updateCombatant(enc: Encounter, id: string, patch: Partial<Combatant>): Encounter {
  const idx = indexOfId(enc.combatants, id);
  if (idx === -1) return enc;
  const updated = enc.combatants.map((c) => (c.id === id ? { ...c, ...patch } : c));

  if (patch.initiative !== undefined && enc.status === 'active') {
    const currentId = currentCombatant(enc)?.id;
    const combatants = sortByInitiative(updated);
    return { ...enc, combatants, turnIndex: Math.max(0, indexOfId(combatants, currentId)) };
  }
  return { ...enc, combatants: updated };
}

/** Manually nudge a combatant up/down the order, keeping the turn anchored. */
export function moveCombatant(enc: Encounter, id: string, direction: -1 | 1): Encounter {
  const idx = indexOfId(enc.combatants, id);
  if (idx === -1) return enc;
  const target = idx + direction;
  if (target < 0 || target >= enc.combatants.length) return enc;

  const currentId = currentCombatant(enc)?.id;
  const combatants = [...enc.combatants];
  [combatants[idx], combatants[target]] = [combatants[target]!, combatants[idx]!];
  return { ...enc, combatants, turnIndex: Math.max(0, indexOfId(combatants, currentId)) };
}

// ---------------- HP transitions ----------------

/** Apply damage: temporary HP absorbs first, then current (may go negative). */
export function applyDamage(c: Combatant, amount: number): Combatant {
  if (!Number.isFinite(amount) || amount <= 0) return c;
  const absorbed = Math.min(c.hp.temp, amount);
  const remaining = amount - absorbed;
  return {
    ...c,
    hp: { ...c.hp, temp: c.hp.temp - absorbed, current: c.hp.current - remaining },
  };
}

/** Heal up to max. Does not touch temporary HP. */
export function applyHealing(c: Combatant, amount: number): Combatant {
  if (!Number.isFinite(amount) || amount <= 0) return c;
  return { ...c, hp: { ...c.hp, current: Math.min(c.hp.max, c.hp.current + amount) } };
}

/** Set temporary HP — 5e/PF2e temp HP does not stack; take the higher value. */
export function setTempHp(c: Combatant, temp: number): Combatant {
  if (!Number.isFinite(temp) || temp < 0) return c;
  return { ...c, hp: { ...c.hp, temp: Math.max(c.hp.temp, temp) } };
}
