import { Effect } from "effect";
import { computed, inject, nextTick, onScopeDispose, provide, ref, watch } from "vue";
export function deepEqual(a, b) {
  if (Object.is(a, b)) return true;
  if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
  if (a instanceof Date || b instanceof Date) {
    return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
  }
  if (Array.isArray(a) || Array.isArray(b)) {
    if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
    return a.every((v, i) => deepEqual(v, b[i]));
  }
  const ak = Object.keys(a);
  const bk = Object.keys(b);
  if (ak.length !== bk.length) return false;
  return ak.every(
    (k) => Object.prototype.hasOwnProperty.call(b, k) && deepEqual(a[k], b[k])
  );
}
const QUIESCENCE_KEY = Symbol("shwfed-form-derived-quiescence");
function createQuiescence() {
  const inFlight = ref(0);
  return {
    inFlight,
    begin: () => {
      inFlight.value += 1;
    },
    end: () => {
      inFlight.value -= 1;
    },
    async settled() {
      for (; ; ) {
        if (inFlight.value > 0) {
          await new Promise((resolve) => {
            const stop = watch(inFlight, (n) => {
              if (n === 0) {
                stop();
                resolve();
              }
            });
          });
        }
        await nextTick();
        if (inFlight.value === 0) return;
      }
    }
  };
}
export function provideDerivedQuiescence() {
  const q = createQuiescence();
  provide(QUIESCENCE_KEY, q);
  return q;
}
export function useDerivedQuiescence() {
  return inject(QUIESCENCE_KEY, null) ?? createQuiescence();
}
export function useDerived(options) {
  const { state, getAt, setAtSilent, isDirty, markDirty } = options.formState;
  const quiescence = options.quiescence;
  const derivedFields = computed(
    () => options.fields().flatMap((field) => {
      const binding = field.binding;
      const derived = field.derived;
      if (typeof binding !== "string" || !derived || typeof derived !== "object") return [];
      const mode = derived.mode;
      const expression = derived.expression;
      if (mode !== "formula" && mode !== "prefill" || typeof expression !== "string") return [];
      return [{ binding, mode, expression }];
    })
  );
  let generation = 0;
  let started = false;
  function evaluateField(field, gen) {
    quiescence.begin();
    Effect.runPromise(options.evaluate(field.expression)).then(
      (result) => {
        if (gen !== generation) return;
        if (field.mode === "prefill" && isDirty(field.binding)) return;
        const normalized = result === void 0 ? null : result;
        if (!deepEqual(getAt(field.binding), normalized)) setAtSilent(field.binding, normalized);
      },
      () => {
      }
    ).finally(() => {
      quiescence.end();
    });
  }
  function runAll() {
    const gen = generation += 1;
    for (const field of derivedFields.value) evaluateField(field, gen);
  }
  watch([state, derivedFields], () => {
    if (started) runAll();
  });
  function claimSeededValues() {
    for (const field of derivedFields.value) {
      if (field.mode === "prefill" && getAt(field.binding) !== void 0) {
        markDirty(field.binding);
      }
    }
  }
  let disposed = false;
  onScopeDispose(() => {
    disposed = true;
  });
  options.ready.then(() => {
    if (disposed) return;
    started = true;
    claimSeededValues();
    runAll();
  }, () => {
    if (disposed) return;
    started = true;
    claimSeededValues();
    runAll();
  });
}
