import { ref, computed, unref, type Ref, type ComputedRef } from 'vue';
import { isEqual, cloneDeep } from 'lodash-es';
import { watch } from 'vue';

type Mode = 'add' | 'edit';
type MaybeRef<T> = T | Ref<T> | ComputedRef<T> | (() => T);

interface Options<T> {
  mode: MaybeRef<Mode>;
  pick?: (data: T) => any;
}

export function useDirtyForm<T extends Record<string, any>>(form: Ref<T>, options: Options<T>) {
  const initial = ref<T | null>(null) as Ref<T | null>;

  const getMode = () => {
    const mode = options.mode;
    return typeof mode === 'function' ? mode() : unref(mode);
  };

  const normalize = (data: T) => {
    return options.pick ? options.pick(data) : data;
  };

  const init = (data?: T) => {
    initial.value = data ? cloneDeep(normalize(data)) : null;
  };

  const isDirty = computed(() => {
    if (getMode() === 'add') return true;
    if (initial.value === null) return true;
    return !isEqual(cloneDeep(normalize(form.value)), initial.value);
  });

  const commit = () => {
    initial.value = cloneDeep(normalize(form.value));
  };

  const reset = () => {
    if (initial.value) {
      form.value = cloneDeep(initial.value);
    }
  };

  watch(
    () => options.mode,
    () => {
      if (getMode() === 'add') {
        initial.value = null;
      }
    }
  );

  return {
    isDirty,
    init,
    commit,
    reset,
  };
}
