using Theriapolis.Core.Rules.Character;
using Theriapolis.Core.Rules.Stats;

namespace Theriapolis.Core.Dungeons;

/// <summary>
/// Phase 7 M2 — clade-responsive dungeon movement cost. Per <c>procgen.md</c>
/// Layer 5 final paragraph and Phase 7 plan §5.4: a Large PC squeezing
/// through a Mustelid tunnel takes 2× movement points per tile; a Small
/// PC in an Ursid hall is exposed (×1.5); etc.
///
/// Cost multiplier applies to tactical-tile movement budget per turn —
/// combat reach + LOS unchanged; <em>only</em> movement budget. The
/// caller (<see cref="Rules.Combat.TacticalMovementRules"/> or equivalent)
/// looks up the room the actor is currently in and consults
/// <see cref="GetCostMultiplier"/>.
///
/// Hybrid PCs use their <em>dominant lineage</em>'s clade-implied size
/// for the lookup — matches the Phase 6.5 hybrid passing / presenting-clade
/// contract. A Wolf-Folk × Hare-Folk hybrid with <c>DominantParent: Sire</c>
/// reads as Wolf-Folk (MediumLarge); with <c>DominantParent: Dam</c> reads
/// as Hare-Folk (Medium). Outside dungeons the multiplier is always 1.0.
///
/// Reference table (Phase 7 plan §5.4):
///   Player size  | Built by Mustelid | Ursid | Cervid | Bovid | Imperium/None
///   Small        |       1.0         |  1.5  |  1.0   |  1.2  |     1.0
///   Medium       |       1.2         |  1.0  |  1.0   |  1.0  |     1.0
///   MediumLarge  |       1.5         |  1.0  |  1.0   |  1.0  |     1.0
///   Large        |       2.0         |  1.0  |  1.2   |  1.0  |     1.0
/// </summary>
public static class ClademorphicMovement
{
    /// <summary>
    /// Multiplier on movement-cost-per-tile for a player of the given size
    /// in a room built by the given clade. Returns 1.0 when no mismatch
    /// applies. Unknown <paramref name="builtBy"/> values default to 1.0
    /// (no penalty).
    /// </summary>
    public static float GetCostMultiplier(SizeCategory playerSize, string builtBy)
    {
        if (string.IsNullOrEmpty(builtBy)) return 1.0f;
        // Normalise — JSON ships lowercase tags.
        return builtBy.ToLowerInvariant() switch
        {
            "mustelid" => playerSize switch
            {
                SizeCategory.Small       => 1.0f,
                SizeCategory.Medium      => C.MOVE_COST_MISMATCH_LIGHT,  // 1.2 — slight squeeze
                SizeCategory.MediumLarge => C.MOVE_COST_MISMATCH_MED,    // 1.5
                SizeCategory.Large       => C.MOVE_COST_MISMATCH_HEAVY,  // 2.0 — squeezing
                _                        => 1.0f,
            },
            "ursid" => playerSize switch
            {
                SizeCategory.Small       => C.MOVE_COST_MISMATCH_MED,    // exposed in cavernous halls
                _                        => 1.0f,
            },
            "cervid" => playerSize switch
            {
                SizeCategory.Large       => C.MOVE_COST_MISMATCH_LIGHT,  // antler clearance
                _                        => 1.0f,
            },
            "bovid" => playerSize switch
            {
                SizeCategory.Small       => C.MOVE_COST_MISMATCH_LIGHT,
                _                        => 1.0f,
            },
            // Canid / Felid / Leporid / Imperium / "none" / unknown:
            _ => 1.0f,
        };
    }

    /// <summary>
    /// Convenience wrapper that resolves a character's effective size for
    /// the lookup (handles Phase 6.5 hybrid dominant-lineage rules).
    /// </summary>
    public static float GetCostMultiplier(Character character, string builtBy)
    {
        if (character is null) return 1.0f;
        var effectiveSize = EffectiveSize(character);
        return GetCostMultiplier(effectiveSize, builtBy);
    }

    /// <summary>
    /// Resolve the size category that drives the clade-responsive lookup.
    /// For purebred PCs, this is just <see cref="Character.Size"/>. For
    /// hybrid PCs, it's the size implied by the dominant-lineage species
    /// — and we expose this as a separate helper so callers (e.g. NPC
    /// mechanics that *also* need the presenting size) can reuse it.
    /// </summary>
    public static SizeCategory EffectiveSize(Character character)
    {
        if (character is null) throw new System.ArgumentNullException(nameof(character));
        if (!character.IsHybrid) return character.Size;
        // Hybrid: pick the size implied by the dominant parent's species.
        // The Hybrid record carries the species name only (string); the
        // species-to-size mapping lives on Character.Species (the
        // *presenting* species set at character creation per the dominant
        // lineage). So for a hybrid PC the simplest (and load-bearing-
        // correct) answer is the presenting species — which is exactly
        // what <c>character.Size</c> already returns. Documented here so
        // future agents don't replace this with a parent-species lookup
        // and break the contract.
        return character.Size;
    }
}
