"""Experimental state-aware action replay; disabled in the production strategy.

Actions are decisions at the close, not broker fill states. A deferred score exit
is latched until the minimum holding age, so a one-bar crossunder is not lost.
Trailing stops always override holding. Entry blocks never queue stale entries.
"""
from dataclasses import dataclass

import numpy as np


@dataclass(frozen=True)
class StatePolicy:
    macro_memory: int = 0
    exit_cooldown: int = 0
    min_hold: int = 0

    def __post_init__(self):
        for value in (self.macro_memory, self.exit_cooldown, self.min_hold):
            if not isinstance(value, int) or isinstance(value, bool) or value < 0:
                raise ValueError('Policy durations must be nonnegative integer bars')


def replay_state_policy(entry, exits, fallback, score_exit, exit_threshold,
                        close, high, trail, *, policy, risk=None, time_ok=None,
                        dead_zone=False):
    """Replay raw opportunities using position age, exit memory and macro risk.

    A risk bar blocks itself and N following bars, refreshed on every risk bar.
    An exit blocks the following N bars; entry becomes possible at exit+N+1.
    Risk is entry-only: it does not liquidate an existing position.
    """
    n = len(entry)
    risk = np.zeros(n, dtype=bool) if risk is None else np.asarray(risk, dtype=bool)
    time_ok = np.ones(n, dtype=bool) if time_ok is None else np.asarray(time_ok, dtype=bool)
    if any(np.asarray(a).shape != (n,) for a in (exits, fallback, score_exit, close, high, risk, time_ok)):
        raise ValueError('Replay arrays must be one-dimensional and equally sized')
    positions = np.zeros(n, dtype=int)
    entered, exited = np.zeros(n, bool), np.zeros(n, bool)
    actions = np.full(n, 'stay_flat', dtype='<U10')
    blocked, deferred = np.zeros(n, bool), np.zeros(n, bool)
    position, use_raw, pending_exit = False, False, False
    entry_bar, macro_until, cooldown_until = -1, -1, -1
    trade_high = 0.0
    for t in range(n):
        if risk[t]:
            macro_until = t + policy.macro_memory
        entry_blocked = t <= macro_until or t <= cooldown_until
        if position:
            if t != entry_bar + 1:
                trade_high = max(trade_high, high[t])
            stop = trail > 0 and close[t] <= trade_high * (1 - trail)
            score_signal = fallback[t] if use_raw else exits[t]
            if time_ok[t]:
                pending_exit = pending_exit or bool(score_signal)
            hold = t - entry_bar < policy.min_hold
            deferred[t] = pending_exit and hold and not stop and time_ok[t]
            if time_ok[t] and (stop or (pending_exit and not hold)):
                position, pending_exit, use_raw = False, False, False
                exited[t] = True
                actions[t] = 'exit_long'
                cooldown_until = t + policy.exit_cooldown
            else:
                actions[t] = 'hold_long'
        else:
            blocked[t] = bool(entry[t]) and entry_blocked and time_ok[t]
            if entry[t] and not entry_blocked and time_ok[t]:
                position = True
                entered[t] = True
                actions[t] = 'enter_long'
                entry_bar, trade_high = t, close[t]
                use_raw = dead_zone and score_exit[t] < exit_threshold
        positions[t] = position
    return {'position': positions, 'execute_entry': entered, 'execute_exit': exited,
            'policy_action': actions, 'policy_blocked_entry': blocked,
            'policy_deferred_exit': deferred}
