import { newUUID } from '@warkypublic/artemis-kit';
import { createSyncStore } from '@warkypublic/zustandsyncstore';
import { produce } from 'immer';

import type { FormerProps, FormerState, FormStateAndProps } from './Former.types';

const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
  FormerState<any> & Partial<FormerProps<any>>,
  FormerProps<any>
>(
  (set, get) => ({
    getAllState: () => {
      return get() as FormStateAndProps<any>;
    },
    getState: (key) => {
      const current = get();
      return current?.[key];
    },
    load: async (reset?: boolean) => {
      try {
        set({ loading: true });
        const keyName = get()?.uniqueKeyField || 'id';
        const keyValue = (get().values as any)?.[keyName] ?? (get().primeData as any)?.[keyName];
        if (get().onAPICall && keyValue !== undefined) {
          let data = await get().onAPICall!(
            'read',
            get().request || 'insert',
            get().values,
            keyValue
          );
          if (get().afterGet) {
            data = await get().afterGet!({ ...data }, get());
          }
          set({ loading: false, values: data });
          get().onChange?.(data, get());
        }
        if (reset && get().getFormMethods) {
          const formMethods = get().getFormMethods!();
          formMethods.reset();
        }
      } catch (e) {
        const errorMessage = (e as Error)?.message ?? e;
        set({ error: errorMessage, loading: false });
        get().onError?.(errorMessage, get());
      }
      set({ loading: false });
    },

    onChange: (values) => {
      set({ values });
    },
    request: 'insert',
    reset: async () => {
      const state = get();
      if (state.getFormMethods) {
        if (state.request !== 'insert') {
          await state.load(true);
        }

        const formMethods = state.getFormMethods!();
        formMethods.reset({ ...state.values, ...state.primeData });
      }
    },
    save: async (e?: React.BaseSyntheticEvent<object, any, any> | undefined) => {
      try {
        const keepOpen = get().keepOpen ?? false;
        set({ loading: true });
        if (get().getFormMethods) {
          const formMethods = get().getFormMethods!();

          let data = formMethods.getValues();

          if (get().beforeSave) {
            const newData = await get().beforeSave!(data, get());
            data = newData;
          }

          let exit = false;
          const handler = formMethods.handleSubmit(
            (newdata) => {
              data = newdata;
            },
            (errors) => {
              const errorMessage = errors.root?.message || 'Validation errors';
              set({ error: errorMessage, loading: false });
              get().onError?.(errorMessage, get());
              exit = true;
            }
          );

          await handler(e);

          //console.log('Former.store.tsx save called', success, e, data, get().getFormMethods);
          if (exit) {
            set({ loading: false });
            return undefined;
          }

          if (get().request === 'delete' && !get().deleteConfirmed) {
            const confirmed = (await get().onConfirmDelete?.(data)) ?? false;
            if (!confirmed) {
              set({ loading: false });
              return undefined;
            }
          }

          if (get().onAPICall) {
            const keyName = get()?.uniqueKeyField || 'id';
            const keyValue =
              (get().values as any)?.[keyName] ?? (get().primeData as any)?.[keyName];
            const savedData = await get().onAPICall!(
              'mutate',
              get().request || 'insert',
              data,
              keyValue
            );
            const newData = { ...data, ...savedData }; //Merge what we had. In case the API doesn't return all fields, we don't want to lose them
            if (get().afterSave) {
              await get().afterSave!(newData, get());
            }

            if (keepOpen) {
              const keyName = get()?.uniqueKeyField || 'id';
              const clearedData = { ...newData };
              delete clearedData[keyName];
              set({ loading: false, values: clearedData });
              get().onChange?.(clearedData, get());
              formMethods.reset(clearedData);
              return newData;
            }

            set({ loading: false, values: newData });
            get().onChange?.(newData, get());
            formMethods.reset(newData); //reset with saved data to clear dirty state
            get().onClose?.(newData);
            return newData;
          }

          if (keepOpen) {
            const keyName = get()?.uniqueKeyField || 'id';
            const clearedData = { ...data };
            delete clearedData[keyName];
            set({ loading: false, values: clearedData });
            formMethods.reset(clearedData);
            get().onChange?.(clearedData, get());
            return data;
          }

          set({ loading: false, values: data });
          formMethods.reset(data); //reset with saved data to clear dirty state
          get().onChange?.(data, get());
          get().onClose?.(data);

          return data;
        }
      } catch (e) {
        const errorMessage = (e as Error)?.message ?? e;
        set({ error: errorMessage, loading: false });
        get().onError?.(errorMessage, get());
      }

      return undefined;
    },
    setRequest: (request) => {
      set({ request });
    },
    setState: (key, value) => {
      set(
        produce((state) => {
          state[key] = value;
        })
      );
    },
    setStateFN: (key, value) => {
      const p = new Promise<void>((resolve, reject) => {
        set(
          produce((state) => {
            if (typeof value === 'function') {
              state[key] = (value as (value: unknown) => unknown)(state[key]);
            } else {
              reject(new Error(`Not a function ${value}`));
              throw Error(`Not a function ${value}`);
            }
          })
        );
        resolve();
      });

      return p;
    },
    validate: async () => {
      if (get().getFormMethods) {
        const formMethods = get().getFormMethods!();
        const isValid = await formMethods.trigger();
        return isValid;
      }
      return true;
    },
    values: undefined,
  }),
  ({ id, onClose, onConfirmDelete, primeData, request, useStoreApi, values }) => {
    let _onConfirmDelete = onConfirmDelete;
    if (!onConfirmDelete) {
      _onConfirmDelete = async () => {
        return confirm('Are you sure you want to delete this item?');
      };
    }

    return {
      id: !id ? newUUID() : id,
      onClose: (data?: any) => {
        const dirty = useStoreApi.getState().dirty;
        const setState = useStoreApi.getState().setState;
        if (dirty) {
          if (confirm('You have unsaved changes. Are you sure you want to close?')) {
            if (onClose) {
              onClose(data);
            } else {
              setState('opened', false);
            }
          }
        } else {
          if (onClose) {
            onClose(data);
          } else {
            setState('opened', false);
          }
        }
      },
      onConfirmDelete: _onConfirmDelete,
      primeData,
      request: (request || 'insert').replace('change', 'update'),
      values: { ...primeData, ...values },
    };
  }
);

export { FormerProvider };
export { useFormerStore };
