import { isEqual } from 'lodash';
import React, { FormEvent, PropsWithChildren, useEffect, useRef, useState } from 'react';
import { useCallbackRef } from '../../hooks/use-callback-ref';
import { BoxFormProps, SilkeBox } from '../silke-box';
import { ErrorMap, hasErrors, SilkeObjectField } from '../silke-object-field';

export type SilkeFormProps<T extends { [key: string]: any }> = {
  children?: React.ReactNode;
  value?: T;
  styles?: React.CSSProperties;
  className?: string;
  /** errors can be used to send in external errors to specific fields */
  errors?: { [key in keyof T]?: string };
  onChange?: (change: T, valid?: boolean) => void;
  onSubmit?: (form: T, valid?: boolean) => void;
  onErrors?: (errors: ErrorMap<T>) => void;
} & Omit<BoxFormProps, 'tag' | 'onChange' | 'onSubmit'>;

export function SilkeForm<T extends { [key: string]: any }>({
  children,
  value,
  errors,
  onChange,
  onSubmit,
  onErrors,
  ...rest
}: SilkeFormProps<T>) {
  const [internalValue, setInternalValue] = useState<T>(value || ({} as T));
  const prevValueRef = useRef<T>(internalValue);
  const errorsRef = useRef<ErrorMap<T>>({});
  const changeCbRef = useCallbackRef(onChange);
  const submitCbRef = useCallbackRef(onSubmit);

  useEffect(() => {
    if (value && !isEqual(value, internalValue)) {
      prevValueRef.current = value;
      setInternalValue(value);
    }
  }, [value]);

  const handleChange = (formData: T) => {
    setInternalValue(formData);
    if (changeCbRef.current) changeCbRef.current(formData, !hasErrors(errorsRef.current));
  };

  const handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    e.stopPropagation();
    if (submitCbRef.current) submitCbRef.current(internalValue, !hasErrors(errorsRef.current));
  };

  const handleReset = (e: FormEvent) => {
    e.preventDefault();
  };

  const handleErrors = (errors: ErrorMap<T>) => {
    errorsRef.current = errors;
    if (onErrors) onErrors(errors);
  };

  return (
    <SilkeBox tag="form" column gap="m" onSubmit={handleSubmit} onReset={handleReset} {...rest}>
      <SilkeObjectField
        name="form"
        errors={errors}
        value={internalValue}
        onChange={handleChange}
        onErrors={handleErrors}
      >
        {children}
      </SilkeObjectField>
    </SilkeBox>
  );
}

export const SilkeFormFields = (props: PropsWithChildren<any>) => {
  return (
    <SilkeBox column gap="l">
      {props.children}
    </SilkeBox>
  );
};
