"""Phase-B chart scanner: detect SL (red) and TP (green) horizontal lines."""
from __future__ import annotations

from dataclasses import dataclass
from typing import Literal

import numpy as np

from .config import Config
from .vision import crop_roi, detect_color_lines, pixel_y_to_price


@dataclass
class Levels:
    sl: float | None
    tp1: float | None
    tp2: float | None
    partial: bool  # True when fewer than 3 lines were present


@dataclass
class LevelsResult:
    status: Literal["waiting", "partial", "complete", "timeout"]
    levels: Levels | None
    elapsed_s: float


def _merge_ys(ys: list[int]) -> list[int]:
    """Sort and deduplicate y-coords within 3px."""
    merged: list[int] = []
    for y in sorted(ys):
        if not merged or abs(y - merged[-1]) > 3:
            merged.append(y)
    return merged


def _ys_match(ys1: list[int], ys2: list[int]) -> bool:
    """True when both lists have same length and each pair is within ±2px."""
    if len(ys1) != len(ys2):
        return False
    return all(abs(a - b) <= 2 for a, b in zip(ys1, ys2))


class LevelsExtractor:
    """Scan chart ROI for red (SL) and green (TP) horizontal lines.

    Requires two consecutive calls with a matching y-set (±2px) before
    declaring "complete".  Until then returns "partial" (any lines) or
    "waiting" (none).  After phaseb_timeout_s returns "timeout".
    """

    def __init__(
        self,
        cfg: Config,
        direction: Literal["BUY", "SELL"],
        start_ts: float,
    ) -> None:
        self._cfg = cfg
        self._direction = direction
        self._start_ts = start_ts
        self._prev_ys: list[int] | None = None

    def step(self, chart_frame_bgr: np.ndarray, ts: float) -> LevelsResult:
        elapsed = ts - self._start_ts

        if elapsed > self._cfg.phaseb_timeout_s:
            return LevelsResult(status="timeout", levels=None, elapsed_s=elapsed)

        chart_img = crop_roi(chart_frame_bgr, self._cfg.chart_roi)

        red_cfg = self._cfg.colors["light_red"]
        green_cfg = self._cfg.colors["light_green"]

        red_ys = detect_color_lines(chart_img, red_cfg.rgb, red_cfg.tolerance)
        green_ys = detect_color_lines(chart_img, green_cfg.rgb, green_cfg.tolerance)
        all_ys = _merge_ys(red_ys + green_ys)

        n = len(all_ys)

        if n == 0:
            self._prev_ys = None
            return LevelsResult(status="waiting", levels=None, elapsed_s=elapsed)

        stable = self._prev_ys is not None and _ys_match(self._prev_ys, all_ys)
        self._prev_ys = list(all_ys)

        if n >= 3 and stable:
            levels = self._assign(all_ys[:3], partial=False)
            return LevelsResult(status="complete", levels=levels, elapsed_s=elapsed)

        levels = self._assign_partial(all_ys)
        return LevelsResult(status="partial", levels=levels, elapsed_s=elapsed)

    def _assign(self, ys: list[int], *, partial: bool) -> Levels:
        """Assign SL/TP from 3 y-coords sorted top→bottom (increasing y = lower price)."""
        prices = [pixel_y_to_price(y, self._cfg.y_axis) for y in ys]
        if self._direction == "BUY":
            # topmost (lowest y) = highest price = TP2; bottom = SL
            return Levels(sl=prices[2], tp1=prices[1], tp2=prices[0], partial=partial)
        else:
            # SELL: topmost = SL; bottom = TP2
            return Levels(sl=prices[0], tp1=prices[1], tp2=prices[2], partial=partial)

    def _assign_partial(self, ys: list[int]) -> Levels:
        """Best-effort assignment for 1-2 (or unstable 3+) lines."""
        n = len(ys)
        if n >= 3:
            return self._assign(ys[:3], partial=True)
        prices = [pixel_y_to_price(y, self._cfg.y_axis) for y in ys]
        if n == 2:
            if self._direction == "BUY":
                return Levels(sl=prices[1], tp1=prices[0], tp2=None, partial=True)
            else:
                return Levels(sl=prices[0], tp1=prices[1], tp2=None, partial=True)
        # n == 1
        return Levels(sl=prices[0], tp1=None, tp2=None, partial=True)
