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

let
  cfg = config.home.mutableFiles;
  file = baseDir: { config, name, ... }: {
    options = {
      url = lib.mkOption {
        type = lib.types.str;
        description = lib.mkDoc ''
          The URL of the file to be fetched.
        '';
        example = "https://github.com/foo-dogsquared/dotfiles.git";
      };

      path = lib.mkOption {
        type = lib.types.str;
        description = lib.mkDoc ''
          Output path of the resource relative to ${baseDir}.
        '';
        default = name;
        apply = p:
          if lib.hasPrefix "/" p then p else "${baseDir}/${p}";
      };

      type = lib.mkOption {
        type = lib.types.enum [ "git" "fetch" ];
        description = lib.mkDoc ''
          Type that configures the behavior for fetching the URL.

          This accept only certain keywords.

          - For `fetch`, the file will be fetched with `curl`.
          - For `git`, it will be fetched with `git clone`.

          The default type is `fetch`.
        '';
        default = "fetch";
        example = "git";
      };
    };
  };
in
{
  options.home.mutableFile = lib.mkOption {
    type = with lib.types; attrsOf (submodule (file config.home.homeDirectory));
    default = { };
    description = lib.mkDoc ''
      An attribute set of mutable files and directories to be fetched into the
      home directory.
    '';
    example = lib.literalExpression ''
      "''${config.xdg.userDirs.documents}/dotfiles" = {
        url = "https://github.com/foo-dogsquared/dotfiles.git";
        type = "git";
      };

      "''${config.xdg.userDirs.documents}/top-secret" = {
        url = "https://example.com/file.zip";
        type = "fetch";
      };
    '';
  };

  config = {
    systemd.user.services.fetch-mutable-files = {
      Unit = {
        Description = "Fetch mutable files from home-manager";
        After = [ "default.target" "network-online.target" ];
        Wants = [ "network-online.target" ];
      };

      Service = {
        # We'll assume this service will download lots of files. We want the
        # temporary files to only last along with the service.
        PrivateUsers = true;
        PrivateTmp = true;

        Type = "oneshot";
        RemainAfterExit = true;
        ExecStart = let
          curl = "${lib.getBin pkgs.curl}/bin/curl";
          git = "${lib.getBin pkgs.curl}/bin/git";
          fetchCmds = lib.mapAttrsToList (file: value:
            let
              inherit (value) type;
              path = lib.escapeShellArg value.path;
              url = lib.escapeURL value.url;
            in ''
              ${lib.optionalString (type == "git") "[ -d ${path} ] || ${git} clone ${url} ${path}"}
              ${lib.optionalString (type == "fetch") "[ -d ${path} ] || ${curl} ${url} --output ${path}"}
            '') cfg;
          shellScript = pkgs.writeShellScript "fetch-mutable-files" ''
            ${lib.concatStringsSep "\n" fetchCmds}
          '';
        in builtins.toString shellScript;
      };

      Install.WantedBy = [ "default.target" ];
    };
  };
}
