{ config, lib, ... }:

let
  cfg = config.disko.disks;
in
{
  options.disko.disks = lib.mkOption {
    description = lib.mdDoc "Disk names to format.";
    type = with lib.types; listOf (submodule (_: {
      options = {
        device = lib.mkOption {
          type = lib.types.str;
          default = null;
          example = "/dev/sda";
          description = "Path of the disk.";
        };

        name = lib.mkOption {
          type = lib.types.str;
          default = null;
          example = "ssd0";
          description = "Name of the disk.";
        };

        partitionTableFormat = lib.mkOption {
          type = lib.types.enum [ "gpt" "msdos" ];
          default = "gpt";
          description = "Which parition table format to use.";
        };

        withBoot = lib.mkOption {
          type = lib.types.bool;
          default = true;
          description = "Wether to include a boot partition.";
        };

        withCeph = lib.mkOption {
          type = lib.types.bool;
          default = false;
          description = "Wether to include a ceph partition.";
        };

        withLuks = lib.mkOption {
          type = lib.types.bool;
          default = true;
          description = "Wether to encrypt the paritions.";
        };

        withZfs = lib.mkOption {
          type = lib.types.bool;
          default = true;
          description = "Wether to include a zfs parition.";
        };
      };
    }));
    default = [ ];
  };

  config = {
    assertions = lib.mkIf (cfg != [ ]) (lib.head (map
      (disk: [
        {
          assertion = disk.withCeph || disk.withZfs;
          message = "Must enable ceph or zfs!";
        }
        {
          assertion = disk.withCeph -> disk.withLuks;
          message = "Ceph requires Luks!";
        }
      ])
      cfg));

    disko = {
      devices = lib.mkIf (cfg != [ ]) (lib.head (map
        (disk:
          let
            diskName = if disk.name != "" then "-${disk.name}" else "";
            luksName = "crypt-${config.networking.hostName}${diskName}";
            zfs = {
              size = "100%FREE";
              content = {
                pool = zfsName;
                type = "zfs";
              };
            };
            zfsName = "${config.networking.hostName}${diskName}";
          in
          {
            disk.${disk.device} = {
              inherit (disk) device;
              type = "disk";
              content = {
                type = "table";
                format = disk.partitionTableFormat;
                partitions = lib.optional disk.withZfs {
                  name = "ESP";
                  start = "1MiB";
                  end = "512MiB";
                  bootable = true;
                  content = {
                    type = "filesystem";
                    format = "vfat";
                    mountpoint = "/boot";
                  };
                } ++ [
                  {
                    name = "root";
                    start = if disk.withZfs then "512MiB" else "1MiB";
                    end = "100%";
                    part-type = "primary";
                    content = lib.optionalAttrs disk.withLuks {
                      type = "luks";
                      name = luksName;
                      askPassword = true;
                      inherit (zfs) content;
                    } // lib.optionalAttrs (!disk.withLuks) zfs.content;
                  }
                ];
              };
            };
          } // {
            zpool.${zfsName} = {
              type = "zpool";
              # -O
              rootFsOptions = {
                acltype = "posixacl";
                compression = "zstd";
                dnodesize = "auto";
                normalization = "formD";
                xattr = "sa";
              };
              # -o
              options = {
                ashift = "12";
                autotrim = "on";
              };
              datasets =
                let
                  dataset = mountpoint: {
                    inherit mountpoint;
                    options = {
                      canmount = "on";
                      inherit mountpoint;
                    };
                    type = "zfs_fs";
                  };

                  datasetNoMount = {
                    mountpoint = null;
                    options = {
                      canmount = "off";
                      mountpoint = "none";
                    };
                    type = "zfs_fs";
                  };
                in
                {
                  "root" = dataset "/";
                  "data" = datasetNoMount;
                  # used by services.postgresqlBackup and later by restic
                  "data/backup" = dataset "/var/backup";
                  "data/etc" = dataset "/etc";
                  "data/lib" = dataset "/var/lib";
                  "home" = dataset "/home";
                  "nix" = lib.recursiveUpdate (dataset "/nix") {
                    options.atime = "off";
                  };
                  "nix/store" = dataset "/nix/store";
                  "nix/var" = dataset "/nix/var";
                  # zfs uses copy on write and requires some free space to delete files when the disk is completely filled
                  "reserved" = lib.recursiveUpdate (dataset "reserved") {
                    mountpoint = null;
                    options = {
                      canmount = "off";
                      mountpoint = "none";
                      reservation = "5GiB";
                    };
                    type = "zfs_fs";
                  };
                };
            };
          })
        cfg));

      # we do not want changes to this module render machines unbootable
      enableConfig = false;
    };
  };
}
