"""Core data models — pure dataclasses, no side effects."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Literal, Optional

Side = str  # "long" or "short"
Action = Literal["enter", "wait", "close", "skip"]
SetupType = str  # "absorption_reversal", "initiation_breakout", etc.


@dataclass
class Candle:
    ts: int  # ms
    open: float
    high: float
    low: float
    close: float
    volume: float  # USD

    @classmethod
    def from_hl(cls, raw: dict) -> Candle:
        return cls(
            ts=int(raw["t"]),
            open=float(raw["o"]),
            high=float(raw["h"]),
            low=float(raw["l"]),
            close=float(raw["c"]),
            volume=float(raw["v"]),
        )

    @property
    def mid(self) -> float:
        return (self.high + self.low) / 2

    @property
    def range(self) -> float:
        return self.high - self.low


@dataclass
class BookLevel:
    price: float
    size: float

    @property
    def usd(self) -> float:
        return self.price * self.size


@dataclass
class BookSnapshot:
    ts: int
    bids: list[BookLevel]
    asks: list[BookLevel]
    mid: float

    @property
    def spread(self) -> float:
        if not self.bids or not self.asks:
            return 999.0
        return self.asks[0].price - self.bids[0].price

    @property
    def spread_bps(self) -> float:
        if self.mid == 0:
            return 999.0
        return self.spread / self.mid * 10000


@dataclass
class TradeTick:
    ts: int
    price: float
    size: float
    side: str  # "buy" or "sell"

    @property
    def usd(self) -> float:
        return self.price * self.size


@dataclass
class OrderflowSnapshot:
    """Single-point orderflow analysis."""
    ts: int
    mid: float
    spread_bps: float

    # Book
    book_imbalance: float       # (bid_depth - ask_depth) / (bid_depth + ask_depth)
    wall_skew: float            # largest_bid_wall / largest_ask_wall
    stacked_bid_count: int      # consecutive bid-dominant levels
    stacked_ask_count: int      # consecutive ask-dominant levels
    bid_depth_usd: float
    ask_depth_usd: float

    # Tape / CVD
    tape_delta_usd: float       # buy_vol_usd - sell_vol_usd in window
    tape_delta_pct: float       # normalized -1..1
    cvd: float                  # cumulative delta
    cvd_divergence: bool        # price vs CVD moving opposite directions
    large_prints: int           # institutional-sized trades
    trades_per_sec: float

    # Absorption
    bid_absorption: float       # 0..1, how much selling is being absorbed
    ask_absorption: float       # 0..1, how much buying is being absorbed

    # Derived
    vwap: float
    price_vs_vwap: float        # (price - vwap) / vwap
    rsi_14: float
    macd_histogram: float
    atr_14: float

    # Volume profile
    vpoc: float                 # volume point of control (price level with most volume)
    vah: float                  # value area high
    val: float                  # value area low

    # Market context
    funding_rate: float
    open_interest: float
    premium_bps: float          # mark vs oracle

    @property
    def omni_score(self) -> float:
        """OMPI-lite: composite pressure score. >1.5 bull, <-1.5 bear."""
        ofi = self.tape_delta_pct * 2.0  # kinetic
        mds = self.book_imbalance         # potential
        return ofi + mds

    @property
    def stance(self) -> str:
        s = self.omni_score
        if s > 1.5:
            return "strong_bull"
        elif s > 0.3:
            return "bull"
        elif s < -1.5:
            return "strong_bear"
        elif s < -0.3:
            return "bear"
        return "neutral"


@dataclass
class Setup:
    """A classified trading setup from strategy analysis."""
    setup_type: SetupType
    side: Side
    coin: str
    confidence: float           # 0..1
    edge: float                 # expected R
    evidence: list[str]         # human-readable reasons
    entry_ref: float            # reference price for entry
    invalidation: float         # where thesis breaks
    target: float               # structural target
    atr: float                  # current ATR for sizing

    @property
    def risk(self) -> float:
        return abs(self.entry_ref - self.invalidation)

    @property
    def reward(self) -> float:
        return abs(self.target - self.entry_ref)

    @property
    def rr(self) -> float:
        if self.risk == 0:
            return 0.0
        return self.reward / self.risk


@dataclass
class TradePlan:
    """Concrete order plan ready for execution."""
    coin: str
    side: Side
    entry: float
    stop: float
    target: float
    qty: float
    leverage: int
    margin_usd: float
    setup: Setup

    @property
    def risk_usd(self) -> float:
        return abs(self.entry - self.stop) * self.qty

    @property
    def reward_usd(self) -> float:
        return abs(self.target - self.entry) * self.qty

    @property
    def rr(self) -> float:
        if self.risk_usd == 0:
            return 0.0
        return self.reward_usd / self.risk_usd


@dataclass
class MarketRegime:
    """Multi-timeframe regime classification."""
    trend_15m: str      # "up", "down", "chop"
    trend_1h: str
    trend_4h: str
    vol_regime: str     # "expanding", "contracting", "normal"
    overall: str        # "trending", "ranging", "breakout", "choppy"


@dataclass
class JournalEntry:
    """Single journal record."""
    ts: int
    event: str          # "signal", "submit", "risk_reject", "close", "exposure_skip"
    coin: str = ""
    side: str = ""
    entry: float = 0.0
    stop: float = 0.0
    target: float = 0.0
    confidence: float = 0.0
    setup_type: str = ""
    evidence: list[str] = field(default_factory=list)
    reason: str = ""
    pnl_usd: float = 0.0
    mfe_pct: float = 0.0
    mae_pct: float = 0.0
    exit_reason: str = ""
    extra: dict = field(default_factory=dict)
