import React from 'react';

type FieldValues = Record<string, unknown>;

interface Control {
  values: FieldValues;
  setValues: React.Dispatch<React.SetStateAction<FieldValues>>;
  setDirtyFields: React.Dispatch<React.SetStateAction<FieldValues>>;
}

function readInputValue(value: unknown): unknown {
  if (
    value
    && typeof value === 'object'
    && 'target' in value
    && value.target
    && typeof value.target === 'object'
  ) {
    const target = value.target as HTMLInputElement;
    return target.type === 'checkbox' ? target.checked : target.value;
  }
  return value;
}

export function useForm(options: { defaultValues?: FieldValues } = {}) {
  const [values, setValues] = React.useState<FieldValues>(options.defaultValues ?? {});
  // Faithful to react-hook-form: `formState.errors` keeps a STABLE object
  // identity across error changes (it proxies a store that is mutated in place),
  // with re-renders driven separately. A component that memoizes on `[errors]`
  // therefore never recomputes on error changes — a real footgun this stub must
  // reproduce so tests can catch it (an earlier `useState({})` handed out a NEW
  // reference per setError, which masked exactly that bug).
  const errorsRef = React.useRef<FieldValues>({});
  const [, bumpErrors] = React.useReducer((n: number) => n + 1, 0);
  const [dirtyFields, setDirtyFields] = React.useState<FieldValues>({});
  const control = React.useMemo<Control>(() => ({
    values,
    setValues,
    setDirtyFields,
  }), [values]);

  const setValue = React.useCallback((name: string, value: unknown, opts?: { shouldDirty?: boolean }) => {
    setValues(prev => ({ ...prev, [name]: value }));
    if (opts?.shouldDirty) {
      setDirtyFields(prev => ({ ...prev, [name]: true }));
    }
  }, []);

  const handleSubmit = React.useCallback((onSubmit: (data: FieldValues) => unknown | Promise<unknown>) => (
    async (event?: { preventDefault?: () => void }) => {
      event?.preventDefault?.();
      await onSubmit(values);
    }
  ), [values]);

  const reset = React.useCallback((nextValues: FieldValues = {}) => {
    setValues(nextValues);
    for (const k of Object.keys(errorsRef.current)) delete errorsRef.current[k];
    bumpErrors();
    setDirtyFields({});
  }, []);

  const register = React.useCallback((name: string) => ({
    name,
    onChange: (event: unknown) => {
      const nextValue = readInputValue(event);
      setValues(prev => ({ ...prev, [name]: nextValue }));
      setDirtyFields(prev => ({ ...prev, [name]: true }));
    },
  }), []);

  const unregister = React.useCallback((_name: string) => {}, []);
  const getValues = React.useCallback((name?: string) => (
    name ? values[name] : values
  ), [values]);
  const setError = React.useCallback((name: string, error: { type?: string; message?: string }) => {
    errorsRef.current[name] = error; // mutate in place — reference stays stable
    bumpErrors();
  }, []);
  const clearErrors = React.useCallback(() => {
    for (const k of Object.keys(errorsRef.current)) delete errorsRef.current[k];
    bumpErrors();
  }, []);

  return {
    control,
    handleSubmit,
    setValue,
    setError,
    clearErrors,
    getValues,
    register,
    unregister,
    reset,
    formState: {
      errors: errorsRef.current,
      isSubmitting: false,
      isDirty: Object.keys(dirtyFields).length > 0,
      dirtyFields,
    },
  };
}

export function useWatch({ control }: { control: Control }): FieldValues {
  return control.values;
}

export function Controller({
  name,
  control,
  render,
}: {
  name: string;
  control: Control;
  rules?: unknown;
  render: (props: { field: { value: unknown; onChange: (value: unknown) => void } }) => React.ReactElement;
}): React.ReactElement {
  const onChange = React.useCallback((value: unknown) => {
    const nextValue = readInputValue(value);
    control.setValues(prev => ({ ...prev, [name]: nextValue }));
    control.setDirtyFields(prev => ({ ...prev, [name]: true }));
  }, [control, name]);

  return render({
    field: {
      value: control.values[name],
      onChange,
    },
  });
}
