{ pkgs, config, lib ? pkgs.lib }:

let
  colors = config.colorScheme.palette;
  mediaImages = config.syscfg.media.main;
  mediaNames = map (image: builtins.baseNameOf (toString image)) mediaImages;
  mediaSourceDir = pkgs.linkFarm "wallpaper-media" (
    map (image: {
      name = builtins.baseNameOf (toString image);
      path = image;
    }) mediaImages
  );

  dither = "atkinson"; # none | floyd-steinberg | atkinson | jjn | burkes | sierra | sierra-lite
  paletteSize = 0;

  hexChars = "0123456789abcdef";
  hexMap = {
    "0" = 0; "1" = 1; "2" = 2; "3" = 3;
    "4" = 4; "5" = 5; "6" = 6; "7" = 7;
    "8" = 8; "9" = 9; "a" = 10; "b" = 11;
    "c" = 12; "d" = 13; "e" = 14; "f" = 15;
  };

  baseColors = [
    colors.base00
    colors.base01
    colors.base02
    colors.base03
    colors.base04
    colors.base05
    colors.base06
    colors.base07
    colors.base08
    colors.base09
    colors.base0A
    colors.base0B
    colors.base0C
    colors.base0D
    colors.base0E
    colors.base0F
  ];

  round = x: builtins.floor (x + 0.5);
  clamp = x:
    if x < 0 then 0 else if x > 255 then 255 else x;
  parseHexByte = byte:
    let
      hi = hexMap.${builtins.substring 0 1 byte};
      lo = hexMap.${builtins.substring 1 1 byte};
    in
    hi * 16 + lo;
  hexToRgb = hex:
    let
      clean = lib.toLower (lib.removePrefix "#" hex);
    in
    {
      r = parseHexByte (builtins.substring 0 2 clean);
      g = parseHexByte (builtins.substring 2 2 clean);
      b = parseHexByte (builtins.substring 4 2 clean);
    };
  componentToHex = value:
    let
      bounded = clamp value;
      hi = builtins.div bounded 16;
      lo = bounded - hi * 16;
    in
    "${builtins.substring hi 1 hexChars}${builtins.substring lo 1 hexChars}";
  rgbToHex = color: "${componentToHex color.r}${componentToHex color.g}${componentToHex color.b}";

  getTint = c: weight: round (c + (255 - c) * weight);
  getShade = c: weight: round (c * weight);
  tint = color: weight: {
    r = getTint color.r weight;
    g = getTint color.g weight;
    b = getTint color.b weight;
  };
  shade = color: weight: {
    r = getShade color.r weight;
    g = getShade color.g weight;
    b = getShade color.b weight;
  };
  genPalette = color:
    let
      tints =
        if paletteSize == 0
        then [ ]
        else lib.genList (i: tint color ((i + 1.0) / paletteSize)) paletteSize;
      shades =
        if paletteSize == 0
        then [ ]
        else lib.genList (i: shade color (i * 1.0 / paletteSize)) paletteSize;
    in
    lib.reverseList tints ++ [ color ] ++ lib.reverseList shades;
  keepColor = color:
    let
      sum = color.r + color.g + color.b;
    in
    sum > 0 && sum < 765;

  paletteColors = lib.concatMap (hex: lib.filter keepColor (genPalette (hexToRgb hex))) baseColors;
  paletteHex = lib.concatStringsSep "," (map rgbToHex paletteColors);
  gifPaletteFile = pkgs.writeText "wallpaper-gifpalette.txt" (
    lib.concatMapStringsSep "\n" (color: "${toString color.r} ${toString color.g} ${toString color.b}") paletteColors
  );

  buildCommands =
    lib.concatMapStringsSep "\n" (name:
      let
        source = "${mediaSourceDir}/${name}";
        target = "build/${name}";
      in
      if lib.hasSuffix ".gif" (lib.toLower name) then ''
        gifsicle --use-colormap ${lib.escapeShellArg (toString gifPaletteFile)} < ${lib.escapeShellArg source} > ${lib.escapeShellArg target}
      '' else ''
        repalette ${lib.escapeShellArg source} ${lib.escapeShellArg target} -p ${lib.escapeShellArg paletteHex} --dither ${lib.escapeShellArg dither}
      ''
    ) mediaNames;
in
assert lib.assertMsg
  (builtins.length mediaNames == builtins.length (lib.unique mediaNames))
  "syscfg.media.main contains duplicate basenames, which would collide in generated wallpaper output.";
pkgs.stdenv.mkDerivation {
  pname = "generated-wallpaper";
  version = "local";
  dontUnpack = true;

  nativeBuildInputs = with pkgs; [
    custom.repalette
    gifsicle
  ];

  buildPhase = ''
    runHook preBuild

    mkdir -p build
    ${buildCommands}

    runHook postBuild
  '';

  installPhase = ''
    runHook preInstall

    mkdir -p $out/share/wallpaper
    cp -r build/. $out/share/wallpaper/

    runHook postInstall
  '';
}
