"""One palette for the figures, and the writer that makes them theme-aware.

Matplotlib writes every colour into the SVG as an inline style, so a figure
saved the ordinary way carries fixed values and cannot follow the page.
`save` replaces each one with the theme's own custom property, keeping the
literal as the fallback so the file still renders when opened on its own.

The page inlines these with the figsvg shortcode rather than pointing an img
element at them, and that is the whole reason this works. An external SVG is
a separate document. It cannot read --ink or --teal from the page, and it
cannot see the data-theme attribute the site's light and dark buttons set.
It would answer prefers-color-scheme instead, which is the operating
system's preference, so a reader whose system is light and who presses the
site's dark button got dark ink on a dark page and lost the axis labels.

Eight of the ten come straight from ff-1's base.css. The two extra line
colours have no counterpart there, so they are mixed from ones that do, with
the color-mix the theme already uses for --tail-end. Nothing here needs a
property of its own, and every colour in a figure switches with the page.
"""
import re

# What matplotlib draws with, and the property each becomes in the file. The
# literals are the theme's own light values wherever the theme has one.
PALETTE = {
    "#00706b": "--teal",
    "#3a49a6": "--indigo",
    "#a93356": "--rose",
    "#b36a1e": "--amber",
    "#0D1620": "--ink",
    "#5A6874": "--muted",
    "#D3DBE3": "--line",
    "#FFFFFF": "--surface",
}

# Two more line colours than the theme names. Mixed from the ones it does,
# so they switch with the page like the rest and the site needs no property
# of its own. color-mix is what ff-1 already uses for --tail-end and the
# pager edges, so this is its own idiom rather than a new one.
MIXED = {
    "#5b4b8a": "color-mix(in oklab, var(--indigo, #3a49a6) 62%, var(--rose, #a93356))",
    "#1f6f4a": "color-mix(in oklab, var(--teal, #00706b) 68%, var(--amber, #b36a1e))",
}

TEAL, INDIGO, ROSE, AMBER = "#00706b", "#3a49a6", "#a93356", "#b36a1e"
VIOLET, GREEN = "#5b4b8a", "#1f6f4a"
INK, MUTED, GRID, PAPER = "#0D1620", "#5A6874", "#D3DBE3", "#FFFFFF"

# The six line colours, in the order the six delay lines are drawn.
LINES = [INDIGO, TEAL, ROSE, AMBER, VIOLET, GREEN]


def stems(ax, x, y, floor=1e-4, **kw):
    """Draw a spike per sample as one path rather than one path each.

    Matplotlib's vlines emits a separate path element for every segment. At
    a couple of thousand spikes that is most of a megabyte of SVG, and the
    page carries the file rather than linking it. Joining the segments with
    gaps of NaN gives one path with the same picture, and samples below the
    floor are dropped because they draw nothing visible.
    """
    import numpy as np

    x = np.asarray(x, dtype=float)
    y = np.asarray(y, dtype=float)
    keep = np.abs(y) > floor
    x, y = x[keep], y[keep]
    xs = np.empty(x.size * 3)
    ys = np.empty(x.size * 3)
    xs[0::3] = x
    xs[1::3] = x
    xs[2::3] = np.nan
    ys[0::3] = 0.0
    ys[1::3] = y
    ys[2::3] = np.nan
    return ax.plot(xs, ys, **kw)


def save(fig, name):
    """Write fig to name as an SVG whose colours are the page's own."""
    import matplotlib.pyplot as plt

    fig.savefig(name, format="svg", transparent=True)
    plt.close(fig)

    text = open(name, encoding="utf-8").read()

    # Matplotlib writes six decimal places. The page carries these files
    # rather than linking them, so the digits are paid for on every visit,
    # and two places is 0.005 pt of error at worst. On this page that is
    # 0.02 of a device pixel on a two-times display.
    #
    # A number whose integer part is zero is left alone. Those are the glyph
    # scale factors, scale(0.015625) and the like, where two places would
    # resize the text and one would erase it.
    text = re.sub(r"(\d+)\.(\d{3,})",
                  lambda m: (m.group(0) if m.group(1) == "0"
                             else f"{float(m.group(0)):.2f}".rstrip("0").rstrip(".")),
                  text)

    # Namespace every id to the file. Matplotlib numbers them from one in
    # each figure and names glyphs after the font, so six drawings in one
    # page carry six of id="text_1" and one #DejaVuSans-61 apiece. A <use>
    # then resolves to whichever came first, which is a different figure's
    # copy. It renders correctly only because all six use the same font at
    # the same size, and it is invalid either way.
    stem = re.sub(r"[^A-Za-z0-9]+", "-", name.rsplit(".", 1)[0]).strip("-")
    text = re.sub(r'(\sid=")([^"]+)"', lambda m: f'{m.group(1)}{stem}-{m.group(2)}"', text)
    text = re.sub(r'((?:xlink:)?href="#)([^"]+)"',
                  lambda m: f'{m.group(1)}{stem}-{m.group(2)}"', text)
    text = re.sub(r'(url\(#)([^)]+)\)',
                  lambda m: f'{m.group(1)}{stem}-{m.group(2)})', text)

    for literal, prop in PALETTE.items():
        text = re.sub(re.escape(literal), f"var({prop}, {literal})", text,
                      flags=re.IGNORECASE)
    for literal, mix in MIXED.items():
        text = re.sub(re.escape(literal), mix, text, flags=re.IGNORECASE)
    with open(name, "w", encoding="utf-8") as f:
        f.write(text)
    print("wrote", name)
