import { trailingSlash, Tree } from "@weborigami/async-tree";

/**
 * Given an old tree and a new tree, return a tree of changes indicated
 * by the values: "added", "changed", or "deleted".
 *
 * @typedef {import("@weborigami/async-tree").Treelike} Treelike
 *
 * @this {import("@weborigami/types").AsyncTree|null}
 * @param {Treelike} oldTreelike
 * @param {Treelike} newTreelike
 */
export default async function changes(oldTreelike, newTreelike) {
  const oldTree = Tree.from(oldTreelike, { deep: true, parent: this });
  const newTree = Tree.from(newTreelike, { deep: true, parent: this });

  const oldKeys = Array.from(await oldTree.keys());
  const newKeys = Array.from(await newTree.keys());

  const oldKeysNormalized = oldKeys.map(trailingSlash.remove);
  const newKeysNormalized = newKeys.map(trailingSlash.remove);

  let result;

  for (const oldKey of oldKeys) {
    const oldNormalized = trailingSlash.remove(oldKey);
    if (!newKeysNormalized.includes(oldNormalized)) {
      result ??= {};
      result[oldKey] = "deleted";
      continue;
    }

    const oldValue = await oldTree.get(oldKey);
    const newValue = await newTree.get(oldKey);

    if (Tree.isAsyncTree(oldValue) && Tree.isAsyncTree(newValue)) {
      const treeChanges = await changes.call(this, oldValue, newValue);
      if (treeChanges && Object.keys(treeChanges).length > 0) {
        result ??= {};
        result[oldKey] = treeChanges;
      }
    } else if (oldValue?.toString && newValue?.toString) {
      const oldText = oldValue.toString();
      const newText = newValue.toString();
      if (oldText !== newText) {
        result ??= {};
        result[oldKey] = "changed";
      }
    } else {
      result ??= {};
      result[oldKey] = "changed";
    }
  }

  for (const newKey of newKeys) {
    const newNormalized = trailingSlash.remove(newKey);
    if (!oldKeysNormalized.includes(newNormalized)) {
      result ??= {};
      result[newKey] = "added";
    }
  }

  return result;
}
