{ config, pkgs, lib, ... }:
let
  cfg = config.environment.robots-txt;

  robots-txt-format = {
    type = let
      coercedStrToNonEmptyListOfStr = lib.types.coercedTo lib.types.str lib.singleton (lib.types.nonEmptyListOf lib.types.str);
    in lib.types.listOf (lib.types.submodule {
      freeformType = lib.types.attrsOf coercedStrToNonEmptyListOfStr;
      options = {
        pre_comment = lib.mkOption {
          description = "Comment to add before the rule";
          type = lib.types.lines;
          default = "";
        };
        post_comment = lib.mkOption {
          description = "Comment to add after the rule";
          type = lib.types.lines;
          default = "";
        };
      };
    });

    generate = name: value: let
      makeComment = comment: lib.pipe comment [
        (lib.splitString "\n")
        (lib.map (line: if line == "" then "#" else "# ${line}"))
        (lib.concatStringsSep "\n")
      ];

      ruleToString = rule: let
        user_agent = rule.User-agent or [];
        pre_comment = rule.pre_comment;
        post_comment = rule.post_comment;
        rest = builtins.removeAttrs rule [ "User-agent" "pre_comment" "post_comment" ];
      in lib.concatStringsSep "\n" (lib.filter (x: x != null) [
        (if (pre_comment != "") then makeComment pre_comment else null)
        (let
          user-agents = lib.concatMapStringsSep "\n" (value: "User-agent: ${value}") user_agent;
        in
          if user_agent == [] then null else user-agents
        )
        (lib.pipe rest [
          (lib.mapAttrsToList (ruleName: map (value: "${ruleName}: ${value}")))
          lib.concatLists
          (lib.concatStringsSep "\n")
        ])
        (if (post_comment != "") then makeComment post_comment else null)
      ]);

      content = lib.concatMapStringsSep "\n\n" ruleToString value;
    in pkgs.writeText name content;
  };
in
{
  options.environment.robots-txt = lib.mkOption {
    default = { };
    description = ''
      Different instances of robots.txt to use with web services.
    '';
    type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: {
      options = {
        enable = lib.mkEnableOption "this instance of robots.txt" // {
          default = true;
        };

        path = lib.mkOption {
          description = "The resulting path of the dir containing the robots.txt file";
          type = lib.types.path;
          readOnly = true;
          default = "/etc/robots-txt/${name}";
        };

        rules = lib.mkOption {
          description = "Rules to include in robots.txt";
          default = [ ];
          example = [
            { User-agent = "Googlebot"; Disallow = "/no-googlebot"; }
            { User-agent = "Bingbot"; Disallow = [ "/no-bingbot" "/no-bingbot2" ]; }
          ];
          type = robots-txt-format.type;
        };

        virtualHost = lib.mkOption {
          description = "An nginx virtual host to add the robots.txt to";
          type = lib.types.nullOr lib.types.str;
          default = null;
        };
      };
    }));
  };

  config = {
    environment.etc = lib.mapAttrs' (name: value: {
      name = "robots-txt/${name}/robots.txt";
      value.source = robots-txt-format.generate name value.rules;
    }) cfg;

    services.nginx.virtualHosts = lib.pipe cfg [
      (lib.filterAttrs (_: value: value.virtualHost != null))
      (lib.mapAttrs' (name: value: {
        name = value.virtualHost;
        value = {
          locations = {
            "= /robots.txt" = {
              extraConfig = ''
                add_header Content-Type text/plain;
              '';
              root = cfg.${name}.path;
            };
          };
        };
      }))
    ];
  };
}