import { useCallback, useEffect, useRef, useState } from "react";
import { getJSON, itemsOrEmpty, readJSONCache } from "../../../api";
import { langQuery, type Lang } from "../../../i18n";
import { sourceLanguageQuery } from "../../../i18nLanguages";
import { MOCK_POSTS } from "../../../mocks/mockPosts";
import type { Post, PostListResponse, PostScope } from "../../../types/post";

const PAGE_SIZE = 20;
const MOCK_DELAY_MS = 200;

const USE_MOCK = import.meta.env.VITE_USE_MOCK_POSTS === "true";

export type PostStreamParams = {
  scope: PostScope;
  type?: string;
  language?: string;
  q?: string;
  sort?: string;
  lang: Lang;
};

export type PostStreamResult = {
  items: Post[];
  isLoading: boolean;
  error: string | null;
  hasMore: boolean;
  loadMore: () => void;
  reset: () => void;
};

function postMatchesType(post: Post, type: string): boolean {
  if (!type || type === "all") return true;
  if (type === "text" || type === "link") {
    return !!post.text && post.text.length > 0;
  }
  return post.attachments.some((a) => {
    const ext = a.filename.split(".").pop()?.toLowerCase() ?? "";
    if (type === "image")
      return a.kind === "image" || a.mime.startsWith("image/");
    if (type === "video")
      return a.kind === "video" || a.mime.startsWith("video/");
    if (type === "music") return a.mime.startsWith("audio/") || ext === "mp3";
    if (type === "pdf") return ext === "pdf" || a.mime === "application/pdf";
    if (type === "ppt")
      return (
        ["ppt", "pptx", "key"].includes(ext) || a.mime.includes("presentation")
      );
    if (type === "archive")
      return ["zip", "rar", "7z", "tar", "gz"].includes(ext);
    return false;
  });
}

function filterMock(params: PostStreamParams): Post[] {
  return MOCK_POSTS.filter((p) => {
    if (
      params.scope.kind === "category" &&
      p.categorySlug !== params.scope.slug
    )
      return false;
    const q = params.q?.trim().toLowerCase();
    if (params.language && p.language !== params.language) return false;
    if (q) {
      const haystack = [
        p.text ?? "",
        p.categorySlug,
        p.postType ?? "",
        ...(p.tags ?? []),
        ...p.attachments.flatMap((a) => [a.filename, a.mime, a.kind]),
      ]
        .join("\n")
        .toLowerCase();
      if (!haystack.includes(q)) return false;
    }
    if (!postMatchesType(p, params.type ?? "all")) return false;
    return true;
  }).sort(
    (a, b) =>
      new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime(),
  );
}

function buildRealUrl(params: PostStreamParams, cursor?: string): string {
  const sp = new URLSearchParams();
  const q = params.q?.trim();
  sp.set("lang", langQuery(params.lang));
  sp.set("limit", String(PAGE_SIZE));
  if (q) sp.set("q", q);
  if (params.scope.kind === "category") sp.set("category", params.scope.slug);
  if (params.type && params.type !== "all") sp.set("type", params.type);
  if (params.sort) sp.set("sort", params.sort);
  if (params.language) sp.set("language", sourceLanguageQuery(params.language));
  if (cursor) sp.set("cursor", cursor);
  return `${q ? "/api/posts/search" : "/api/posts"}?${sp.toString()}`;
}

type CachedStream = {
  items: Post[];
  cursor: string | undefined;
  hasMore: boolean;
};

/**
 * Session cache of loaded stream state, keyed by the request params. Lets a
 * view that's been seen before (e.g. switching Home ⇄ All) restore instantly —
 * all loaded pages, no skeleton, no refetch — instead of reloading from page 1.
 * In-memory only: a full page reload starts fresh.
 */
const streamCache = new Map<string, CachedStream>();

function streamKey(params: PostStreamParams): string {
  return buildRealUrl(params);
}

/**
 * Warm the cache for a stream view before the user navigates to it, so opening
 * the page shows content immediately instead of starting to load on arrival.
 * No-op for the mock backend or when the first page is already cached.
 */
