import { useManualRefHistory } from "@vueuse/core";
import { nextTick, ref, watch } from "vue";
import { deepEqual } from "./derived.js";
export function deepClone(value) {
  if (value === null || typeof value !== "object") return value;
  if (value instanceof Date) {
    return new value.constructor(value.getTime());
  }
  if (typeof File !== "undefined" && value instanceof File) return value;
  if (typeof Blob !== "undefined" && value instanceof Blob) return value;
  if (Array.isArray(value)) return value.map((v) => deepClone(v));
  const out = {};
  for (const key of Object.keys(value)) {
    out[key] = deepClone(value[key]);
  }
  return out;
}
function sameDirty(a, b) {
  return a.length === b.length && a.every((p) => b.includes(p));
}
export function useFormHistory(state, options) {
  const { dirty } = options;
  function capture() {
    return { state: state.value, dirty: [...dirty.value] };
  }
  function clone(s) {
    return { state: deepClone(s.state), dirty: [...s.dirty] };
  }
  const tracked = ref(capture());
  const history = useManualRefHistory(tracked, {
    dump: clone,
    parse: clone,
    capacity: 50
  });
  let navigating = false;
  let recording = false;
  let snapshotPending = false;
  let initialSnapshot;
  async function scheduleSnapshot() {
    if (snapshotPending) return;
    snapshotPending = true;
    await options.quiescence.settled();
    snapshotPending = false;
    if (!recording || navigating) return;
    tracked.value = capture();
    const last = history.last.value.snapshot;
    if (!deepEqual(tracked.value.state, last.state) || !sameDirty(tracked.value.dirty, last.dirty)) {
      history.commit();
    }
  }
  watch(state, () => {
    if (recording && !navigating) void scheduleSnapshot();
  });
  options.seeded.then(() => options.quiescence.settled()).then(() => {
    tracked.value = capture();
    history.commit();
    history.clear();
    initialSnapshot = clone(tracked.value);
    recording = true;
  });
  function navigate(run) {
    navigating = true;
    run();
    state.value = deepClone(tracked.value.state);
    dirty.value = new Set(tracked.value.dirty);
    void nextTick(() => {
      navigating = false;
    });
  }
  function reset() {
    if (!initialSnapshot) return;
    state.value = deepClone(initialSnapshot.state);
    dirty.value = new Set(initialSnapshot.dirty);
  }
  return {
    undo: () => navigate(() => history.undo()),
    redo: () => navigate(() => history.redo()),
    reset,
    canUndo: history.canUndo,
    canRedo: history.canRedo
  };
}
