"""
X-Check runner advancement tables.

Maps X-Check defensive play results to runner advancement based on:
- on_base_code: Current runner configuration (0-7 bit field)
- defender_in: Whether defender was playing in (groundballs only)
- error_result: Error type from 3d6 roll ('NO', 'E1', 'E2', 'E3', 'RP')

For groundballs (G1, G2, G3):
- Lookup returns GroundballResultType (1-13)
- Maps to existing groundball_X() functions in runner_advancement.py

For flyballs (F1, F2, F3):
- Delegates to existing flyball logic (FLYOUT_A, FLYOUT_B, FLYOUT_C)
- Errors override outs: all runners advance E# bases

For hits with errors:
- Formula: All runners advance <Hit Bases> + E#
- SI1/SI2 = 1 hit base, DO2/DO3 = 2 hit bases, TR3 = 3 hit bases

Author: Claude
Date: 2025-11-02
"""

import logging

from app.core.runner_advancement import (
    AdvancementResult,
    GroundballResultType,
    RunnerMovement,
)

logger = logging.getLogger(f"{__name__}")

# ============================================================================
# GROUNDBALL ADVANCEMENT TABLES
# ============================================================================
# Structure: {on_base_code: {(defender_in, error_result): GroundballResultType}}
#
# Tuple key format: (defender_in: bool, error_result: str)
# - defender_in: True if defender playing in (infield_in or corners_in)
# - error_result: 'NO', 'E1', 'E2', 'E3', 'RP'
#
# on_base_code mapping (NOT a bit field):
#   0 = Bases Empty
#   1 = Runner on 1st
#   2 = Runner on 2nd
#   3 = Runner on 3rd
#   4 = Runners on 1st and 2nd
#   5 = Runners on 1st and 3rd
#   6 = Runners on 2nd and 3rd
#   7 = Bases loaded (R1 + R2 + R3)
#
# Error handling:
# - 'NO': Use result from table (normal out/advancement)
# - 'E1'/'E2'/'E3': Error overrides out, all runners advance E# bases
# - 'RP': Rare play (currently stubbed as SAFE_ALL_ADVANCE_ONE)

