import {
  type ReactNode,
  useCallback,
  useEffect,
  useMemo,
  useState,
} from "react";
import { FollowContext, type FollowContextValue } from "./FollowContext.ts";
import { API_URL } from "../config/api.ts";
import { useAuth } from "../hooks/useAuth.ts";
import type { FollowStatus } from "../model.ts";

export function FollowProvider({ children }: { children: ReactNode }) {
  const { token, authFetch } = useAuth();
  const [followedUserIds, setFollowedUserIds] = useState<Set<string>>(
    new Set(),
  );
  const [followedPlaylistIds, setFollowedPlaylistIds] = useState<Set<string>>(
    new Set(),
  );
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => {
    if (!token) {
      // Deferred to a microtask (rather than called directly) so this
      // doesn't fire synchronously within the effect body — same timing
      // for the user, just not in the same tick.
      queueMicrotask(() => {
        setFollowedUserIds(new Set());
        setFollowedPlaylistIds(new Set());
        setIsLoaded(false);
      });
      return;
    }
    const controller = new AbortController();
    fetch(`${API_URL}/api/follows/status`, {
      headers: { Authorization: `Bearer ${token}` },
      signal: controller.signal,
    })
      .then((r) => r.json())
      .then((body) => {
        if (!body.success) return;
        const status = body.data as FollowStatus;
        setFollowedUserIds(new Set(status.followedUserIds));
        setFollowedPlaylistIds(new Set(status.followedPlaylistIds));
        setIsLoaded(true);
      })
      .catch((err) => {
        if (err.name !== "AbortError") setIsLoaded(true);
      });
    return () => {
      controller.abort();
    };
  }, [token]);

  const followUser = useCallback(async (userId: string) => {
    setFollowedUserIds((prev) => new Set([...prev, userId]));
    try {
      const res = await authFetch(`${API_URL}/api/follows/users/${userId}`, {
        method: "POST",
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
    } catch {
      setFollowedUserIds((prev) => {
        const n = new Set(prev);
        n.delete(userId);
        return n;
      });
    }
  }, [authFetch]);

  const unfollowUser = useCallback(async (userId: string) => {
    setFollowedUserIds((prev) => {
      const n = new Set(prev);
      n.delete(userId);
      return n;
    });
    try {
      const res = await authFetch(`${API_URL}/api/follows/users/${userId}`, {
        method: "DELETE",
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
    } catch {
      setFollowedUserIds((prev) => new Set([...prev, userId]));
    }
  }, [authFetch]);

  const followPlaylist = useCallback(async (playlistId: string) => {
    setFollowedPlaylistIds((prev) => new Set([...prev, playlistId]));
    try {
      const res = await authFetch(
        `${API_URL}/api/follows/playlists/${playlistId}`,
        { method: "POST" },
      );
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
    } catch {
      setFollowedPlaylistIds((prev) => {
        const n = new Set(prev);
        n.delete(playlistId);
        return n;
      });
    }
  }, [authFetch]);

  const unfollowPlaylist = useCallback(async (playlistId: string) => {
    setFollowedPlaylistIds((prev) => {
      const n = new Set(prev);
      n.delete(playlistId);
      return n;
    });
    try {
      const res = await authFetch(
        `${API_URL}/api/follows/playlists/${playlistId}`,
        { method: "DELETE" },
      );
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
    } catch {
      setFollowedPlaylistIds((prev) => new Set([...prev, playlistId]));
    }
  }, [authFetch]);

  const value: FollowContextValue = useMemo(() => ({
    followedUserIds,
    followedPlaylistIds,
    followUser,
    unfollowUser,
    followPlaylist,
    unfollowPlaylist,
    isLoaded,
  }), [
    followedUserIds,
    followedPlaylistIds,
    followUser,
    unfollowUser,
    followPlaylist,
    unfollowPlaylist,
    isLoaded,
  ]);

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