"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import type { z } from "zod";
export const CHECK_FIELDS = "Please check the highlighted fields.";
type FieldSchema = { shape: Record<string, z.ZodType> };
export function useFieldValidation(schema: FieldSchema) {
  const [errors, setErrors] = useState<Record<string, string>>({});
  const dirty = useRef(new Set<string>()),
    deferred = useRef(new Set<HTMLInputElement>()),
    pressed = useRef(false);
  const check = useCallback(
    (name: string, value: string, eager: boolean) => {
      if (!Object.hasOwn(schema.shape, name)) return;
      const result = schema.shape[name].safeParse(value),
        message = result.success ? undefined : result.error.issues[0]?.message;
      setErrors((previous) => {
        if (previous[name] === message || (!eager && !previous[name]))
          return previous;
        const next = { ...previous };
        if (message) next[name] = message;
        else delete next[name];
        return next;
      });
    },
    [schema],
  );
  const flush = useCallback(
    (turnstileDone = false) => {
      if (
        pressed.current ||
        (!turnstileDone &&
          document.activeElement?.closest(".turnstile-space > div"))
      )
        return;
      for (const field of deferred.current)
        check(field.name, field.value, true);
      deferred.current.clear();
    },
    [check],
  );
  useEffect(() => {
    const press = (event: Event) => {
        if ((event as MouseEvent).button === 0) pressed.current = true;
      },
      release = () => {
        pressed.current = false;
        setTimeout(flush);
      };
    const listeners = [
      ["pointerdown", press],
      ["mousedown", press],
      ["pointerup", release],
      ["mouseup", release],
      ["pointercancel", release],
      ["keydown", release],
      ["contextmenu", release],
      [
        "pointermove",
        (event: Event) => {
          if (pressed.current && !(event as PointerEvent).buttons) release();
        },
      ],
      [
        "change",
        (event: Event) => {
          if (event.target instanceof HTMLSelectElement) release();
        },
      ],
      [
        "blur",
        (event: Event) => {
          if (event.target === window) release();
        },
      ],
      ["focus", () => flush()],
    ] as const;
    for (const [type, listener] of listeners)
      window.addEventListener(type, listener, true);
    return () => {
      for (const [type, listener] of listeners)
        window.removeEventListener(type, listener, true);
    };
  }, [flush]);
  const onChange = (event: React.ChangeEvent<HTMLFormElement>) => {
    const field = event.target as unknown as HTMLInputElement;
    dirty.current.add(field.name);
    check(field.name, field.value, field instanceof HTMLSelectElement);
  };
  const onBlur = (event: React.FocusEvent<HTMLFormElement>) => {
    const field = event.target as unknown as HTMLInputElement;
    if (document.activeElement === field || !dirty.current.has(field.name))
      return;
    deferred.current.add(field);
    setTimeout(flush);
  };
  const report = (issues: z.ZodError["issues"]) => {
    const next: Record<string, string> = {};
    for (const issue of issues) {
      const field = String(issue.path[0]);
      next[field] ??= issue.message;
    }
    setErrors(next);
    return Object.keys(next)[0];
  };
  const clear = () => setErrors({});
  const reset = () => {
    dirty.current.clear();
    deferred.current.clear();
  };
  return { errors, flush, onChange, onBlur, report, clear, reset };
}
