/**
 * Form Page Factory
 */

import { mdiChartTimelineVariant } from '@mdi/js';
import Head from 'next/head';
import React, { ReactElement, useEffect, useState } from 'react';
import { Field, Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import CardBox from '../components/CardBox';
import LayoutAuthenticated from '../layouts/Authenticated';
import SectionMain from '../components/SectionMain';
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
import FormField from '../components/FormField';
import BaseDivider from '../components/BaseDivider';
import BaseButtons from '../components/BaseButtons';
import BaseButton from '../components/BaseButton';
import { getPageTitle } from '../config';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
import { SelectField } from '../components/SelectField';
import { SelectFieldMany } from '../components/SelectFieldMany';
import { SwitchField } from '../components/SwitchField';
import FormImagePicker from '../components/FormImagePicker';
import type { RootState } from '../stores/store';
import type { AsyncThunk } from '@reduxjs/toolkit';

// Field types supported by the factory
export type FormFieldType =
  | 'text'
  | 'email'
  | 'number'
  | 'textarea'
  | 'select'
  | 'selectMany'
  | 'enumSelect'
  | 'switch'
  | 'image'
  | 'date'
  | 'datetime'
  | 'password'
  | 'custom';

// Field configuration for dynamic form rendering
export interface FormFieldConfig {
  name: string;
  label: string;
  type: FormFieldType;
  placeholder?: string;
  itemRef?: string; // For select fields - entity to fetch from
  showField?: string; // For select fields - field to display
  options?: Array<{ value: string; label: string }>; // For enum selects
  path?: string; // For image fields
  schema?: { size?: number; formats?: string[] }; // For image fields
  component?: React.ComponentType<unknown>; // For custom field types
  props?: Record<string, unknown>; // Additional props for custom components
}

interface FormPageConfig<T> {
  entityName: string;
  entityTitle: string;
  singularTitle: string;
  mode: 'create' | 'edit';
  sliceSelector: (state: RootState) => { [key: string]: T | T[] };
  fetchAction?: AsyncThunk<unknown, { id: string }, object>;
  createAction?: AsyncThunk<unknown, T, object>;
  updateAction?: AsyncThunk<unknown, { id: string; data: T }, object>;
  permission: string;
  initialValues: T;
  fields: FormFieldConfig[];
  validate?: (values: T) => Record<string, string>;
}

export function createFormPage<T extends Record<string, unknown>>(
  config: FormPageConfig<T>,
) {
  const {
    entityName,
    entityTitle,
    singularTitle,
    mode,
    sliceSelector,
    fetchAction,
    createAction,
    updateAction,
    permission,
    initialValues,
    fields,
    validate,
  } = config;

  const FormPage = () => {
    const router = useRouter();
    const dispatch = useAppDispatch();
    const entityState = useAppSelector(sliceSelector);
    const entityData = entityState[entityName];
    const { id } = router.query as { id?: string };

    const [formValues, setFormValues] = useState<T>(initialValues);

    // Fetch data for edit mode
    useEffect(() => {
      if (mode === 'edit' && id && fetchAction) {
        dispatch(fetchAction({ id }));
      }
    }, [id, dispatch]);

    // Sync form values with fetched data
    useEffect(() => {
      if (
        mode === 'edit' &&
        entityData &&
        typeof entityData === 'object' &&
        !Array.isArray(entityData)
      ) {
        const newValues = { ...initialValues };
        Object.keys(initialValues).forEach((key) => {
          if (key in (entityData as Record<string, unknown>)) {
            (newValues as Record<string, unknown>)[key] = (
              entityData as Record<string, unknown>
            )[key];
          }
        });
        setFormValues(newValues);
      }
    }, [entityData]);

    const handleSubmit = async (data: T) => {
      if (mode === 'edit' && updateAction && id) {
        await dispatch(updateAction({ id, data }));
      } else if (mode === 'create' && createAction) {
        await dispatch(createAction(data));
      }
      await router.push(`/${entityName}/${entityName}-list`);
    };

    const pageTitle =
      mode === 'edit' ? `Edit ${singularTitle}` : `New ${singularTitle}`;

    return (
      <>
        <Head>
          <title>{getPageTitle(pageTitle)}</title>
        </Head>
        <SectionMain>
          <SectionTitleLineWithButton
            icon={mdiChartTimelineVariant}
            title={pageTitle}
            main
          >
            {''}
          </SectionTitleLineWithButton>
          <CardBox>
            <Formik
              enableReinitialize
              initialValues={formValues}
              validate={validate}
              onSubmit={(values) => handleSubmit(values)}
            >
              <Form>
                {fields.map((field) => (
                  <FormField
                    key={field.name}
                    label={field.label}
                    labelFor={field.name}
                  >
                    {renderField(field, formValues)}
                  </FormField>
                ))}

                <BaseDivider />
                <BaseButtons>
                  <BaseButton type='submit' color='info' label='Submit' />
                  <BaseButton type='reset' color='info' outline label='Reset' />
                  <BaseButton
                    type='reset'
                    color='danger'
                    outline
                    label='Cancel'
                    onClick={() =>
                      router.push(`/${entityName}/${entityName}-list`)
                    }
                  />
                </BaseButtons>
              </Form>
            </Formik>
          </CardBox>
        </SectionMain>
      </>
    );
  };

  FormPage.getLayout = function getLayout(page: ReactElement) {
    return (
      <LayoutAuthenticated permission={permission}>{page}</LayoutAuthenticated>
    );
  };

  return FormPage;
}

// Helper function to render form fields based on type
function renderField<T>(
  field: FormFieldConfig,
  formValues: T,
): React.ReactNode {
  const {
    name,
    type,
    placeholder,
    itemRef,
    showField,
    options,
    path,
    schema,
    component: CustomComponent,
    props,
  } = field;

  switch (type) {
    case 'text':
    case 'email':
    case 'password':
      return (
        <Field
          name={name}
          type={type}
          placeholder={placeholder || field.label}
        />
      );

    case 'number':
      return (
        <Field
          name={name}
          type='number'
          placeholder={placeholder || field.label}
        />
      );

    case 'textarea':
      return (
        <Field
          as='textarea'
          name={name}
          placeholder={placeholder || field.label}
        />
      );

    case 'select':
      return (
        <Field
          name={name}
          id={name}
          component={SelectField}
          options={(formValues as Record<string, unknown>)[name]}
          itemRef={itemRef}
          showField={showField}
        />
      );

    case 'enumSelect':
      return (
        <Field name={name} id={name} component='select'>
          {options?.map((opt) => (
            <option key={opt.value} value={opt.value}>
              {opt.label}
            </option>
          ))}
        </Field>
      );

    case 'selectMany':
      return (
        <Field
          name={name}
          id={name}
          component={SelectFieldMany}
          options={(formValues as Record<string, unknown>)[name] || []}
          itemRef={itemRef}
          showField={showField}
        />
      );

    case 'switch':
      return <Field name={name} id={name} component={SwitchField} />;

    case 'image':
      return (
        <Field
          label={field.label}
          color='info'
          path={path}
          name={name}
          id={name}
          schema={schema || { size: undefined, formats: undefined }}
          component={FormImagePicker}
        />
      );

    case 'date':
      return (
        <Field
          name={name}
          type='date'
          placeholder={placeholder || field.label}
        />
      );

    case 'datetime':
      return (
        <Field
          name={name}
          type='datetime-local'
          placeholder={placeholder || field.label}
        />
      );

    case 'custom':
      if (CustomComponent) {
        return (
          <Field name={name} id={name} component={CustomComponent} {...props} />
        );
      }
      return <Field name={name} placeholder={placeholder || field.label} />;

    default:
      return <Field name={name} placeholder={placeholder || field.label} />;
  }
}

export default createFormPage;
