import {
  useCallback,
  useEffect,
  useLayoutEffect,
  useRef,
  useState,
} from "react";
import { Link, useParams } from "react-router";

import { API_URL } from "../config/api.ts";
import { friendlyFetchError } from "../utils/apiError.ts";
import type {
  PaginatedData,
  Playlist,
  PublicUser,
  RawPlaylist,
} from "../model.ts";
import { deserializePlaylist, deserializePublicUser } from "../model.ts";
import { useAuth } from "../hooks/useAuth.ts";
import { useWS } from "../hooks/useWS.ts";
import { useInfiniteScroll } from "../hooks/useInfiniteScroll.ts";
import { useFeedCache } from "../hooks/useFeedCache.ts";
import { Avatar } from "../components/Avatar.tsx";
import { PlaylistCard } from "../components/PlaylistCard.tsx";
import { NewPlaylistForm } from "../components/NewPlaylistForm.tsx";
import { ConfirmModal } from "../components/ConfirmModal.tsx";
import { PageShell } from "../components/PageShell.tsx";
import { PageError } from "../components/PageError.tsx";

const PAGE_SIZE = 20;
const hydratePlaylist = (raw: Playlist): Playlist =>
  deserializePlaylist(raw as unknown as RawPlaylist);

interface PlaylistFeed {
  items: Playlist[];
  hasMore: boolean;
  page: number;
  loadingMore: boolean;
}

type State =
  | { status: "loading" }
  | { status: "error"; error: string }
  | {
    status: "loaded";
    profileUser: PublicUser;
    created: PlaylistFeed;
    followed: PlaylistFeed;
  };

function initialFeed(items: Playlist[], hasMore: boolean): PlaylistFeed {
  return { items, hasMore, page: 1, loadingMore: false };
}

