import {
  useEffect,
  useState,
  useCallback,
  useRef,
  memo,
  useMemo,
} from "react";
import { motion, AnimatePresence } from "framer-motion";
import {
  Plus,
  ChevronRight,
  Loader2,
  ArrowUp,
  Sparkles,
  Users,
  RefreshCw,
  Share2,
  MessageCircle,
} from "lucide-react";
import { useNavigate } from "react-router-dom";
import { useQueryClient } from "@tanstack/react-query";
import { supabase, createRealtimeChannel } from "@/integrations/supabase/client";
import { CHANNELS } from "@/lib/realtimeBridge";
import { useAuth } from "@/hooks/useAuth";
import { useLike } from "@/hooks/useLike";
import AppLayout from "@/components/AppLayout";
import BrandLogo from "@/components/BrandLogo";
import Avatar from "@/components/Avatar";
import PostCard from "@/components/PostCard";
import StoryCard from "@/components/StoryCard";
import NotesBar from "@/components/NotesBar";
import { AdInFeed } from "@/components/AdPost";
import { preloadPage } from "@/lib/preload";
import { instantCache } from "@/lib/instantCache";
import { cn } from "@/lib/utils";

// ── Public exports ────────────────────────────────────────────────────────────
export const FEED_QUERY_KEY = ["feed-posts"];

/**
 * Fire-and-forget helper to record a share event.
 * Safe to call without awaiting — any RPC errors are swallowed.
 */
export async function trackShare(
  postId: string,
  type: "link" | "native" | "copy" | "story" | string
): Promise<void> {
  try {
    await supabase.rpc("record_post_share", {
      p_post_id: postId,
      p_share_type: type,
    });
  } catch {
    // RPC may not exist yet — silently ignore
  }
}

// ── Constants ─────────────────────────────────────────────────────────────────
/** Number of posts revealed per virtual display window chunk */
const CHUNK_SIZE = 8;
/** ms a post must stay in-viewport before a view event is recorded */
const VIEW_THRESHOLD_MS = 1500;

// ── Types ─────────────────────────────────────────────────────────────────────
interface Story {
  id: string;
  user_id: string;
  image_url: string;
  is_sponsored?: boolean;
  profiles?: any;
}

interface Post {
  id: string;
  user_id: string;
  image_url: string;
  image_urls?: string[] | null;
  caption: string | null;
  likes_count: number;
  comments_count: number;
  created_at: string;
  profiles?: any;
  liked_by_me?: boolean;
  media_type?: "image" | "video" | "audio";
  video_url?: string | null;
  thumbnail_url?: string | null;
  audio_url?: string | null;
  scale_type?: "original" | "square" | "wide" | "tall";
  music_metadata?: any;
  is_sponsored?: boolean;
  sponsor_name?: string;
  sponsor_url?: string;
}

/** Cursor used for keyset / cursor-based pagination */
interface FeedCursor {
  lastPostId: string;
  lastCreatedAt: string;
}

// ── Session Seen & Diversification Algorithm Helpers ─────────────────────────
const SESSION_SEEN_KEY = "bekmora_feed_seen_v1";

function getSessionSeenPosts(): Set<string> {
  try {
    const raw = sessionStorage.getItem(SESSION_SEEN_KEY);
    return raw ? new Set(JSON.parse(raw)) : new Set();
  } catch {
    return new Set();
  }
}

function recordSessionSeenPost(postId: string) {
  try {
    const seen = getSessionSeenPosts();
    seen.add(postId);
    const arr = Array.from(seen).slice(-400);
    sessionStorage.setItem(SESSION_SEEN_KEY, JSON.stringify(arr));
  } catch {}
}

function hashIdWithSeed(id: string, seed: number): number {
  let h = seed;
  for (let i = 0; i < id.length; i++) {
    h = (h * 31 + id.charCodeAt(i)) & 0xffffffff;
  }
  return Math.abs(h);
}

function interleaveCreators(scoredPosts: { p: Post; score: number }[]): Post[] {
  const result: Post[] = [];
  const remaining = [...scoredPosts];
  const lastCreatorIds: string[] = [];

  while (remaining.length > 0) {
    let idx = remaining.findIndex(
      (item) => !lastCreatorIds.includes(item.p.user_id)
    );
    if (idx === -1) {
      idx = 0;
    }
    const [chosen] = remaining.splice(idx, 1);
    result.push(chosen.p);
    lastCreatorIds.push(chosen.p.user_id);
    if (lastCreatorIds.length > 2) lastCreatorIds.shift();
  }

  return result;
}

// ── Memoised PostCard ─────────────────────────────────────────────────────────
const MemoPostCard = memo(PostCard);

// ── Skeleton card (shimmer) ───────────────────────────────────────────────────
const FeedPostSkeleton = () => (
  <div className="bg-[#0F172A] rounded-3xl p-5 space-y-4 shadow-sm border border-white/[0.04] overflow-hidden relative">
    {/* shimmer sweep */}
    <div className="absolute inset-0 -translate-x-full animate-[shimmer_2s_infinite] bg-gradient-to-r from-transparent via-white/5 to-transparent z-10 pointer-events-none" />
    {/* header */}
    <div className="flex items-center gap-3">
      <div className="w-10 h-10 rounded-full bg-slate-800/60 shrink-0" />
      <div className="space-y-2 flex-1">
        <div className="h-3 w-32 bg-slate-800/60 rounded-full" />
        <div className="h-2 w-20 bg-slate-800/40 rounded-full" />
      </div>
    </div>
    {/* media placeholder */}
    <div className="h-[320px] sm:h-[400px] w-full bg-slate-800/40 rounded-2xl" />
    {/* action row */}
    <div className="flex items-center gap-4 pt-1">
      <div className="w-6 h-6 rounded-full bg-slate-800/60" />
      <div className="w-6 h-6 rounded-full bg-slate-800/60" />
      <div className="w-6 h-6 rounded-full bg-slate-800/60" />
    </div>
    {/* caption lines */}
    <div className="space-y-2">
      <div className="h-2.5 w-3/4 bg-slate-800/40 rounded-full" />
      <div className="h-2.5 w-1/2 bg-slate-800/30 rounded-full" />
    </div>
  </div>
);

