import { batch, createEffect, createSignal, onMount } from "solid-js";
import { createStore, reconcile } from "solid-js/store";

export const createLocalStore = function <T extends Record<string, any>>(
  initState: T,
  {
    prefix = "app",
    serializer = (v: any) => JSON.stringify(v),
    deserializer = (v: any) => JSON.parse(v),
    migrate = undefined as
      | ((getState: () => Record<string, any>) => boolean)
      | undefined,
  } = {}
) {
  const [state, setState] = createStore(initState);
  const [mounted, setMounted] = createSignal(false);
  const localStorage = globalThis.localStorage;
  const storePrefix = `${prefix}-`;

  if (localStorage) {
    let mounts = 0;
    let mounted_ = false;
    const updating = {} as Record<string, any>;
    const keys = Object.keys(state);
    const changedBeforeMount = {} as Record<string, any>;

    if (migrate) {
      let migratedState: Record<string, any> | undefined = undefined;
      const getMigrateState = () => {
        if (migratedState) return migratedState;

        migratedState = {};

        for (const key of Object.keys(localStorage)) {
          if (!key.startsWith(storePrefix)) {
            continue;
          }

          migratedState[key.slice(storePrefix.length)] = deserializer(
            localStorage.getItem(key)
          );
        }

        return migratedState;
      };
      const migrated = migrate(getMigrateState);
      if (migrated && migratedState != undefined) {
        for (const [key, value] of Object.entries(migratedState)) {
          localStorage.setItem(`${storePrefix}${key}`, serializer(value));
        }
      }
      migratedState = undefined;
    }

    for (const key of keys) {
      let storeKey = `${storePrefix}${key}`;
      let mountValue = localStorage.getItem(storeKey);
      let initRun = true;
      const [updatingCount, setUpdatingCount] = createSignal(0);

      // TODO: Implement localStorage listener

      createEffect(() => {
        // During mounts we want to always run this effect even if the state value hasnt changed
        // We need to run it always to reset updating[key]
        updatingCount();

        const isInitRun = initRun;
        initRun = false;
        const value = serializer(state[key]);

        if (isInitRun && mountValue) {
          return;
        }

        // If the key is getting mounted at the moment, we skip the localStorage set
        if (updating[key]) {
          updating[key] = false;
          return;
        }

        if (!mounted_) {
          changedBeforeMount[key] = true;
        }

        if (value === undefined) {
          localStorage.removeItem(storeKey);
        } else {
          localStorage.setItem(storeKey, value);
        }
      });

      onMount(() => {
        if (!changedBeforeMount[key] && mountValue) {
          updating[key] = true;
          batch(function () {
            setUpdatingCount(updatingCount() + 1);
            setState(key as any, reconcile(deserializer(mountValue)));
          });
        }

        mounts++;
        mountValue = null;
        if (mounts === keys.length) {
          setMounted(true);
          mounted_ = true;
        }
      });
    }
  }

  return [state, setState, mounted] as [
    typeof state,
    typeof setState,
    typeof mounted
  ];
};
