/**
 * Zod form utilities for React
 * Provides predictable error and touched state handling for forms
 */

import { useCallback, useMemo, useRef, useState } from "react";
import type { FormEvent } from "react";
import { ZodError, type ZodIssue, type ZodType } from "zod";
import { parseError } from "@/shared/utils/error-handling";

export type FormErrors<_TValues extends Record<string, unknown>> = Record<
  string,
  string | undefined
>;
export type FormTouched<_TValues extends Record<string, unknown>> = Record<
  string,
  boolean | undefined
>;

export interface ZodFormOptions<TValues extends Record<string, unknown>> {
  schema: ZodType<TValues>;
  initialValues: TValues;
  onSubmit?: (data: TValues) => Promise<void> | void;
}

export interface UseZodFormReturn<TValues extends Record<string, unknown>> {
  values: TValues;
  errors: FormErrors<TValues>;
  touched: FormTouched<TValues>;
  submitError: string | null;
  isSubmitting: boolean;
  isValid: boolean;
  setValue: <K extends keyof TValues>(field: K, value: TValues[K]) => void;
  setTouched: <K extends keyof TValues>(field: K, touched: boolean) => void;
  setTouchedField: <K extends keyof TValues>(field: K, touched?: boolean) => void;
  validate: () => boolean;
  validateField: <K extends keyof TValues>(field: K) => boolean;
  handleSubmit: (event?: FormEvent) => Promise<void>;
  reset: () => void;
}

function issuesToErrors<TValues extends Record<string, unknown>>(
  issues: ZodIssue[]
): FormErrors<TValues> {
  const nextErrors: FormErrors<TValues> = {};

  for (const issue of issues) {
    const [first, ...rest] = issue.path;
    const key = issue.path.join(".");

    if (typeof first === "string" && nextErrors[first] === undefined) {
      nextErrors[first] = issue.message;
    }

    if (key) {
      nextErrors[key] = issue.message;

      if (rest.length > 0) {
        const topLevelKey = String(first);
        if (nextErrors[topLevelKey] === undefined) {
          nextErrors[topLevelKey] = issue.message;
        }
      }
    } else if (nextErrors["_form"] === undefined) {
      nextErrors["_form"] = issue.message;
    }
  }

  return nextErrors;
}

function clearFieldErrors<TValues extends Record<string, unknown>>(
  fieldKey: string,
  prev: FormErrors<TValues>
): FormErrors<TValues> {
  const prefix = `${fieldKey}.`;
  const hasDirectError = prev[fieldKey] !== undefined;
  const hasNestedError = Object.keys(prev).some(key => key.startsWith(prefix));

  if (!hasDirectError && !hasNestedError) {
    return prev;
  }

  const next: FormErrors<TValues> = { ...prev };
  delete next[fieldKey];
  for (const key of Object.keys(next)) {
    if (key.startsWith(prefix)) {
      delete next[key];
    }
  }
  return next;
}

function clearFormLevelError<TValues extends Record<string, unknown>>(
  prev: FormErrors<TValues>
): FormErrors<TValues> {
  if (prev["_form"] === undefined) return prev;
  const next: FormErrors<TValues> = { ...prev };
  delete next["_form"];
  return next;
}

function buildFieldErrors<TValues extends Record<string, unknown>>(
  prev: FormErrors<TValues>,
  fieldKey: string,
  relatedIssues: ZodIssue[],
  allIssues: ZodIssue[]
): FormErrors<TValues> {
  const next: FormErrors<TValues> = { ...prev };

  if (relatedIssues.length > 0) {
    next[fieldKey] = relatedIssues[0]?.message ?? "";
    for (const issue of relatedIssues) {
      const nestedKey = issue.path.join(".");
      if (nestedKey) {
        next[nestedKey] = issue.message;
      }
    }
  } else {
    delete next[fieldKey];
  }

  const formLevelIssue = allIssues.find(issue => issue.path.length === 0);
  if (formLevelIssue) {
    next["_form"] = formLevelIssue.message;
  } else if (relatedIssues.length === 0) {
    delete next["_form"];
  }

  return next;
}

