/**
 * Generic config hook for loading and saving a single entity.
 */
import { useCallback, useEffect, useRef, useState } from "react";
import { handleFetchError } from "../utils/fetchError";

export interface UseConfigItemResult<T, U> {
  data: T | null;
  loading: boolean;
  error: string | null;
  saving: boolean;
  saveError: string | null;
  refresh: () => void;
  save: (update: U) => Promise<void>;
}

export interface UseConfigItemOptions<T, U> {
  fetchFn: (signal: AbortSignal) => Promise<T>;
  saveFn: (update: U) => Promise<void>;
  mergeOnSave?: (prev: T | null, update: U) => T | null;
}

export function useConfigItem<T, U>(
  options: UseConfigItemOptions<T, U>
): UseConfigItemResult<T, U> {
  const { fetchFn, saveFn, mergeOnSave } = options;
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [saving, setSaving] = useState(false);
  const [saveError, setSaveError] = useState<string | null>(null);
  const abortRef = useRef<AbortController | null>(null);

  const refresh = useCallback((): void => {
    abortRef.current?.abort();
    const controller = new AbortController();
    abortRef.current = controller;

    setLoading(true);
    setError(null);

    fetchFn(controller.signal)
      .then((nextData) => {
        if (controller.signal.aborted) return;
        setData(nextData);
        setLoading(false);
      })
      .catch((err: unknown) => {
        if (controller.signal.aborted) return;
        handleFetchError(err, setError, "Failed to load data");
        setLoading(false);
      });
  }, [fetchFn]);

  useEffect(() => {
    refresh();

    return (): void => {
      abortRef.current?.abort();
    };
  }, [refresh]);

  const save = useCallback(
    async (update: U): Promise<void> => {
      setSaving(true);
      setSaveError(null);

      try {
        await saveFn(update);
        if (mergeOnSave) {
          setData((prevData) => mergeOnSave(prevData, update));
        }
      } catch (err: unknown) {
        const message = err instanceof Error ? err.message : "Failed to save data";
        setSaveError(message);
        throw err;
      } finally {
        setSaving(false);
      }
    },
    [saveFn, mergeOnSave]
  );

  return { data, loading, error, saving, saveError, refresh, save };
}
