import { useEffect, useImperativeHandle, useRef } from 'react';
import { FormProvider, useForm } from 'react-hook-form';

import type { Ref } from 'react';
import type { SubmitHandler } from 'react-hook-form';
import type { FormApi, FormConfig, FormConfigField } from './types';

import { useI18n } from '~/src/lib/i18n';
import { Layout } from '~/src/ui/layouts/layout';
import { LayoutGrid } from '~/src/ui/layouts/layoutGrid';
import { Separator } from '~/src/ui/layouts/separator';
import { Button } from '~/src/ui/primitives/button';
import { FormCheckbox } from './FormCheckbox';
import { FormInput } from './FormInput';
import { FormLabelValue } from './FormLabelValue';
import { FormParagraph } from './FormParagraph';
import { FormRadio } from './FormRadio';
import { FormSelect } from './FormSelect';
import { FormSwitch } from './FormSwitch';
import { FormTextarea } from './FormTextarea';
import { materializeField, splitFieldsBySubmitPosition } from './utils';

export interface FormProps {
  config: FormConfig;
  ref?: Ref<FormApi | null>;
  onSubmit?: SubmitHandler<Record<string, any>>;
  onChange?: (data: Record<string, any>) => void;
  defaultValues?: Record<string, any>;
  mode?: 'onSubmit' | 'onChange' | 'onBlur' | 'all';
  size?: 'medium' | 'small';
}

export const Form = ({
  config,
  ref,
  onSubmit,
  onChange,
  defaultValues,
  mode,
  size = 'medium',
}: FormProps) => {
  const resolvedDefaults = {
    ...Object.fromEntries(
      (config.fields ?? [])
        .filter((f) => f.defaultValue !== undefined)
        .map((f) => [f.name, f.defaultValue])
    ),
    ...defaultValues,
  };

  const i18n = useI18n('form');
  const methods = useForm({ mode, defaultValues: resolvedDefaults });
  const formRef = useRef<HTMLFormElement>(null);
  const onChangeRef = useRef(onChange);
  onChangeRef.current = onChange;

  useEffect(() => {
    methods.reset(resolvedDefaults);
  }, [JSON.stringify(resolvedDefaults)]);

  useImperativeHandle(ref, () => ({
    submit() {
      formRef.current?.requestSubmit();
    },
  }));

  useEffect(() => {
    if (!onChangeRef.current) return;

    return methods.subscribe({
      formState: {
        values: true,
      },
      callback: ({ values }) => {
        onChangeRef.current?.(values);
      },
    });
  }, [methods.subscribe]);

  const onSubmitHandler: SubmitHandler<any> = (data) => {
    if (onSubmit) {
      onSubmit(data);
    }
  };

  const renderField = (fieldOrPreset: FormConfigField, index: number) => {
    const field = materializeField(fieldOrPreset, i18n);

    if (!field) {
      return null;
    }

    switch (field.type) {
      case 'textarea':
        return <FormTextarea key={field.name} {...field} size={size} />;
      case 'select':
        return <FormSelect key={field.name} {...field} size={size} />;
      case 'radio':
        return <FormRadio key={field.name} {...field} />;
      case 'boolean':
        return <FormSwitch key={field.name} {...field} />;
      case 'checkbox':
        return <FormCheckbox key={field.name} {...field} />;
      case 'keyValue':
        return <FormLabelValue key={field.name} {...field} />;
      case 'paragraph':
        return <FormParagraph key={field.name} {...field} />;
      case 'separator':
        return <Separator key={`separator-${index}`} />;
      default:
        return <FormInput key={field.name} {...field} size={size} />;
    }
  };

  const { above, below } = splitFieldsBySubmitPosition(config.fields ?? []);

  return (
    <FormProvider {...methods}>
      <form
        ref={formRef}
        noValidate
        onSubmit={methods.handleSubmit(onSubmitHandler)}
        style={{ width: '100%' }}
      >
        <Layout column gap="6">
          <LayoutGrid rowGap="5">
            {above.map((field, index) => renderField(field, index))}
          </LayoutGrid>
          {config.submit && (
            <Layout justify="center">
              <Button type="submit" variant="round">
                {config.submit.label || 'Submit'}
              </Button>
            </Layout>
          )}
          {below.length > 0 && (
            <LayoutGrid rowGap="5">
              {below.map((field, index) => renderField(field, index))}
            </LayoutGrid>
          )}
        </Layout>
      </form>
    </FormProvider>
  );
};
