{ config, lib, pkgs, ... }:
let
  cfg = config.services.bluemap;
  format = pkgs.formats.hocon { };

  coreConfig = format.generate "core.conf" cfg.coreSettings;
  webappConfig = format.generate "webapp.conf" cfg.webappSettings;
  webserverConfig = format.generate "webserver.conf" cfg.webserverSettings;

  storageFolder = pkgs.linkFarm "storage"
    (lib.attrsets.mapAttrs' (name: value:
      lib.nameValuePair "${name}.conf"
        (format.generate "${name}.conf" value))
      cfg.storage);

  mapsFolder = pkgs.linkFarm "maps"
    (lib.attrsets.mapAttrs' (name: value:
      lib.nameValuePair "${name}.conf"
        (format.generate "${name}.conf" value.settings))
      cfg.maps);

  webappConfigFolder = pkgs.linkFarm "bluemap-config" {
    "maps" = mapsFolder;
    "storages" = storageFolder;
    "core.conf" = coreConfig;
    "webapp.conf" = webappConfig;
    "webserver.conf" = webserverConfig;
    "packs" = cfg.resourcepacks;
    "addons" = cfg.resourcepacks; # TODO
  };

  renderConfigFolder = name: value: pkgs.linkFarm "bluemap-${name}-config" {
    "maps" = pkgs.linkFarm "maps" {
      "${name}.conf" = (format.generate "${name}.conf" value.settings);
    };
    "storages" = storageFolder;
    "core.conf" = coreConfig;
    "webapp.conf" = format.generate "webapp.conf" (cfg.webappSettings // { "update-settings-file" = false; });
    "webserver.conf" = webserverConfig;
    "packs" = value.resourcepacks;
    "addons" = cfg.resourcepacks; # TODO
  };

  inherit (lib) mkOption;
in {
  options.services.bluemap = {
    enable = lib.mkEnableOption "bluemap";

    eula = mkOption {
      type = lib.types.bool;
      description = ''
        By changing this option to true you confirm that you own a copy of minecraft Java Edition,
        and that you agree to minecrafts EULA.
      '';
      default = false;
    };

    defaultWorld = mkOption {
      type = lib.types.path;
      description = ''
        The world used by the default map ruleset.
        If you configure your own maps you do not need to set this.
      '';
      example = lib.literalExpression "\${config.services.minecraft.dataDir}/world";
    };

    enableRender = mkOption {
      type = lib.types.bool;
      description = "Enable rendering";
      default = true;
    };

    webRoot = mkOption {
      type = lib.types.path;
      default = "/var/lib/bluemap/web";
      description = "The directory for saving and serving the webapp and the maps";
    };

    enableNginx = mkOption {
      type = lib.types.bool;
      default = true;
      description = "Enable configuring a virtualHost for serving the bluemap webapp";
    };

    host = mkOption {
      type = lib.types.str;
      default = "bluemap.${config.networking.domain}";
      defaultText = lib.literalExpression "bluemap.\${config.networking.domain}";
      description = "Domain to configure nginx for";
    };

    onCalendar = mkOption {
      type = lib.types.str;
      description = ''
        How often to trigger rendering the map,
        in the format of a systemd timer onCalendar configuration.
        See {manpage}`systemd.timer(5)`.
      '';
      default = "*-*-* 03:10:00";
    };

    coreSettings = mkOption {
      type = lib.types.submodule {
        freeformType = format.type;
        options = {
          data = mkOption {
            type = lib.types.path;
            description = "Folder for where bluemap stores its data";
            default = "/var/lib/bluemap";
          };
          metrics = lib.mkEnableOption "Sending usage metrics containing the version of bluemap in use";
        };
      };
      description = "Settings for the core.conf file, [see upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/core.conf).";
    };

    webappSettings = mkOption {
      type = lib.types.submodule {
        freeformType = format.type;
      };
      default = {
        enabled = true;
        webroot = cfg.webRoot;
      };
      defaultText = lib.literalExpression ''
        {
          enabled = true;
          webroot = config.services.bluemap.webRoot;
        }
      '';
      description = "Settings for the webapp.conf file, see [upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/webapp.conf).";
    };

    webserverSettings = mkOption {
      type = lib.types.submodule {
        freeformType = format.type;
        options = {
          enabled = mkOption {
            type = lib.types.bool;
            description = ''
              Enable bluemap's built-in webserver.
              Disabled by default in nixos for use of nginx directly.
            '';
            default = false;
          };
        };
      };
      default = { };
      description = ''
        Settings for the webserver.conf file, usually not required.
        [See upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/webserver.conf).
      '';
    };

    maps = mkOption {
      type = lib.types.attrsOf (lib.types.submodule {
        options = {
          resourcepacks = mkOption {
            type = lib.types.path;
            default = cfg.resourcepacks;
            defaultText = lib.literalExpression "config.services.bluemap.resourcepacks";
            description = "A set of resourcepacks/mods to extract models from loaded in alphabetical order";
          };
          settings = mkOption {
            type = (lib.types.submodule {
              freeformType = format.type;
              options = {
                world = mkOption {
                  type = lib.types.path;
                  description = "Path to world folder containing the dimension to render";
                };
              };
            });
            description = ''
              Settings for files in `maps/`.
              See the default for an example with good options for the different world types.
              For valid values [consult upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/maps/map.conf).
            '';
          };
        };
      });
      default = {
        "overworld".settings = {
          world = "${cfg.defaultWorld}";
          ambient-light = 0.1;
          cave-detection-ocean-floor = -5;
        };

        "nether".settings = {
          world = "${cfg.defaultWorld}/DIM-1";
          sorting = 100;
          sky-color = "#290000";
          void-color = "#150000";
          ambient-light = 0.6;
          world-sky-light = 0;
          remove-caves-below-y = -10000;
          cave-detection-ocean-floor = -5;
          cave-detection-uses-block-light = true;
          max-y = 90;
        };

        "end".settings = {
          world = "${cfg.defaultWorld}/DIM1";
          sorting = 200;
          sky-color = "#080010";
          void-color = "#080010";
          ambient-light = 0.6;
          world-sky-light = 0;
          remove-caves-below-y = -10000;
          cave-detection-ocean-floor = -5;
        };
      };
      defaultText = lib.literalExpression ''
        {
          "overworld".settings = {
            world = "''${cfg.defaultWorld}";
            ambient-light = 0.1;
            cave-detection-ocean-floor = -5;
          };

          "nether".settings = {
            world = "''${cfg.defaultWorld}/DIM-1";
            sorting = 100;
            sky-color = "#290000";
            void-color = "#150000";
            ambient-light = 0.6;
            world-sky-light = 0;
            remove-caves-below-y = -10000;
            cave-detection-ocean-floor = -5;
            cave-detection-uses-block-light = true;
            max-y = 90;
          };

          "end".settings = {
            world = "''${cfg.defaultWorld}/DIM1";
            sorting = 200;
            sky-color = "#080010";
            void-color = "#080010";
            ambient-light = 0.6;
            world-sky-light = 0;
            remove-caves-below-y = -10000;
            cave-detection-ocean-floor = -5;
          };
        };
      '';
      description = ''
        map-specific configuration.
        These correspond to views in the webapp and are usually
        different dimension of a world or different render settings of the same dimension.
        If you set anything in this option you must configure all dimensions yourself!
      '';
    };

    storage = mkOption {
      type = lib.types.attrsOf (lib.types.submodule {
        freeformType = format.type;
        options = {
          storage-type = mkOption {
            type = lib.types.enum [ "FILE" "SQL" ];
            description = "Type of storage config";
            default = "FILE";
          };
        };
      });
      description = ''
        Where the rendered map will be stored.
        Unless you are doing something advanced you should probably leave this alone and configure webRoot instead.
        [See upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/tree/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages)
      '';
      default = {
        "file" = {
          root = "${cfg.webRoot}/maps";
        };
      };
      defaultText = lib.literalExpression ''
        {
          "file" = {
            root = "''${config.services.bluemap.webRoot}/maps";
          };
        }
      '';
    };

    resourcepacks = mkOption {
      type = lib.types.path;
      default = pkgs.linkFarm "resourcepacks" { };
      description = ''
        A set of resourcepacks/mods to extract models from loaded in alphabetical order.
        Can be overriden on a per-map basis with `services.bluemap.maps.<name>.resourcepacks`.
      '';
    };
  };


  config = lib.mkIf cfg.enable {
    assertions =
      [ { assertion = config.services.bluemap.eula;
          message = ''
            You have enabled bluemap but have not accepted minecraft's EULA.
            You can achieve this through setting `services.bluemap.eula = true`
          '';
        }
      ];

    services.bluemap.coreSettings.accept-download = cfg.eula;

    systemd.services."render-bluemap-maps" = lib.mkIf cfg.enableRender {
      serviceConfig = {
        Type = "oneshot";
        Group = "nginx";
        UMask = "026";
      };
      script = lib.strings.concatStringsSep "\n" ((lib.attrsets.mapAttrsToList
        (name: value: "${lib.getExe pkgs.bluemap} -c ${renderConfigFolder name value} -r")
        cfg.maps) ++ [ "${lib.getExe pkgs.bluemap} -c ${webappConfigFolder} -gs" ]);
    };

    systemd.timers."render-bluemap-maps" = lib.mkIf cfg.enableRender {
      wantedBy = [ "timers.target" ];
      timerConfig = {
        OnCalendar = cfg.onCalendar;
        Persistent = true;
        Unit = "render-bluemap-maps.service";
      };
    };

    services.nginx.virtualHosts = lib.mkIf cfg.enableNginx {
      "${cfg.host}" = {
        root = config.services.bluemap.webRoot;
        locations = {
          "~* ^/maps/[^/]*/tiles/".extraConfig = ''
            error_page 404 = @empty;
          '';
          "@empty".return = "204";
        };
      };
    };
  };

  meta = {
    maintainers = with lib.maintainers; [ dandellion h7x4 ];
  };
}