function applySubmitError<TValues extends Record<string, unknown>>(
  setErrors: (fn: (prev: FormErrors<TValues>) => FormErrors<TValues>) => void,
  setTouchedState: (fn: (prev: FormTouched<TValues>) => FormTouched<TValues>) => void,
  setSubmitError: (msg: string) => void,
  error: unknown
): void {
  const parsed = parseError(error);
  const message = parsed.message;
  setSubmitError(message);

  if (parsed.fieldErrors && Object.keys(parsed.fieldErrors).length > 0) {
    setErrors(prev => ({ ...prev, ...parsed.fieldErrors, ["_form"]: message }));
    setTouchedState(prev => {
      const next = { ...prev };
      for (const key of Object.keys(parsed.fieldErrors!)) {
        next[key] = true;
      }
      return next;
    });
  } else {
    setErrors(prev => ({ ...prev, ["_form"]: message }));
  }
}

export function useZodForm<TValues extends Record<string, unknown>>({
  schema,
  initialValues,
  onSubmit,
}: ZodFormOptions<TValues>): UseZodFormReturn<TValues> {
  const [values, setValues] = useState<TValues>(initialValues);
  const [errors, setErrors] = useState<FormErrors<TValues>>({});
  const [touched, setTouchedState] = useState<FormTouched<TValues>>({});
  const [submitError, setSubmitError] = useState<string | null>(null);
  const [isSubmitting, setIsSubmitting] = useState(false);

  const clearFieldError = useCallback((field: keyof TValues) => {
    setErrors(prev => clearFieldErrors(String(field), prev));
  }, []);

  const validate = useCallback((): boolean => {
    try {
      schema.parse(values);
      setErrors({});
      return true;
    } catch (error) {
      if (error instanceof ZodError) {
        setErrors(issuesToErrors<TValues>(error.issues));
      }
      return false;
    }
  }, [schema, values]);

  const validateField = useCallback(
    <K extends keyof TValues>(field: K): boolean => {
      const result = schema.safeParse(values);

      if (result.success) {
        clearFieldError(field);
        setErrors(prev => clearFormLevelError(prev));
        return true;
      }

      const relatedIssues = result.error.issues.filter(issue => issue.path[0] === field);
      setErrors(prev => buildFieldErrors(prev, String(field), relatedIssues, result.error.issues));
      return relatedIssues.length === 0;
    },
    [schema, values, clearFieldError]
  );

  const setValue = useCallback(
    <K extends keyof TValues>(field: K, value: TValues[K]): void => {
      setValues(prev => ({ ...prev, [field]: value }));
      clearFieldError(field);
    },
    [clearFieldError]
  );

  const setTouched = useCallback(<K extends keyof TValues>(field: K, value: boolean): void => {
    setTouchedState(prev => ({ ...prev, [String(field)]: value }));
  }, []);

  const setTouchedField = useCallback(
    <K extends keyof TValues>(field: K, value: boolean = true): void => {
      setTouched(field, value);
      void validateField(field);
    },
    [setTouched, validateField]
  );

  const handleSubmit = useCallback(
    async (event?: FormEvent): Promise<void> => {
      event?.preventDefault();
      if (!onSubmit) return;
      if (!validate()) return;

      setIsSubmitting(true);
      setSubmitError(null);
      setErrors(prev => clearFormLevelError(prev));

      try {
        await onSubmit(values);
      } catch (error) {
        applySubmitError(setErrors, setTouchedState, setSubmitError, error);
      } finally {
        setIsSubmitting(false);
      }
    },
    [validate, onSubmit, values]
  );

  const initialValuesRef = useRef(initialValues);
  initialValuesRef.current = initialValues;

  const reset = useCallback((): void => {
    setValues(initialValuesRef.current);
    setErrors({});
    setTouchedState({});
    setSubmitError(null);
    setIsSubmitting(false);
  }, []);

  const isValid = useMemo(() => Object.values(errors).every(error => !error), [errors]);

  return useMemo(
    () => ({
      values,
      errors,
      touched,
      submitError,
      isSubmitting,
      isValid,
      setValue,
      setTouched,
      setTouchedField,
      validate,
      validateField,
      handleSubmit,
      reset,
    }),
    [
      values,
      errors,
      touched,
      submitError,
      isSubmitting,
      isValid,
      setValue,
      setTouched,
      setTouchedField,
      validate,
      validateField,
      handleSubmit,
      reset,
    ]
  );
}
