"""Position state machine for staged exit control.

State progression is monotonic (promotion-only) except terminal EXITED.
"""

from __future__ import annotations

from dataclasses import dataclass
from enum import StrEnum


class PositionState(StrEnum):
    HOLDING = "HOLDING"
    BE_LOCK = "BE_LOCK"
    ARMED = "ARMED"
    EXITED = "EXITED"


_STATE_RANK: dict[PositionState, int] = {
    PositionState.HOLDING: 0,
    PositionState.BE_LOCK: 1,
    PositionState.ARMED: 2,
    PositionState.EXITED: 3,
}


@dataclass(frozen=True)
class StateTransitionInput:
    unrealized_pnl_pct: float
    be_arm_pct: float
    arm_pct: float
    hard_stop_hit: bool = False
    trailing_stop_hit: bool = False
    model_exit_signal: bool = False
    be_lock_threat: bool = False


def evaluate_exit_first(inp: StateTransitionInput) -> bool:
    """Return True when terminal exit conditions are met.

    EXITED must be evaluated before any promotion.
    """
    # model_exit_signal is assist-only and must not trigger EXIT directly.
    return inp.hard_stop_hit or inp.trailing_stop_hit or inp.be_lock_threat


def promote_state(current: PositionState, inp: StateTransitionInput) -> PositionState:
    """Promote to highest admissible state for current tick/bar.

    Rules:
    - EXITED has highest precedence and is terminal.
    - Promotions are monotonic (no downgrade).
    """
    if current == PositionState.EXITED:
        return PositionState.EXITED

    if evaluate_exit_first(inp):
        return PositionState.EXITED

    target = PositionState.HOLDING
    if inp.unrealized_pnl_pct >= inp.arm_pct:
        target = PositionState.ARMED
    elif inp.unrealized_pnl_pct >= inp.be_arm_pct:
        target = PositionState.BE_LOCK
    elif inp.model_exit_signal:
        # Model signal assists risk posture by tightening to BE_LOCK.
        target = PositionState.BE_LOCK

    return target if _STATE_RANK[target] > _STATE_RANK[current] else current
