import { format } from 'date-fns';
import { isPossiblePhoneNumber } from 'libphonenumber-js';
import { isNil } from 'lodash';
import { z } from 'zod';

import {
  type DynamicFormComponent,
  type ErrorTextsDefinition,
  type SelectComponent,
} from '../types';

import { fileSchema, numberSchema, stringSchema } from './validationSchemas';

const DEFAULT_STAR_RATING_MIN = 1;
const DEFAULT_STAR_RATING_MAX = 5;
const MAX_PATTERN_LENGTH = 256;

/**
 * Function to get the component validationschema
 * @returns The validation schema for the component
 */
export const getComponentSchema = (
  component: DynamicFormComponent,
  errorTexts: ErrorTextsDefinition,
) => {
  const emptyFieldError = errorTexts.required;

  const applyRequiredRefine = (schema: any) => {
    if (component.content.required) {
      return schema.refine((value: any) => !isNil(value), {
        message: emptyFieldError,
      });
    }
    return schema;
  };

  const numberPreprocess = (val: any) => {
    if (isNil(val) || val === '') return null;
    const num = Number(val);
    return isNaN(num) ? val : num;
  };

  const datePreprocess = (pattern?: string) => (val: any) => {
    if (isNil(val) || val === '') return null;

    if (!pattern) return val;

    const date = new Date(val as string);
    if (isNaN(date.getTime())) return null;

    return format(date, pattern);
  };

  const dropDownPreprocess = (val: any, dropdownComponent: SelectComponent) => {
    if (isNil(val) || val === '') return null;

    return (
      dropdownComponent.content.choices?.findIndex(
        choice => choice === val.label,
      ) ?? null
    );
  };

  let baseSchema;

  switch (component.type) {
    case 'MULTIPLE_CHOICE':
      baseSchema = applyRequiredRefine(
        numberSchema(errorTexts.required).nullable(),
      );
      break;

    case 'STAR_RATING':
      baseSchema = applyRequiredRefine(
        z.preprocess(
          numberPreprocess,
          numberSchema(errorTexts.required)
            .min(component.validations?.minimum ?? DEFAULT_STAR_RATING_MIN)
            .max(component.validations?.maximum ?? DEFAULT_STAR_RATING_MAX)
            .nullable(),
        ),
      );
      break;

    case 'CHECKBOX':
      baseSchema = z
        .array(numberSchema(errorTexts.required))
        .min(
          component.validations?.minItems ??
            (component.content.required ? 1 : 0),
          emptyFieldError,
        );
      break;

    case 'TEXT':
      baseSchema = stringSchema(errorTexts.required);

      if (component.content.required) {
        baseSchema = baseSchema.min(1, emptyFieldError);
      }

      if (component.validations?.maxLength) {
        baseSchema = baseSchema.max(component.validations.maxLength);
      }

      if (component.validations?.pattern) {
        const pattern = component.validations.pattern;
        if (pattern.length <= MAX_PATTERN_LENGTH) {
          const re = new RegExp(`^(?:${pattern})$`);
          baseSchema = baseSchema.regex(re);
        }
      }
      break;

    case 'DROPDOWN':
      baseSchema = applyRequiredRefine(
        z.preprocess(
          val => dropDownPreprocess(val, component as SelectComponent),
          numberSchema(errorTexts.required).nullable(),
        ),
      );
      break;

    case 'FILE':
      baseSchema = z
        .array(fileSchema(errorTexts.required))
        .min(
          component.validations?.minItems ??
            (component.content.required ? 1 : 0),
          emptyFieldError,
        );

      if (component.validations?.maxItems) {
        baseSchema = baseSchema.max(component.validations.maxItems);
      }
      break;

    case 'SIGNATURE':
      baseSchema = applyRequiredRefine(fileSchema(errorTexts.required));
      break;

    case 'FLOAT':
      baseSchema = applyRequiredRefine(
        z.preprocess(
          numberPreprocess,
          numberSchema(errorTexts.required).nullable(),
        ),
      );
      break;

    case 'INTEGER':
      baseSchema = applyRequiredRefine(
        z.preprocess(
          numberPreprocess,
          numberSchema(errorTexts.required).int().nullable(),
        ),
      );
      break;

    case 'DATE':
      baseSchema = applyRequiredRefine(
        z.preprocess(
          datePreprocess(component.validations?.pattern),
          stringSchema(errorTexts.required).nullable(),
        ),
      );
      break;

    case 'TIME':
      baseSchema = applyRequiredRefine(
        z.preprocess(
          datePreprocess(component.validations?.pattern || 'HH:mm'),
          stringSchema(errorTexts.required).nullable(),
        ),
      );
      break;

    case 'PHONE':
      // PHONE follows the same pattern as TEXT (both wrap stringSchema), so
      // the required guard is applied manually instead of via applyRequiredRefine
      // (which only checks for nil and would let "" through for string fields).
      baseSchema = stringSchema(errorTexts.required);

      if (component.content.required) {
        baseSchema = baseSchema.min(1, emptyFieldError);
      }

      // `isPossiblePhoneNumber` is called without a country argument because
      // FormInputPhone (the UI that feeds this field) uses MuiTelInput with
      // `forceCallingCode`, so values always arrive in E.164 international
      // format (e.g. "+54 11 5735 7914"). If a value ever reaches the schema
      // in national-only format, validation will correctly reject it as
      // unparseable rather than silently passing.
      baseSchema = baseSchema.refine(
        (value: string) => !value || isPossiblePhoneNumber(value),
        { message: errorTexts.invalidPhone ?? 'Invalid phone number' },
      );
      break;

    default:
      if (component.content.required) {
        baseSchema = z
          .any()
          .refine(
            value =>
              Boolean(value) && (!Array.isArray(value) || value.length > 0),
            { message: emptyFieldError },
          );
      } else {
        baseSchema = z.any().optional();
      }
      break;
  }

  return baseSchema;
};
