import { useEffect, useMemo, useRef } from "react";
import { useLocation, useSearchParams } from "react-router-dom";
import { postJSON } from "../../api";
import { useI18n } from "../../i18n";
import type { PostScope } from "../../types/post";
import { Reveal } from "../../motion";
import { Skeleton } from "../Skeleton";
import { FilterChips } from "./FilterChips";
import { SectionHeader } from "../SectionHeader";
import { MessageBubble } from "./MessageBubble";
import { useGroupedByDay } from "./hooks/useGroupedByDay";
import { usePostStream } from "./hooks/usePostStream";

export type MessageStreamProps = {
  scope: PostScope;
  title?: string;
};

export function MessageStream({ scope, title }: MessageStreamProps) {
  const { t, lang } = useI18n();
  const [sp, setSp] = useSearchParams();
  const { hash } = useLocation();

  const type = sp.get("type") || "all";
  const q = (sp.get("q") || "").trim();
  const sort = sp.get("sort") || "";

  const params = useMemo(
    () => ({ scope, type, q, sort, lang }),
    [scope, type, q, sort, lang],
  );

  const { items, isLoading, error, hasMore, loadMore, reset } =
    usePostStream(params);
  const groups = useGroupedByDay(items, lang);
  const retryLabel = lang === "zh-CN" ? "重试" : "Retry";

  const sentinelRef = useRef<HTMLDivElement>(null);
  const hasMoreRef = useRef(hasMore);
  const isLoadingRef = useRef(isLoading);
  useEffect(() => {
    hasMoreRef.current = hasMore;
  }, [hasMore]);
  useEffect(() => {
    isLoadingRef.current = isLoading;
  }, [isLoading]);

  useEffect(() => {
    if (q) postJSON("/api/search-log", { query: q }).catch(() => {});
  }, [q]);

  useEffect(() => {
    const el = sentinelRef.current;
    if (!el) return;
    const io = new IntersectionObserver(
      (entries) => {
        for (const entry of entries) {
          if (
            entry.isIntersecting &&
            hasMoreRef.current &&
            !isLoadingRef.current
          ) {
            loadMore();
          }
        }
      },
      // Prefetch the next page well before the sentinel is visible so content
      // is already in place as the user scrolls — no blank gap at the bottom.
      { rootMargin: "1000px" },
    );
    io.observe(el);
    return () => io.disconnect();
  }, [loadMore]);

  // When arriving with a `#post-<id>` hash (e.g. from a recommended card),
  // scroll to that bubble — loading more pages until it shows up — then give
  // it a brief highlight so the user can see where they landed.
  const targetPostId = hash.startsWith("#post-")
    ? hash.slice("#post-".length)
    : "";
  const handledTargetRef = useRef<string>("");

  useEffect(() => {
    handledTargetRef.current = "";
  }, [targetPostId]);

  useEffect(() => {
    if (!targetPostId || handledTargetRef.current === targetPostId) return;

    const el = document.getElementById(`post-${targetPostId}`);
    if (el) {
      handledTargetRef.current = targetPostId;
      const frame = window.requestAnimationFrame(() => {
        el.scrollIntoView({ block: "start", behavior: "smooth" });
        el.classList.add("ark-bubble-highlight");
        window.setTimeout(
          () => el.classList.remove("ark-bubble-highlight"),
          2000,
        );
      });
      return () => window.cancelAnimationFrame(frame);
    }

    // Not loaded yet — keep paging until it appears or the stream is exhausted.
    if (hasMore && !isLoading) loadMore();
  }, [targetPostId, items, hasMore, isLoading, loadMore]);

  const updateParam = (key: string, value: string) => {
    const n = new URLSearchParams(sp);
    if (!value || value === "all") n.delete(key);
    else n.set(key, value);
    setSp(n, { replace: true });
  };

  const isInitialLoad = isLoading && items.length === 0;

  return (
    <div className="mx-auto max-w-full md:max-w-[820px] lg:max-w-[1080px] xl:max-w-[1180px]">
      {/* Title + filters stay pinned below the global header so users always
          see which page they're on and can switch filters while scrolling. */}
      <div className="sticky top-[64px] z-30 bg-ark-bg md:top-[70px]">
        {title ? (
          <div className="px-4 pb-1 pt-2 md:px-0">
            <SectionHeader title={title} />
          </div>
        ) : null}
        <FilterChips type={type} onTypeChange={(v) => updateParam("type", v)} />
      </div>

      <div className="flex flex-col gap-3 px-4 pt-4 md:px-0 md:pt-2">
        {isInitialLoad ? (
          <>
            {Array.from({ length: 10 }).map((_, i) => (
              <div
                key={i}
                className="mx-auto w-full max-w-[358px] md:max-w-[680px] lg:max-w-[900px] xl:max-w-[1120px]"
              >
                <Skeleton
                  className={`rounded-2xl ${
                    i % 3 === 0 ? "h-[220px]" : "h-[80px]"
                  }`}
                />
              </div>
            ))}
          </>
        ) : (
          <>
            {groups.map((group) => (
              <div key={group.dayKey} className="flex flex-col gap-3">
                {group.items.map((post, index) => (
                  <Reveal key={post.id} delay={Math.min(index, 8) * 0.05}>
                    <MessageBubble post={post} />
                  </Reveal>
                ))}
              </div>
            ))}

            {!isLoading && !error && items.length === 0 ? (
              <p className="py-10 text-center text-sm text-neutral-400">
                {t("noResults")}
              </p>
            ) : null}

            {error ? (
              <div className="my-4 flex items-center justify-between gap-3 rounded-xl border border-red-900 bg-red-950/40 px-4 py-3 text-sm text-red-200">
                <span className="break-all">{error}</span>
                <button
                  type="button"
                  onClick={() => reset()}
                  className="shrink-0 rounded-full border border-red-700 px-3 py-1 text-xs text-red-100 hover:border-red-500"
                >
                  {retryLabel}
                </button>
              </div>
            ) : null}

            {isLoading ? (
              <div className="py-4 text-center text-xs text-neutral-500">…</div>
            ) : null}
          </>
        )}

        <div ref={sentinelRef} aria-hidden className="h-1" />
      </div>
    </div>
  );
}
