{ config, lib, ... }:

let
  cfg = config.disko;
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.";
        };

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

        withCeph = lib.mkOption {
          type = lib.types.bool;
          default = true;
          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.disks != [ ]) (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.disks));

    disko.devices = lib.mkIf (cfg.disks != [ ]) (lib.head (map
      (disk:
        let
          diskName = if disk.name != "" then "-${disk.name}" else "";
          luksName = "crypt-${config.networking.hostName}${diskName}";
          rootSize = 200; # size of the zfs partition if inside of lvm
          vgName = "lvm-${config.networking.hostName}${diskName}";
          zfs = {
            size = if (!disk.withCeph) then "100%FREE" else "${toString rootSize}GiB";
            content = {
              pool = zfsName;
              type = "zfs";
            };
          };
          zfsName = "${config.networking.hostName}${diskName}";
        in
        {
          disk.${disk.device} = {
            inherit (disk) device;
            type = "disk";
            content = {
              type = "table";
              format = "gpt";
              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;
                      # trim potential new lines to not have them in the password
                      keyFile = "tr -d '\n' </$PWD/keyFile";
                      content = {
                        type = "lvm_pv";
                        vg = vgName;
                      };
                    } // lib.optionalAttrs (!disk.withLuks) zfs.content;
                }
              ];
            };
          };
        } // lib.optionalAttrs disk.withLuks {
          lvm_vg.${vgName} = {
            type = "lvm_vg";
            lvs = lib.optionalAttrs disk.withCeph
              {
                ceph.size = "100%FREE";
              } // lib.optionalAttrs disk.withZfs { inherit zfs; };
          };
        } // {
          zpool.${zfsName} = {
            type = "zpool";
            rootFsOptions.acltype = "posixacl";
            options = {
              ashift = "12";
              autotrim = "on";
            };
            datasets =
              let
                dataset = mountpoint: {
                  inherit mountpoint;
                  options = {
                    canmount = "on";
                    compression = "zstd";
                    dnodesize = "auto";
                    normalization = "formD";
                    xattr = "sa";
                    inherit mountpoint;
                  };
                  type = "zfs_fs";
                };
              in
              {
                "data" = dataset "/";
                "data/etc" = dataset "/etc";
                "data/home" = dataset "/home";
                "data/var" = dataset "/var";
                # used by services.postgresqlBackup and later by restic
                "data/var/backup" = dataset "/var/backup";
                "data/var/lib" = dataset "/var/lib";
                "data/var/log" = dataset "/var/log";
                "nixos" = lib.recursiveUpdate (dataset "nixos") {
                  mountpoint = null;
                  options = {
                    canmount = "off";
                    mountpoint = "none";
                  };
                  type = "zfs_fs";
                };
                "nixos/nix" = dataset "/nix";
                "nixos/nix/store" = lib.recursiveUpdate (dataset "/nix/store") {
                  options.atime = "off";
                };
                "nixos/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.disks));
  };
}
