"use client";

import { LoaderCircle, Save } from "lucide-react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useRef, useState } from "react";

import { Button } from "@/components/ui/button";

type FormSaveButtonProps = {
  formId?: string;
  formIds?: string[];
  formSelector?: string;
  formSelectors?: string[];
  label?: string;
  reloadDocumentOnSuccess?: boolean;
};

function serializeForm(form: HTMLFormElement) {
  return JSON.stringify(
    Array.from(new FormData(form).entries()).map(([key, value]) => [
      key,
      value instanceof File ? `${value.name}:${value.size}:${value.type}` : value,
    ]),
  );
}

export function FormSaveButton({
  formId,
  formIds,
  formSelector,
  formSelectors,
  label = "Speichern",
  reloadDocumentOnSuccess = false,
}: FormSaveButtonProps) {
  const pathname = usePathname();
  const router = useRouter();
  const searchParams = useSearchParams();
  const [activeFormId, setActiveFormId] = useState<string | null>(formId ?? null);
  const [isDirty, setIsDirty] = useState(false);
  const [isSubmitting, setIsSubmitting] = useState(false);
  const baselineRef = useRef<Map<string, string>>(new Map());
  const dirtyFormsRef = useRef<Set<string>>(new Set());
  const activeFormIdRef = useRef<string | null>(formId ?? null);
  const frameRef = useRef<number | null>(null);
  const pendingSubmissionRef = useRef<{
    formId: string;
    originUrl: string;
  } | null>(null);

  const currentUrl = `${pathname}?${searchParams.toString()}`;

  useEffect(() => {
    if (!formId && !formIds?.length && !formSelector && !formSelectors?.length) {
      activeFormIdRef.current = null;
      pendingSubmissionRef.current = null;
      setActiveFormId(null);
      setIsDirty(false);
      setIsSubmitting(false);
      dirtyFormsRef.current.clear();
    }
  }, [formId, formIds, formSelector, formSelectors]);

  useEffect(() => {
    const formsById = [formId, ...(formIds ?? [])]
      .filter((value): value is string => Boolean(value))
      .map((id) => document.getElementById(id))
      .filter((form): form is HTMLFormElement => form instanceof HTMLFormElement);
    const formsBySelector = [formSelector, ...(formSelectors ?? [])]
      .filter((value): value is string => Boolean(value))
      .flatMap((selector) => Array.from(document.querySelectorAll(selector)))
      .filter((form): form is HTMLFormElement => form instanceof HTMLFormElement);
    const forms = Array.from(new Map([...formsById, ...formsBySelector].map((form) => [form.id, form])).values());

    if (forms.length === 0) {
      if (frameRef.current !== null) {
        cancelAnimationFrame(frameRef.current);
        frameRef.current = null;
      }
      baselineRef.current.clear();
      dirtyFormsRef.current.clear();
      activeFormIdRef.current = formId ?? null;
      setActiveFormId(formId ?? null);
      setIsDirty(false);
      setIsSubmitting(false);
      return undefined;
    }

    const availableIds = forms.map((form) => form.id).filter(Boolean);
    const fallbackFormId = availableIds[0] ?? null;

    const selectDirtyFormId = () => {
      const dirtyIds = availableIds.filter((id) => dirtyFormsRef.current.has(id));

      if (dirtyIds.length === 0) {
        return activeFormIdRef.current && availableIds.includes(activeFormIdRef.current)
          ? activeFormIdRef.current
          : fallbackFormId;
      }

      if (activeFormIdRef.current && dirtyFormsRef.current.has(activeFormIdRef.current)) {
        return activeFormIdRef.current;
      }

      return dirtyIds[0] ?? fallbackFormId;
    };

    const syncDirtyState = () => {
      const nextActiveFormId = selectDirtyFormId();

      activeFormIdRef.current = nextActiveFormId;
      setActiveFormId(nextActiveFormId);
      setIsDirty(dirtyFormsRef.current.size > 0);
    };

    const evaluateForm = (form: HTMLFormElement) => {
      const baseline = baselineRef.current.get(form.id);
      const current = serializeForm(form);

      if (baseline === undefined) {
        return;
      }

      if (current !== baseline) {
        dirtyFormsRef.current.add(form.id);
        return;
      }

      dirtyFormsRef.current.delete(form.id);
    };

    const scheduleSync = (nextActiveFormId?: string) => {
      if (nextActiveFormId) {
        activeFormIdRef.current = nextActiveFormId;
      }

      if (frameRef.current !== null) {
        cancelAnimationFrame(frameRef.current);
      }

      frameRef.current = requestAnimationFrame(() => {
        frameRef.current = null;
        for (const form of forms) {
          evaluateForm(form);
        }
        syncDirtyState();
      });
    };

    const syncBaseline = (form: HTMLFormElement) => {
      baselineRef.current.set(form.id, serializeForm(form));
      dirtyFormsRef.current.delete(form.id);
      syncDirtyState();
      setIsSubmitting(false);
    };

    const handleFormActivity = (form: HTMLFormElement) => {
      setIsSubmitting(false);
      scheduleSync(form.id);
    };

    const handleSubmit = (form: HTMLFormElement, event: SubmitEvent) => {
      if (pendingSubmissionRef.current) {
        event.preventDefault();
        event.stopPropagation();
        return;
      }

      activeFormIdRef.current = form.id;
      pendingSubmissionRef.current = {
        formId: form.id,
        originUrl: currentUrl,
      };
      setActiveFormId(form.id);
      setIsSubmitting(true);
      setIsDirty(false);
    };

    for (const form of forms) {
      if (!form.id) {
        continue;
      }

      syncBaseline(form);

      const onFocusIn = () => handleFormActivity(form);
      const onInput = () => handleFormActivity(form);
      const onChange = () => handleFormActivity(form);
      const onReset = () => syncBaseline(form);
      const onSubmit = (event: Event) => handleSubmit(form, event as SubmitEvent);

      form.addEventListener("focusin", onFocusIn);
      form.addEventListener("input", onInput);
      form.addEventListener("change", onChange);
      form.addEventListener("reset", onReset);
      form.addEventListener("submit", onSubmit);

      (form as HTMLFormElement & {
        __saveButtonHandlers?: {
          onFocusIn: () => void;
          onInput: () => void;
          onChange: () => void;
          onReset: () => void;
          onSubmit: (event: Event) => void;
        };
      }).__saveButtonHandlers = { onFocusIn, onInput, onChange, onReset, onSubmit };
    }

    const nextActive =
      activeFormIdRef.current && availableIds.includes(activeFormIdRef.current)
        ? activeFormIdRef.current
        : fallbackFormId;
    activeFormIdRef.current = nextActive;
    setActiveFormId(nextActive);
    for (const form of forms) {
      evaluateForm(form);
    }
    syncDirtyState();

    return () => {
      if (frameRef.current !== null) {
        cancelAnimationFrame(frameRef.current);
        frameRef.current = null;
      }

      for (const form of forms) {
        const handlers = (form as HTMLFormElement & {
          __saveButtonHandlers?: {
            onFocusIn: () => void;
            onInput: () => void;
            onChange: () => void;
            onReset: () => void;
            onSubmit: (event: Event) => void;
          };
        }).__saveButtonHandlers;

        if (!handlers) {
          continue;
        }

        form.removeEventListener("focusin", handlers.onFocusIn);
        form.removeEventListener("input", handlers.onInput);
        form.removeEventListener("change", handlers.onChange);
        form.removeEventListener("reset", handlers.onReset);
        form.removeEventListener("submit", handlers.onSubmit);
        delete (
          form as HTMLFormElement & {
            __saveButtonHandlers?: {
              onFocusIn: () => void;
              onInput: () => void;
              onChange: () => void;
              onReset: () => void;
              onSubmit: (event: Event) => void;
            };
          }
        ).__saveButtonHandlers;
      }
    };
  }, [currentUrl, formId, formIds, formSelector, formSelectors, pathname, searchParams]);

  useEffect(() => {
    const pendingSubmission = pendingSubmissionRef.current;

    if (!pendingSubmission) {
      return;
    }

    const hasError = searchParams.has("error");
    const hasSuccess = searchParams.has("success") || searchParams.has("__saved");
    const navigated = currentUrl !== pendingSubmission.originUrl;

    if (hasError) {
      pendingSubmissionRef.current = null;
      setIsSubmitting(false);
      dirtyFormsRef.current.delete(pendingSubmission.formId);
      return;
    }

    if (!hasSuccess && !navigated) {
      return;
    }

    pendingSubmissionRef.current = null;
    if (reloadDocumentOnSuccess) {
      window.location.reload();
      return;
    }

    router.refresh();

    if (!searchParams.has("__saved")) {
      return;
    }

    const nextParams = new URLSearchParams(searchParams.toString());
    nextParams.delete("__saved");

    const nextQuery = nextParams.toString();
    router.replace(nextQuery ? `${pathname}?${nextQuery}` : pathname, {
      scroll: false,
    });
  }, [currentUrl, pathname, reloadDocumentOnSuccess, router, searchParams]);

  return (
    <Button type="submit" form={activeFormId ?? undefined} disabled={!activeFormId || !isDirty || isSubmitting}>
      {isSubmitting ? <LoaderCircle className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
      {isSubmitting ? "Speichert..." : label}
    </Button>
  );
}