export function prefetchPostStream(params: PostStreamParams): void {
  if (USE_MOCK) return;
  const url = buildRealUrl(params);
  if (readJSONCache<PostListResponse>(url)) return;
  getJSON<PostListResponse>(url).catch(() => {});
}

export function usePostStream(params: PostStreamParams): PostStreamResult {
  const [items, setItems] = useState<Post[]>([]);
  const [hasMore, setHasMore] = useState(true);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const reqIdRef = useRef(0);
  const cursorRef = useRef<string | undefined>(undefined);
  const hasMoreRef = useRef(true);
  const loadingRef = useRef(false);

  const fetchPage = useCallback(
    async (resetting: boolean) => {
      if (loadingRef.current) return;
      if (!resetting && !hasMoreRef.current) return;
      loadingRef.current = true;
      setIsLoading(true);
      setError(null);
      const myReq = ++reqIdRef.current;

      let showedCached = false;

      try {
        if (USE_MOCK) {
          await new Promise((r) => setTimeout(r, MOCK_DELAY_MS));
          const all = filterMock(params);
          const offset = resetting ? 0 : Number(cursorRef.current ?? "0");
          const slice = all.slice(offset, offset + PAGE_SIZE);
          const nextOffset = offset + slice.length;
          const more = nextOffset < all.length;
          if (myReq !== reqIdRef.current) return;
          setItems((prev) => (resetting ? slice : [...prev, ...slice]));
          const nextCursor = more ? String(nextOffset) : undefined;
          cursorRef.current = nextCursor;
          setHasMore(more);
          hasMoreRef.current = more;
        } else {
          const url = buildRealUrl(
            params,
            resetting ? undefined : cursorRef.current,
          );

          if (resetting) {
            const cachedPage = readJSONCache<PostListResponse>(url);
            if (cachedPage && myReq === reqIdRef.current) {
              showedCached = true;
              const cachedItems = itemsOrEmpty(cachedPage.items);
              setItems(cachedItems);
              cursorRef.current = cachedPage.nextCursor;
              const cachedMore = !!cachedPage.nextCursor;
              setHasMore(cachedMore);
              hasMoreRef.current = cachedMore;
            }
          }

          const res = await getJSON<PostListResponse>(url);
          if (myReq !== reqIdRef.current) return;
          const freshItems = itemsOrEmpty(res.items);
          setItems((prev) =>
            resetting ? freshItems : [...prev, ...freshItems],
          );
          cursorRef.current = res.nextCursor;
          const more = !!res.nextCursor;
          setHasMore(more);
          hasMoreRef.current = more;
        }
      } catch (e) {
        if (myReq !== reqIdRef.current) return;
        if (!showedCached) setError(String(e));
      } finally {
        if (myReq === reqIdRef.current) setIsLoading(false);
        loadingRef.current = false;
      }
    },
    [params],
  );

  useEffect(() => {
    // Restore a previously-loaded view instantly (no reset, no refetch).
    const cached = streamCache.get(streamKey(params));
    if (cached) {
      setItems(cached.items);
      cursorRef.current = cached.cursor;
      setHasMore(cached.hasMore);
      hasMoreRef.current = cached.hasMore;
      setIsLoading(false);
      setError(null);
      return;
    }
    setItems([]);
    cursorRef.current = undefined;
    setHasMore(true);
    hasMoreRef.current = true;
    fetchPage(true);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    params.scope.kind,
    params.scope.kind === "category" ? params.scope.slug : "",
    params.type,
    params.language,
    params.q,
    params.sort,
    params.lang,
  ]);

  // Persist loaded state so returning to this view restores it from cache.
  useEffect(() => {
    if (items.length === 0) return;
    streamCache.set(streamKey(params), {
      items,
      cursor: cursorRef.current,
      hasMore,
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [items, hasMore]);

  const loadMore = useCallback(() => {
    fetchPage(false);
  }, [fetchPage]);

  const reset = useCallback(() => {
    fetchPage(true);
  }, [fetchPage]);

  return { items, isLoading, error, hasMore, loadMore, reset };
}

export { USE_MOCK as POST_STREAM_USES_MOCK };
