"use client";

import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode,
} from "react";

import { apiFetch } from "@/lib/client/api";
import type { StreamingProviderDescriptor } from "@/services/providers/types";
import type { UserSettings } from "@/lib/types";

/**
 * Application session context.
 *
 * The server already knows whether the visitor is signed in (the header is a
 * Server Component). This provider exists so that Client Components – the
 * header search box, the "+ My List" buttons, the player – can react to
 * mutations without a full page reload.
 *
 * No persistent client storage is used: state lives in React and is
 * re-hydrated from `/api/bootstrap` after a server mutation.
 */

export interface PublicUser {
  id: number;
  username: string;
  email: string;
  displayName: string;
  avatarPath: string | null;
  role: string;
}

export interface ProviderDirectory {
  streaming: Record<"movie" | "tv" | "anime", StreamingProviderDescriptor[]>;
  download: Record<"movie" | "tv" | "anime", StreamingProviderDescriptor[]>;
}

interface BootstrapPayload {
  user: PublicUser | null;
  settings: UserSettings | null;
  providers: ProviderDirectory;
  defaults: { streamingProviderId: string | null; downloadProviderId: string | null };
}

interface SessionContextValue extends BootstrapPayload {
  ready: boolean;
  isAuthenticated: boolean;
  streamingProviders: StreamingProviderDescriptor[];
  downloadProviders: StreamingProviderDescriptor[];
  refresh: () => Promise<void>;
  setUser: (user: PublicUser | null) => void;
  setSettings: (settings: UserSettings | null) => void;
  signOut: (allDevices?: boolean) => Promise<void>;
}

const SessionContext = createContext<SessionContextValue | null>(null);

export function SessionProvider({
  children,
  initialUser,
  initialSettings,
  initialProviders,
  initialDefaults,
}: {
  children: ReactNode;
  initialUser: PublicUser | null;
  initialSettings: UserSettings | null;
  initialProviders: ProviderDirectory;
  initialDefaults: { streamingProviderId: string | null; downloadProviderId: string | null };
}) {
  const [user, setUser] = useState<PublicUser | null>(initialUser);
  const [settings, setSettings] = useState<UserSettings | null>(initialSettings);
  const [providers, setProviders] = useState<ProviderDirectory>(initialProviders);
  const [defaults, setDefaults] = useState(initialDefaults);
  // A server-rendered session means the client already has the truth, so the
  // provider is ready immediately. Only the anonymous case needs a probe.
  const [probed, setProbed] = useState(() => initialUser !== null);
  const ready = probed || initialUser !== null;

  // Re-sync after a client-side sign-in/sign-out or `router.refresh()`.
  const refresh = useCallback(async () => {
    try {
      const data = await apiFetch<BootstrapPayload>("/api/bootstrap");
      setUser(data.user);
      setSettings(data.settings);
      setProviders(data.providers);
      setDefaults(data.defaults);
    } catch {
      /* keep the server-rendered snapshot if the probe fails */
    } finally {
      setProbed(true);
    }
  }, []);

  useEffect(() => {
    if (initialUser) return;
    let cancelled = false;

    void (async () => {
      try {
        const data = await apiFetch<BootstrapPayload>("/api/bootstrap");
        if (cancelled) return;
        setUser(data.user);
        setSettings(data.settings);
        setProviders(data.providers);
        setDefaults(data.defaults);
      } catch {
        /* offline – keep the anonymous snapshot */
      } finally {
        if (!cancelled) setProbed(true);
      }
    })();

    return () => {
      cancelled = true;
    };
  }, [initialUser]);

  const signOut = useCallback(
    async (allDevices = false) => {
      try {
        await apiFetch("/api/auth/logout", {
          method: "POST",
          body: JSON.stringify({ allDevices }),
        });
      } catch {
        /* the cookie is cleared server-side regardless */
      }
      setUser(null);
      setSettings(null);
      setDefaults({ streamingProviderId: null, downloadProviderId: null });
    },
    [],
  );

  const value = useMemo<SessionContextValue>(
    () => ({
      user,
      settings,
      providers,
      defaults,
      ready,
      isAuthenticated: Boolean(user),
      streamingProviders: providers.streaming.movie,
      downloadProviders: providers.download.movie,
      refresh,
      setUser,
      setSettings,
      signOut,
    }),
    [defaults, providers, ready, refresh, settings, signOut, user],
  );

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

export function useSession(): SessionContextValue {
  const ctx = useContext(SessionContext);
  if (!ctx) throw new Error("useSession must be used inside <SessionProvider>");
  return ctx;
}
