"use client";

import {
  createContext,
  ReactNode,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
} from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

import {
  READER_DEFAULTS,
  ReaderSettings,
  ReaderSettingsPartial,
} from "@karakeep/shared/types/readers";

import { useTRPC } from "../trpc";

export interface UseReaderSettingsOptions {
  /**
   * Get local overrides (device-specific settings stored locally)
   */
  getLocalOverrides: () => ReaderSettingsPartial;
  /**
   * Save local overrides to local storage
   */
  saveLocalOverrides: (overrides: ReaderSettingsPartial) => void;
  /**
   * Optional session overrides (for live preview in web).
   * If provided, these take highest precedence.
   */
  sessionOverrides?: ReaderSettingsPartial;
  /**
   * Callback when session overrides should be cleared (after successful server save)
   */
  onClearSessionOverrides?: () => void;
}

export function useReaderSettings(options: UseReaderSettingsOptions) {
  const api = useTRPC();
  const {
    getLocalOverrides,
    saveLocalOverrides,
    sessionOverrides = {},
    onClearSessionOverrides,
  } = options;

  const [localOverrides, setLocalOverrides] = useState<ReaderSettingsPartial>(
    {},
  );
  const [pendingServerSave, setPendingServerSave] =
    useState<ReaderSettings | null>(null);

  const { data: serverSettings } = useQuery(api.users.settings.queryOptions());
  const queryClient = useQueryClient();

  // Load local overrides on mount
  useEffect(() => {
    setLocalOverrides(getLocalOverrides());
  }, [getLocalOverrides]);

  // Clear pending state when server settings match what we saved
  useEffect(() => {
    if (pendingServerSave && serverSettings) {
      const serverMatches =
        serverSettings.readerFontSize === pendingServerSave.fontSize &&
        // Tolerate minor float normalization differences for lineHeight
        Math.abs(
          (serverSettings.readerLineHeight ?? 0) - pendingServerSave.lineHeight,
        ) < 1e-6 &&
        serverSettings.readerFontFamily === pendingServerSave.fontFamily;
      if (serverMatches) {
        setPendingServerSave(null);
      }
    }
  }, [serverSettings, pendingServerSave]);

  const { mutate: updateServerSettings, isPending: isSaving } = useMutation(
    api.users.updateSettings.mutationOptions({
      onSettled: async () => {
        await queryClient.refetchQueries(api.users.settings.pathFilter());
      },
    }),
  );

  // Separate mutation for saving defaults (clears local overrides on success)
  const { mutate: saveServerSettings, isPending: isSavingDefaults } =
    useMutation(
      api.users.updateSettings.mutationOptions({
        onSuccess: () => {
          // Clear local and session overrides after successful server save
          setLocalOverrides({});
          saveLocalOverrides({});
          onClearSessionOverrides?.();
        },
        onError: () => {
          // Clear pending state so we don't show values that failed to persist
          setPendingServerSave(null);
        },
        onSettled: async () => {
          await queryClient.refetchQueries(api.users.settings.pathFilter());
        },
      }),
    );

  // Compute effective settings with precedence: session → local → pendingSave → server → default
  const settings: ReaderSettings = useMemo(
    () => ({
      fontSize:
        sessionOverrides.fontSize ??
        localOverrides.fontSize ??
        pendingServerSave?.fontSize ??
        serverSettings?.readerFontSize ??
        READER_DEFAULTS.fontSize,
      lineHeight:
        sessionOverrides.lineHeight ??
        localOverrides.lineHeight ??
        pendingServerSave?.lineHeight ??
        serverSettings?.readerLineHeight ??
        READER_DEFAULTS.lineHeight,
      fontFamily:
        sessionOverrides.fontFamily ??
        localOverrides.fontFamily ??
        pendingServerSave?.fontFamily ??
        serverSettings?.readerFontFamily ??
        READER_DEFAULTS.fontFamily,
    }),
    [sessionOverrides, localOverrides, pendingServerSave, serverSettings],
  );

  // Get the server setting values (for UI indicators)
  const serverDefaults: ReaderSettingsPartial = useMemo(
    () => ({
      fontSize: serverSettings?.readerFontSize ?? undefined,
      lineHeight: serverSettings?.readerLineHeight ?? undefined,
      fontFamily: serverSettings?.readerFontFamily ?? undefined,
    }),
    [serverSettings],
  );

  // Update local override (per-device, immediate)
  const updateLocal = useCallback(
    (updates: ReaderSettingsPartial) => {
      setLocalOverrides((prev) => {
        const newOverrides = { ...prev, ...updates };
        saveLocalOverrides(newOverrides);
        return newOverrides;
      });
    },
    [saveLocalOverrides],
  );

  // Clear a specific local override
  const clearLocal = useCallback(
    (key: keyof ReaderSettings) => {
      setLocalOverrides((prev) => {
        const { [key]: _, ...rest } = prev;
        saveLocalOverrides(rest);
        return rest;
      });
    },
    [saveLocalOverrides],
  );

  // Clear all local overrides
  const clearAllLocal = useCallback(() => {
    setLocalOverrides({});
    saveLocalOverrides({});
  }, [saveLocalOverrides]);

  // Save current effective settings as server default (syncs across devices)
  const saveAsDefault = useCallback(
    (settingsToSave?: ReaderSettingsPartial) => {
      const toSave: ReaderSettings = {
        fontSize: settingsToSave?.fontSize ?? settings.fontSize,
        lineHeight: settingsToSave?.lineHeight ?? settings.lineHeight,
        fontFamily: settingsToSave?.fontFamily ?? settings.fontFamily,
      };
      // Set pending state to prevent flicker while server syncs
      setPendingServerSave(toSave);
      saveServerSettings({
        readerFontSize: toSave.fontSize,
        readerLineHeight: toSave.lineHeight,
        readerFontFamily: toSave.fontFamily,
      });
    },
    [settings, saveServerSettings],
  );

  // Clear a specific server default (set to null)
  const clearDefault = useCallback(
    (key: keyof ReaderSettings) => {
      const serverKeyMap = {
        fontSize: "readerFontSize",
        lineHeight: "readerLineHeight",
        fontFamily: "readerFontFamily",
      } as const;
      updateServerSettings({ [serverKeyMap[key]]: null });
    },
    [updateServerSettings],
  );

  // Clear all server defaults
  const clearAllDefaults = useCallback(() => {
    updateServerSettings({
      readerFontSize: null,
      readerLineHeight: null,
      readerFontFamily: null,
    });
  }, [updateServerSettings]);

  // Check if there are any local overrides
  const hasLocalOverrides = Object.keys(localOverrides).length > 0;

  // Check if there are any server defaults
  const hasServerDefaults =
    serverSettings?.readerFontSize != null ||
    serverSettings?.readerLineHeight != null ||
    serverSettings?.readerFontFamily != null;

  return {
    // Current effective settings (what should be displayed)
    settings,

    // Raw values for UI indicators
    localOverrides,
    serverDefaults,

    // Status flags
    hasLocalOverrides,
    hasServerDefaults,
    isSaving: isSaving || isSavingDefaults,

    // Internal state setters (for web's context-based approach)
    setLocalOverrides,

    // Actions
    updateLocal,
    clearLocal,
    clearAllLocal,
    saveAsDefault,
    clearDefault,
    clearAllDefaults,
  };
}

// Context for sharing reader settings state across components
export type ReaderSettingsContextValue = ReturnType<typeof useReaderSettings>;

const ReaderSettingsContext = createContext<ReaderSettingsContextValue | null>(
  null,
);

export interface ReaderSettingsProviderProps extends UseReaderSettingsOptions {
  children: ReactNode;
}

/**
 * Provider that creates a single instance of reader settings state
 * and shares it across all child components.
 */
export function ReaderSettingsProvider({
  children,
  ...options
}: ReaderSettingsProviderProps) {
  const value = useReaderSettings(options);

  return (
    <ReaderSettingsContext.Provider value={value}>
      {children}
    </ReaderSettingsContext.Provider>
  );
}

/**
 * Hook to access shared reader settings from context.
 * Must be used within a ReaderSettingsProvider.
 */
export function useReaderSettingsContext() {
  const context = useContext(ReaderSettingsContext);
  if (!context) {
    throw new Error(
      "useReaderSettingsContext must be used within a ReaderSettingsProvider",
    );
  }
  return context;
}
