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

with lib;

let

  xcfg = config.services.xserver;
  dmcfg = config.services.displayManager;
  xEnv = config.systemd.services.display-manager.environment;
  cfg = xcfg.displayManager.lightdm;
  sessionData = dmcfg.sessionData;

  setSessionScript = pkgs.callPackage ./account-service-util.nix { };

  inherit (pkgs) lightdm writeScript writeText;

  # lightdm runs with clearenv(), but we need a few things in the environment for X to startup
  xserverWrapper = writeScript "xserver-wrapper"
    ''
      #! ${pkgs.bash}/bin/bash
      ${concatMapStrings (n: "export ${n}=\"${getAttr n xEnv}\"\n") (attrNames xEnv)}

      display=$(echo "$@" | xargs -n 1 | grep -P ^:\\d\$ | head -n 1 | sed s/^://)
      if [ -z "$display" ]
      then additionalArgs=":0 -logfile /var/log/X.0.log"
      else additionalArgs="-logfile /var/log/X.$display.log"
      fi

      exec ${xcfg.displayManager.xserverBin} ${toString xcfg.displayManager.xserverArgs} $additionalArgs "$@"
    '';

  usersConf = writeText "users.conf"
    ''
      [UserList]
      minimum-uid=1000
      hidden-users=${concatStringsSep " " dmcfg.hiddenUsers}
      hidden-shells=/run/current-system/sw/bin/nologin
    '';

  lightdmConf = writeText "lightdm.conf"
    ''
      [LightDM]
      ${optionalString cfg.greeter.enable ''
        greeter-user = ${config.users.users.lightdm.name}
        greeters-directory = ${cfg.greeter.package}
      ''}
      sessions-directory = ${dmcfg.sessionData.desktops}/share/xsessions:${dmcfg.sessionData.desktops}/share/wayland-sessions
      ${cfg.extraConfig}

      [Seat:*]
      xserver-command = ${xserverWrapper}
      session-wrapper = ${dmcfg.sessionData.wrapper}
      ${optionalString cfg.greeter.enable ''
        greeter-session = ${cfg.greeter.name}
      ''}
      ${optionalString dmcfg.autoLogin.enable ''
        autologin-user = ${dmcfg.autoLogin.user}
        autologin-user-timeout = ${toString cfg.autoLogin.timeout}
        autologin-session = ${sessionData.autologinSession}
      ''}
      ${optionalString (xcfg.displayManager.setupCommands != "") ''
        display-setup-script=${pkgs.writeScript "lightdm-display-setup" ''
          #!${pkgs.bash}/bin/bash
          ${xcfg.displayManager.setupCommands}
        ''}
      ''}
      ${cfg.extraSeatDefaults}
    '';

in
{
  meta = with lib; {
    maintainers = with maintainers; [ ] ++ teams.pantheon.members;
  };

  # Note: the order in which lightdm greeter modules are imported
  # here determines the default: later modules (if enable) are
  # preferred.
  imports = [
    ./lightdm-greeters/gtk.nix
    ./lightdm-greeters/mini.nix
    ./lightdm-greeters/enso-os.nix
    ./lightdm-greeters/pantheon.nix
    ./lightdm-greeters/lomiri.nix
    ./lightdm-greeters/tiny.nix
    ./lightdm-greeters/slick.nix
    ./lightdm-greeters/mobile.nix
    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "lightdm" "autoLogin" "enable" ] [
      "services"
      "displayManager"
      "autoLogin"
      "enable"
    ])
    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "lightdm" "autoLogin" "user" ] [
     "services"
     "displayManager"
     "autoLogin"
     "user"
    ])
  ];

  options = {

    services.xserver.displayManager.lightdm = {

      enable = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Whether to enable lightdm as the display manager.
        '';
      };

      greeter =  {
        enable = mkOption {
          type = types.bool;
          default = true;
          description = ''
            If set to false, run lightdm in greeterless mode. This only works if autologin
            is enabled and autoLogin.timeout is zero.
          '';
        };
        package = mkOption {
          type = types.package;
          description = ''
            The LightDM greeter to login via. The package should be a directory
            containing a .desktop file matching the name in the 'name' option.
          '';

        };
        name = mkOption {
          type = types.str;
          description = ''
            The name of a .desktop file in the directory specified
            in the 'package' option.
          '';
        };
      };

      extraConfig = mkOption {
        type = types.lines;
        default = "";
        example = ''
          user-authority-in-system-dir = true
        '';
        description = "Extra lines to append to LightDM section.";
      };

      background = mkOption {
        type = types.either types.path (types.strMatching "^#[0-9]\{6\}$");
        # Manual cannot depend on packages, we are actually setting the default in config below.
        defaultText = literalExpression "pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom.gnomeFilePath";
        description = ''
          The background image or color to use.
        '';
      };

      extraSeatDefaults = mkOption {
        type = types.lines;
        default = "";
        example = ''
          greeter-show-manual-login=true
        '';
        description = "Extra lines to append to SeatDefaults section.";
      };

      # Configuration for automatic login specific to LightDM
      autoLogin.timeout = mkOption {
        type = types.int;
        default = 0;
        description = ''
          Show the greeter for this many seconds before automatic login occurs.
        '';
      };

    };
  };

  config = mkIf cfg.enable {

    assertions = [
      { assertion = xcfg.enable;
        message = ''
          LightDM requires services.xserver.enable to be true
        '';
      }
      { assertion = dmcfg.autoLogin.enable -> sessionData.autologinSession != null;
        message = ''
          LightDM auto-login requires that services.displayManager.defaultSession is set.
        '';
      }
      { assertion = !cfg.greeter.enable -> (dmcfg.autoLogin.enable && cfg.autoLogin.timeout == 0);
        message = ''
          LightDM can only run without greeter if automatic login is enabled and the timeout for it
          is set to zero.
        '';
      }
    ];

    # Keep in sync with the defaultText value from the option definition.
    services.xserver.displayManager.lightdm.background = mkDefault pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom.gnomeFilePath;

    # Set default session in session chooser to a specified values – basically ignore session history.
    # Auto-login is already covered by a config value.
    services.displayManager.preStart = optionalString (!dmcfg.autoLogin.enable && dmcfg.defaultSession != null) ''
      ${setSessionScript}/bin/set-session ${dmcfg.defaultSession}
    '';

    # setSessionScript needs session-files in XDG_DATA_DIRS
    services.displayManager.environment.XDG_DATA_DIRS = "${dmcfg.sessionData.desktops}/share/";

    # setSessionScript wants AccountsService
    systemd.services.display-manager.wants = [
      "accounts-daemon.service"
    ];

    # lightdm relaunches itself via just `lightdm`, so needs to be on the PATH
    services.displayManager.execCmd = ''
      export PATH=${lightdm}/sbin:$PATH
      exec ${lightdm}/sbin/lightdm
    '';

    # Replaces getty
    systemd.services.display-manager.conflicts = [
      "getty@tty7.service"
      # TODO: Add "plymouth-quit.service" so LightDM can control when plymouth
      # quits. Currently this breaks switching to configurations with plymouth.
     ];

    # Pull in dependencies of services we replace.
    systemd.services.display-manager.after = [
      "rc-local.service"
      "systemd-machined.service"
      "systemd-user-sessions.service"
      "getty@tty7.service"
      "user.slice"
    ];

    # user.slice needs to be present
    systemd.services.display-manager.requires = [
      "user.slice"
    ];

    # lightdm stops plymouth so when it fails make sure plymouth stops.
    systemd.services.display-manager.onFailure = [
      "plymouth-quit.service"
    ];

    systemd.services.display-manager.serviceConfig = {
      BusName = "org.freedesktop.DisplayManager";
      IgnoreSIGPIPE = "no";
      # This allows lightdm to pass the LUKS password through to PAM.
      # login keyring is unlocked automatic when autologin is used.
      KeyringMode = "shared";
      KillMode = "mixed";
      StandardError = "inherit";
    };

    environment.etc."lightdm/lightdm.conf".source = lightdmConf;
    environment.etc."lightdm/users.conf".source = usersConf;

    services.dbus.enable = true;
    services.dbus.packages = [ lightdm ];

    # lightdm uses the accounts daemon to remember language/window-manager per user
    services.accounts-daemon.enable = true;

    # Enable the accounts daemon to find lightdm's dbus interface
    environment.systemPackages = [ lightdm ];

    security.polkit.enable = true;

    security.pam.services.lightdm.text = ''
        auth      substack      login
        account   include       login
        password  substack      login
        session   include       login
    '';

    security.pam.services.lightdm-greeter.text = ''
        auth     required       pam_succeed_if.so audit quiet_success user = lightdm
        auth     optional       pam_permit.so

        account  required       pam_succeed_if.so audit quiet_success user = lightdm
        account  sufficient     pam_unix.so

        password required       pam_deny.so

        session  required       pam_succeed_if.so audit quiet_success user = lightdm
        session  required       pam_env.so conffile=/etc/pam/environment readenv=0
        session  optional       ${config.systemd.package}/lib/security/pam_systemd.so
        session  optional       pam_keyinit.so force revoke
        session  optional       pam_permit.so
    '';

    security.pam.services.lightdm-autologin.text = ''
        auth      requisite     pam_nologin.so

        auth      required      pam_succeed_if.so uid >= 1000 quiet
        auth      required      pam_permit.so

        account   sufficient    pam_unix.so

        password  requisite     pam_unix.so nullok yescrypt

        session   optional      pam_keyinit.so revoke
        session   include       login
    '';

    users.users.lightdm = {
      home = "/var/lib/lightdm";
      group = "lightdm";
      uid = config.ids.uids.lightdm;
    };

    systemd.tmpfiles.rules = [
      "d /run/lightdm 0711 lightdm lightdm -"
      "d /var/cache/lightdm 0711 root lightdm -"
      "d /var/lib/lightdm 1770 lightdm lightdm -"
      "d /var/lib/lightdm-data 1775 lightdm lightdm -"
      "d /var/log/lightdm 0711 root lightdm -"
    ];

    users.groups.lightdm.gid = config.ids.gids.lightdm;
    services.xserver.tty     = null; # We might start multiple X servers so let the tty increment themselves..
    services.xserver.display = null; # We specify our own display (and logfile) in xserver-wrapper up there
  };
}
