"""Plot specification models.

Defines the declarative structure for plots: what channels to show,
how to style them, corridors, cursor positions, etc.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

import numpy as np
from numpy.typing import NDArray

from impakt.channel.model import Channel
from impakt.transform.base import TransformChain


@dataclass
class PlotStyle:
    """Visual style for a single trace."""

    color: str = ""
    line_width: float = 1.5
    line_dash: str = "solid"  # solid, dash, dot, dashdot
    label: str = ""
    opacity: float = 1.0


@dataclass
class CorridorStyle:
    """Visual style for a tolerance corridor."""

    fill_color: str = "rgba(100, 100, 255, 0.15)"
    line_color: str = "rgba(100, 100, 255, 0.4)"
    line_width: float = 1.0
    line_dash: str = "dash"


@dataclass
class Corridor:
    """A tolerance corridor (min/max envelope).

    Can be loaded from file (CSV with time, lower, upper columns)
    or defined programmatically.
    """

    name: str
    upper: NDArray[np.floating[Any]]
    lower: NDArray[np.floating[Any]]
    time: NDArray[np.floating[Any]]
    style: CorridorStyle = field(default_factory=CorridorStyle)

    @classmethod
    def from_csv(cls, path: str, name: str = "") -> Corridor:
        """Load a corridor from a CSV file.

        Expected columns: time, lower, upper (or time, min, max).
        """
        data = np.loadtxt(path, delimiter=",", skiprows=1)
        return cls(
            name=name or path,
            time=data[:, 0],
            lower=data[:, 1],
            upper=data[:, 2],
        )

    @classmethod
    def from_bounds(
        cls,
        time: NDArray[np.floating[Any]],
        center: NDArray[np.floating[Any]],
        tolerance: float,
        name: str = "",
    ) -> Corridor:
        """Create a corridor as center +/- tolerance."""
        return cls(
            name=name,
            time=time,
            lower=center - tolerance,
            upper=center + tolerance,
        )


@dataclass
class ChannelRef:
    """Reference to a channel within a plot specification.

    Can refer to a channel by test_id + channel_name, or hold a
    direct Channel object.
    """

    test_id: str = ""
    channel_name: str = ""
    channel: Channel | None = None
    transform_chain: TransformChain | None = None
    style: PlotStyle = field(default_factory=PlotStyle)

    def resolve(self, channel: Channel | None = None) -> Channel:
        """Resolve to a concrete Channel, applying transforms if any."""
        ch = channel or self.channel
        if ch is None:
            raise ValueError(
                f"Cannot resolve ChannelRef: no channel provided "
                f"(test_id={self.test_id}, name={self.channel_name})"
            )
        if self.transform_chain:
            ch = self.transform_chain.apply(ch)
        return ch

    @property
    def label(self) -> str:
        """Display label for this channel reference."""
        if self.style.label:
            return self.style.label
        parts = []
        if self.test_id:
            parts.append(self.test_id)
        if self.channel_name:
            parts.append(self.channel_name)
        elif self.channel:
            parts.append(self.channel.code.short_label)
        return "/".join(parts) if parts else "Unknown"


@dataclass
class CursorValues:
    """Values at the two X-axis cursor positions."""

    x1: float
    x2: float
    values: list[dict[str, Any]] = field(default_factory=list)
    # Each dict: {label, value_at_x1, value_at_x2, delta, unit}

    def as_table(self) -> str:
        """Format as a text table."""
        if not self.values:
            return f"Cursors at x1={self.x1:.6f}, x2={self.x2:.6f} — no channels"

        lines = [
            f"{'Channel':<40} {'@ x1':>12} {'@ x2':>12} {'Delta':>12} {'Unit':<8}",
            "-" * 88,
        ]
        for v in self.values:
            lines.append(
                f"{v['label']:<40} {v['value_at_x1']:>12.4f} "
                f"{v['value_at_x2']:>12.4f} {v['delta']:>12.4f} {v.get('unit', ''):<8}"
            )
        return "\n".join(lines)


@dataclass
class PlotSpec:
    """Complete specification for a single plot.

    This is the declarative description that PlotEngine renders.
    """

    channels: list[ChannelRef] = field(default_factory=list)
    corridors: list[Corridor] = field(default_factory=list)
    x_cursors: tuple[float, float] | None = None
    x_range: tuple[float, float] | None = None
    y_range: tuple[float, float] | None = None
    title: str = ""
    x_label: str = "Time (s)"
    y_label: str = ""
    show_legend: bool = True
    show_grid: bool = True
    height: int = 500
    width: int = 900
