"""
prediction_pipeline.py

Live wiring for this session's engine chain -- stream_features ->
trend_engine -> structure_engine -> regime_engine -> setup_engine ->
prediction_engine -> entry_score.py -- the live counterpart to
simulate_prediction_pipeline.py's offline replay, same relationship as
regime_strategies.py (live-callable) vs simulate_regime_strategies.py
(offline harness).

evaluate_entry_pipeline() is monitor.py's alternative to
entry_engine.evaluate_entry(), selected via config.json's
entry.decision_engine == "prediction_pipeline" (default stays "legacy" --
entry_engine.evaluate_entry() is unchanged and still the default path).
It returns an EntryDecision-shaped result (same field names as
entry_engine.EntryDecision: should_enter, confirmation_score,
reasons_for, reasons_against) so monitor.py's call site only needs to
choose which function to call, not restructure around a different shape.

CALLER-HELD STATE: same contract as every engine built this session --
this module holds nothing itself. monitor.py keeps one dict per symbol
(Monitor._pipeline_state[symbol]) across poll cycles and passes it in as
`state`; this function mutates it in place (prior_features/
prior_prediction/confirmation_state) and also returns it for convenience.
A fresh {} for a symbol (first call ever, or a deliberate reset) is
fine -- everything defaults to a cold start, exactly like the offline
replay's per-symbol loop.

DELIBERATELY LOW EXPECTED FIRE RATE, KNOWN GOING IN: four days of
offline replay (2026-09-01 through 09-04, see data/snapshots/<date>/
prediction_pipeline_backtest.json) found 3 entries across 4 days / 405
symbol-days -- prediction_engine.py's score rarely clears entry_score's
min_prediction_score threshold. That backtest ran with bars_sub=[]
throughout (the Yahoo snapshot data has no real sub-minute ticks) --
LIVE, stream.py's real sub-minute buffer means velocity_sub/slope_sub/
trade_flow/pressure are actually populated here, which several
prediction_engine.py components read directly and default to neutral
values without. Live behavior may therefore differ from the backtest in
either direction -- exactly why this is a paper-mode test, not a
conclusion drawn from the backtest alone.
"""

from dataclasses import dataclass, field

from stream_features import compute_features
from trend_engine import classify_trend
from structure_engine import analyze_structure
from regime_engine import classify_regime
from setup_engine import evaluate_setups
from prediction_engine import compute_prediction
from entry_score import compute_entry_score, ACTION_READY


@dataclass
class PipelineDecision:
    symbol: str
    should_enter: bool
    confirmation_score: float
    reasons_for: list = field(default_factory=list)
    reasons_against: list = field(default_factory=list)


def evaluate_entry_pipeline(symbol: str, bars: list, bars_sub: list, quote,
                             avg_vol_baseline: float, session_elapsed_minutes: float,
                             state: dict, sub_bucket_seconds: int = 30, as_of=None) -> PipelineDecision:
    """
    bars / bars_sub / quote: same shapes stream.StreamManager already
        hands entry_engine.evaluate_entry() (bars, quote) plus the new
        get_bars_sub() this session added.
    avg_vol_baseline: same "expected normal volume" baseline
        entry_engine/intraday_health already use.
    session_elapsed_minutes: market_time.minutes_since_open() -- drives
        regime_engine's OPENING_VOLATILITY window check.
    state: caller-held per-symbol dict, see module docstring.
    """
    state.setdefault("prior_features", None)
    state.setdefault("prior_prediction", None)
    state.setdefault("confirmation_state", {})

    features = compute_features(symbol, bars, bars_sub, quote, avg_vol_baseline=avg_vol_baseline,
                                 prior=state["prior_features"], sub_bucket_seconds=sub_bucket_seconds,
                                 as_of=as_of)
    trend = classify_trend(features)
    structure = analyze_structure(symbol, bars)
    regime = classify_regime(symbol, features, trend, structure, session_elapsed_minutes)
    setups = evaluate_setups(features, trend, structure, bars)
    prediction = compute_prediction(symbol, features, trend, structure, regime=regime,
                                     setups=setups, prior=state["prior_prediction"])
    entry_reading = compute_entry_score(symbol, features, trend, structure, prediction,
                                         regime=regime, setups=setups,
                                         confirmation_state=state["confirmation_state"], as_of=as_of)

    state["prior_features"] = features
    state["prior_prediction"] = prediction
    state["confirmation_state"] = entry_reading.confirmation

    should_enter = entry_reading.action == ACTION_READY
    return PipelineDecision(
        symbol=symbol, should_enter=should_enter, confirmation_score=entry_reading.entry_score,
        reasons_for=entry_reading.reasons if should_enter else [],
        reasons_against=[] if should_enter else entry_reading.reasons,
    )
