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

with lib;
let
  cfg = config.my.programs.nvim;
  boolToString = bool: if bool then "true" else "false";
  quote = str: ''"${toString str}"'';
  listToString = list: ''{ ${concatStringsSep ", " (map quote list)} }'';
  keybinding =
    { key
    , cmd
    , func
    , mode
    , desc
    }:
    let
      cmdString =
        if cmd != null
        then quote cmd
        else
          (
            if func != null
            then func
            else abort "Either cmd or function must be set"
          );
    in
    ''{ ${quote key}, ${cmdString}, mode = ${quote mode}, ${optionalString (desc != null) "desc = ${quote desc},"} }'';
  lazySpecFromPlugin =
    { plugin
    , dependencies
    , init
    , conf
    , lazy
    , event
    , enabled
    , cmd
    , ft
    , priority
    , keys
    }:
    ''
      {
        dir = "${plugin}",
        name = "${plugin.name}",
        lazy = ${boolToString lazy},
        enabled = ${boolToString enabled},
        dependencies = { ${concatStringsSep ", " (map lazySpecFromPlugin dependencies)} },
        ${optionalString (init != null)
        "init = function(plugin)
          ${toString init}
        end,"
        }
        ${optionalString (conf != null)
        "config = function(plugin, opts)
          ${toString conf}
        end,"
        }
        keys = { ${concatStringsSep ",\n" (map keybinding keys)} },
        event = ${listToString event},
        cmd = ${listToString cmd},
        ft = ${listToString ft},
        priority = ${toString priority},
      }
    '';
  lazySpecs = concatStringsSep ", " (map lazySpecFromPlugin cfg.plugins);
  lazy = ''
    require("lazy").setup({
      ${lazySpecs}
    })
  '';
in
{
  imports = [ ./plugins ];

  options.my.programs.nvim = {
    enable = mkEnableOption "nvim";
    plugins = mkOption {
      default = [ ];
      description = ''
        List of plugins with config.
      '';
      type = with types; listOf (
        let
          sub = submodule {
            options = {
              conf = mkOption {
                type = nullOr str;
                default = null;
                description = ''
                  Lua code to be executed when the plugin is loaded.
                '';
              };
              dependencies = mkOption {
                type = listOf sub;
                default = [ ];
                description = ''
                  List of plugins this plugin depends on.
                '';
              };
              init = mkOption {
                type = nullOr str;
                default = null;
                description = ''
                  Lua code to be executed when the plugin is initialized.
                '';
              };
              event = mkOption {
                type = listOf str;
                default = [ ];
                description = ''
                  Event to load the plugin on.
                '';
              };
              lazy = mkOption {
                type = bool;
                default = false;
                description = ''
                  Whether to load the plugin lazily.
                '';
              };
              plugin = mkOption {
                type = package;
                description = ''
                  The plugin package.
                '';
              };
              enabled = mkOption {
                type = bool;
                default = true;
                description = ''
                  Whether to enable the plugin.
                '';
              };
              cmd = mkOption {
                type = listOf str;
                default = [ ];
                description = ''
                  Command to load the plugin.
                '';
              };
              ft = mkOption {
                type = listOf str;
                default = [ ];
                description = ''
                  Filetype to load the plugin on.
                '';
              };
              priority = mkOption {
                type = int;
                default = 50;
                description = ''
                  Priority to load the plugin.
                '';
              };
              keys = mkOption {
                default = [ ];
                description = ''
                  List of keybindings.
                '';
                type = listOf (submodule {
                  options = {
                    key = mkOption {
                      type = str;
                      description = ''
                        Key to bind.
                      '';
                    };
                    cmd = mkOption {
                      type = nullOr str;
                      default = null;
                      description = ''
                        Command to execute.
                      '';
                    };
                    func = mkOption {
                      type = nullOr str;
                      default = null;
                      description = ''
                        Function to execute.
                      '';
                    };
                    mode = mkOption {
                      type = str;
                      default = "n";
                      description = ''
                        Mode to bind the key in.
                      '';
                    };
                    desc = mkOption {
                      type = nullOr str;
                      default = null;
                      description = ''
                        Description of the keybinding.
                      '';
                    };
                  };
                });
              };
            };
          };
        in
        sub
      );
    };
  };


  config = mkIf cfg.enable {
    home-manager.users.moritz = {
      home.packages = with pkgs; [
        (
          if config.my.programs.hyprland.enable
          then neovide-hyprland
          else neovide
        )
      ];

      programs.neovim = {
        enable = true;
        package = pkgs.neovim-nightly;
        vimAlias = true;
        vimdiffAlias = true;
        withNodeJs = true;
        withPython3 = true;
        extraLuaConfig = lib.concatLines [ (builtins.readFile ./options.lua) lazy ];
        extraPackages = with pkgs; [
          alejandra
          black
          deadnix
          isort
          jq
          nil
          nixpkgs-fmt
          nodePackages.bash-language-server
          nodePackages.cspell
          rustfmt
          shellcheck
          shfmt
          statix
          stylua
          sumneko-lua-language-server
          taplo
          yamlfmt
        ];
        plugins = with pkgs.vimPlugins; [
          lazy-nvim
          nvim-treesitter.withAllGrammars
        ];
      };
    };
  };
}
