"""Shared helpers for the static-export gallery.

Every chart in scripts/static-charts/ is a plain plotly.py figure that is
rendered to disk by Kaleido: a 2x PNG (what the post shows), an SVG (the
vector version, linked next to each chart) and the figure JSON (for the
per-chart code page at /python/<name>/, which is the only place the
interactive version appears).

Run everything with `python3 scripts/static-charts/generate_all.py`.
Kaleido v1 needs a Chrome/Chromium binary; point BROWSER_PATH at one if it
isn't on the PATH.
"""
import io
import json
import os
import sys
from pathlib import Path

import pandas as pd
import requests

HERE = Path(__file__).parent
ROOT = HERE.parent.parent
OUT = ROOT / "src/assets/static/static-charts"
THUMBS = OUT / "thumbs"
CACHE = HERE / "_cache"
for d in (OUT, THUMBS, CACHE):
    d.mkdir(parents=True, exist_ok=True)

# ── Typography ───────────────────────────────────────────────────────────────
FONT = "Inter, Helvetica Neue, Helvetica, Arial, sans-serif"
SERIF = "Georgia, Liberation Serif, Times New Roman, serif"
MONO = "JetBrains Mono, SF Mono, Menlo, Liberation Mono, monospace"

# ── Palette ──────────────────────────────────────────────────────────────────
INK = "#1c2024"
MUTED = "#6b6f76"
FAINT = "#a3a7ae"
GRID = "#e8e8ed"
RULE = "#d9d9e0"
BG = "#ffffff"

VIOLET = "#845EEE"
TEAL = "#52B3D0"
GREEN = "#55B685"
PINK = "#DA5597"
ORANGE = "#E9A23B"
BLUE = "#3B6FD4"
RED = "#D1495B"
GOLD = "#C9A227"
SLATE = "#5C6B8A"
COLORWAY = [VIOLET, TEAL, GREEN, PINK, ORANGE, BLUE, RED, GOLD, SLATE]

# Diverging red↔blue ramp (colorblind-safe, from ColorBrewer RdBu).
RDBU = [
    [0.0, "#2166ac"], [0.25, "#67a9cf"], [0.5, "#f7f7f7"],
    [0.75, "#ef8a62"], [1.0, "#b2182b"],
]
# Sequential violet ramp that starts nearly white.
VIOLETS = [[0, "#f3effd"], [0.5, "#b39cf4"], [1, "#4d2fb0"]]


def hex_to_rgba(hex_color: str, alpha: float) -> str:
    h = hex_color.lstrip("#")
    r, g, b = (int(h[i:i + 2], 16) for i in (0, 2, 4))
    return f"rgba({r},{g},{b},{alpha})"


# ── Layout ───────────────────────────────────────────────────────────────────
def base_layout(**overrides) -> dict:
    """A quiet, print-like baseline: white paper, hairline grid, no box."""
    axis = dict(
        showgrid=True, gridcolor=GRID, gridwidth=1,
        zeroline=False, showline=False, ticks="",
        tickfont=dict(size=13, color=MUTED),
        title=dict(font=dict(size=13, color=MUTED), standoff=10),
        automargin=True,
    )
    layout = dict(
        paper_bgcolor=BG, plot_bgcolor=BG,
        font=dict(family=FONT, size=13, color=INK),
        colorway=COLORWAY,
        margin=dict(l=70, r=40, t=110, b=80),
        xaxis=axis, yaxis=axis,
        hovermode="closest",
        showlegend=False,
    )
    for k, v in overrides.items():
        if isinstance(v, dict) and isinstance(layout.get(k), dict):
            layout[k] = {**layout[k], **v}
        else:
            layout[k] = v
    return layout


def titles(fig, title: str, subtitle: str = "", source: str = "",
           x: float = 0.0, top: float = 1.0, size: int = 22, lift: int = 0, source_shift: int = -42) -> None:
    """Editorial title block: bold title, muted subtitle, tiny source line.

    Placed in paper coordinates so they align with the plot's left edge rather
    than with the axis labels, the way a newspaper graphic is set.
    """
    fig.add_annotation(
        text=f"<b>{title}</b>", x=x, y=top, xref="paper", yref="paper",
        xanchor="left", yanchor="bottom", yshift=(30 if subtitle else 12) + lift + 18 * subtitle.count("<br>"),
        showarrow=False, font=dict(size=size, color=INK), align="left",
    )
    if subtitle:
        fig.add_annotation(
            text=subtitle, x=x, y=top, xref="paper", yref="paper",
            xanchor="left", yanchor="bottom", yshift=10 + lift,
            showarrow=False, font=dict(size=14, color=MUTED), align="left",
        )
    if source:
        fig.add_annotation(
            text=source, x=x, y=0, xref="paper", yref="paper",
            xanchor="left", yanchor="top", yshift=source_shift,
            showarrow=False, font=dict(size=11.5, color=MUTED), align="left",
        )


def panel_label(fig, letter: str, x: float, y: float, size: int = 16) -> None:
    """Journal-style bold panel letter (a, b, c …) in paper coordinates."""
    fig.add_annotation(
        text=f"<b>{letter}</b>", x=x, y=y, xref="paper", yref="paper",
        xanchor="right", yanchor="bottom", showarrow=False,
        font=dict(size=size, color=INK),
    )


# ── Data ─────────────────────────────────────────────────────────────────────
def _cached(url: str, suffix: str) -> Path:
    import hashlib
    key = hashlib.sha1(url.encode()).hexdigest()[:16]
    return CACHE / f"{key}{suffix}"


def fetch_text(url: str, **kwargs) -> str:
    """GET a URL, caching the body on disk so re-renders are offline."""
    p = _cached(url, ".txt")
    if p.exists():
        return p.read_text()
    r = requests.get(url, timeout=120, **kwargs)
    r.raise_for_status()
    p.write_text(r.text)
    return r.text


def fetch_csv(url: str, **kwargs) -> pd.DataFrame:
    return pd.read_csv(io.StringIO(fetch_text(url)), **kwargs)


def fetch_json(url: str):
    return json.loads(fetch_text(url))


# ── Save ─────────────────────────────────────────────────────────────────────
def _thumb(png_path: Path, max_px: int = 480) -> Path:
    from PIL import Image
    out = THUMBS / f"{png_path.stem}.jpg"
    im = Image.open(png_path).convert("RGB")
    im.thumbnail((max_px, max_px))
    im.save(out, "JPEG", quality=82, optimize=True)
    return out


def _optimize_png(path: Path) -> None:
    """Lossless re-encode (Kaleido's PNGs are unoptimized; this trims 15–30%)."""
    from PIL import Image
    Image.open(path).save(path, "PNG", optimize=True)


def save(n: int, fig, width: int = 1280, height: int = 800, scale: float = 2) -> None:
    """Write static-NN.png (scale x), static-NN.svg, static-NN.json + a thumb."""
    fig.update_layout(width=width, height=height)
    stem = f"static-{n:02d}"
    png = OUT / f"{stem}.png"
    png.write_bytes(fig.to_image(format="png", width=width, height=height, scale=scale))
    _optimize_png(png)
    svg = OUT / f"{stem}.svg"
    svg.write_bytes(fig.to_image(format="svg", width=width, height=height))
    fig.update_layout(width=None, height=None)
    (OUT / f"{stem}.json").write_text(fig.to_json())
    thumb = _thumb(png)
    kb = lambda p: p.stat().st_size // 1024
    print(f"  saved {stem}.png ({kb(png)} KB) + .svg ({kb(svg)} KB) + .json + thumb ({kb(thumb)} KB)")
