{
  lib,
  config,
  ...
}: let
  inherit (lib) filterAttrs concatStrings concatStringsSep mapAttrsToList concatLists foldlAttrs concatMapAttrs mapAttrs mapAttrs' nameValuePair boolToString;
  inherit (builtins) typeOf toString stringLength;

  # build up serialisation machinery from here for various types

  # list -> array
  array = a: "[${concatStringsSep "," a}]";
  # attrset -> hashmap
  _assoc = a: mapAttrsToList (name: val: "${name}: ${val}") a;
  assoc = a: ''    {
        ${concatStringsSep ",\n" (concatLists (map _assoc a))}
        }'';
  # attrset -> struct
  _struct_kv = k: v:
    if v == null
    then ""
    else (concatStringsSep ":" [k (serialise.${typeOf v} v)]);
  _struct_concat = s:
    foldlAttrs (
      acc: k: v:
        if stringLength acc > 0
        then concatStringsSep ", " [acc (_struct_kv k v)]
        else _struct_kv k v
    ) ""
    s;
  _struct_filt = s:
    _struct_concat (filterAttrs (k: v: v != null) s);
  struct = s: "(${_struct_filt s})";
  toQuotedString = s: ''"${toString s}"'';

  # make an attrset for struct serialisation
  serialise = {
    int = toString;
    float = toString;
    bool = boolToString;
    string = toString;
    path = toString;
    null = toString;
    set = struct;
    list = array;
  };

  # define the key for a keybind
  defineBinding = binding:
    struct {
      inherit (binding) modifiers;
      key =
        if isNull binding.key
        then null
        else toQuotedString binding.key;
    };

  # map keybinding from list of attrset to hashmap of (mod,key): action
  _mapBindings = bindings:
    map (
      inner: {"${defineBinding inner}" = maybeToString (checkAction inner.action);}
    )
    bindings;
  mapBindings = bindings:
    assoc (_mapBindings bindings);

  # check a keybinding's action
  # escape with quotes if it's a Spawn action
  checkAction = a:
    if typeOf a == "set" && a.type == "Spawn"
    then {
      inherit (a) type;
      data = toQuotedString a.data;
    }
    else a;

  maybeToString = s:
    if typeOf s == "set"
    then concatStrings [s.type "(" (toString s.data) ")"]
    else s;

  mapCosmicSettings = application: options:
    mapAttrs' (k: v:
      nameValuePair "cosmic/${application}/v${options.version}/${k}" {
        enable = true;
        text = serialise.${typeOf v} v;
      })
    options.option;
  # deconstructKeybindAttr = attr: mapAttrs' (
  #   name: value:
  # ) attr;
  # rec_attr = lib.types.attrsOf (lib.types.oneOf lib.types.string rec_attr);
in {
  options.cosmic = {
    enable = with lib;
      mkOption {
        default = false;
        type = types.bool;
      };

    defaultKeybindings = with lib;
      mkOption {
        default = true;
        type = types.bool;
      };

    # binds = with lib;
    #   mkOption {
    #     default = {};
    #     type = with types; rec_attr;
    #   };

    keybindings = with lib;
      mkOption {
        default = [];
        type = with types;
          listOf (submodule {
            options = {
              modifiers = mkOption {
                type = listOf str;
                default = [];
              };
              key = mkOption {
                type = nullOr str;
                default = null;
              };
              action = mkOption {
                type = either str (submodule {
                  options = {
                    type = mkOption {
                      type = str;
                    };
                    data = mkOption {
                      type = oneOf [str int];
                      default = "";
                    };
                  };
                });
              };
            };
          });
      };

    otherSettings = with lib;
      mkOption {
        default = {};
        type = with types;
          attrsOf (submodule {
            options = {
              version = mkOption {
                type = str;
                default = "1";
              };
              option = mkOption {
                type = attrsOf anything;
              };
            };
          });
      };
  };

  # TODO we need to split between system_actions, workspaces, custom
  config.xdg.configFile =
    {
      "cosmic/com.system76.CosmicSettings.Shortcuts/v1/custom".text = mapBindings config.cosmic.keybindings;
      "cosmic/com.system76.CosmicSettings.Shortcuts/v1/defaults" = {
        text = "{}";
        enable = !config.cosmic.defaultKeybindings;
      };
    }
    // concatMapAttrs (
      application: options:
        mapCosmicSettings application options
    )
    config.cosmic.otherSettings;
}
