import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import type { ReactNode } from 'react';
import { ZinoClient } from './client';
import type { User } from './types';

interface ZinoContextValue {
  client: ZinoClient;
  user: User | null;
  login: (email: string, password: string, orgId?: string) => Promise<void>;
  logout: () => void;
}

const ZinoContext = createContext<ZinoContextValue | null>(null);

export function ZinoProvider({ baseUrl, children }: { baseUrl: string; children: ReactNode }) {
  // One client per provider instance. onAuthError clears the user so routes
  // bounce back to /login.
  const clientRef = useRef<ZinoClient>();
  if (!clientRef.current) {
    clientRef.current = new ZinoClient(baseUrl);
  }
  const client = clientRef.current;

  // Restore the session synchronously from the persisted JWT so a hard
  // navigation to a protected route doesn't flash through /login.
  const [user, setUser] = useState<User | null>(() => client.currentUser());

  useEffect(() => {
    client.setAuthErrorHandler(() => setUser(null));
  }, [client]);

  const value = useMemo<ZinoContextValue>(
    () => ({
      client,
      user,
      login: async (email, password, orgId) => {
        const res = await client.login(email, password, orgId);
        setUser(res.user ?? client.currentUser());
      },
      logout: () => {
        client.logout();
        setUser(null);
      },
    }),
    [client, user],
  );

  return <ZinoContext.Provider value={value}>{children}</ZinoContext.Provider>;
}

export function useZino(): ZinoContextValue {
  const ctx = useContext(ZinoContext);
  if (!ctx) throw new Error('useZino must be used within <ZinoProvider>');
  return ctx;
}

// --- minimal data-fetching hook (no extra deps) ---

export interface QueryState<T> {
  data: T | null;
  loading: boolean;
  error: string | null;
  refetch: () => void;
}

/**
 * Fire `fn` whenever a dependency in `deps` changes. Tracks loading/error and
 * ignores results from a stale call. `enabled=false` short-circuits.
 * `refetchMs` (opt-in) polls on that interval; the interval is cleared on
 * unmount, dep change, or when disabled.
 */
export function useQuery<T>(
  fn: () => Promise<T>,
  deps: unknown[],
  enabled = true,
  refetchMs?: number,
): QueryState<T> {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(enabled);
  const [error, setError] = useState<string | null>(null);
  const [tick, setTick] = useState(0);

  const refetch = useCallback(() => setTick((t) => t + 1), []);

  useEffect(() => {
    if (!enabled) {
      setLoading(false);
      return;
    }
    let live = true;
    setLoading(true);
    setError(null);
    fn()
      .then((d) => {
        if (live) setData(d);
      })
      .catch((e: unknown) => {
        if (live) setError(e instanceof Error ? e.message : ((e as { message?: string })?.message ?? 'Request failed'));
      })
      .finally(() => {
        if (live) setLoading(false);
      });
    return () => {
      live = false;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [...deps, tick, enabled]);

  // Opt-in background polling. Kept separate from the fetch effect so the
  // interval doesn't restart on every refetch; ticking triggers it instead.
  useEffect(() => {
    if (!enabled || !refetchMs || refetchMs <= 0) return;
    const id = setInterval(() => setTick((t) => t + 1), refetchMs);
    return () => clearInterval(id);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [...deps, enabled, refetchMs]);

  return { data, loading, error, refetch };
}
