"""
PPM (procs-per-minute) proc chance calculation.

Mirrors MidsReborn/Core/Base/Data_Classes/Effect.cs's `ActualProbability` getter
and MidsReborn/Core/Base/Data_Classes/Power.cs's `AoEModifier` property, field
for field (line numbers as read from the MidsReborn source):

  Effect.cs ~320-350 (ActualProbability), ~369-370 (Min/MaxProcChance)
  Power.cs  ~614-616 (AoEModifier)

Key mechanic (documented on the Homecoming wiki as "PPM procs"): the recharge
time used in the formula is the power's recharge time counting ONLY locally
slotted recharge enhancement - global recharge buffs (Hasten, Force of Will,
recharge set bonuses, etc.) are subtracted back out before the calculation.
This is why `ActualProbability` takes `power.RechargeTime` (current, fully
buffed recharge) and `globalRecharge` (the global-only fraction) and inverts
out the local-only component rather than just using RechargeTime directly.

For powers with ProcsPerMinute <= 0 (plain flat-chance effects, not PPM procs),
callers should just use the effect's own `base_probability` - this module only
covers the ProcsPerMinute > 0 path (`base_probability` is accepted here purely
as the fallback/floor exactly like the C# getter does).
"""
import math

EPSILON = 1e-6
MAX_PROC_CHANCE = 0.9


def aoe_modifier(effect_area, radius=0.0, arc=0):
    """Mirrors Power.AoEModifier. effect_area: "Cone" | "Sphere" | anything else."""
    if effect_area == "Cone":
        return 1 + radius * 0.150000005960464 - radius * 0.000366669992217794 * (360 - arc)
    if effect_area == "Sphere":
        return 1 + radius * 0.150000005960464
    return 1.0


def min_proc_chance(procs_per_minute):
    """Mirrors Effect.MinProcChance."""
    if procs_per_minute > 0:
        return procs_per_minute * 0.015 + 0.05
    return 0.05


def recharge_time_from_bonus(base_recharge_time, recharge_bonus_fraction):
    """Convenience: CoH recharge buffs are additive and divide into recharge
    time as 1/(1+bonus). recharge_bonus_fraction=0.5 means +50% recharge buff."""
    return base_recharge_time / (1.0 + recharge_bonus_fraction)


def actual_probability(
    base_probability,
    procs_per_minute,
    power_type_click,
    power_effect_area="None",
    power_radius=0.0,
    power_arc=0,
    power_base_recharge_time=0.0,
    power_recharge_time=0.0,
    power_cast_time_real=0.0,
    global_recharge_buff_fraction=0.0,
):
    """Mirrors Effect.ActualProbability exactly (the ModifyEffects character
    override step is intentionally omitted - that's a per-character/build-level
    tweak layered on afterward by callers who need it, not part of the base
    PPM math).

    global_recharge_buff_fraction: the *global-only* recharge bonus as a
    fraction (e.g. 0.7 for +70% from Hasten + global set bonuses), i.e. the
    C# code's `(BuffHaste(false) - 100) / 100`.
    power_recharge_time: the power's CURRENT recharge time, after ALL recharge
    buffs (global AND any locally slotted recharge enhancement) are applied.
    power_base_recharge_time: the power's unenhanced base recharge time.
    """
    if procs_per_minute <= 0:
        probability = base_probability
    else:
        area_factor = aoe_modifier(power_effect_area, power_radius, power_arc) * 0.75 + 0.25

        if abs(power_recharge_time) < EPSILON:
            recharge_val = 0.0
        else:
            recharge_val = power_base_recharge_time / (
                power_base_recharge_time / power_recharge_time - global_recharge_buff_fraction
            )

        if power_type_click:
            probability = procs_per_minute * (recharge_val + power_cast_time_real) / (60.0 * area_factor)
        else:
            probability = procs_per_minute * 10 / (60.0 * area_factor)

        probability = max(min_proc_chance(procs_per_minute), min(MAX_PROC_CHANCE, probability))

    return max(0.0, min(1.0, probability))


def effect_proc_chance(db, effect, power, global_recharge_buff_fraction=0.0, local_recharge_bonus_fraction=0.0):
    """Convenience wrapper taking a parsed effect dict + the HOST power dict
    (the power the proc enhancement is actually slotted into - NOT the proc
    enhancement's own dummy virtual power, which has zeroed-out recharge/cast
    fields and will always yield recharge_val == 0)."""
    ppm = effect.get("procs_per_minute", 0.0) or 0.0
    base_recharge = power.get("base_recharge_time", 0.0) or 0.0
    current_recharge = recharge_time_from_bonus(
        base_recharge, global_recharge_buff_fraction + local_recharge_bonus_fraction
    )
    return actual_probability(
        base_probability=effect.get("base_probability", 0.0) or 0.0,
        procs_per_minute=ppm,
        power_type_click=(power.get("power_type") == "Click"),
        power_effect_area=power.get("effect_area", "None"),
        power_radius=power.get("radius", 0.0) or 0.0,
        power_arc=power.get("arc", 0) or 0,
        power_base_recharge_time=base_recharge,
        power_recharge_time=current_recharge,
        power_cast_time_real=power.get("cast_time", 0.0) or 0.0,
        global_recharge_buff_fraction=global_recharge_buff_fraction,
    )
