/**
 * useTableStatePersistence – server-side table state with:
 * - Save only when state actually changed (stable JSON compare).
 * - Debounced save to avoid storms.
 * - Abort in-flight PUT when a new save is triggered (last write wins).
 * - Optional toast on persistent errors only.
 */
import { ref } from "vue";
import { useBaseToast } from "./useBaseToast.js";
import { tableStateService } from "../services/tableStateService.js";

const SAVE_DEBOUNCE_MS = 500;

/**
 * Stable JSON stringify for object comparison (keys sorted so order doesn't matter for top-level).
 */
function stableStateJson(state) {
  if (state == null) return JSON.stringify(state);
  const cwRaw =
    state.columnWidths && typeof state.columnWidths === "object"
      ? state.columnWidths
      : {};
  const columnWidths = Object.keys(cwRaw)
    .sort()
    .reduce((acc, k) => {
      const v = cwRaw[k];
      if (typeof v === "number" && Number.isFinite(v)) acc[k] = v;
      return acc;
    }, {});
  const normalized = {
    visibleColumns: state.visibleColumns ?? [],
    columnOrder: state.columnOrder ?? [],
    globalSearch: state.globalSearch ?? "",
    filters: state.filters ?? {},
    columnWidths,
    columnDefaultsSig:
      typeof state.columnDefaultsSig === "string" ? state.columnDefaultsSig : "",
  };
  return JSON.stringify(normalized);
}

/**
 * @param {string} tableKey
 * @param {() => object} getState - function that returns current state { visibleColumns, columnOrder, globalSearch, filters, columnWidths? }
 * @param {{ showToastOnError?: boolean }} [options]
 * @returns {{ load: () => Promise<object|null>, scheduleSave: () => void, flushSave: () => Promise<void>, resetServerState: () => Promise<void>, saveError: import('vue').Ref<boolean> }}
 */
export function useTableStatePersistence(tableKey, getState, options = {}) {
  const { showToastOnError = true } = options;
  const toast = useBaseToast();
  const saveError = ref(false);

  let lastSavedJson = null;
  let debounceTimer = null;
  let abortController = null;

  /**
   * Load state from server. Returns state object or null.
   */
  async function load() {
    try {
      const { data } = await tableStateService.get(tableKey);
      saveError.value = false;
      return data?.state ?? null;
    } catch (err) {
      if (showToastOnError && err?.response?.status >= 400) {
        toast.add({
          severity: "warn",
          summary: "Tabelinstellingen",
          detail:
            err?.response?.data?.message || "Kon instellingen niet laden.",
          life: 4000,
        });
      }
      saveError.value = true;
      return null;
    }
  }

  async function performSave() {
    const state = getState();
    const json = stableStateJson(state);
    if (json === lastSavedJson) return;

    if (abortController) abortController.abort();
    abortController = new AbortController();
    const signal = abortController.signal;

    try {
      await tableStateService.put(tableKey, state, { signal });
      lastSavedJson = json;
      saveError.value = false;
    } catch (err) {
      if (
        err?.name === "CanceledError" ||
        err?.code === "ERR_CANCELED" ||
        signal?.aborted
      )
        return;
      saveError.value = true;
      if (showToastOnError && err?.response?.status >= 400) {
        const msg =
          err?.response?.data?.errors?.state?.[0] ||
          err?.response?.data?.message ||
          "Kon instellingen niet opslaan.";
        toast.add({
          severity: "error",
          summary: "Tabelinstellingen",
          detail: msg,
          life: 5000,
        });
      }
    } finally {
      if (abortController?.signal === signal) abortController = null;
    }
  }

  /**
   * Persist current state (debounced, only if changed, cancel previous request on new trigger).
   */
  function scheduleSave() {
    if (debounceTimer) clearTimeout(debounceTimer);
    debounceTimer = setTimeout(async () => {
      debounceTimer = null;
      await performSave();
    }, SAVE_DEBOUNCE_MS);
  }

  /**
   * Flush any pending debounced save immediately (used before unmount/navigation).
   */
  async function flushSave() {
    if (debounceTimer) {
      clearTimeout(debounceTimer);
      debounceTimer = null;
    }
    await performSave();
  }

  /**
   * Delete server state (e.g. reset to default). Caller should then set local state to defaults.
   */
  async function resetServerState() {
    if (debounceTimer) {
      clearTimeout(debounceTimer);
      debounceTimer = null;
    }
    if (abortController) {
      abortController.abort();
      abortController = null;
    }
    try {
      await tableStateService.delete(tableKey);
      lastSavedJson = null;
      saveError.value = false;
    } catch (err) {
      saveError.value = true;
      if (showToastOnError && err?.response?.status >= 400) {
        toast.add({
          severity: "warn",
          summary: "Tabelinstellingen",
          detail:
            err?.response?.data?.message || "Kon instellingen niet resetten.",
          life: 4000,
        });
      }
    }
  }

  /**
   * Call after applying loaded state so we don't immediately re-save.
   */
  function markSaved(state) {
    lastSavedJson = stableStateJson(state);
  }

  return {
    load,
    scheduleSave,
    flushSave,
    resetServerState,
    markSaved,
    saveError,
  };
}
