import type { ModifierApplication, Perk, PerkModifierTarget } from '@fog-explorer/api-interfaces';

export interface AppliedModifier extends ModifierApplication {
  readonly preApplyProbability: number;
  readonly postApplyProbability: number;
}

export interface PerkMathResult {
  readonly probability: number;
  readonly applied: AppliedModifier[];
}

const P_MIN = 0;
const P_MAX = 1;

/**
 * Applies all perk modifiers targeting `successChance` from the given perk
 * slots, filtered by encounter tags. Additives are summed first, then
 * multiplicatives applied in order. Result is clamped to [0, 1].
 *
 * Separating this from the resolver keeps the probability pipeline testable
 * independently and makes modifier-overflow edge cases visible.
 */
export function applyPerkModifiers(
  baseProbability: number,
  perks: Perk[],
  encounterTags: string[],
  target: PerkModifierTarget = 'successChance'
): PerkMathResult {
  let probability = baseProbability;
  const applied: AppliedModifier[] = [];

  // Two-pass: collect additive sum, then apply multiplicatives.
  // This matches standard RPG convention: add-then-multiply prevents
  // multiplicative stacking from compounding excessively.
  let additiveSum = 0;
  const multiplicativePerks: Array<{ perk: Perk; amount: number }> = [];

  for (const perk of perks) {
    for (const mod of perk.modifiers) {
      if (mod.target !== target) continue;
      if (!conditionMatches(mod.condition, encounterTags)) continue;

      if (mod.type === 'additive') {
        additiveSum += mod.amount;
      } else {
        multiplicativePerks.push({ perk, amount: mod.amount });
      }
    }
  }

  // Apply additive sum in one step.
  if (additiveSum !== 0) {
    const pre = probability;
    probability += additiveSum;

    // Record one entry per contributing perk.
    for (const perk of perks) {
      for (const mod of perk.modifiers) {
        if (mod.target !== target || mod.type !== 'additive') continue;
        if (!conditionMatches(mod.condition, encounterTags)) continue;
        applied.push({
          perkKey: perk.key,
          target: mod.target,
          type: mod.type,
          effectiveAmount: mod.amount,
          preApplyProbability: pre,
          postApplyProbability: probability,
        });
      }
    }
  }

  // Apply multiplicatives individually so each can be logged.
  for (const { perk, amount } of multiplicativePerks) {
    const pre = probability;
    probability *= 1 + amount;
    applied.push({
      perkKey: perk.key,
      target,
      type: 'multiplicative',
      effectiveAmount: amount,
      preApplyProbability: pre,
      postApplyProbability: probability,
    });
  }

  return { probability: clamp(probability, P_MIN, P_MAX), applied };
}

function conditionMatches(
  condition: { encounterTags: string[] } | undefined,
  encounterTags: string[]
): boolean {
  if (!condition) return true;
  return condition.encounterTags.some((t) => encounterTags.includes(t));
}

function clamp(value: number, min: number, max: number): number {
  return Math.max(min, Math.min(max, value));
}
