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

with lib;
let
  cfg = config.theming;

  # Font module type
  fontModule = types.submodule {
    options = {
      name = mkOption {
        type = types.str;
        description = "Font family name.";
      };
      package = mkOption {
        type = types.anything;
        description = "Font package";
      };
      recommendedSize = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "Recommended size for displaying this font.";
      };
      fallbackFonts = mkOption {
        type = types.listOf types.str;
        default = [ ];
        description = "Fallback fonts for specified font.";
      };
    };
  };

  fontModules = [
    # Import all fonts
    ./fonts/cozette-vector.nix
    ./fonts/cozette.nix
    ./fonts/dejavu-sans.nix
    ./fonts/dejavu-serif.nix
    ./fonts/dina.nix
    ./fonts/fira-code.nix
    ./fonts/nerd-fonts-symbols.nix
    ./fonts/noto-color-emoji.nix
    ./fonts/wqy-bitmapsong.nix
    ./fonts/wqy-microhei-mono.nix
    ./fonts/wqy-microhei.nix
    ./fonts/wqy-zenhei-mono.nix
    ./fonts/wqy-zenhei-sharp.nix
    ./fonts/wqy-zenhei.nix
  ];

  # Gather enabled fonts.
  enabledFonts = [
    cfg.fonts.serif.name
    cfg.fonts.sansSerif.name
    cfg.fonts.monospace.name
    cfg.fonts.emoji.name
  ] ++ map (font: font.name) cfg.fonts.extraFonts;

  # Flatten dependencies of fonts
  fontPackages =
    converge
      (
        fonts:
        listToAttrs (
          map
            (font: {
              name = font;
              value = true;
            })
            (
              flatten (map (font: [ font.name ] ++ cfg.fonts.pkgs.${font.name}.fallbackFonts) (attrsToList fonts))
            )
        )
      )
      (
        listToAttrs (
          map (font: {
            name = font;
            value = true;
          }) enabledFonts
        )
      );

  # Convert set of fonts to list of packages
  fontNameList = map (font: font.name) (attrsToList fontPackages);
  fontPackageList = map (font: cfg.fonts.pkgs.${font}.package) fontNameList;
