{ pkgs-unstable, ... }:
{
  home.packages = with pkgs-unstable; [
    (writers.writePython3Bin "change_colors_json" {
      libraries = with python3Packages; [ numpy pillow scikit-learn ];
      flakeIgnore = [ "E302" "E305" "E226" "E501" ];
    } /*python */ ''
from colorsys import hls_to_rgb, rgb_to_hls
import json
import sys
from typing import Literal, cast
from numpy.typing import NDArray
from sklearn.cluster import KMeans
import numpy as np
from PIL import Image

def fc(c: int) -> str:
    assert c < 256
    s = str(hex(c))[2:]
    if c < 16:
        return "0" + s
    elif len(s) == 1:
        return s + s
    else:
        return s

class Color(object):
    def __init__(self, rgb: tuple[int, ...], frequency: float):
        assert len(rgb) == 3, "RGB values must be a tuple of length 3"
        self.rgb = cast(tuple[int, int, int], rgb)
        self.freq: float = frequency

    def __lt__(self, other: "Color") -> bool:
        return self.freq < other.freq

    @property
    def hls(self) -> tuple[float, float, float]:
        return rgb_to_hls(r=self.rgb[0] / 255, g=self.rgb[1] / 255, b=self.rgb[2] / 255)

    @property
    def luminance(self) -> float:
        return np.dot(np.array([0.2126, 0.7152, 0.0722]), self.rgb)

def k_means_extraction(arr: NDArray[float], height: int, width: int, palette_size: int) -> list[Color]:
    arr = np.reshape(arr, (width * height, -1))
    model = KMeans(n_clusters=palette_size, n_init="auto", init="k-means++", random_state=2024)
    labels = model.fit_predict(arr)
    palette = np.array(model.cluster_centers_, dtype=int)
    color_count = np.bincount(labels)
    color_frequency = color_count / float(np.sum(color_count))
    colors = []
    for color, freq in zip(palette, color_frequency):
        colors.append(Color(color, freq))
    return colors


class Palette:
    def __init__(self, colors: list[Color]):
        self.colors = colors
        self.frequencies = [c.freq for c in colors]

    def __getitem__(self, item: int) -> Color:
        return self.colors[item]

    def __len__(self) -> int:
        return self.number_of_colors

def ensure_color(c: Color, alter_sat: bool) -> list[int]:
    hue, lum, sat = c.hls
    if alter_sat:
        new_sat = min(sat**0.5, 1)
    else:
        new_sat = sat
    new_lum = max(lum, 0.5)
    r, g, b = hls_to_rgb(h=hue, l=new_lum, s=new_sat)
    return [int(r*255), int(g*255), int(b*255)]

def list_to_hex(ilist: list[int]) -> str:
    return f"#{fc(ilist[0])}{fc(ilist[1])}{fc(ilist[2])}"

def alter_hue(ilist: list[int], hue: int) -> list[int]:
    assert hue >= 0 and hue <= 360
    r, g, b = ilist
    h, l, s = rgb_to_hls((r/255), (g/255), (b/255))
    new_hue = (((h*360) + hue) % 360) / 360
    r, g, b = hls_to_rgb(h=new_hue, l=l, s=s)
    return [int(r*255), int(g*255), int(b*255)]

def alter_l(ilist: list[int], l_in_1_0: float) -> list[int]:
    assert l_in_1_0 >= 0 and l_in_1_0 <= 1
    r, g, b = ilist
    h, _, s = rgb_to_hls((r/255), (g/255), (b/255))
    r, g, b = hls_to_rgb(h=h, l=l_in_1_0, s=s)
    return [int(r*255), int(g*255), int(b*255)]

def extract_colors(
    image: str,
    palette_size: int = 5,
    resize: bool = True,
    sort_mode: Literal["luminance", "frequency"] | None = None,
) -> Palette:

    img = Image.open(image).convert("RGB")

    # open the image
    img = img.resize((256, 256))
    width, height = img.size
    arr = np.asarray(img)

    colors = k_means_extraction(arr, height, width, palette_size)

    if sort_mode == "luminance":
        colors.sort(key=lambda c: c.luminance, reverse=False)
    else:
        colors.sort(reverse=True)

    return Palette(colors)
if __name__ == "__main__":
    try:
        img = sys.argv[1]
    except IndexError:
        img = "/home/nx2/Pictures/wallpapers/absolute-cinema-acid.png"

    palette = extract_colors(image=img, palette_size=3)

    accent = ensure_color(c=palette[0], alter_sat=False)
    secondary = ensure_color(c=palette[1], alter_sat=True)
    tertiary = ensure_color(c=palette[2], alter_sat=False)
    weird = alter_hue(ilist=secondary, hue=180)
    special = alter_hue(ilist=accent, hue=180)
    foreground = alter_l(accent, 0.9)
    background = alter_l(accent, 0.1)

    d = {
        "base": {
            "foreground": list_to_hex(foreground),
            "background": list_to_hex(background)
        },
        "to_alter": {
            "accent": list_to_hex(accent),
            "secondary": list_to_hex(secondary),
            "tertiary": list_to_hex(tertiary),
            "special": list_to_hex(special),
            "weird": list_to_hex(weird)
        }
    }

    with open("/home/nx2/nix-dots/flake-modules/colors.json", "w") as f:
        f.write(json.dumps(d, indent=4))
'')
  ];
}
