# Taken from: https://github.com/urob/dotfiles/blob/main/lib/mkSymlinkAttrs.nix

# This function adds and interpretes outOfStoreSymlink option to home.file attribute sets.
#
# Usage:
#   home.file = mkSymlinkAttrs {
#     .foo = { source = "foo"; outOfStoreSymlink = true; recursive = true; };
#     .bar = { source = "foo/bar"; outOfStoreSymlink = true; };
#   };
{ pkgs, hm, context, runtimeRoot, ... }:

let
  inherit (pkgs) lib;

  # Swap a path inside the nix store with the same path in runtimeRoot
  runtimePath = path:
    let
      rootStr = toString context; # context is the `self` passed to flake outputs
      pathStr = toString path;
    in
    assert lib.assertMsg (lib.hasPrefix rootStr pathStr)
      "${pathStr} does not start with ${rootStr}";
    runtimeRoot + lib.removePrefix rootStr pathStr;

  # Make outOfStoreSymlink against runtimeRoot. This replicates
  # config.lib.file.mkOutOfStoreSymlink as_mkOutOfStoreSymlink and wraps it to
  # replace the target path in the nix store with the original target path
  # inside runtimeRoot. This is necessary because flakes live in the nix store.
  mkOutOfStoreSymlink =
    let
      _mkOutOfStoreSymlink = path:
        let
          pathStr = toString path;
          name = hm.strings.storeFileName (baseNameOf pathStr);
        in
        pkgs.runCommandLocal name { } ''ln -s ${lib.strings.escapeShellArg pathStr} $out'';
    in
    file: _mkOutOfStoreSymlink (runtimePath file);

  # Recursively make OutOfStoreSymlinks for all files inside path.
  mkRecursiveOutOfStoreSymlink = path: link:
    builtins.listToAttrs (
      map
        (file: {
          name = link + "${lib.removePrefix (toString path) (toString file)}";
          value = { source = mkOutOfStoreSymlink file; };
        })
        (lib.filesystem.listFilesRecursive path)
    );

  # Remove custom attributes from attribute set.
  rmopts = attrs: builtins.removeAttrs attrs [ "source" "recursive" "outOfStoreSymlink" ];

in fileAttrs: lib.attrsets.concatMapAttrs
  (
    name: value:
    # Make outOfStoreSymlinks
    if value.outOfStoreSymlink or false
    then
      if value.recursive or false
      then
        lib.attrsets.mapAttrs
          (_: attrs: attrs // rmopts value)
          (mkRecursiveOutOfStoreSymlink value.source name)
      else { "${name}" = { source = mkOutOfStoreSymlink value.source; } // rmopts value; }
    # Handle all other cases as usual
    else { "${name}" = value; }
  )
  fileAttrs
