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

let
  cfg = config.theme.md3-evo;
in
{
  options.theme.md3-evo = {
    enable = lib.mkEnableOption "the MD3-EVO theme";
    auto-dark = {
      enable = lib.mkEnableOption "Automatically switch between light and dark mode";
      lon = lib.mkOption {
        type = lib.types.float;
      };
      lat = lib.mkOption {
        type = lib.types.float;
      };
    };
    flavour = lib.mkOption {
      type = lib.types.enum [
        "content"
        "expressive"
        "fidelity"
        "fruit-salad"
        "monochrome"
        "neutral"
        "rainbow"
        "tonal-spot"
      ];
      default = "tonal-spot";
      description = "The flavour of the theme";
    };
    contrast = lib.mkOption {
      type = lib.types.numbers.between (-1) 1;
      default = 0;
      description = "Use a modified contrast";
    };
    transparency = lib.mkOption {
      type = lib.types.numbers.between 0 1;
      default = 0.9;
      description = "The transparency of apps";
    };
    radius = lib.mkOption {
      type = lib.types.ints.positive;
      default = 24;
      description = "The radius of the corners";
    };
    padding = lib.mkOption {
      type = lib.types.ints.positive;
      default = 12;
      description = "The padding of the windows";
    };
    blur = lib.mkOption {
      type = lib.types.ints.positive;
      default = 16;
      description = "The blur amount of windows";
    };
    semantic = {
      blend = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = "Blend the colors";
      };
      danger = lib.mkOption {
        type = lib.types.str;
        default = "#ff0000";
        description = "The color of danger";
      };
      warning = lib.mkOption {
        type = lib.types.str;
        default = "#ffff00";
        description = "The color of warning";
      };
      success = lib.mkOption {
        type = lib.types.str;
        default = "#00ff00";
        description = "The color of success";
      };
      info = lib.mkOption {
        type = lib.types.str;
        default = "#0000ff";
        description = "The color of info";
      };
    };
    syntax = {
      blend = lib.mkOption {
        type = lib.types.bool;
        default = true;
        description = "Blend the colors";
      };
      keywords = lib.mkOption {
        type = lib.types.str;
        default = "#ff8000";
        description = "The color of keywords";
      };
      functions = lib.mkOption {
        type = lib.types.str;
        default = "#0000ff";
        description = "The color of functions";
      };
      properties = lib.mkOption {
        type = lib.types.str;
        default = "#ff00ff";
        description = "The color of properties";
      };
      constants = lib.mkOption {
        type = lib.types.str;
        default = "#ff00ff";
        description = "The color of constants";
      };
      strings = lib.mkOption {
        type = lib.types.str;
        default = "#00ff00";
        description = "The color of variables";
      };
      numbers = lib.mkOption {
        type = lib.types.str;
        default = "#00ffff";
        description = "The color of numbers";
      };
      structures = lib.mkOption {
        type = lib.types.str;
        default = "#ffff00";
        description = "The color of structures";
      };
      types = lib.mkOption {
        type = lib.types.str;
        default = "#00ffff";
        description = "The color of types";
      };
    };
    ansi = {
      blend = lib.mkOption {
        type = lib.types.bool;
        default = true;
        description = "Blend the colors";
      };
      red = lib.mkOption {
        type = lib.types.str;
        default = "#ff0000";
        description = "The color of red";
      };
      green = lib.mkOption {
        type = lib.types.str;
        default = "#00ff00";
        description = "The color of green";
      };
      yellow = lib.mkOption {
        type = lib.types.str;
        default = "#ffff00";
        description = "The color of yellow";
      };
      orange = lib.mkOption {
        type = lib.types.str;
        default = "#ff8000";
        description = "The color of orange";
      };
      blue = lib.mkOption {
        type = lib.types.str;
        default = "#0000ff";
        description = "The color of blue";
      };
      magenta = lib.mkOption {
        type = lib.types.str;
        default = "#ff00ff";
        description = "The color of magenta";
      };
      cyan = lib.mkOption {
        type = lib.types.str;
        default = "#00ffff";
        description = "The color of cyan";
      };
    };
  };

  config =
    let
      theme-script = (
        pkgs.writeShellApplication {
          name = "theme";
          runtimeInputs = [
            pkgs.matugen
            pkgs.swww
            pkgs.zenity
            pkgs.sunwait
          ];
          text = ''
            WALLPAPER=${config.xdg.configHome}/matugen/wallpaper
            STATE=${config.xdg.stateHome}/md3-evo

            SCHEME=$(dconf read /org/gnome/desktop/interface/color-scheme)
            if [ "$SCHEME" = "'prefer-light'" ]; then
              MODE="light"
            else
              MODE="dark"
            fi

            if [ ! -d "$STATE" ]; then
              mkdir -p "$STATE"
            fi
            if [ -f "$STATE/mode" ]; then
              MODE=$(cat "$STATE/mode")
            fi

            if [ $# -eq 0 ]; then
              echo -e "\033[1mUsage:\033[0m mode|light|dark|auto|toggle|wallpaper"
              exit 1
            elif [ "$1" = "mode" ]; then
              echo -e "$MODE"
              exit 0
            elif [ "$1" = "wallpaper" ]; then
              if [ $# -eq 1 ]; then
                PICKED=$(zenity --file-selection --file-filter='Images | *.png *.jpg *.jpeg *.svg *.bmp *.gif')
                cp "$PICKED" "$WALLPAPER"
              else
                cp "$2" "$WALLPAPER"
              fi
            elif [ "$1" = "toggle" ]; then
              if [ "$MODE" = "light" ]; then
                MODE="dark"
              else
                MODE="light"
              fi
              echo "$MODE" > "$STATE/mode"
            elif [ "$1" = "light" ] || [ "$1" = "dark" ] || [ "$1" == "auto" ]; then
              MODE="$1"
              echo "$MODE" > "$STATE/mode"
            elif [ "$1" = "init" ]; then
              echo -e "\033[1mSetting up matugen\033[0m"
            else
              echo -e "\033[31mInvalid argument\033[0m"
              exit 1
            fi

            if [ ! -f $WALLPAPER ]; then
              echo -e "\033[31,1mNo wallpaper set\033[0m"
              exit 1
            fi

            THEME_SERVICE_PATH="${config.xdg.configHome}/systemd/user/theme-init.timer"
            if [ "$MODE" = "auto" ]; then
              TIME=$(sunwait poll ${builtins.toString cfg.auto-dark.lat}N ${builtins.toString cfg.auto-dark.lon}E || :)
              if [ "$TIME" = "DAY" ]; then
                MODE="light"
                NEXT=6
              else
                MODE="dark"
                NEXT=4
              fi
              NEXT=$(sunwait report ${builtins.toString cfg.auto-dark.lat}N ${builtins.toString cfg.auto-dark.lon}E | awk "/Daylight:/ {print \$$NEXT}")
              cat <<EOF | tee "$THEME_SERVICE_PATH" > /dev/null
            [Unit]
            Description=Next theme change timer

            [Timer]
            OnCalendar=*-*-* $(date -d "$NEXT today + 5 minutes" +'%H:%M'):00
            AccuracySec=1min

            [Install]
            WantedBy=timers.target
            EOF
            else
              rm -f "$THEME_SERVICE_PATH"
            fi
            systemctl --user daemon-reload &> /dev/null || :
            systemctl --user restart theme-init.timer &> /dev/null || :

            if command -v niri &> /dev/null; then
              niri msg action do-screen-transition --delay-ms 500
            fi

            if [ "$MODE" = "light" ]; then
             GTK_THEME="adw-gtk3"
            else
             GTK_THEME="adw-gtk3-dark"
            fi

            matugen image "$WALLPAPER" --type scheme-${cfg.flavour} --contrast ${builtins.toString cfg.contrast} --mode "$MODE"

            dconf write /org/gnome/desktop/interface/gtk-theme "'$GTK_THEME'"
            dconf write /org/gnome/desktop/interface/color-scheme "'prefer-$MODE'"
            dconf write /org/gnome/desktop/interface/icon-theme "'Adwaita'"
          '';
        }
      );
    in
    lib.mkIf cfg.enable {
      home.packages = [
        pkgs.adw-gtk3
        pkgs.swww
        theme-script
      ];

      gtk = {
        gtk3.extraCss = # css
          "@import './theme.css';";
        gtk4.extraCss = # css
          "@import './theme.css';";
        theme = {
          name = "Adwaita";
        };
        iconTheme = {
          name = "Tela";
          package = pkgs.tela-icon-theme;
        };
      };
      qt.platformTheme.name = "qtct";

      systemd.user.services = {
        swww-daemon = {
          Unit = {
            Description = "Swww Daemon";
            After = [ "graphical-session.target" ];
          };
          Install.WantedBy = [ "graphical-session.target" ];
          Service = {
            ExecStart = "${pkgs.swww}/bin/swww-daemon";
            Restart = "always";
          };
        };
        theme-init = {
          Unit = {
            Description = "MD3 Evo Theme Init";
            After = [
              "graphical-session.target"
              "swww-daemon.service"
            ];
          };
          Install.WantedBy = [ "graphical-session.target" ];
          Service = {
            ExecStart = "${lib.getExe theme-script} init";
            Restart = "on-failure";
          };
        };
      };

      wayland.windowManager.hyprland = {
        settings = {
          windowrulev2 = [ "float,class:^(zenity)$" ];
          decoration = {
            inactive_opacity = 0.8;
            shadow = {
              enabled = true;
              range = 32;
              render_power = 8;
              color = "rgba(000000aa)";
              color_inactive = "rgba(00000011)";
            };
          };
          animations = {
            enabled = "yes";
            bezier = [
              "expoOut, 0.16, 1, 0.3, 1"
            ];
            animation = [
              "windowsIn, 1, 5, expoOut, slide bottom"
              "windows, 1, 5, expoOut, slide"
              "windowsOut, 1, 5, expoOut, slide bottom"
              "border, 1, 10, default"
              "fade, 1, 7, default"
              "fadeShadow, 1, 10, default"
              "fadeDim, 1, 10, default"
              "workspaces, 1, 6, default"
            ];
          };
        };
        extraConfig = ''
          source=./theme.conf
        '';
      };

      # TODO: include is coming in the next release
      /*
        xdg.configFile.niri-config = {
          enable = osConfig.programs.niri.enable;
          target = "niri/override.kdl";
          text = ''
            include "${config.xdg.configHome}/niri/config.kdl"
            include "${config.programs.matugen.settings.templates.niri.output_path}"
          '';
        };
        home.sessionVariables.NIRI_CONFIG = "${config.xdg.configHome}/niri/override.kdl";
      */

      programs = {
        kitty.extraConfig = ''
          include ${config.programs.matugen.settings.templates.kitty.output_path}
        '';

        nixvim = {
          opts.termguicolors = true;
          colorscheme = "md3-evo";
          autoCmd = [
            {
              event = [ "Signal" ];
              pattern = [ "SIGUSR1" ];
              command = # vim
                "colorscheme md3-evo";
              nested = true;
            }
          ];
          plugins.lualine.settings.options.theme.__raw = # lua
            "function() return vim.g.lualine_theme end";
        };
        matugen = {
          enable = true;
          settings = {
            config = {
              version_check = false;

              reload_apps_list = {
                waybar = config.programs.waybar.enable;
                dunst = config.services.dunst.enable;
              };

              wallpaper.command = lib.getExe pkgs.swww;

              custom_colors =
                let
                  mkColor = category: color: {
                    inherit (cfg.${category}) blend;
                    color = cfg.${category}.${color};
                  };
                in
                {
                  red = mkColor "ansi" "red";
                  green = mkColor "ansi" "green";
                  yellow = mkColor "ansi" "yellow";
                  orange = mkColor "ansi" "orange";
                  blue = mkColor "ansi" "blue";
                  magenta = mkColor "ansi" "magenta";
                  cyan = mkColor "ansi" "cyan";

                  keywords = mkColor "syntax" "keywords";
                  functions = mkColor "syntax" "functions";
                  constants = mkColor "syntax" "constants";
                  properties = mkColor "syntax" "properties";
                  strings = mkColor "syntax" "strings";
                  numbers = mkColor "syntax" "numbers";
                  structures = mkColor "syntax" "structures";
                  types = mkColor "syntax" "types";

                  danger = mkColor "semantic" "danger";
                  warning = mkColor "semantic" "warning";
                  success = mkColor "semantic" "success";
                  info = mkColor "semantic" "info";
                };

              custom_keywords = {
                inherit (cfg) flavour;
                padding = builtins.toString cfg.padding;
                double_padding = builtins.toString (cfg.padding * 2);
                radius = builtins.toString cfg.radius;
                transparency = builtins.toString cfg.transparency;
                blur = builtins.toString cfg.blur;
                contrast = builtins.toString cfg.contrast;
                transparency_hex =
                  let
                    zeroPad = hex: if builtins.stringLength hex == 1 then "0${hex}" else hex;
                  in
                  zeroPad (lib.trivial.toHexString (builtins.floor (cfg.transparency * 255)));
              };
            };

            templates =
              let
                gtk = pkgs.writeText "gtk4.css" (import ./gtk.nix);
                signal =
                  name: signal:
                  (pkgs.writeShellScript "kill-${name}" ''
                    pkill -${signal} -u "$USER" -x ${name} || :
                  '');
              in
              {
                nvim = {
                  input_path = ./nvim.vim;
                  output_path = "${config.xdg.configHome}/nvim/colors/md3-evo.vim";
                  post_hook = signal "nvim" "USR1";
                };
                gtk3 = {
                  input_path = gtk;
                  output_path = "${config.xdg.configHome}/gtk-3.0/theme.css";
                };
                gtk4 = {
                  input_path = gtk;
                  output_path = "${config.xdg.configHome}/gtk-4.0/theme.css";
                };
                vesktop = {
                  input_path = ./discord.css;
                  output_path = "${config.xdg.configHome}/vesktop/themes/matugen.theme.css";
                };
              }
              // (lib.optionalAttrs osConfig.programs.niri.enable {
                niri = {
                  input_path = ./niri.kdl;
                  output_path = "${config.xdg.configHome}/niri/md3-evo.kdl";
                };
              })
              // (lib.optionalAttrs config.programs.kitty.enable {
                kitty = {
                  input_path = ./kitty.conf;
                  output_path = "${config.xdg.configHome}/kitty/theme.conf";
                  post_hook = signal ".kitty-wrapped" "USR1";
                };
              })
              // (lib.optionalAttrs osConfig.programs.hyprland.enable {
                hyprland = {
                  input_path = ./hyprland.conf;
                  output_path = "${config.xdg.configHome}/hypr/theme.conf";
                  post_hook = pkgs.writeShellScript "reload-hyprland-theme" ''
                    if command -v hyprctl &> /dev/null; then
                      hyprctl reload
                    fi
                  '';
                };
              })
              // (lib.optionalAttrs config.programs.waybar.enable {
                waybar = {
                  input_path = ./waybar.css;
                  output_path = "${config.xdg.configHome}/waybar/style.css";
                  post_hook = signal "waybar" "USR2";
                };
              })
              // (lib.optionalAttrs config.services.swaync.enable {
                swaync = {
                  input_path = ./swaync.css;
                  output_path = "${config.xdg.configHome}/swaync/style.css";
                  post_hook = pkgs.writeShellScript "reload-swaync" ''
                    if which swaync-client; then
                      swaync-client --reload-css
                    fi
                  '';
                };
              });
          };
        };
      };
    };
}