export function UserPlaylists() {
  const { username } = useParams();
  const { user: me, authFetch, token } = useAuth();
  const { lastPlaylistEvent, deletedPlaylistIds } = useWS();

  const { cached: cachedCreated, saveState: saveCreated } = useFeedCache<
    Playlist
  >(
    `feed:user-playlists:${username ?? ""}`,
    hydratePlaylist,
  );
  const { cached: cachedFollowed, saveState: saveFollowed } = useFeedCache<
    Playlist
  >(
    `feed:user-followed-playlists:${username ?? ""}`,
    hydratePlaylist,
  );

  const [state, setState] = useState<State>({ status: "loading" });
  const [confirmDeleteId, setConfirmDeleteId] = useState<string | null>(null);

  useEffect(() => {
    if (!username) return;
    setState({ status: "loading" });

    const authHeaders: HeadersInit = token
      ? { Authorization: `Bearer ${token}` }
      : {};

    if (cachedCreated && cachedFollowed) {
      fetch(`${API_URL}/api/users/${username}`)
        .then((r) => r.json())
        .then((body) => {
          if (!body.success) throw new Error("User not found");
          setState({
            status: "loaded",
            profileUser: deserializePublicUser(body.data),
            created: {
              items: cachedCreated.items,
              hasMore: cachedCreated.hasMore,
              page: cachedCreated.page,
              loadingMore: false,
            },
            followed: {
              items: cachedFollowed.items,
              hasMore: cachedFollowed.hasMore,
              page: cachedFollowed.page,
              loadingMore: false,
            },
          });
        })
        .catch((err) =>
          setState({
            status: "error",
            error: friendlyFetchError(err),
          })
        );
      return;
    }

    Promise.all([
      fetch(`${API_URL}/api/users/${username}`),
      fetch(
        `${API_URL}/api/users/${username}/playlists?page=1&limit=${PAGE_SIZE}`,
        { headers: authHeaders },
      ),
      fetch(
        `${API_URL}/api/users/${username}/followed-playlists?page=1&limit=${PAGE_SIZE}`,
      ),
    ])
      .then(([userRes, createdRes, followedRes]) =>
        Promise.all([userRes.json(), createdRes.json(), followedRes.json()])
      )
      .then(([userBody, createdBody, followedBody]) => {
        if (!userBody.success) throw new Error("User not found");
        const createdData = createdBody.success
          ? createdBody.data as PaginatedData<RawPlaylist>
          : { items: [], hasMore: false };
        const followedData = followedBody.success
          ? followedBody.data as PaginatedData<RawPlaylist>
          : { items: [], hasMore: false };
        setState({
          status: "loaded",
          profileUser: deserializePublicUser(userBody.data),
          created: initialFeed(
            createdData.items.map(deserializePlaylist),
            createdData.hasMore,
          ),
          followed: initialFeed(
            followedData.items.map(deserializePlaylist),
            followedData.hasMore,
          ),
        });
      })
      .catch((err) =>
        setState({
          status: "error",
          error: friendlyFetchError(err),
        })
      );
  }, [username]);

  const loadMoreCreated = useCallback(() => {
    if (
      state.status !== "loaded" || !state.created.hasMore ||
      state.created.loadingMore || !username
    ) return;
    const nextPage = state.created.page + 1;
    setState((s) =>
      s.status === "loaded"
        ? { ...s, created: { ...s.created, loadingMore: true } }
        : s
    );
    fetch(
      `${API_URL}/api/users/${username}/playlists?page=${nextPage}&limit=${PAGE_SIZE}`,
      { headers: token ? { Authorization: `Bearer ${token}` } : {} },
    )
      .then((r) => r.json())
      .then((body) => {
        const { items, hasMore } = body.data as PaginatedData<RawPlaylist>;
        setState((s) =>
          s.status === "loaded"
            ? {
              ...s,
              created: {
                items: [...s.created.items, ...items.map(deserializePlaylist)],
                hasMore,
                page: nextPage,
                loadingMore: false,
              },
            }
            : s
        );
      })
      .catch(() =>
        setState((s) =>
          s.status === "loaded"
            ? { ...s, created: { ...s.created, loadingMore: false } }
            : s
        )
      );
  }, [state, username, token]);

  const loadMoreFollowed = useCallback(() => {
    if (
      state.status !== "loaded" || !state.followed.hasMore ||
      state.followed.loadingMore || !username
    ) return;
    const nextPage = state.followed.page + 1;
    setState((s) =>
      s.status === "loaded"
        ? { ...s, followed: { ...s.followed, loadingMore: true } }
        : s
    );
    fetch(
      `${API_URL}/api/users/${username}/followed-playlists?page=${nextPage}&limit=${PAGE_SIZE}`,
    )
      .then((r) => r.json())
      .then((body) => {
        const { items, hasMore } = body.data as PaginatedData<RawPlaylist>;
        setState((s) =>
          s.status === "loaded"
            ? {
              ...s,
              followed: {
                items: [...s.followed.items, ...items.map(deserializePlaylist)],
                hasMore,
                page: nextPage,
                loadingMore: false,
              },
            }
            : s
        );
      })
      .catch(() =>
        setState((s) =>
          s.status === "loaded"
            ? { ...s, followed: { ...s.followed, loadingMore: false } }
            : s
        )
      );
  }, [state, username]);

  const createdSentinelRef = useInfiniteScroll(
    loadMoreCreated,
    state.status === "loaded" && state.created.hasMore &&
      !state.created.loadingMore,
  );
  const followedSentinelRef = useInfiniteScroll(
    loadMoreFollowed,
    state.status === "loaded" && state.followed.hasMore &&
      !state.followed.loadingMore,
  );

  // Real-time WS playlist updates
  useEffect(() => {
    if (!lastPlaylistEvent || state.status !== "loaded") return;
    const ev = lastPlaylistEvent;
    const isOwnProfile = me?.username === state.profileUser.username;

    if (ev.type === "created" && ev.playlist?.userId === state.profileUser.id) {
      if (ev.playlist.isPublic || isOwnProfile) {
        setState((s) => {
          if (s.status !== "loaded") return s;
          if (s.created.items.some((p) => p.id === ev.playlist!.id)) return s;
          return {
            ...s,
            created: {
              ...s.created,
              items: [ev.playlist!, ...s.created.items],
            },
          };
        });
      }
    } else if (ev.type === "updated") {
      setState((s) => {
        if (s.status !== "loaded") return s;
        const updatedCreated = ev.playlist?.userId === state.profileUser.id
          ? s.created.items
            .map((p) => p.id === ev.playlist!.id ? ev.playlist! : p)
            .filter((p) => p.isPublic || isOwnProfile)
          : s.created.items;
        const updatedFollowed = s.followed.items.map((p) =>
          p.id === ev.playlist?.id ? ev.playlist! : p
        ).filter((p) => p.isPublic);
        return {
          ...s,
          created: { ...s.created, items: updatedCreated },
          followed: { ...s.followed, items: updatedFollowed },
        };
      });
    } else if (ev.type === "deleted") {
      setState((s) =>
        s.status !== "loaded" ? s : {
          ...s,
          created: {
            ...s.created,
            items: s.created.items.filter((p) => p.id !== ev.playlistId),
          },
          followed: {
            ...s.followed,
            items: s.followed.items.filter((p) => p.id !== ev.playlistId),
          },
        }
      );
    }
  }, [lastPlaylistEvent, me]);

  useEffect(() => {
    if (!deletedPlaylistIds.size || state.status !== "loaded") return;
    setState((s) =>
      s.status !== "loaded" ? s : {
        ...s,
        created: {
          ...s.created,
          items: s.created.items.filter((p) => !deletedPlaylistIds.has(p.id)),
        },
        followed: {
          ...s.followed,
          items: s.followed.items.filter((p) => !deletedPlaylistIds.has(p.id)),
        },
      }
    );
  }, [deletedPlaylistIds]);

  // Scroll save
  useEffect(() => {
    if (state.status !== "loaded") return;
    let timer: ReturnType<typeof setTimeout>;
    const onScroll = () => {
      clearTimeout(timer);
      timer = setTimeout(() => {
        if (state.status !== "loaded") return;
        const y = globalThis.scrollY;
        saveCreated(
          state.created.items,
          state.created.page,
          state.created.hasMore,
          y,
        );
        saveFollowed(
          state.followed.items,
          state.followed.page,
          state.followed.hasMore,
          y,
        );
      }, 100);
    };
    globalThis.addEventListener("scroll", onScroll, { passive: true });
    return () => {
      globalThis.removeEventListener("scroll", onScroll);
      clearTimeout(timer);
    };
  }, [state, saveCreated, saveFollowed]);

  const scrollRestored = useRef(false);
  useLayoutEffect(() => {
    if (cachedCreated?.scrollY == null || scrollRestored.current) return;
    if (state.status === "loaded") {
      globalThis.scrollTo(0, cachedCreated.scrollY);
      scrollRestored.current = true;
    }
  }, [state.status, cachedCreated]);

  const handleDelete = async (playlistId: string) => {
    await authFetch(`${API_URL}/api/playlists/${playlistId}`, {
      method: "DELETE",
    });
    setState((s) =>
      s.status === "loaded"
        ? {
          ...s,
          created: {
            ...s.created,
            items: s.created.items.filter((p) => p.id !== playlistId),
          },
        }
        : s
    );
  };

  if (state.status === "loading") {
    return (
      <PageShell>
        <p className="page-loading">Loading…</p>
      </PageShell>
    );
  }

  if (state.status === "error") {
    return (
      <PageError
        message={state.error}
        actions={
          <Link to={`/users/${username}`} className="btn-border">
            ← Back to profile
          </Link>
        }
      />
    );
  }

  const { profileUser, created, followed } = state;
  const isOwnProfile = me?.username === profileUser.username;

  return (
    <PageShell>
      <div className="profile-subpage-header">
        <Link to={`/users/${username}`} className="profile-subpage-back">
          ← {profileUser.username}
        </Link>
        <div className="profile-subpage-title-row">
          <Avatar
            userId={profileUser.id}
            username={profileUser.username}
            hasAvatar={!!profileUser.avatarMime}
            size={36}
          />
          <h1 className="profile-subpage-title">Playlists</h1>
          {isOwnProfile && (
            <NewPlaylistForm
              toggleClassName="btn-primary"
              onCreated={(p) =>
                setState((s) => {
                  if (s.status !== "loaded") return s;
                  if (s.created.items.some((pl) => pl.id === p.id)) return s;
                  return {
                    ...s,
                    created: { ...s.created, items: [p, ...s.created.items] },
                  };
                })}
            />
          )}
        </div>
      </div>

      <section className="profile-section">
        <div className="profile-section-header">
          <h2 className="profile-section-title">
            Created ({created.items.length}
            {created.hasMore ? "+" : ""})
          </h2>
        </div>
        {created.items.length === 0
          ? <p className="empty-state">No playlists yet.</p>
          : (
            <ul className="dump-feed">
              {created.items.map((p) => (
                <PlaylistCard
                  key={p.id}
                  playlist={p}
                  isOwner={isOwnProfile}
                  onDelete={isOwnProfile
                    ? () => setConfirmDeleteId(p.id)
                    : undefined}
                />
              ))}
            </ul>
          )}
        <div ref={createdSentinelRef} />
        {created.loadingMore && (
          <p className="feed-loading-more">Loading more…</p>
        )}
      </section>

      <section className="profile-section">
        <div className="profile-section-header">
          <h2 className="profile-section-title">
            Followed ({followed.items.length}
            {followed.hasMore ? "+" : ""})
          </h2>
        </div>
        {followed.items.length === 0
          ? <p className="empty-state">No followed playlists yet.</p>
          : (
            <ul className="dump-feed">
              {followed.items.map((p) => (
                <PlaylistCard key={p.id} playlist={p} />
              ))}
            </ul>
          )}
        <div ref={followedSentinelRef} />
        {followed.loadingMore && (
          <p className="feed-loading-more">Loading more…</p>
        )}
      </section>

      {confirmDeleteId && (
        <ConfirmModal
          message="Delete this playlist? This cannot be undone."
          confirmLabel="Delete playlist"
          onConfirm={() => {
            handleDelete(confirmDeleteId);
            setConfirmDeleteId(null);
          }}
          onCancel={() => setConfirmDeleteId(null)}
        />
      )}
    </PageShell>
  );
}
