"""
CIE 色度图底图渲染与缓存（工业版）

特点：
- colour-science 谱迹渲染
- numpy RGBA 缓存
- 内存 + 磁盘缓存
- 支持 light / dark UI
- 启动预热
- 线程安全

调用方式：

bg, bbox = get_cie1931_background(mode="dark")
ax.imshow(bg, extent=bbox)

"""

from __future__ import annotations

import hashlib
import os
import threading
from typing import Tuple

import numpy as np

# ----------------------------
# 配置
# ----------------------------

# 渲染分辨率
_DIAGRAM_RES = 1024

# 缓存版本（风格变化时递增）
_CACHE_VERSION = "v2"

# UI 颜色
_DARK_BG = "#0f1115"
_LIGHT_BG = "#ffffff"

_BBox = Tuple[float, float, float, float]

_CIE1931_BBOX: _BBox = (0.0, 0.8, 0.0, 0.9)
_CIE1976_BBOX: _BBox = (0.0, 0.65, 0.0, 0.6)

_memory_cache: dict[str, np.ndarray] = {}
_lock = threading.Lock()


# ----------------------------
# cache path
# ----------------------------

def _cache_dir() -> str:
    here = os.path.dirname(os.path.abspath(__file__))
    root = os.path.abspath(os.path.join(here, "..", ".."))

    d = os.path.join(root, "settings", "cache")
    os.makedirs(d, exist_ok=True)

    return d


def _cache_key(kind: str, bbox: _BBox, mode: str) -> str:
    sig = f"{kind}|{bbox}|{mode}|{_DIAGRAM_RES}|{_CACHE_VERSION}"
    h = hashlib.md5(sig.encode()).hexdigest()[:10]

    return f"chromaticity_{kind}_{mode}_{h}.npy"


def _cache_path(kind: str, bbox: _BBox, mode: str) -> str:
    return os.path.join(_cache_dir(), _cache_key(kind, bbox, mode))


# ----------------------------
# 渲染
# ----------------------------

def _render_chromaticity(kind: str, bbox: _BBox, mode: str) -> np.ndarray:
    """
    通过 colour-science 渲染 chromaticity 图。
    """

    import matplotlib

    prev_backend = matplotlib.get_backend()

    try:
        matplotlib.use("Agg", force=True)
    except Exception:
        pass

    import matplotlib.pyplot as plt
    import colour
    from colour.plotting import (
        plot_chromaticity_diagram_CIE1931,
        plot_chromaticity_diagram_CIE1976UCS,
    )

    if mode == "dark":
        colour.plotting.colour_style("dark")
        bg_color = _DARK_BG
    else:
        colour.plotting.colour_style("light")
        bg_color = _LIGHT_BG

    xmin, xmax, ymin, ymax = bbox

    aspect = (xmax - xmin) / (ymax - ymin)

    height = _DIAGRAM_RES
    width = int(round(height * aspect))

    fig = plt.figure(
        figsize=(width / 100.0, height / 100.0),
        dpi=100
    )

    fig.patch.set_facecolor(bg_color)

    ax = fig.add_axes([0, 0, 1, 1])
    ax.set_facecolor(bg_color)

    if kind == "cie1931":

        plot_chromaticity_diagram_CIE1931(
            axes=ax,
            show=False,
            title=False,
            tight_layout=False,
            bounding_box=bbox,
            transparent_background=False,
        )

    elif kind == "cie1976":

        plot_chromaticity_diagram_CIE1976UCS(
            axes=ax,
            show=False,
            title=False,
            tight_layout=False,
            bounding_box=bbox,
            transparent_background=False,
        )

    else:
        plt.close(fig)
        raise ValueError(f"unknown diagram kind: {kind}")

    ax.set_xlim(xmin, xmax)
    ax.set_ylim(ymin, ymax)

    ax.set_axis_off()
    ax.set_position([0, 0, 1, 1])

    fig.canvas.draw()

    buf = np.asarray(fig.canvas.buffer_rgba()).copy()
    buf = np.flipud(buf)

    plt.close(fig)

    try:
        matplotlib.use(prev_backend, force=True)
    except Exception:
        pass

    return buf


# ----------------------------
# load / render
# ----------------------------

def _load_or_render(kind: str, bbox: _BBox, mode: str) -> np.ndarray:

    key = _cache_key(kind, bbox, mode)

    with _lock:

        if key in _memory_cache:
            return _memory_cache[key]

        disk = _cache_path(kind, bbox, mode)

        if os.path.isfile(disk):

            try:
                arr = np.load(disk)

                _memory_cache[key] = arr

                return arr

            except Exception:

                try:
                    os.remove(disk)
                except OSError:
                    pass

        arr = _render_chromaticity(kind, bbox, mode)

        _memory_cache[key] = arr

        try:
            np.save(disk, arr)
        except Exception:
            pass

        return arr


# ----------------------------
# public API
# ----------------------------

def get_cie1931_background(mode: str = "dark") -> Tuple[np.ndarray, _BBox]:
    """
    获取 CIE1931 背景图

    mode:
        "dark"
        "light"
    """

    return _load_or_render("cie1931", _CIE1931_BBOX, mode), _CIE1931_BBOX


def get_cie1976_background(mode: str = "dark") -> Tuple[np.ndarray, _BBox]:

    return _load_or_render("cie1976", _CIE1976_BBOX, mode), _CIE1976_BBOX


# ----------------------------
# cache control
# ----------------------------

def clear_cache(*, disk: bool = False) -> None:
    """
    清空缓存
    """

    with _lock:

        _memory_cache.clear()

        if disk:

            d = _cache_dir()

            for name in os.listdir(d):

                if name.startswith("chromaticity_"):

                    try:
                        os.remove(os.path.join(d, name))
                    except OSError:
                        pass


# ----------------------------
# warmup
# ----------------------------

def warmup_cache(mode: str = "dark") -> None:
    """
    启动预热缓存

    可在软件启动时调用，避免首次绘图卡顿。
    """

    get_cie1931_background(mode)
    get_cie1976_background(mode)