"""Plotly-based plot engine.

Renders PlotSpec objects into interactive Plotly figures with
support for corridors, dual X-cursors, and export.
"""

from __future__ import annotations

from typing import Any

import numpy as np
import plotly.graph_objects as go

from impakt.channel.model import Channel
from impakt.plot.spec import ChannelRef, Corridor, CursorValues, PlotSpec, PlotStyle

# Default color palette (colorblind-friendly)
DEFAULT_COLORS = [
    "#1f77b4",  # blue
    "#ff7f0e",  # orange
    "#2ca02c",  # green
    "#d62728",  # red
    "#9467bd",  # purple
    "#8c564b",  # brown
    "#e377c2",  # pink
    "#7f7f7f",  # gray
    "#bcbd22",  # olive
    "#17becf",  # cyan
]


class PlotEngine:
    """Renders PlotSpec into Plotly figures."""

    def render(self, spec: PlotSpec) -> go.Figure:
        """Render a PlotSpec into an interactive Plotly figure."""
        fig = go.Figure()

        # Add corridor fills first (behind data traces)
        for corridor in spec.corridors:
            self._add_corridor(fig, corridor)

        # Add channel traces
        for i, ch_ref in enumerate(spec.channels):
            ch = ch_ref.channel
            if ch is None:
                continue

            # Apply transforms if any
            if ch_ref.transform_chain:
                ch = ch_ref.transform_chain.apply(ch)

            style = ch_ref.style
            color = style.color or DEFAULT_COLORS[i % len(DEFAULT_COLORS)]
            label = style.label or ch_ref.label

            fig.add_trace(
                go.Scatter(
                    x=ch.time,
                    y=ch.data,
                    mode="lines",
                    name=label,
                    line=dict(
                        color=color,
                        width=style.line_width,
                        dash=style.line_dash,
                    ),
                    opacity=style.opacity,
                    hovertemplate=f"{label}<br>t=%{{x:.6f}}s<br>%{{y:.4f}} {ch.unit}<extra></extra>",
                )
            )

        # Add cursor lines
        if spec.x_cursors:
            x1, x2 = spec.x_cursors
            for x_val, label in [(x1, "x1"), (x2, "x2")]:
                fig.add_vline(
                    x=x_val,
                    line_dash="dash",
                    line_color="gray",
                    line_width=1,
                    annotation_text=f"{label}={x_val:.6f}s",
                    annotation_position="top",
                )

        # Layout
        fig.update_layout(
            title=spec.title,
            xaxis_title=spec.x_label,
            yaxis_title=spec.y_label,
            showlegend=spec.show_legend,
            height=spec.height,
            width=spec.width,
            template="plotly_white",
            hovermode="x unified",
            legend=dict(
                orientation="h",
                yanchor="bottom",
                y=-0.3,
                xanchor="center",
                x=0.5,
            ),
        )

        if spec.show_grid:
            fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor="rgba(128,128,128,0.2)")
            fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor="rgba(128,128,128,0.2)")

        if spec.x_range:
            fig.update_xaxes(range=list(spec.x_range))
        if spec.y_range:
            fig.update_yaxes(range=list(spec.y_range))

        return fig

    def _add_corridor(self, fig: go.Figure, corridor: Corridor) -> None:
        """Add a corridor (tolerance band) to the figure."""
        style = corridor.style

        # Upper bound
        fig.add_trace(
            go.Scatter(
                x=corridor.time,
                y=corridor.upper,
                mode="lines",
                name=f"{corridor.name} (upper)",
                line=dict(color=style.line_color, width=style.line_width, dash=style.line_dash),
                showlegend=False,
            )
        )

        # Lower bound with fill to upper
        fig.add_trace(
            go.Scatter(
                x=corridor.time,
                y=corridor.lower,
                mode="lines",
                name=f"{corridor.name} (lower)",
                line=dict(color=style.line_color, width=style.line_width, dash=style.line_dash),
                fill="tonexty",
                fillcolor=style.fill_color,
                showlegend=True,
            )
        )

    def to_image(self, spec: PlotSpec, format: str = "png", scale: float = 2.0) -> bytes:
        """Render to a static image.

        Args:
            spec: Plot specification.
            format: Image format ('png', 'svg', 'pdf', 'jpeg').
            scale: Resolution multiplier.

        Returns:
            Image bytes.
        """
        fig = self.render(spec)
        return fig.to_image(format=format, scale=scale)

    def to_html(self, spec: PlotSpec, include_plotlyjs: bool = True) -> str:
        """Render to standalone HTML."""
        fig = self.render(spec)
        return fig.to_html(include_plotlyjs=include_plotlyjs)


def cursor_values(
    spec_or_channels: PlotSpec | list[Channel],
    x1: float,
    x2: float,
) -> CursorValues:
    """Compute interpolated values at two X-axis positions.

    Args:
        spec_or_channels: PlotSpec or list of Channels.
        x1: First cursor position (time).
        x2: Second cursor position (time).

    Returns:
        CursorValues with interpolated values for each channel.
    """
    channels: list[tuple[str, Channel]] = []

    if isinstance(spec_or_channels, PlotSpec):
        for ref in spec_or_channels.channels:
            ch = ref.channel
            if ch is None:
                continue
            if ref.transform_chain:
                ch = ref.transform_chain.apply(ch)
            channels.append((ref.label, ch))
    else:
        for ch in spec_or_channels:
            channels.append((ch.code.short_label if ch.code.is_valid else ch.name, ch))

    values = []
    for label, ch in channels:
        v1 = ch.value_at(x1)
        v2 = ch.value_at(x2)
        values.append(
            {
                "label": label,
                "value_at_x1": v1,
                "value_at_x2": v2,
                "delta": v2 - v1,
                "unit": ch.unit,
            }
        )

    return CursorValues(x1=x1, x2=x2, values=values)
