"""Legend and panel-label helpers for consistent multi-panel figures."""

from __future__ import annotations

import warnings
from typing import Optional

from matplotlib.axes import Axes
from matplotlib.colors import to_rgba

from .colors import INK

_PANEL_LOCS = {
    "upper left": (0.02, 0.98, "left", "top"),
    "upper right": (0.98, 0.98, "right", "top"),
    "lower left": (0.02, 0.08, "left", "bottom"),
    "lower right": (0.98, 0.08, "right", "bottom"),
}


def style_legend(
    ax: Axes,
    loc: str = "outside right upper",
    frameon: bool = False,
    title: Optional[str] = None,
    **kwargs,
):
    """Add a figure-level legend, placed outside the axes by default.

    Builds on `ax`'s handles/labels (or explicit `handles=`/`labels=` kwargs)
    but attaches the legend to `ax`'s figure via `fig.legend(...)`, so it sits
    outside the plot area rather than overlapping the data.

    A `title` is strongly encouraged: an untitled legend floating outside the
    axes loses its visual link to what it's describing, e.g.
    `style_legend(ax, title="Series")`. Without one, this emits a warning
    rather than failing — the legend still renders.
    """
    if title is None:
        warnings.warn(
            "style_legend() called without a title — an outside legend reads better with one, "
            "e.g. style_legend(ax, title='Series').",
            stacklevel=2,
        )

    fig = ax.get_figure()
    assert fig is not None, "ax must be attached to a figure"
    handles = kwargs.pop("handles", None)
    labels = kwargs.pop("labels", None)
    if handles is None or labels is None:
        handles, labels = ax.get_legend_handles_labels()

    # matplotlib-stubs' `loc` Literal doesn't include the "outside ..." compound
    # locations matplotlib actually supports at runtime (e.g. "outside right upper").
    legend = fig.legend(handles, labels, loc=loc, frameon=frameon, title=title, **kwargs)  # ty: ignore[invalid-argument-type]
    if legend.get_title() is not None:
        legend.get_title().set_fontweight("bold")
    return legend


def panel_label(
    ax: Axes,
    label: str,
    loc: str = "lower right",
    fontweight: str = "bold",
    box: bool = True,
    **kwargs,
):
    """Add a panel label like "(a)" for multi-panel thesis/paper figures.

    Defaults to the bottom-right corner, colored to match the axes' xlabel,
    on a light-grey semi-transparent rounded box with a slim solid border.
    Pass `box=False` for bare text with no box.
    """
    try:
        x, y, ha, va = _PANEL_LOCS[loc]
    except KeyError as exc:
        raise ValueError(f"Unknown panel_label loc {loc!r}. Choose from {sorted(_PANEL_LOCS)}.") from exc

    kwargs.setdefault("color", ax.xaxis.label.get_color())
    if box:
        kwargs.setdefault(
            "bbox",
            dict(
                boxstyle="round,pad=0.3",
                facecolor=to_rgba(INK["gridline"], alpha=0.8),
                edgecolor=INK["baseline"],
                linewidth=0.8,
            ),
        )

    return ax.text(
        x,
        y,
        f"({label})",
        transform=ax.transAxes,
        ha=ha,
        va=va,
        fontweight=fontweight,
        **kwargs,
    )
