// resources\assets\ts\share\formsSlice.ts
import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import axios from 'B/axios';

export type FormSubmitState = 'none' | 'media' | 'form' | 'complete';
type State = {
  par: any; // params
  forms: any;
  flatForms: any;
  isDialogOpen: boolean;
  submitState: FormSubmitState;
  isSubmittingMedia: boolean;
  isSubmittingForm: boolean;
  isValidForm: boolean;
  mediaUploadCount: number;
  uploadedMediaCount: number;
  comment: string;
  htmlInfos: string;
  isHtmlInfosOpen: boolean;
  list: any;
  listFilter: string;
};

const initialState: State = {
  par: null,
  forms: {},
  flatForms: [],
  isDialogOpen: false,
  submitState: 'none',
  isSubmittingMedia: false,
  isSubmittingForm: false,
  isValidForm: false,
  mediaUploadCount: 0,
  uploadedMediaCount: 0,
  comment: '',
  htmlInfos: '',
  isHtmlInfosOpen: false,
  list: [],
  listFilter: '',
};

function validateEmail(email) {
  const re = /\S+@\S+\.\S+/;
  return re.test(email);
}

function normalizeItem(items, depth, indexes, nItems) {
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    const isForm = item.typ === 'form' && item.items !== undefined;
    if (isForm) {
      nItems.push({
        ...item,
        depth: depth + 1,
        indexes: [...indexes, i],
      });
      normalizeItem(item.items, depth + 1, [...indexes, i], nItems);
    } else {
      nItems.push({ ...item, depth, indexes: [...indexes, i] });
    }
  }
}

function formsFromFlat(formItems, flat) {
  let deleteIndex = null;
  let deleteItems = null;

  for (let i = 0; i < flat.length; i++) {
    const flatItem = flat[i];
    const isReadOnly = flatItem.readonly === true;

    // pick values
    let value = null;
    if (flatItem.typ === 'media') {
      value = flatItem?.d?.pics ?? [];
    } else if (flatItem.typ === 'chk') {
      value = flatItem?.d?.chk ?? [];
    } else {
      value = flatItem?.d?.inp ?? null;
    }

    // dig into nested form to mark a form to update value later
    const isDelete = flatItem.isDelete;
    let items = formItems;
    let lastItem;
    for (let idx of flatItem.indexes) {
      // mark a item to delete
      if (isDelete) {
        deleteIndex = idx;
        deleteItems = items;
      }
      lastItem = items[idx];
      items = items[idx].items;
    }

    // update value
    if (value === null) continue;
    if (lastItem.d === undefined) {
      lastItem.d = {};
    }
    if (flatItem.typ === 'media') {
      lastItem.d = { pics: value };
    } else if (flatItem.typ === 'chk') {
      lastItem.d = { chk: value };
    } else {
      lastItem.d = { inp: value };
    }

    if (isReadOnly) {
      lastItem.d = { ...lastItem.d, readonly: true };
    }
  }

  // delete marked item
  if (deleteIndex !== null) {
    deleteItems.splice(deleteIndex, 1);
  }

  return formItems;
}

export const importForm = createAsyncThunk(
  'tasko-form/import/form',
  async (par: any, thunkAPI) => {
    let data = par.d;
    let resid = undefined;
    if (data != undefined) {
      resid = data.resid;
    }
    let path = null;
    if (resid == undefined) path = par['by.id'] + '.json';
    else path = par['by.id'] + '/res/' + resid + '.json';

    const response = await axios.get(`/api/agb/${path}`);
    const isError = response.status !== 200;
    if (isError) {
      return thunkAPI.rejectWithValue('network error');
    }

    let form = JSON.parse(JSON.stringify(response.data));
    if (resid === undefined) {
      form.rel = form.id;
      form.id = null;
    }

    let flattenForms = [];
    const depth = 0;
    const indexes = [];
    normalizeItem(form.items, depth, indexes, flattenForms);

    return { form, flattenForms, par };
  }
);

export const importList = createAsyncThunk(
  'tasko-form/import/list',
  async (listsrc: string) => {
    const r = await axios.get(`/api/lst/${listsrc}`);
    const list = r.data.sort(function (a, b) {
      return a['name'].trim().localeCompare(b['name'].trim());
    });

    return { list };
  }
);

interface FormInfoInterface {
  htmlInfos: string;
  comment: string;
}
export const importFormInfo = createAsyncThunk<FormInfoInterface, number>(
  'task-form/import/form-comment',
  async (agbID: number) => {
    const { data } = await axios.get(`agbs/${agbID}/forminfo`);
    return data;
  }
);

