import { useCallback, useEffect, useMemo, useRef, useState as useReactState, useSyncExternalStore } from "react";
import {
  create as c,
  GetSet,
  createBubble as cb,
  BubbleType,
  ValType,
  MemoReturnType,
  createEffect as _createEffect,
  createMemo as _createMemo,
  atom as _atom,
  molecule as _molecule,
} from "get-set-immutable";
export const create = c;
export const useGet = <T extends Object, S extends Object = {}>(
  getSetInstance: GetSet<T, S> | BubbleType<T>,
  resetOnUnmount: boolean = false
): T & S => {
  const [state, setState] = useReactState(getSetInstance.getState());
  useEffect(() => {
    const fn = () => {
      setState(getSetInstance.getState());
    };
    return getSetInstance.subscribe(fn);
  }, [setState]);
  useEffect(() => {
    return () => {
      resetOnUnmount && getSetInstance.reset();
    };
  }, []);
  //@ts-ignore
  return useMemo<T & S>(() => {
    return typeof state === "object" ? {
      ...state,
      //@ts-ignore
      ...(getSetInstance?.actions || {}),
    } : state;
  }, [state]);
};

export const useVal = <T extends Object, S extends Object>(
  getSetInstance: GetSet<T, S>,
  resetOnUnmount: boolean = false
): ValType<T> & { actions?: S } => {
  const val: ValType<T> & { actions?: S } = useCallback(
    function (
      newStateFn?: T | ((currentState: T) => void) | undefined,
      versionName?: string
    ) {
      if (arguments.length === 0) {
        return getSetInstance.val();
      } else {
        return getSetInstance.val(newStateFn, versionName);
      }
    },
    [getSetInstance.get()]
  );

  val.actions = getSetInstance.actions;
  useGet(getSetInstance, resetOnUnmount);
  return val;
};

export const useStore = <T extends Object, S extends Object = {}>(
  state: GetSet<T, S>,
  resetOnUnmount: boolean = false
): [state: T, actions: S | undefined] => {
  //@ts-ignore
  const s = useGet(state, resetOnUnmount);
  return [s, state.actions];
};

export function useVS<T extends Object>(
  initialState: T,
  key?: string
): (
  newVal?: T | undefined | ((currentState: T) => void),
  versionName?: string
) => T {
  const state = useRef(create(initialState));
  const _val = state.current.val;
  const [count, setCount] = useReactState(0);
  const update = () => {
    if (key) {
      setTimeout(
        () => viewStore.set((s: any) => ({ ...s, [key]: _val() })),
        100
      );
    }
  };
  const val = useCallback(
    // here props should be T | undefined | ((currentState: T) => T)
    function (
      newState?: T | undefined | ((currentState: T) => void),
      versionName?: string
    ) {
      /// make it if else
      ///convert the below ternary to if else
      if (arguments.length > 0) {
        setCount(count + 1);
        return _val(newState, versionName);
      } else return _val();
    },
    [count]
  );
  //update();
  useOnlyOnce(update);
  useEffect(() => {
    let s = viewStore.get();
    const unsub = state.current.subscribe(() => {
      setCount(count + 1);
      update();
    });

    return () => {
      unsub();
      if (key && viewStore.get()?.[key]) {
        viewStore.update((s) => delete s[key]);
      }
    };
  }, []);
  return val;
}

export const viewStore = create<any, any>({});

export const createBubble = cb;

export function useReset<T extends Object, S extends Object = {}>(
  stateOrStore: (BubbleType<T> | GetSet<T, S>)[]
) {
  useEffect(() => {
    return stateOrStore.forEach((store) => store.reset);
  }, []);
}
export const useOnlyOnce = (fn: Function) => {
  const hasBeenUsed = useRef(false);
  if (hasBeenUsed.current === false) {
    fn();
    hasBeenUsed.current = true;
  }
};

export function useSelector<
  T extends Object,
  S extends Object | null | undefined
>(getSetInstance: GetSet<T>, selectorFn: (state: T) => S) {
  const [state, setState] = useReactState(selectorFn(getSetInstance.getState()));
  useEffect(() => {
    const fn = () => {
      setState(selectorFn(getSetInstance.getState()));
    };
    return getSetInstance.subscribe(fn);
  }, [setState]);
  return state;
}

export const useMemoValue = <T>(memo: MemoReturnType<T>) => {
  return useSyncExternalStore(memo.subscribe, memo.get);
};

export const useState = <T extends Object>(initialState: T) => {
  const store = useRef(create(initialState));
  return [useGet(store.current), store.current.val];
};


export const createEffect = _createEffect; 
export const createMemo = _createMemo; 
export const atom = _atom; 
export const molecule = _molecule; 