in
{
  imports = [
    # Import all themes
    ./themes/catppuccin.nix
    ./themes/gruvbox.nix
    ./themes/oxocarbon.nix
    ./themes/papercolor.nix
    ./themes/sakura.nix
    ./themes/nord.nix
  ];

  options.modules.theming.enable = mkEnableOption "theming";

  options.theming =
    let
      colors = config.theming.schemeColors;
    in
    {
      darkMode = mkOption {
        type = types.bool;
        default = false;
        example = true;
        description = "Whether the app should use dark mode.";
      };

      colorScheme = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Base 16 color scheme to use for styling. See stylix documentation for more information.";
      };

      clientSideDecorations = mkOption {
        type = types.bool;
        default = false;
        description = "Whether to enable client side decorations for windows.";
      };

      schemeColors = mkOption {
        type = types.attrsOf types.anything;
        default = config.lib.stylix.colors;
        description = "Generated colors from scheme";
      };

      colors = {
        bg = mkOption {
          type = types.str;
          default = colors.base00;
        };
        fg = mkOption {
          type = types.str;
          default = colors.base05;
        };
        bg-status = mkOption {
          type = types.str;
          default = colors.base01;
        };
        fg-status = mkOption {
          type = types.str;
          default = colors.base04;
        };
        bg-selection = mkOption {
          type = types.str;
          default = colors.base02;
        };
        bg-highlight = mkOption {
          type = types.str;
          default = colors.base03;
        };
        fg-search = mkOption {
          type = types.str;
          default = colors.base0A;
        };
        accent = mkOption {
          type = types.str;
          default = colors.base0E;
        };
        border-focused = mkOption {
          type = types.str;
          default = cfg.colors.fg;
        };
        border-unfocused = mkOption {
          type = types.str;
          default = cfg.colors.bg-selection;
        };
      };

      colorsCSS = mkOption {
        type = types.lines;
        default =
          ":root {\n"
          + concatStrings (
            map (color: "  --nix-color-${color.name}: #${color.value};\n") (attrsToList cfg.colors)
          )
          + "}\n\n";
        description = "Colors as css variables";
      };

      layout = {
        borderRadius = mkOption {
          type = types.int;
          default = 0;
          description = "Border radius of windows.";
        };

        borderSize = mkOption {
          type = types.int;
          default = 1;
          description = "Size of borders used throughout UI.";
        };

        windowPadding = mkOption {
          type = types.int;
          default = 2;
          description = "Margin of each window, actual space between windows will be twice this number.";
        };
      };

      fonts = {
        pkgs = mkOption {
          type = types.attrsOf fontModule;
          default = builtins.listToAttrs (
            map (module: {
              name = module.name;
              value = module;
            }) (map (module: (import module) { inherit lib config pkgs; }) fontModules)
          );
          description = "All available font modules.";
        };

        installed = mkOption {
          type = types.listOf types.str;
          default = fontNameList;
          description = "List of installed fonts.";
        };

        serif = mkOption {
          type = fontModule;
          description = "Default serif font";
        };

        sansSerif = mkOption {
          type = fontModule;
          description = "Default sansSerif font.";
        };

        monospace = mkOption {
          type = fontModule;
          description = "Default monospace font.";
        };

        emoji = mkOption {
          type = fontModule;
          description = "Default emoji font.";
        };

        interface = mkOption {
          type = fontModule;
          description = "Default emoji font.";
        };

        extraFonts = mkOption {
          type = types.listOf fontModule;
          default = [ ];
          description = "Additional fonts to install.";
        };
      };
    };

  config = mkIf config.modules.theming.enable {
    # Enable fontconfig
    modules.fontconfig.enable = true;

    # Install configured fonts
    home.packages = fontPackageList;

    # Configure gnome theme
    dconf.settings = {
      "org/gnome/desktop/interface" = {
        color-scheme = if cfg.darkMode then "prefer-dark" else "prefer-light";
      };
    };

    # Configure qt theme
    qt = {
      enable = true;
      platformTheme.name = "adwaita";
      style.name = if cfg.darkMode then "adwaita-dark" else "adwaita-light";
    };

    # Configure gtk theme
    gtk =
      let
        disableCSD = ''
          headerbar.default-decoration {
            margin-bottom: 50px;
            margin-top: -100px;
          }

          window.csd,
          window.csd decoration {
            box-shadow: none;
          }
        '';
      in
      {
        enable = true;

        theme = {
          name = if cfg.darkMode then "Adwaita-dark" else "Adwaita-light";
          package = pkgs.gnome-themes-extra;
        };

        gtk3.extraCss = mkIf (!cfg.clientSideDecorations) disableCSD;
        gtk4.extraCss = mkIf (!cfg.clientSideDecorations) disableCSD;
      };

    # TODO: This should just straight up not be here
    programs.direnv = {
      enable = true;
      nix-direnv.enable = true;
    };
    modules.git.ignores = [
      ".direnv"
    ];

    # TODO: Make cursors configurable using modules.
    home.pointerCursor = {
      gtk.enable = true;
      name = lib.mkForce "BreezeX-RosePine-Linux";
      package = lib.mkForce pkgs.rose-pine-cursor;
      size = lib.mkForce 24;
      x11 = {
        defaultCursor = lib.mkForce "BreezeX-RosePine-Linux";
        enable = true;
      };
    };

    # Enable stylix
    # TODO: Move to own module
    stylix = {
      enable = true;
      autoEnable = false;

      targets = {
        foot.enable = true;
        nixvim.enable = true;
        qutebrowser.enable = true;
        vscode.enable = true;
        zathura.enable = true;
      };

      base16Scheme = cfg.colorScheme;
      polarity = if cfg.darkMode then "dark" else "light";

      fonts = {
        serif = getAttrs [
          "name"
          "package"
        ] cfg.fonts.serif;
        sansSerif = getAttrs [
          "name"
          "package"
        ] cfg.fonts.sansSerif;
        monospace = getAttrs [
          "name"
          "package"
        ] cfg.fonts.monospace;
        emoji = getAttrs [
          "name"
          "package"
        ] cfg.fonts.emoji;

        sizes = {
          applications = mkDefault cfg.fonts.serif.recommendedSize;
          desktop = mkDefault cfg.fonts.monospace.recommendedSize; # TODO: See below
          popups = mkDefault cfg.fonts.monospace.recommendedSize; # TODO: Add dedicated UI font
          terminal = mkDefault cfg.fonts.monospace.recommendedSize;
        };
      };
    };
  };
}