# G1 Advancement Table (from rulebook)
G1_ADVANCEMENT_TABLE: dict[int, dict[tuple[bool, str], GroundballResultType]] = {
    # Base code 0: Bases empty
    0: {
        (False, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_HOLD,  # Result 1
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (
            False,
            "RP",
        ): GroundballResultType.SAFE_ALL_ADVANCE_ONE,  # TODO: Actual RP logic
        (
            True,
            "NO",
        ): GroundballResultType.BATTER_OUT_RUNNERS_HOLD,  # N/A fallback to normal
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 1: R1 only
    1: {
        (False, "NO"): GroundballResultType.DOUBLE_PLAY_AT_SECOND,  # Result 2
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (
            True,
            "NO",
        ): GroundballResultType.DOUBLE_PLAY_AT_SECOND,  # Result 2 (Chart: Infield In = 2)
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 2: R2 only
    2: {
        (False, "NO"): GroundballResultType.DECIDE_OPPORTUNITY,  # Result 12
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.DECIDE_OPPORTUNITY,  # Result 12
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 3: R3 only
    3: {
        (
            False,
            "NO",
        ): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3 (Chart: Normal = 3)
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (
            True,
            "NO",
        ): GroundballResultType.BATTER_OUT_RUNNERS_HOLD,  # Result 1 (Chart: Infield In = 1)
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 4: R1 + R2
    4: {
        (
            False,
            "NO",
        ): GroundballResultType.CONDITIONAL_DOUBLE_PLAY,  # Result 13 (Chart: Normal = 13)
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (
            True,
            "NO",
        ): GroundballResultType.DOUBLE_PLAY_AT_SECOND,  # Result 2 (Chart: Infield In = 2)
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 5: R1 + R3
    5: {
        (False, "NO"): GroundballResultType.CONDITIONAL_DOUBLE_PLAY,  # Result 13
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.DOUBLE_PLAY_AT_SECOND,  # Result 2
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 6: R2 + R3
    6: {
        (False, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 7: Bases loaded (R1 + R2 + R3)
    7: {
        (False, "NO"): GroundballResultType.DOUBLE_PLAY_AT_SECOND,  # Result 2
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.DOUBLE_PLAY_HOME_TO_FIRST,  # Result 10
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
}

# G2 Advancement Table (from rulebook)
G2_ADVANCEMENT_TABLE: dict[int, dict[tuple[bool, str], GroundballResultType]] = {
    # Base code 0: Bases empty
    0: {
        (False, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_HOLD,  # Result 1
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_HOLD,  # N/A fallback
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 1: R1 only
    1: {
        (False, "NO"): GroundballResultType.BATTER_SAFE_FORCE_OUT_AT_SECOND,  # Result 4
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.BATTER_SAFE_FORCE_OUT_AT_SECOND,  # Result 4
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 2: R2 only
    2: {
        (False, "NO"): GroundballResultType.DECIDE_OPPORTUNITY,  # Result 12
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.DECIDE_OPPORTUNITY,  # Result 12
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 3: R3 only
    3: {
        (False, "NO"): GroundballResultType.CONDITIONAL_ON_MIDDLE_INFIELD,  # Result 5
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (
            True,
            "NO",
        ): GroundballResultType.BATTER_OUT_RUNNERS_HOLD,  # Result 1 (Chart: Infield In = 1)
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 4: R1 + R2
    4: {
        (
            False,
            "NO",
        ): GroundballResultType.BATTER_SAFE_FORCE_OUT_AT_SECOND,  # Result 4 (Chart: Normal = 4)
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.BATTER_SAFE_FORCE_OUT_AT_SECOND,  # Result 4
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 5: R1 + R3
    5: {
        (False, "NO"): GroundballResultType.BATTER_SAFE_FORCE_OUT_AT_SECOND,  # Result 4
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.BATTER_SAFE_FORCE_OUT_AT_SECOND,  # Result 4
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 6: R2 + R3
    6: {
        (False, "NO"): GroundballResultType.CONDITIONAL_ON_MIDDLE_INFIELD,  # Result 5
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 7: Bases loaded (R1 + R2 + R3)
    7: {
        (False, "NO"): GroundballResultType.BATTER_SAFE_FORCE_OUT_AT_SECOND,  # Result 4
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.BATTER_SAFE_LEAD_OUT,  # Result 11
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
}

# G3 Advancement Table (from rulebook)
G3_ADVANCEMENT_TABLE: dict[int, dict[tuple[bool, str], GroundballResultType]] = {
    # Base code 0: Bases empty
    0: {
        (False, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_HOLD,  # Result 1
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_HOLD,  # N/A fallback
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 1: R1 only
    1: {
        (False, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 2: R2 only
    2: {
        (False, "NO"): GroundballResultType.DECIDE_OPPORTUNITY,  # Result 12
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 3: R3 only
    3: {
        (False, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.DECIDE_OPPORTUNITY,  # DECIDE
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 4: R1 + R2
    4: {
        (False, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 5: R1 + R3
    5: {
        (False, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 6: R2 + R3
    6: {
        (False, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.DECIDE_OPPORTUNITY,  # DECIDE
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
    # Base code 7: Bases loaded (R1 + R2 + R3)
    7: {
        (False, "NO"): GroundballResultType.BATTER_OUT_RUNNERS_ADVANCE,  # Result 3
        (False, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (False, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (False, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (False, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "NO"): GroundballResultType.BATTER_SAFE_LEAD_OUT,  # Result 11
        (True, "E1"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
        (True, "E2"): GroundballResultType.SAFE_ALL_ADVANCE_TWO,
        (True, "E3"): GroundballResultType.SAFE_ALL_ADVANCE_THREE,
        (True, "RP"): GroundballResultType.SAFE_ALL_ADVANCE_ONE,
    },
}


def get_groundball_advancement(
    result_type: str,  # 'G1', 'G2', or 'G3'
    on_base_code: int,
    defender_in: bool,
    error_result: str,
) -> GroundballResultType:
    """
    Get GroundballResultType for X-Check groundball.

    Args:
        result_type: 'G1', 'G2', or 'G3'
        on_base_code: Current base situation (0-7)
        defender_in: Is defender playing in?
        error_result: 'NO', 'E1', 'E2', 'E3', 'RP'

    Returns:
        GroundballResultType to pass to existing groundball functions

    Raises:
        ValueError: If parameters invalid
    """
    # Select table
    tables = {
        "G1": G1_ADVANCEMENT_TABLE,
        "G2": G2_ADVANCEMENT_TABLE,
        "G3": G3_ADVANCEMENT_TABLE,
    }

    if result_type not in tables:
        raise ValueError(f"Unknown groundball type: {result_type}")

    table = tables[result_type]

    # Validate on_base_code
    if on_base_code not in table:
        raise ValueError(f"on_base_code {on_base_code} not in {result_type} table")

    # Lookup
    key = (defender_in, error_result)

    if key not in table[on_base_code]:
        raise ValueError(
            f"Key {key} not in {result_type} table for on_base_code {on_base_code}"
        )

    return table[on_base_code][key]


def get_hit_advancement_with_error(
    hit_type: str,  # 'SI1', 'SI2', 'DO2', 'DO3', 'TR3'
    error_result: str,  # 'NO', 'E1', 'E2', 'E3', 'RP'
) -> int:
    """
    Calculate total bases advanced for hit + error.

    Formula: <Hit Bases> + E#
    - SI1/SI2 = 1 hit base
    - DO2/DO3 = 2 hit bases
    - TR3 = 3 hit bases

    Args:
        hit_type: Type of hit
        error_result: Error type

    Returns:
        Total bases to advance (all runners including batter)
    """
    # Base hit advancement
    hit_bases = {
        "SI1": 1,
        "SI2": 1,
        "DO2": 2,
        "DO3": 2,
        "TR3": 3,
    }

    # Error bonus
    error_bonus = {
        "NO": 0,
        "E1": 1,
        "E2": 2,
        "E3": 3,
        "RP": 1,  # TODO: Actual RP logic (using E1 for now)
    }

    base_advancement = hit_bases.get(hit_type, 0)
    bonus = error_bonus.get(error_result, 0)

    return base_advancement + bonus


def get_error_advancement_bases(error_result: str) -> int:
    """
    Get number of bases to advance on error (for flyouts and popouts).

    When error occurs on out results (F1/F2/F3, FO, PO), the out is negated
    and all runners (including batter) advance E# bases.

    Args:
        error_result: 'NO', 'E1', 'E2', 'E3', 'RP'

    Returns:
        Number of bases to advance
    """
    error_advances = {
        "NO": 0,
        "E1": 1,
        "E2": 2,
        "E3": 3,
        "RP": 1,  # TODO: Actual RP logic (using E1 for now)
    }

    return error_advances.get(error_result, 0)


def build_advancement_from_code(
    on_base_code: int, gb_type: GroundballResultType, result_name: str = "G1"
) -> AdvancementResult:
    """
    Build AdvancementResult from on_base_code and GroundballResultType.

    This function handles all X-Check advancement scenarios by creating
    RunnerMovement objects based on the base situation code and result type.

    NOTE: lineup_id is set to 0 as placeholder - PlayResolver must populate
    actual lineup IDs when applying the result to GameState.

    Args:
        on_base_code: Base situation (0-7 bit field)
        gb_type: The groundball result type from table lookup
        result_name: Name for description (e.g., "G1", "G2", "G3")

    Returns:
        AdvancementResult with movements, outs, runs
    """
    movements = []
    outs = 0
    runs = 0

    # Decode on_base_code (sequential mapping, NOT bit field)
    # 0=Empty, 1=R1, 2=R2, 3=R3, 4=R1+R2, 5=R1+R3, 6=R2+R3, 7=Loaded
    on_base_mapping = {
        0: (False, False, False),  # Empty
        1: (True, False, False),  # R1
        2: (False, True, False),  # R2
        3: (False, False, True),  # R3
        4: (True, True, False),  # R1+R2
        5: (True, False, True),  # R1+R3
        6: (False, True, True),  # R2+R3
        7: (True, True, True),  # Loaded
    }
    r1_on, r2_on, r3_on = on_base_mapping.get(on_base_code, (False, False, False))

    # Handle based on GroundballResultType
    if gb_type == GroundballResultType.SAFE_ALL_ADVANCE_ONE:
        # Error E1: Everyone advances 1 base
        # Batter to 1st
        movements.append(
            RunnerMovement(lineup_id=0, from_base=0, to_base=1, is_out=False)
        )
        # Runners advance 1
        if r1_on:
            movements.append(
                RunnerMovement(lineup_id=0, from_base=1, to_base=2, is_out=False)
            )
        if r2_on:
            movements.append(
                RunnerMovement(lineup_id=0, from_base=2, to_base=3, is_out=False)
            )
        if r3_on:
            movements.append(
                RunnerMovement(lineup_id=0, from_base=3, to_base=4, is_out=False)
            )
            runs += 1
        description = f"{result_name} + E1: Batter safe at 1st, all runners advance 1"

    elif gb_type == GroundballResultType.SAFE_ALL_ADVANCE_TWO:
        # Error E2: Everyone advances 2 bases
        # Batter to 2nd
        movements.append(
            RunnerMovement(lineup_id=0, from_base=0, to_base=2, is_out=False)
        )
        # Runners advance 2
        if r1_on:
            movements.append(
                RunnerMovement(lineup_id=0, from_base=1, to_base=3, is_out=False)
            )
        if r2_on:
            movements.append(
                RunnerMovement(lineup_id=0, from_base=2, to_base=4, is_out=False)
            )
            runs += 1
        if r3_on:
            movements.append(
                RunnerMovement(lineup_id=0, from_base=3, to_base=4, is_out=False)
            )
            runs += 1
        description = f"{result_name} + E2: Batter safe at 2nd, all runners advance 2"

    elif gb_type == GroundballResultType.SAFE_ALL_ADVANCE_THREE:
        # Error E3: Everyone advances 3 bases
        # Batter to 3rd
        movements.append(
            RunnerMovement(lineup_id=0, from_base=0, to_base=3, is_out=False)
        )
        # All runners score
        if r1_on:
            movements.append(
                RunnerMovement(lineup_id=0, from_base=1, to_base=4, is_out=False)
            )
            runs += 1
        if r2_on:
            movements.append(
                RunnerMovement(lineup_id=0, from_base=2, to_base=4, is_out=False)
            )
            runs += 1
        if r3_on:
            movements.append(
                RunnerMovement(lineup_id=0, from_base=3, to_base=4, is_out=False)
            )
            runs += 1
        description = f"{result_name} + E3: Batter safe at 3rd, all runners score"

    else:
        # For non-error results (1-13), we need GameState to properly resolve
        # This is a limitation - we'll return a placeholder that indicates
        # PlayResolver needs to handle this with full game state
        logger.warning(
            f"X-Check {result_name}: GroundballResultType {gb_type} requires GameState "
            f"for proper resolution. Returning placeholder."
        )
        # Batter out as fallback
        movements.append(
            RunnerMovement(lineup_id=0, from_base=0, to_base=0, is_out=True)
        )
        outs = 1
        description = f"{result_name}: Result {gb_type} (requires GameState resolution)"

    return AdvancementResult(
        movements=movements,
        outs_recorded=outs,
        runs_scored=runs,
        result_type=gb_type,
        description=description,
    )


def build_flyball_advancement_with_error(
    on_base_code: int, error_result: str, flyball_type: str = "F1"
) -> AdvancementResult:
    """
    Build AdvancementResult for flyball + error.

    When error occurs on flyball, the out is negated and all runners
    (including batter) advance E# bases.

    NOTE: lineup_id is set to 0 as placeholder - PlayResolver must populate
    actual lineup IDs when applying the result to GameState.

    Args:
        on_base_code: Base situation (0-7 bit field)
        error_result: 'E1', 'E2', 'E3', or 'RP'
        flyball_type: Name for description (e.g., "F1", "F2", "F3")

    Returns:
        AdvancementResult with movements (no outs)
    """
    movements = []
    runs = 0

    # Get advancement bases
    bases_to_advance = get_error_advancement_bases(error_result)

    if bases_to_advance == 0:
        # No error - should not be called this way
        # Return batter out as fallback
        movements.append(
            RunnerMovement(lineup_id=0, from_base=0, to_base=0, is_out=True)
        )
        description = f"{flyball_type}: Batter out (no error)"
        return AdvancementResult(
            movements=movements,
            outs_recorded=1,
            runs_scored=0,
            result_type=None,
            description=description,
        )

    # Decode on_base_code (sequential mapping, NOT bit field)
    on_base_mapping = {
        0: (False, False, False),  # Empty
        1: (True, False, False),  # R1
        2: (False, True, False),  # R2
        3: (False, False, True),  # R3
        4: (True, True, False),  # R1+R2
        5: (True, False, True),  # R1+R3
        6: (False, True, True),  # R2+R3
        7: (True, True, True),  # Loaded
    }
    r1_on, r2_on, r3_on = on_base_mapping.get(on_base_code, (False, False, False))

    # Batter advances
    batter_to_base = min(bases_to_advance, 4)
    movements.append(
        RunnerMovement(lineup_id=0, from_base=0, to_base=batter_to_base, is_out=False)
    )
    if batter_to_base == 4:
        runs += 1

    # Runners advance
    if r1_on:
        r1_to_base = min(1 + bases_to_advance, 4)
        movements.append(
            RunnerMovement(lineup_id=0, from_base=1, to_base=r1_to_base, is_out=False)
        )
        if r1_to_base == 4:
            runs += 1

    if r2_on:
        r2_to_base = min(2 + bases_to_advance, 4)
        movements.append(
            RunnerMovement(lineup_id=0, from_base=2, to_base=r2_to_base, is_out=False)
        )
        if r2_to_base == 4:
            runs += 1

    if r3_on:
        r3_to_base = min(3 + bases_to_advance, 4)
        movements.append(
            RunnerMovement(lineup_id=0, from_base=3, to_base=r3_to_base, is_out=False)
        )
        if r3_to_base == 4:
            runs += 1

    description = (
        f"{flyball_type} + {error_result}: All runners advance {bases_to_advance} bases"
    )

    return AdvancementResult(
        movements=movements,
        outs_recorded=0,  # Error negates out
        runs_scored=runs,
        result_type=None,
        description=description,
    )
