import { useCallback, useEffect, useRef, useState } from "react";
import {
  Loader2,
  Plus,
  Newspaper,
  FileText,
} from "lucide-react";
import { api } from "@/client";
import { useChannelSocket } from "@/hooks/use-channel";
import { Button } from "@/components/ui/button";
import ArticleCard from "./article-card";
import ArticleDetail from "./article-detail";
import type { ArticleItem, ArticleDetail as ArticleDetailType } from "./article-types";

type Props = {
  roomId: string;
  roomName: string;
  currentUserId?: string;
  onCompose?: () => void;
};

export default function ArticleFeed({ roomId, roomName, currentUserId, onCompose }: Props) {
  const [articles, setArticles] = useState<ArticleItem[]>([]);
  const [loading, setLoading] = useState(true);
  const [hasMore, setHasMore] = useState(true);
  const [detailId, setDetailId] = useState<string | null>(null);
  const [detail, setDetail] = useState<ArticleDetailType | null>(null);
  const [loadingDetail, setLoadingDetail] = useState(false);
  const loaderRef = useRef<HTMLDivElement>(null);

  const loadArticles = useCallback(
    async (before?: string) => {
      try {
        const params: Record<string, string | number> = { limit: 20 };
        if (before) params.before = before;
        const res = await api.get<
          Record<string, unknown>
        >(`/api/v1/ws/channels/${roomId}/articles`, { params });

        const data = res.data as Record<string, unknown>;
        const list = (data.articles ?? []) as ArticleItem[];
        const more = (data.has_more ?? false) as boolean;

        if (before) {
          setArticles((prev) => [...prev, ...list]);
        } else {
          setArticles(list);
        }
        setHasMore(more);
      } catch {
        // ignore
      } finally {
        setLoading(false);
      }
    },
    [roomId],
  );

  // Reset on room change and (re)load
  useEffect(() => {
    let cancelled = false;
    const doLoad = async () => {
      setLoading(true);
      setArticles([]);
      setHasMore(true);
      setDetailId(null);
      setDetail(null);
      try {
        const params: Record<string, string | number> = { limit: 20 };
        const res = await api.get<Record<string, unknown>>(
          `/api/v1/ws/channels/${roomId}/articles`,
          { params },
        );
        if (cancelled) return;
        const data = res.data as Record<string, unknown>;
        setArticles((data.articles ?? []) as ArticleItem[]);
        setHasMore((data.has_more ?? false) as boolean);
      } catch {
        // ignore
      } finally {
        if (!cancelled) setLoading(false);
      }
    };
    doLoad();
    return () => {
      cancelled = true;
    };
  }, [roomId]);

  // Load detail when detailId changes
  useEffect(() => {
    if (!detailId) {
      // eslint-disable-next-line react-hooks/set-state-in-effect -- clear on deselect
      setDetail(null);
      return;
    }
    let cancelled = false;
    setLoadingDetail(true);
    api
      .get<Record<string, unknown>>(
        `/api/v1/ws/channels/${roomId}/articles/${detailId}`,
      )
      .then((res) => {
        if (cancelled) return;
        const d = res.data as unknown as ArticleDetailType;
        setDetail(d);
        setArticles((prev) =>
          prev.map((a) =>
            a.id === detailId ? { ...a, view_count: d.view_count } : a,
          ),
        );
      })
      .catch(() => setDetailId(null))
      .finally(() => {
        if (!cancelled) setLoadingDetail(false);
      });

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

  // Intersection observer for infinite scroll
  useEffect(() => {
    const el = loaderRef.current;
    if (!el || !hasMore || loading) return;

    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0]?.isIntersecting && hasMore && !loading && articles.length > 0) {
          const last = articles[articles.length - 1];
          loadArticles(last.id);
        }
      },
      { threshold: 0.1 },
    );

    observer.observe(el);
    return () => observer.disconnect();
  }, [hasMore, loading, articles, loadArticles]);

  // Listen to websocket events for article updates
  const { onEvent } = useChannelSocket();
  useEffect(() => {
    return onEvent((event) => {
      if (!event.room || event.room.id !== roomId) return;

      if (event.type === "article.liked" || event.type === "article.unliked") {
        const d = event.data as { article_id: string; like_count: number };
        setArticles((prev) =>
          prev.map((a) =>
            a.id === d.article_id ? { ...a, like_count: d.like_count } : a,
          ),
        );
        if (detail?.id === d.article_id) {
          setDetail((prev) =>
            prev ? { ...prev, like_count: d.like_count } : null,
          );
        }
      }

      if (event.type === "article.comment.created") {
        const d = event.data as {
          comment: { article: string };
          comment_count: number;
        };
        setArticles((prev) =>
          prev.map((a) =>
            a.id === d.comment.article
              ? { ...a, comment_count: d.comment_count }
              : a,
          ),
        );
        if (detail?.id === d.comment.article) {
          setDetail((prev) =>
            prev ? { ...prev, comment_count: d.comment_count } : null,
          );
        }
      }

      if (event.type === "article.comment.deleted") {
        const d = event.data as { article_id: string; comment_count: number };
        setArticles((prev) =>
          prev.map((a) =>
            a.id === d.article_id
              ? { ...a, comment_count: d.comment_count }
              : a,
          ),
        );
        if (detail?.id === d.article_id) {
          setDetail((prev) =>
            prev ? { ...prev, comment_count: d.comment_count } : null,
          );
        }
      }
    });
  }, [roomId, onEvent, detail?.id]);

  return (
    <div className="relative flex min-h-0 flex-1 flex-col">
      {/* Header */}
      <div className="flex shrink-0 items-center gap-3 border-b border-border/40 px-5 py-3">
        <Newspaper className="size-5 text-primary/50" />
        <div className="min-w-0 flex-1">
          <h2 className="truncate text-sm font-semibold">{roomName}</h2>
          <p className="text-[11px] text-muted-foreground/40">
            {articles.length}  articles
          </p>
        </div>
        <Button
          className="h-8 cursor-pointer gap-1.5 rounded-lg"
          onClick={onCompose}
          size="sm"
        >
          <Plus className="size-4" />
          Write
        </Button>
      </div>

      {/* Article grid */}
      <div className="min-h-0 flex-1 overflow-y-auto overscroll-contain">
        {loading ? (
          <div className="flex justify-center py-20">
            <Loader2 className="size-5 animate-spin text-muted-foreground/25" />
          </div>
        ) : articles.length === 0 ? (
          <div className="flex flex-col items-center justify-center py-20">
            <div className="grid size-16 place-items-center rounded-2xl bg-muted/20 ring-1 ring-border/20">
              <FileText className="size-7 text-muted-foreground/15" />
            </div>
            <p className="mt-4 text-sm font-medium text-muted-foreground/40">
              No articles yet
            </p>
            <p className="mt-1 text-[12px] text-muted-foreground/30">
              Be the first to share
            </p>
            <Button
              className="mt-4 cursor-pointer"
              onClick={onCompose}
              size="sm"
              variant="outline"
            >
              <Plus className="mr-1.5 size-4" />
              Write
            </Button>
          </div>
        ) : (
          <>
            {/* Masonry grid */}
            <div className="columns-1 gap-4 p-4 sm:columns-2 lg:columns-3 xl:columns-4">
              {articles.map((article) => (
                <div
                  className="mb-4 break-inside-avoid"
                  key={article.id}
                >
                  <ArticleCard
                    article={article}
                    onClick={(id) => setDetailId(id)}
                  />
                </div>
              ))}
            </div>

            {/* Loader sentinel */}
            <div ref={loaderRef} className="flex justify-center py-6">
              {hasMore && (
                <Loader2 className="size-4 animate-spin text-muted-foreground/20" />
              )}
              {!hasMore && articles.length > 0 && (
                <span className="text-[11px] text-muted-foreground/25">
                  — You have reached the end —
                </span>
              )}
            </div>
          </>
        )}
      </div>

      {/* Article detail panel */}
      {detailId && (
        <>
          <div
            className="absolute inset-0 z-20 bg-background/60 backdrop-blur-sm"
            onClick={() => setDetailId(null)}
          />
          <div className="absolute inset-0 right-0 z-30 overflow-y-auto bg-card shadow-2xl sm:inset-y-2 sm:right-2 sm:w-[min(640px,90vw)] sm:rounded-2xl sm:border sm:border-border/30">
            {loadingDetail ? (
              <div className="flex justify-center py-20">
                <Loader2 className="size-5 animate-spin text-muted-foreground/25" />
              </div>
            ) : detail ? (
              <ArticleDetail
                article={detail}
                currentUserId={currentUserId}
                onClose={() => setDetailId(null)}
                onUpdated={(updated) => {
                  setDetail(updated);
                  setArticles((prev) =>
                    prev.map((a) =>
                      a.id === updated.id
                        ? {
                            ...a,
                            title: updated.title,
                            summary: updated.summary,
                            cover_url: updated.cover_url,
                            tags: updated.tags,
                            like_count: updated.like_count,
                            comment_count: updated.comment_count,
                            view_count: updated.view_count,
                            is_pinned: updated.is_pinned,
                            updated_at: updated.updated_at,
                          }
                        : a,
                    ),
                  );
                }}
              />
            ) : null}
          </div>
        </>
      )}

    </div>
  );
}