// ── Story skeleton ────────────────────────────────────────────────────────────
const StorySkeleton = () => (
  <div className="flex flex-col items-center gap-1.5 shrink-0 animate-pulse">
    <div className="w-16 h-16 rounded-full bg-slate-800/60" />
    <div className="w-12 h-2 rounded-full bg-slate-800/40" />
  </div>
);

// ── Main Feed component ───────────────────────────────────────────────────────
export default function Feed() {
  const navigate = useNavigate();
  const { user, profile } = useAuth();
  const { toggleLike } = useLike();
  const queryClient = useQueryClient();

  const currentUserPhoto =
    profile?.photo_url ||
    user?.user_metadata?.photo_url ||
    user?.user_metadata?.avatar_url ||
    (user ? instantCache.get<string>(`user_avatar_${user.id}`) : null);
  const currentUserName =
    profile?.display_name ||
    user?.user_metadata?.full_name ||
    user?.user_metadata?.user_name ||
    "Você";

  useEffect(() => {
    if (user && profile?.photo_url) {
      instantCache.set(`user_avatar_${user.id}`, profile.photo_url, 60 * 60 * 1000);
    }
  }, [user, profile?.photo_url]);

  // ── UI state (Hydrated synchronously from InstantCache for 0ms initial render) ──
  const initialCachedPosts = useMemo(() => instantCache.get<Post[]>("feed_posts") || [], []);
  const initialCachedStories = useMemo(() => instantCache.get<Story[]>("feed_stories") || [], []);

  const [feedMode, setFeedMode] = useState<"for-you" | "following">("for-you");
  const [topMediaType, setTopMediaType] = useState<"stories" | "notes">("stories");
  const [stories, setStories] = useState<Story[]>(initialCachedStories);
  const [storiesLoading, setStoriesLoading] = useState(initialCachedStories.length === 0);
  const [posts, setPosts] = useState<Post[]>(initialCachedPosts);
  const [followingUserIds, setFollowingUserIds] = useState<Set<string>>(new Set());
  const [loading, setLoading] = useState(initialCachedPosts.length === 0);
  const [loadingMore, setLoadingMore] = useState(false);
  const [displayCount, setDisplayCount] = useState(CHUNK_SIZE);
  const [newPostsCount, setNewPostsCount] = useState(0);
  const [showScrollTop, setShowScrollTop] = useState(false);
  const [hasNextPage, setHasNextPage] = useState(true);

  // ── Refs ─────────────────────────────────────────────────────────────────────
  const storyScrollRef = useRef<HTMLDivElement>(null);
  const lastFeedTimestampRef = useRef<string | null>(null);
  const sentinelRef = useRef<HTMLDivElement>(null);
  const cursorRef = useRef<FeedCursor | null>(null);
  /** Posts whose view has already been recorded this session */
  const viewedPostsRef = useRef<Set<string>>(new Set());
  /** Per-post view timers: postId → setTimeout handle */
  const viewTimersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
  const prefetchingRef = useRef(false);

  // ── Preload adjacent pages ───────────────────────────────────────────────────
  useEffect(() => {
    preloadPage("/messages");
    preloadPage("/notifications");
    preloadPage("/profile");
    preloadPage("/search");
  }, []);

  const [refreshSeed, setRefreshSeed] = useState(() => Date.now());

  // ── Algorithmic ranking com penalidade de vistos, gravidade temporal e intercalação de criadores ──
  const rankPosts = useCallback(
    (rawPosts: Post[], followedIds: Set<string>, seed: number): Post[] => {
      const now = Date.now();
      const seenInSession = getSessionSeenPosts();

      const scored = rawPosts.map((p) => {
        if (p.is_sponsored) return { p, score: Infinity };

        // 1. Engajamento composto
        const likesScore = (p.likes_count || 0) * 3.2;
        const commentsScore = (p.comments_count || 0) * 5.5;
        const savesScore = (p.saves_count || 0) * 7.0;
        const engagement = likesScore + commentsScore + savesScore;

        // 2. Afinidade e relações sociais
        const isFollowed = followedIds.has(p.user_id);
        const followedBoost = isFollowed ? 38 : 0;
        const isSelf = user?.id === p.user_id;
        const selfBoost = isSelf ? 25 : 0;
        const verifiedBoost = p.profiles?.is_verified ? 14 : 0;

        // 3. Gravidade temporal (half-life equilibrado para circular publicações)
        const ageHours = Math.max(0.05, (now - new Date(p.created_at).getTime()) / 3_600_000);
        const timeDecay = Math.pow(ageHours + 1.8, 1.15);

        // 4. Frescor na sessão atual vs penalidade para posts já vistos
        const isSeen = seenInSession.has(p.id);
        const seenMultiplier = isSeen ? 0.22 : 1.6;

        // 5. Entropia de exploração dinâmica (±14%) para trazer variedade e desatar empates a cada refresh
        const jitterVal = (hashIdWithSeed(p.id, seed) % 29) - 14;
        const jitterMultiplier = 1 + jitterVal / 100;

        const score =
          ((engagement + followedBoost + selfBoost + verifiedBoost + 15) / timeDecay) *
          seenMultiplier *
          jitterMultiplier;

        return { p, score };
      });

      // Ordenação pela pontuação algorítmica
      scored.sort((a, b) => b.score - a.score);

      // Intercalação de criadores para diversificar o feed e evitar repetição consecutiva
      return interleaveCreators(scored);
    },
    [user]
  );

  // ── Mark posts as seen (fire and forget) ────────────────────────────────────
  const markPostsSeen = useCallback(
    (postIds: string[]) => {
      if (!user || postIds.length === 0) return;
      supabase
        .rpc("mark_posts_seen", { p_user_id: user.id, p_post_ids: postIds })
        .then()
        .catch(() => {/* RPC may not exist yet */});
    },
    [user]
  );

  // ── Client-side post fetch (fallback / pagination) ───────────────────────────
  const fetchPostsClientSide = useCallback(
    async (followedSet: Set<string>, cursor?: FeedCursor): Promise<Post[]> => {
      let query = supabase
        .from("posts")
        .select(
          "*, profiles!posts_user_id_fkey(display_name,photo_url,username,is_verified,profile_theme)"
        )
        .order("created_at", { ascending: false })
        .limit(60);

      if (cursor) {
        query = query.lt("created_at", cursor.lastCreatedAt);
      }

      const [postsRes, likesRes] = await Promise.all([
        query,
        user
          ? supabase.from("post_likes").select("post_id").eq("user_id", user.id)
          : Promise.resolve({ data: [] as { post_id: string }[] }),
      ]);

      if (!postsRes.data) return [];

      const likedSet = new Set(
        ((likesRes as any)?.data || []).map((l: any) => l.post_id)
      );

      return (postsRes.data as any[]).map((x) => ({
        ...x,
        liked_by_me: likedSet.has(x.id),
      }));
    },
    [user]
  );

  // ── Initial feed load (Parallelized SWR) ───────────────────────────────────
  const load = useCallback(async () => {
    // 1. Show cached data immediately (Stale-While-Revalidate)
    const cachedPosts = queryClient.getQueryData<Post[]>(FEED_QUERY_KEY) || instantCache.get<Post[]>("feed_posts");
    if (cachedPosts && cachedPosts.length > 0) {
      setPosts(cachedPosts);
      setLoading(false);
    }
    const cachedStories = instantCache.get<Story[]>("feed_stories");
    if (cachedStories && cachedStories.length > 0) {
      setStories(cachedStories);
      setStoriesLoading(false);
    }

    try {
      // 2. Fetch follows, stories, and posts in parallel
      const followsPromise = user
        ? supabase
            .from("follows")
            .select("following_id")
            .eq("follower_id", user.id)
        : Promise.resolve({ data: [] as any });

      const storiesPromise = supabase
        .from("stories")
        .select(
          "id,user_id,image_url,is_sponsored,profiles!stories_user_id_fkey(display_name,photo_url,username,is_verified,profile_theme)"
        )
        .gt("expires_at", new Date().toISOString())
        .order("created_at", { ascending: false })
        .limit(30);

      const postsPromise = fetchPostsClientSide(new Set<string>());

      const [followRes, storiesRes, rawPosts] = await Promise.all([
        followsPromise,
        storiesPromise,
        postsPromise,
      ]);

      // Process Followed Users
      let followedSet = new Set<string>();
      if (followRes.data) {
        followedSet = new Set((followRes.data as any[]).map((f) => f.following_id));
        setFollowingUserIds(followedSet);
      }

      // Process Stories
      if (storiesRes.data) {
        setStories(storiesRes.data as any);
        instantCache.set("feed_stories", storiesRes.data, 5 * 60 * 1000);
      }
      setStoriesLoading(false);

      // Process Posts & Ranking
      const ranked = rankPosts(rawPosts, followedSet, refreshSeed);
      setPosts(ranked);
      queryClient.setQueryData(FEED_QUERY_KEY, ranked);
      instantCache.set("feed_posts", ranked, 60 * 1000); // 1 minuto de cache para não congelar o feed
      setHasNextPage(rawPosts.length >= 20);

      if (ranked.length > 0) {
        lastFeedTimestampRef.current = ranked[0].created_at;
        cursorRef.current = {
          lastPostId: ranked[ranked.length - 1].id,
          lastCreatedAt: ranked[ranked.length - 1].created_at,
        };
      }

      // Mark loaded posts as seen (fire-and-forget)
      markPostsSeen(ranked.map((p) => p.id));
    } catch (err) {
      console.error("[Feed] Load error:", err);
    } finally {
      setDisplayCount(CHUNK_SIZE);
      setNewPostsCount(0);
      setLoading(false);
      setStoriesLoading(false);
    }
  }, [user, queryClient, rankPosts, fetchPostsClientSide, markPostsSeen, refreshSeed]);

  useEffect(() => {
    load();
  }, [load]);

  // ── Realtime: likes, comments, and posts on global shared channel ────────────
  useEffect(() => {
    const channel = createRealtimeChannel(CHANNELS.FEED);
    channel
      .on("broadcast", { event: "*" }, (raw: any) => {
        if (!raw) return;

        // 1. Sincronização de Curtidas em Tempo Real
        if (raw.type === "like_change" && raw.postId) {
          setPosts((prev) =>
            prev.map((p) => {
              if (p.id === raw.postId) {
                return {
                  ...p,
                  likes_count:
                    raw.likesCount !== undefined
                      ? raw.likesCount
                      : raw.liked
                      ? (p.likes_count || 0) + 1
                      : Math.max(0, (p.likes_count || 0) - 1),
                  liked_by_me: raw.userId === user?.id ? raw.liked : p.liked_by_me,
                };
              }
              return p;
            })
          );
        }

        // 2. Sincronização de Comentários em Tempo Real
        if (raw.type === "comment_change" && raw.postId) {
          setPosts((prev) =>
            prev.map((p) => {
              if (p.id === raw.postId) {
                const delta = raw.action === "add" ? 1 : -1;
                const nextCount =
                  raw.commentsCount !== undefined
                    ? raw.commentsCount
                    : Math.max(0, (p.comments_count || 0) + delta);
                return { ...p, comments_count: nextCount };
              }
              return p;
            })
          );
        }

        // 3. Novo Post publicado
        if (raw.type === "post_new") {
          setNewPostsCount((prev) => prev + 1);
        }

        // 4. Post excluído
        if (raw.type === "post_delete" && raw.postId) {
          setPosts((prev) => prev.filter((p) => p.id !== raw.postId));
        }
      })
      .on("postgres_changes", { event: "INSERT", schema: "public", table: "posts" }, () => {
        if (lastFeedTimestampRef.current) {
          setNewPostsCount((prev) => prev + 1);
        }
      })
      .on("postgres_changes", { event: "UPDATE", schema: "public", table: "posts" }, (payload) => {
        const { new: newRecord } = payload;
        setPosts((prev) =>
          prev.map((p) =>
            p.id === newRecord.id && newRecord.likes_count !== undefined
              ? {
                  ...p,
                  likes_count: newRecord.likes_count,
                  comments_count: newRecord.comments_count ?? p.comments_count,
                }
              : p
          )
        );
      })
      .subscribe();

    return () => {
      supabase.removeChannel(channel);
    };
  }, [user?.id]);

  // ── Scroll-to-top button visibility ─────────────────────────────────────────
  useEffect(() => {
    const handleScroll = () => setShowScrollTop(window.scrollY > 400);
    window.addEventListener("scroll", handleScroll, { passive: true });
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  const scrollToTop = useCallback(() => {
    window.scrollTo({ top: 0, behavior: "smooth" });
  }, []);

  // ── Load new realtime posts ──────────────────────────────────────────────────
  const loadNewPosts = useCallback(async () => {
    if (!lastFeedTimestampRef.current) return;
    const { data: newPosts } = await supabase
      .from("posts")
      .select(
        "*, profiles!posts_user_id_fkey(display_name,photo_url,username,is_verified,profile_theme)"
      )
      .gt("created_at", lastFeedTimestampRef.current)
      .order("created_at", { ascending: false });

    if (!newPosts?.length) return;

    let enriched: Post[] = newPosts as any[];
    if (user) {
      const { data: likes } = await supabase
        .from("post_likes")
        .select("post_id")
        .eq("user_id", user.id);
      const likedSet = new Set((likes || []).map((l) => l.post_id));
      enriched = (newPosts as any[]).map((x) => ({
        ...x,
        liked_by_me: likedSet.has(x.id),
      }));
    }

    setPosts((prev) => {
      const ranked = rankPosts([...enriched, ...prev], followingUserIds, Date.now());
      queryClient.setQueryData(FEED_QUERY_KEY, ranked);
      return ranked;
    });
    setDisplayCount(CHUNK_SIZE);
    setNewPostsCount(0);
    if (newPosts.length > 0) {
      lastFeedTimestampRef.current = newPosts[0].created_at;
    }
    markPostsSeen(enriched.map((p) => p.id));
  }, [user, rankPosts, followingUserIds, queryClient, markPostsSeen]);

  // ── Cursor-based "load more" ─────────────────────────────────────────────────
  const loadMore = useCallback(async () => {
    if (loadingMore || !hasNextPage) return;

    // If we still have posts in local buffer, just reveal the next chunk
    const filtered =
      feedMode === "following"
        ? posts.filter((p) => p.user_id && followingUserIds.has(p.user_id))
        : posts;

    if (displayCount < filtered.length) {
      setDisplayCount((prev) => Math.min(prev + CHUNK_SIZE, filtered.length));
      return;
    }

    // Fetch the next page from the server using cursor
    if (!cursorRef.current) return;
    setLoadingMore(true);

    try {
      const cursor = cursorRef.current;
      let nextPosts: Post[] = [];
      let usedRpc = false;

      if (user) {
        try {
          const { data: rpcData, error: rpcErr } = await (supabase.rpc as any)(
            "get_personalized_feed",
            {
              p_user_id: user.id,
              p_limit: 20,
              p_cursor_post_id: cursor.lastPostId,
              p_cursor_created_at: cursor.lastCreatedAt,
            }
          );
          if (!rpcErr && rpcData && Array.isArray(rpcData)) {
            const { data: likeRows } = await supabase
              .from("post_likes")
              .select("post_id")
              .eq("user_id", user.id);
            const likedSet = new Set((likeRows || []).map((l: any) => l.post_id));
            nextPosts = (rpcData as any[]).map((x: any) => ({
              ...x,
              liked_by_me: likedSet.has(x.id),
            }));
            usedRpc = true;
          }
        } catch {
          /* fall through to client-side */
        }
      }

      if (!usedRpc) {
        nextPosts = await fetchPostsClientSide(followingUserIds, cursor);
      }

      if (nextPosts.length === 0) {
        setHasNextPage(false);
        return;
      }

      // Deduplicate by id before merging
      const existingIds = new Set(posts.map((p) => p.id));
      const fresh = nextPosts.filter((p) => !existingIds.has(p.id));

      if (fresh.length === 0) {
        setHasNextPage(false);
        return;
      }

      const merged = [...posts, ...fresh];
      setPosts(merged);
      queryClient.setQueryData(FEED_QUERY_KEY, merged);
      setDisplayCount((prev) => prev + CHUNK_SIZE);
      setHasNextPage(fresh.length >= 20);

      const lastPost = fresh[fresh.length - 1];
      cursorRef.current = {
        lastPostId: lastPost.id,
        lastCreatedAt: lastPost.created_at,
      };

      markPostsSeen(fresh.map((p) => p.id));
    } catch (err) {
      console.error("[Feed] loadMore error:", err);
    } finally {
      setLoadingMore(false);
    }
  }, [
    loadingMore,
    hasNextPage,
    displayCount,
    feedMode,
    posts,
    followingUserIds,
    user,
    queryClient,
    fetchPostsClientSide,
    markPostsSeen,
  ]);

  // ── IntersectionObserver — sentinel drives pagination ────────────────────────
  useEffect(() => {
    if (!sentinelRef.current || loading) return;
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting) loadMore();
      },
      { rootMargin: "400px" }
    );
    observer.observe(sentinelRef.current);
    return () => observer.disconnect();
  }, [loading, loadMore]);

  // ── Prefetch next page silently when 80% of buffer is displayed ───────────────
  useEffect(() => {
    const filtered =
      feedMode === "following"
        ? posts.filter((p) => p.user_id && followingUserIds.has(p.user_id))
        : posts;

    const threshold = filtered.length * 0.8;
    if (
      displayCount >= threshold &&
      hasNextPage &&
      !loadingMore &&
      !prefetchingRef.current &&
      cursorRef.current
    ) {
      prefetchingRef.current = true;
      const cursor = cursorRef.current;

      fetchPostsClientSide(followingUserIds, cursor)
        .then((nextPosts) => {
          if (nextPosts.length === 0) {
            setHasNextPage(false);
            return;
          }
          const existingIds = new Set(posts.map((p) => p.id));
          const fresh = nextPosts.filter((p) => !existingIds.has(p.id));
          if (fresh.length > 0) {
            const merged = [...posts, ...fresh];
            setPosts(merged);
            queryClient.setQueryData(FEED_QUERY_KEY, merged);
            const lastPost = fresh[fresh.length - 1];
            cursorRef.current = {
              lastPostId: lastPost.id,
              lastCreatedAt: lastPost.created_at,
            };
            setHasNextPage(fresh.length >= 20);
          }
        })
        .catch(() => {/* silently ignore prefetch errors */})
        .finally(() => {
          prefetchingRef.current = false;
        });
    }
  }, [
    displayCount,
    posts,
    followingUserIds,
    feedMode,
    hasNextPage,
    loadingMore,
    fetchPostsClientSide,
    queryClient,
  ]);

  // ── View tracking via IntersectionObserver ────────────────────────────────────
  useEffect(() => {
    if (!user || loading) return;

    // Map from observed element → post id
    const observedEls = new Map<Element, string>();

    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          const postId = observedEls.get(entry.target);
          if (!postId) return;

          if (entry.isIntersecting) {
            // Skip if already viewed this session
            if (viewedPostsRef.current.has(postId)) return;

            const timer = setTimeout(() => {
              if (!viewedPostsRef.current.has(postId)) {
                viewedPostsRef.current.add(postId);
                recordSessionSeenPost(postId);
                // Fire-and-forget view RPC
                supabase
                  .rpc("record_post_view", {
                    p_post_id: postId,
                    p_view_duration_ms: VIEW_THRESHOLD_MS,
                  })
                  .then()
                  .catch(() => {/* RPC may not exist yet */});
              }
              viewTimersRef.current.delete(postId);
            }, VIEW_THRESHOLD_MS);

            viewTimersRef.current.set(postId, timer);
          } else {
            // Post left viewport before threshold — cancel the timer
            const existingTimer = viewTimersRef.current.get(postId);
            if (existingTimer) {
              clearTimeout(existingTimer);
              viewTimersRef.current.delete(postId);
            }
          }
        });
      },
      { threshold: 0.5 }
    );

    // Observe all [data-post-id] elements in the DOM
    document.querySelectorAll("[data-post-id]").forEach((el) => {
      const postId = el.getAttribute("data-post-id");
      if (postId) {
        observedEls.set(el, postId);
        observer.observe(el);
      }
    });

    return () => {
      observer.disconnect();
      // Clear all pending view timers on cleanup
      viewTimersRef.current.forEach((timer) => clearTimeout(timer));
      viewTimersRef.current.clear();
    };
  }, [user, loading, displayCount, feedMode]);

  // ── Toggle like (optimistic) ─────────────────────────────────────────────────
  const handleToggleLike = useCallback(
    (post: Post) => {
      if (!user) return;
      const wasLiked = !!post.liked_by_me;
      setPosts((prev) =>
        prev.map((x) =>
          x.id === post.id
            ? {
                ...x,
                liked_by_me: !wasLiked,
                likes_count: wasLiked
                  ? Math.max(0, (x.likes_count || 0) - 1)
                  : (x.likes_count || 0) + 1,
              }
            : x
        )
      );
      toggleLike(post.id, wasLiked);
    },
    [user, toggleLike]
  );

  // ── Filtered posts for the active tab ────────────────────────────────────────
  const displayedPosts = useMemo(() => {
    if (feedMode === "following") {
      return posts
        .filter((p) => p.user_id && (followingUserIds.has(p.user_id) || (user && p.user_id === user.id)))
        .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
    }
    return posts;
  }, [feedMode, posts, followingUserIds, user]);

  // User's own stories
  const myStories = useMemo(() => {
    if (!user) return [];
    return stories.filter((s) => s.user_id === user.id);
  }, [stories, user]);
  const hasMyStory = myStories.length > 0;

  // Deduplicate stories: one entry per other user (exclude current user so they aren't duplicated next to "Seu story")
  // Regra estilo Instagram: apenas criadores que o usuário logado segue ou patrocinados
  const otherStoryGroups = useMemo(
    () =>
      Array.from(
        stories
          .filter((s) => s.is_sponsored || (user && followingUserIds.has(s.user_id)))
          .reduce((map, s) => {
            if (!map.has(s.user_id)) map.set(s.user_id, s);
            return map;
          }, new Map<string, Story>())
          .values()
      ),
    [stories, user, followingUserIds]
  );

  // ── Render ───────────────────────────────────────────────────────────────────
  return (
    <AppLayout>
      <div className="w-full space-y-6 pb-8">

        {/* ── Feed Mode Tabs (Para Você / Seguindo) ───────────────────────── */}
        <div className="sticky top-14 lg:top-0 z-20 bg-[#090A0E]/90 backdrop-blur-2xl border-b border-white/[0.06] -mx-4 px-4 sm:mx-0 sm:px-0 sm:rounded-2xl pt-2 pb-2 flex items-center justify-center">
          <div className="relative flex items-center gap-1 bg-[#111317]/80 p-1 rounded-full border border-white/[0.08]">
            <button
              onClick={() => {
                if (feedMode === "for-you") {
                  const newSeed = Date.now();
                  setRefreshSeed(newSeed);
                  setPosts((prev) => rankPosts(prev, followingUserIds, newSeed));
                  scrollToTop();
                } else {
                  setFeedMode("for-you");
                }
              }}
              className={cn(
                "relative z-10 px-5 py-2 rounded-full text-xs font-bold transition-all duration-300 flex items-center gap-1.5 min-w-[110px] justify-center min-h-[40px] tap-tactile",
                feedMode === "for-you" ? "text-white" : "text-slate-400 hover:text-white"
              )}
            >
              {feedMode === "for-you" && (
                <motion.div
                  layoutId="activeFeedModeTab"
                  transition={{ type: "spring", stiffness: 400, damping: 32 }}
                  className="absolute inset-0 rounded-full bg-gradient-to-r from-red-500 via-orange-500 to-amber-500 shadow-md shadow-orange-500/25"
                />
              )}
              <span className="relative z-10 flex items-center gap-1.5">
                <Sparkles className="w-3.5 h-3.5" />
                Para Você
              </span>
            </button>

            <button
              onClick={() => setFeedMode("following")}
              className={cn(
                "relative z-10 px-5 py-2 rounded-full text-xs font-bold transition-all duration-300 flex items-center gap-1.5 min-w-[110px] justify-center min-h-[40px] tap-tactile",
                feedMode === "following" ? "text-white" : "text-slate-400 hover:text-white"
              )}
            >
              {feedMode === "following" && (
                <motion.div
                  layoutId="activeFeedModeTab"
                  transition={{ type: "spring", stiffness: 400, damping: 32 }}
                  className="absolute inset-0 rounded-full bg-gradient-to-r from-red-500 via-orange-500 to-amber-500 shadow-md shadow-orange-500/25"
                />
              )}
              <span className="relative z-10 flex items-center gap-1.5">
                <Users className="w-3.5 h-3.5" />
                Seguindo
                {followingUserIds.size > 0 && (
                  <span className="text-[10px] opacity-80 font-mono tabular-nums">
                    ({followingUserIds.size})
                  </span>
                )}
              </span>
            </button>
          </div>
        </div>

        {/* ── Stories & Notes Header Bar ─────────────────────────────────── */}
        <div className="space-y-3">
          <div className="flex items-center justify-between px-1">
            <div className="flex items-center gap-1.5 p-1 rounded-2xl bg-[#111317]/80 border border-white/[0.08]">
              <button
                onClick={() => setTopMediaType("stories")}
                className={cn(
                  "relative px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all",
                  topMediaType === "stories" ? "text-white" : "text-slate-400 hover:text-white"
                )}
              >
                {topMediaType === "stories" && (
                  <motion.div
                    layoutId="feedTopMediaPill"
                    className="absolute inset-0 rounded-xl bg-gradient-to-r from-red-500 to-orange-500 shadow-md shadow-orange-500/20"
                    transition={{ type: "spring", stiffness: 380, damping: 28 }}
                  />
                )}
                <span className="relative z-10 flex items-center gap-1.5">
                  <Sparkles className="w-3.5 h-3.5 text-amber-300" /> Stories
                </span>
              </button>

              <button
                onClick={() => setTopMediaType("notes")}
                className={cn(
                  "relative px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all",
                  topMediaType === "notes" ? "text-white" : "text-slate-400 hover:text-white"
                )}
              >
                {topMediaType === "notes" && (
                  <motion.div
                    layoutId="feedTopMediaPill"
                    className="absolute inset-0 rounded-xl bg-gradient-to-r from-red-500 to-orange-500 shadow-md shadow-orange-500/20"
                    transition={{ type: "spring", stiffness: 380, damping: 28 }}
                  />
                )}
                <span className="relative z-10 flex items-center gap-1.5">
                  <MessageCircle className="w-3.5 h-3.5 text-orange-300" /> Notas
                </span>
              </button>
            </div>

            {topMediaType === "stories" ? (
              <button
                onClick={() => navigate("/create-story")}
                className="text-xs font-bold text-amber-400 hover:text-amber-300 transition-colors flex items-center gap-1 min-h-[44px]"
              >
                Criar story <ChevronRight className="w-3.5 h-3.5" />
              </button>
            ) : null}
          </div>

          <AnimatePresence mode="wait">
            {topMediaType === "stories" ? (
              <motion.div
                key="stories-tray"
                initial={{ opacity: 0, y: 6 }}
                animate={{ opacity: 1, y: 0 }}
                exit={{ opacity: 0, y: -6 }}
                transition={{ duration: 0.18 }}
              >
                {/* Scroll row with left/right edge-fade indicators */}
                <div className="relative -mx-4 sm:-mx-0">
                  {/* Left fade */}
                  <div className="absolute left-0 top-0 bottom-2 w-8 bg-gradient-to-r from-[#050914] to-transparent z-10 pointer-events-none sm:hidden" />
                  {/* Right fade */}
                  <div className="absolute right-0 top-0 bottom-2 w-10 bg-gradient-to-l from-[#050914] to-transparent z-10 pointer-events-none" />

                  <div
                    ref={storyScrollRef}
                    className="flex gap-3 overflow-x-auto pb-2 px-4 sm:px-0 scrollbar-hide select-none"
                  >
                    {/* Create Story / View My Story button */}
                    <motion.div
                      whileTap={{ scale: 0.94 }}
                      className="flex flex-col items-center gap-1.5 shrink-0 group min-h-[44px] cursor-pointer select-none"
                    >
                      <div
                        onClick={() => {
                          if (hasMyStory && user?.id) {
                            navigate(`/stories/${user.id}`);
                          } else {
                            navigate("/create-story");
                          }
                        }}
                        className={cn(
                          "relative w-16 h-16 rounded-full transition-all duration-200",
                          hasMyStory
                            ? "story-ring-unseen shadow-md shadow-sky-500/20 p-[2.5px] active:scale-95"
                            : "border-2 border-dashed border-white/20 hover:border-sky-400/60 p-0.5"
                        )}
                      >
                        <div className="w-full h-full rounded-full bg-[#0B1120] flex items-center justify-center overflow-hidden">
                          {currentUserPhoto ? (
                            <img
                              src={currentUserPhoto}
                              alt={currentUserName}
                              className="w-full h-full object-cover group-hover:scale-105 transition-transform"
                            />
                          ) : (
                            <Avatar
                              url={currentUserPhoto}
                              name={currentUserName}
                              size="md"
                            />
                          )}
                        </div>
                        <button
                          type="button"
                          onClick={(e) => {
                            e.stopPropagation();
                            navigate("/create-story");
                          }}
                          title="Adicionar ao story"
                          aria-label="Adicionar story"
                          className="absolute bottom-0 right-0 w-5 h-5 rounded-full bg-gradient-to-tr from-sky-500 to-blue-600 text-white flex items-center justify-center ring-2 ring-[#050914] shadow-md hover:scale-110 active:scale-95 transition-transform"
                        >
                          <Plus className="w-3.5 h-3.5 stroke-[3px]" />
                        </button>
                      </div>
                      <span className="text-[11px] text-slate-300 font-semibold truncate max-w-[64px]">
                        Seu story
                      </span>
                    </motion.div>

                    {/* Story cards or skeletons */}
                    {storiesLoading ? (
                      [1, 2, 3, 4].map((i) => <StorySkeleton key={i} />)
                    ) : (
                      otherStoryGroups.map((s) => (
                        <StoryCard
                          key={s.user_id}
                          user_id={s.user_id}
                          profiles={s.profiles}
                          is_sponsored={s.is_sponsored}
                        />
                      ))
                    )}
                  </div>
                </div>
              </motion.div>
            ) : (
              <motion.div
                key="notes-tray"
                initial={{ opacity: 0, y: 6 }}
                animate={{ opacity: 1, y: 0 }}
                exit={{ opacity: 0, y: -6 }}
                transition={{ duration: 0.18 }}
                className="glass-card-2026 p-4 rounded-3xl"
              >
                <NotesBar />
              </motion.div>
            )}
          </AnimatePresence>
        </div>

        {/* ── Realtime New Posts Floating Pill ────────────────────────────── */}
        <AnimatePresence>
          {newPostsCount > 0 && (
            <motion.div
              initial={{ opacity: 0, y: -20 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: -20 }}
              className="sticky top-14 z-30 flex justify-center pointer-events-none"
            >
              <button
                onClick={() => {
                  loadNewPosts();
                  scrollToTop();
                }}
                className="pointer-events-auto flex items-center gap-2 px-5 py-2.5 rounded-full bg-sky-600 hover:bg-sky-500 text-white text-xs font-bold shadow-xl shadow-sky-600/40 transition-all active:scale-95"
              >
                <RefreshCw className="w-3.5 h-3.5 animate-spin" />
                {newPostsCount} nova{newPostsCount > 1 ? "s" : ""} publicação
                {newPostsCount > 1 ? "ões" : ""}
                <ArrowUp className="w-3.5 h-3.5" />
              </button>
            </motion.div>
          )}
        </AnimatePresence>

        {/* ── Posts Feed List ─────────────────────────────────────────────── */}
        {loading ? (
          // 5 shimmer skeleton cards
          <div className="space-y-6">
            {[1, 2, 3, 4, 5].map((i) => (
              <FeedPostSkeleton key={i} />
            ))}
          </div>
        ) : displayedPosts.length === 0 ? (
          /* ── Improved animated empty state ── */
          <motion.div
            initial={{ opacity: 0, y: 16 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ type: "spring", stiffness: 280, damping: 24 }}
            className="py-20 space-y-6 text-center"
          >
            <motion.div
              animate={{ scale: [1, 1.08, 1], rotate: [0, -8, 8, 0] }}
              transition={{ duration: 3, repeat: Infinity, ease: "easeInOut" }}
              className="w-20 h-20 mx-auto rounded-3xl bg-slate-900/80 border border-white/[0.08] flex items-center justify-center text-sky-400 shadow-xl shadow-sky-500/10"
            >
              {feedMode === "following" ? (
                <Users className="w-9 h-9" />
              ) : (
                <Sparkles className="w-9 h-9" />
              )}
            </motion.div>

            <div>
              <p className="text-lg font-bold text-white">
                {feedMode === "following"
                  ? "Nenhuma publicação dos que você segue"
                  : "Tudo pronto para decolar 🚀"}
              </p>
              <p className="text-xs text-slate-400 mt-2 max-w-xs mx-auto leading-relaxed">
                {feedMode === "following"
                  ? "Siga criadores na aba Comunidade para ver suas postagens aqui!"
                  : "Seja o primeiro a compartilhar uma foto ou vídeo com a comunidade!"}
              </p>
            </div>

            <div className="flex gap-2 justify-center flex-wrap">
              {feedMode === "following" ? (
                <button
                  onClick={() => navigate("/community")}
                  className="px-6 py-2.5 rounded-full bg-sky-600 hover:bg-sky-500 text-white text-xs font-bold transition-all shadow-md shadow-sky-600/30 min-h-[44px]"
                >
                  Explorar Criadores
                </button>
              ) : (
                <>
                  <button
                    onClick={() => navigate("/create-post")}
                    className="px-6 py-2.5 rounded-full bg-sky-600 hover:bg-sky-500 text-white text-xs font-bold transition-all shadow-md shadow-sky-600/30 flex items-center gap-1.5 min-h-[44px]"
                  >
                    <Plus className="w-4 h-4" /> Criar publicação
                  </button>
                  <button
                    onClick={() => navigate("/community")}
                    className="px-6 py-2.5 rounded-full bg-white/[0.06] hover:bg-white/[0.1] border border-white/[0.08] text-white text-xs font-bold transition-all min-h-[44px]"
                  >
                    Explorar Comunidade
                  </button>
                </>
              )}
            </div>
          </motion.div>
        ) : (
          <div className="space-y-7">
            <AnimatePresence mode="popLayout">
              {(() => {
                const visiblePosts = displayedPosts.slice(0, displayCount);
                const items: JSX.Element[] = [];
                const adPositions =
                  visiblePosts.length >= 10
                    ? [3, 7]
                    : visiblePosts.length >= 5
                    ? [Math.floor(visiblePosts.length / 2)]
                    : [];

                visiblePosts.forEach((post, i) => {
                  items.push(
                    <motion.div
                      key={post.id}
                      // data-post-id consumed by view-tracking IntersectionObserver
                      data-post-id={post.id}
                      initial={{ opacity: 0, y: 14 }}
                      animate={{ opacity: 1, y: 0 }}
                      transition={{
                        delay: i * 0.02,
                        type: "spring",
                        stiffness: 320,
                        damping: 26,
                      }}
                      layout
                    >
                      <MemoPostCard
                        {...post}
                        onToggleLike={handleToggleLike}
                        index={i}
                      />
                    </motion.div>
                  );

                  if (adPositions.includes(i)) {
                    items.push(
                      <motion.div
                        key={`ad-${i}`}
                        initial={{ opacity: 0, y: 14 }}
                        animate={{ opacity: 1, y: 0 }}
                        transition={{ delay: (i + 0.5) * 0.02 }}
                      >
                        <AdInFeed />
                      </motion.div>
                    );
                  }
                });

                return items;
              })()}
            </AnimatePresence>

            {/* ── Infinite scroll sentinel ─────────────────────────────── */}
            <div ref={sentinelRef} className="flex items-center justify-center py-6">
              {loadingMore ? (
                <div className="flex items-center gap-2 text-slate-400 text-xs font-semibold">
                  <Loader2 className="w-4 h-4 text-sky-400 animate-spin" />
                  Carregando mais…
                </div>
              ) : !hasNextPage && displayCount >= displayedPosts.length ? (
                <div className="flex flex-col items-center gap-2 text-slate-500 text-xs">
                  <Share2 className="w-5 h-5 opacity-40" />
                  <span>Você viu tudo por enquanto!</span>
                </div>
              ) : null}
            </div>
          </div>
        )}

        {/* ── Scroll-to-top FAB ────────────────────────────────────────────── */}
        <AnimatePresence>
          {showScrollTop && (
            <motion.button
              initial={{ opacity: 0, scale: 0.8 }}
              animate={{ opacity: 1, scale: 1 }}
              exit={{ opacity: 0, scale: 0.8 }}
              onClick={scrollToTop}
              className="fixed bottom-20 right-6 z-40 w-11 h-11 rounded-2xl bg-slate-900/90 border border-white/[0.1] text-sky-400 shadow-2xl flex items-center justify-center hover:bg-slate-800 transition-all active:scale-90"
              aria-label="Voltar ao topo"
            >
              <ArrowUp className="w-5 h-5 stroke-[2.5px]" />
            </motion.button>
          )}
        </AnimatePresence>
      </div>
    </AppLayout>
  );
}