type SubmitArgs = {
  afterSubmit?: () => void;
};
export const submitForm = createAsyncThunk(
  'tasko-form/submit_form',
  async (args: SubmitArgs | void, thunkAPI) => {
    // check and reject if form is INVALID
    // await thunkAPI.dispatch(validateForm());
    const mainState = thunkAPI.getState() as any;
    const state: State = mainState.mainForm;
    if (state.isValidForm === false) {
      return;
    }

    const forms = { ...state.forms };
    const par = { ...state.par };

    const now = Date.now();
    if (forms.id == null) {
      forms.id =
        'web.' +
        Math.floor(Math.random() * 0xffffff).toString(16) +
        now.toString(16);
    }
    forms.date = Math.floor(now / 1000);
    forms.fertig = true;
    forms.ergID = par.ergID;
    const apiPost = await axios.post('/api/agbdata.php', forms);
    if ((args as SubmitArgs)?.afterSubmit !== undefined) {
      (args as SubmitArgs).afterSubmit();
    }
  }
);

const slice = createSlice({
  name: 'tasko-form',
  initialState,
  reducers: {
    toggleHtmlInfos(state) {
      state.isHtmlInfosOpen = !state.isHtmlInfosOpen;
    },
    closeDialog(state) {
      state.isDialogOpen = false;
    },
    setSubmitState(state, action: PayloadAction<FormSubmitState>) {
      state.submitState = action.payload;
    },
    setText(state, action) {
      const { index, value } = action.payload;
      let { flatForms } = state;
      flatForms[index] = { ...flatForms[index], d: { inp: value } };
    },
    setCheck(state, action) {
      const { chkedLst, index } = action.payload;
      let { flatForms } = state;
      flatForms[index] = { ...flatForms[index], d: { chk: chkedLst } };
    },
    setFile(state, action) {
      const { uploadName, fileName, index } = action.payload;

      if (state.flatForms[index]?.d?.pics === undefined) {
        state.flatForms[index].d = { pics: [] };
      }
      state.flatForms[index].d.pics.push({
        realname: fileName,
        filename: uploadName,
      });
      state.mediaUploadCount++;
    },
    removeFile(state, action) {
      const { index, picIndex } = action.payload;
      state.flatForms[index].d.pics.splice(picIndex, 1);
      state.mediaUploadCount--;
    },
    incrementUploadedFileCount(state) {
      state.isSubmittingMedia = true;
      state.submitState = 'media';
      state.uploadedMediaCount++;
    },
    addForm(state, action) {
      const { formIndexes, repeatIndex } = action.payload;

      // copy exsting data from flat items to form structure
      let nestedForm = formsFromFlat(state.forms.items, state.flatForms);

      // add new fields into new form items
      let items = nestedForm;
      let lastItem;
      for (let i = 0; i < formIndexes.length; i++) {
        const idx = formIndexes[i];
        lastItem = items[idx];
        items = items[idx].items;
      }

      const repeatItem = lastItem.tmp[repeatIndex];
      if (lastItem.items === undefined) {
        lastItem.items = [];
      }
      // no duplicate list type allowed
      const haveListAlready =
        lastItem.items.length > 0 && lastItem.items[0].typ === 'list';
      if (haveListAlready) return;

      lastItem.items.push({ ...repeatItem, removable: true });

      // flatten again
      let flatForms = [];
      normalizeItem(nestedForm, 0, [], flatForms);
      state.flatForms = flatForms;
    },
    addPreFilledForm(state, action) {
      const { formIndexes, formItems } = action.payload;

      // copy exsting data from flat items to form structure
      let nestedForm = formsFromFlat(state.forms.items, state.flatForms);

      // add new fields into new form items
      let items = nestedForm;
      let lastItem;
      for (let i = 0; i < formIndexes.length - 1; i++) {
        const idx = formIndexes[i];
        lastItem = items[idx];
        items = items[idx].items;
      }

      if (lastItem.items === undefined) {
        lastItem.items = [];
      }

      const reachedMaxItemsCount =
        lastItem.max !== undefined && lastItem.items.length > lastItem.max;
      if (reachedMaxItemsCount) return;

      lastItem.items.push({ ...formItems, removable: true });

      // flatten again
      let flatForms = [];
      normalizeItem(nestedForm, 0, [], flatForms);
      state.flatForms = flatForms;
    },
    setListFilter(state, action) {
      state.listFilter = action.payload;
    },
    removeForm(state, action) {
      const indexToRemove = action.payload;
      state.flatForms[indexToRemove].isDelete = true;
      state.forms.items = formsFromFlat(state.forms.items, state.flatForms);

      // re-flatten
      let newFlatForms = [];
      normalizeItem(state.forms.items, 0, [], newFlatForms);
      state.flatForms = newFlatForms;
    },
    validateForm(state) {
      let isInvalid = false;
      for (let index = 0; index < state.flatForms.length; index++) {
        const { flatForms } = state;
        const form = flatForms[index];

        // check email format
        if (form.inptyp === 'email') {
          const passEmail =
            form?.d?.inp !== undefined &&
            form?.d?.inp !== '' &&
            validateEmail(form.d.inp) === true;

          if (passEmail) {
            delete state.flatForms[index].error;
          } else {
            state.flatForms[index].error = true;
            isInvalid = true;
          }
        }

        // check date min max
        const isCheckDateRange =
          form.typ === 'date' &&
          form.range !== undefined &&
          form.range[0] !== 0 &&
          form.range[1] !== 0 &&
          form?.d?.inp !== undefined &&
          form?.d?.inp !== '';
        if (isCheckDateRange) {
          const covertToDate = (range) => {
            const now = new Date();
            const res = new Date();
            res.setDate(now.getDate() + range);

            return res;
          };

          const min = covertToDate(form.range[0]).getTime();
          const max = covertToDate(form.range[1]).getTime();
          const currentInput = new Date(form.d.inp).getTime();
          const isWithinRange = currentInput >= min && currentInput <= max;
          if (isWithinRange) {
            delete state.flatForms[index].error;
            delete state.flatForms[index].isRangeError;
          } else {
            state.flatForms[index].error = true;
            state.flatForms[index].isRangeError = true;
            isInvalid = true;
          }
        }

        // check for required forms
        if (form.required !== true) {
          continue;
        }
        const inputType = form.typ;
        switch (inputType) {
          case 'txt':
          case 'num':
          case 'time':
          case 'spin': {
            const passTextRequired =
              form?.d?.inp !== undefined && form?.d?.inp !== '';
            if (passTextRequired) {
              delete state.flatForms[index].error;
            } else {
              state.flatForms[index].error = true;
              isInvalid = true;
            }
            break;
          }

          case 'chk': {
            // skip checkbox required check
            break;
          }

          case 'date': {
            // if rangeError is turned on ignore
            if (state.flatForms[index].isRangeError) {
              break;
            }
            const passTextRequired =
              form?.d?.inp !== undefined && form?.d?.inp !== '';
            if (passTextRequired) {
              delete state.flatForms[index].error;
            } else {
              state.flatForms[index].error = true;
              isInvalid = true;
            }
            break;
          }

          case 'media': {
            const passMediaRequired =
              inputType === 'media' &&
              form?.d?.pics?.length !== undefined &&
              form?.d?.pics?.length > 0;

            if (passMediaRequired) {
              delete state.flatForms[index].error;
            } else {
              state.flatForms[index].error = true;
              isInvalid = true;
            }
            break;
          }

          default:
            break;
        }
      }

      state.forms.items = formsFromFlat(state.forms.items, state.flatForms);
      state.isValidForm = !isInvalid;
    },
  },
  extraReducers: (builder) => {
    builder.addCase(importForm.fulfilled, (state, action) => {
      state.flatForms = action.payload.flattenForms;
      state.forms = action.payload.form;
      state.par = action.payload.par;
    });

    builder
      .addCase(submitForm.pending, (state) => {
        state.isSubmittingForm = true;
      })
      .addCase(submitForm.fulfilled, () => {
        return initialState;
      });

    builder.addCase(importFormInfo.fulfilled, (state, action) => {
      state.comment = action.payload.comment;
      state.htmlInfos = action.payload.htmlInfos;
    });

    builder.addCase(importList.fulfilled, (state, action) => {
      state.list = action.payload.list;
    });
  },
});

export const {
  validateForm,
  addForm,
  removeFile,
  removeForm,
  setCheck,
  setFile,
  setText,
  closeDialog,
  toggleHtmlInfos,
  addPreFilledForm,
  setListFilter,
} = slice.actions;
export default slice.reducer;

type MediaUpload = { file: File; name: string };
export const submitMedia = createAsyncThunk(
  'tasko-form/submit_single_media',
  async ({ file, name }: MediaUpload, thunkAPI) => {
    const formData = new FormData();
    formData.append('formData', file);
    await axios.post(`/api/public/${name}`, formData);
    thunkAPI.dispatch(slice.actions.incrementUploadedFileCount());
  }
);
