"use client";

import { useEffect, useMemo, useState, useCallback, useRef } from "react";
import { useSession } from "@/auth/session-provider";
import { useSetAtom } from "jotai";
import { loginModalAtom } from "@/components/state/loginAtom";
import { useAppToast, useNumberFormatter } from "@/app/[lang]/providers";
import { useTranslations, useLocale } from "next-intl";
import { useQueryClient } from "@tanstack/react-query";
import { useYearlyAuctionSocket } from "@/lib/socket/useYearlyAuctionSocket";
import { useAvailablePaddlesForAuction } from "@/lib/clientQueries";
import YearlyPaddleModal from "@/components/paddles/YearlyPaddleModal";
import { getPrefetchedGroupAuctionTerms } from "@/lib/groupAuctionShowTerms";
import WinnerOverlay from "./WinnerOverlay";
import LiveRoomsTable from "@/components/annaulMazad/LiveRoomsTable";
import CurrentAuctionCard from "./CurrentAuctionCard";
import AdminAnnouncementsCard from "./AdminAnnouncementsCard";
import BookletDownloadCard from "@/components/annaulMazad/BookletDownloadCard";
import GroupAuctionLocationMapSection from "@/components/annaulMazad/GroupAuctionLocationMapSection";
import YearlyVoiceStreamControl from "@/components/yearly/YearlyVoiceStreamControl";
import YearlyLiveConnectionBanner from "@/components/yearly/YearlyLiveConnectionBanner";
import type { AdminAnnouncementPayload } from "@/lib/socket/yearlyTypes";
import type {
  GroupAuction,
  AuctionGroupRoom,
  SingleAuction,
} from "@/actions/group-auctions";
import {
  getActiveAuctionGroup,
  getAuctionGroupById,
} from "@/actions/group-auctions";
import { getUserPaddlesForGroupAuction } from "@/actions/paddle-subscriptions";
import DynamicButton from "@/components/button";
import { validateAnnualBidDelta } from "@/lib/auction/biddingRules";
import { formatPaddleDisplay } from "@/lib/utils";
import { toMoneyNumber } from "@/lib/moneyParse";
import {
  playAuctionSound,
  playStartGroupAuctionSound,
} from "@/lib/utils/soundPlayer";
import { usePaymentSuccess } from "@/hooks/usePaymentSuccess";
import { extractCloseOutcomeFromPayload } from "@/lib/socket/yearlyCloseRowStatus";
import {
  extractYearlyPauseFromPayload,
  mergeYearlyPauseFromSources,
} from "@/lib/socket/yearlyPausePayload";
import {
  coerceYearlyBidsArray,
  collectPaddlesByUserId,
  mapYearlyStateBidsToUiBidders,
  mergeYearlyHighestBidderPreservingPaddle,
  mergeYearlyUiBiddersPreservingPaddles,
  yearlyActiveRestPayloadToState,
} from "@/lib/socket/yearlyGroupStateNormalize";
import { parseSingleAuctionsResponse } from "@/components/annaulMazad/live-rooms-table/utils";
import type {
  LiveClosedSummary,
  SingleAuctionListResponse,
} from "@/components/annaulMazad/live-rooms-table/types";

interface Bidder {
  name: string;
  paddle: number;
  paddleUniqueId?: string | null;
  amount: number;
  timestamp?: string;
  userId?: string;
}

interface YearlyAuctionLiveClientProps {
  groupAuction: GroupAuction;
  initialRooms: AuctionGroupRoom[];
  initialInnerAuctions?: SingleAuction[];
  initialInnerNextCursor?: string | null;
  videoUrl?: string;
  maxDays: number;
  animalType: "horse" | "camel";
}

type ToastKind = "success" | "error" | "info" | "warning";

function auctionGroupIdsEqual(a: unknown, b: unknown): boolean {
  if (a == null || b == null) return false;
  return String(a).trim() === String(b).trim();
}

function findRoomByAuctionGroupId(
  rooms: AuctionGroupRoom[],
  auctionGroupId: string | null,
): AuctionGroupRoom | null {
  if (!auctionGroupId || !rooms?.length) return null;
  return rooms.find((r) => auctionGroupIdsEqual(r.id, auctionGroupId)) ?? null;
}

function posterUrlFromRoom(room: AuctionGroupRoom | null): string | null {
  if (!room) return null;
  const u = room.main_image?.url;
  if (typeof u === "string" && u.trim()) return u.trim();
  const mu = (room as AuctionGroupRoom & { main_image_url?: unknown })
    .main_image_url;
  if (typeof mu === "string" && mu.trim()) return mu.trim();
  return null;
}

function posterUrlFromSingleAuctionRow(first: unknown): string | null {
  if (!first || typeof first !== "object") return null;
  const x = first as Record<string, unknown>;
  const main = x.main_image as { url?: string } | null | undefined;
  if (typeof main?.url === "string" && main.url.trim()) return main.url.trim();
  const mf = x.media_files as Record<string, unknown> | null | undefined;
  const mm = mf?.main_image as { url?: string } | null | undefined;
  if (typeof mm?.url === "string" && mm.url.trim()) return mm.url.trim();
  if (typeof x.main_image_url === "string" && x.main_image_url.trim()) {
    return x.main_image_url.trim();
  }
  const animal = x.animal as Record<string, unknown> | null | undefined;
  const am = animal?.main_image as { url?: string } | null | undefined;
  if (typeof am?.url === "string" && am.url.trim()) return am.url.trim();
  if (typeof x.image === "string" && x.image.trim()) return x.image.trim();
  if (typeof x.image_url === "string" && x.image_url.trim()) {
    return x.image_url.trim();
  }
  return null;
}

function getYoutubeVideoId(raw: string | null | undefined): string | null {
  const v = (raw || "").trim();
  if (!v) return null;

  try {
    const url = new URL(v);
    const host = url.hostname.toLowerCase();
    const parts = url.pathname.split("/").filter(Boolean);

    if (host.includes("youtu.be")) {
      return parts[0] || null;
    }

    if (
      host.includes("youtube.com") ||
      host.includes("youtube-nocookie.com") ||
      host.includes("m.youtube.com")
    ) {
      const searchV = url.searchParams.get("v");
      if (searchV) return searchV;

      if (parts[0] === "embed" && parts[1]) return parts[1];

      if ((parts[0] === "live" || parts[0] === "shorts") && parts[1]) {
        return parts[1];
      }
    }

    return null;
  } catch {
    return null;
  }
}

function isYoutubeUrl(raw: string | null | undefined): boolean {
  return !!getYoutubeVideoId(raw);
}

function buildYoutubeWatchUrl(raw: string | null | undefined): string {
  const id = getYoutubeVideoId(raw);
  return id ? `https://www.youtube.com/watch?v=${id}` : "";
}

function buildYoutubeThumbnail(raw: string | null | undefined): string {
  const id = getYoutubeVideoId(raw);
  return id ? `https://i.ytimg.com/vi/${id}/hqdefault.jpg` : "";
}

/** TikTok video / live URLs (share links, vm short links, web live pages). */
function isTikTokUrl(raw: string | null | undefined): boolean {
  const v = (raw || "").trim().toLowerCase();
  return (
    v.includes("tiktok.com") ||
    v.includes("tiktok.tv") ||
    v.includes("vm.tiktok.com") ||
    v.includes("vt.tiktok.com")
  );
}

/**
 * Extract a numeric TikTok content / live room id from common URL shapes.
 */
function extractTikTokNumericId(pathname: string): string | null {
  const liveOrVideo = pathname.match(/\/(?:video|live)\/(\d{8,22})\b/);
  if (liveOrVideo?.[1]) return liveOrVideo[1];
  const vPath = pathname.match(/\/v\/(\d{8,22})\b/);
  if (vPath?.[1]) return vPath[1];
  const atVideo = pathname.match(/\/@[^/]+\/video\/(\d{8,22})\b/);
  if (atVideo?.[1]) return atVideo[1];
  return null;
}

/**
 * Normalize TikTok URLs for the live page iframe.
 * Uses official player when a numeric id is present:
 * https://developers.tiktok.com/doc/embed-player/
 * Falls back to the original URL (e.g. /@handle/live) if no id is found.
 */
function normalizeTikTokEmbedUrl(raw: string): string {
  const v = raw.trim();
  const lower = v.toLowerCase();

  if (lower.includes("tiktok.com/player/v1/")) return v;

  if (lower.includes("tiktok.com") && lower.includes("/embed/")) {
    if (v.startsWith("http")) return v;
    return `https://www.tiktok.com${v.startsWith("/") ? v : `/${v}`}`;
  }

  try {
    const url = new URL(v);
    const host = url.hostname.toLowerCase();
    const onTikTok =
      host.includes("tiktok.com") ||
      host.includes("tiktok.tv") ||
      host.includes("vm.tiktok.com") ||
      host.includes("vt.tiktok.com");
    if (!onTikTok) return v;

    const roomFromQuery =
      url.searchParams.get("room_id") ||
      url.searchParams.get("roomId") ||
      url.searchParams.get("enter_room_id");
    if (roomFromQuery && /^\d+$/.test(roomFromQuery)) {
      return `https://www.tiktok.com/player/v1/${roomFromQuery}`;
    }

    const fromPath = extractTikTokNumericId(url.pathname);
    if (fromPath) {
      return `https://www.tiktok.com/player/v1/${fromPath}`;
    }

    return v;
  } catch {
    return v;
  }
}

/** Twitter/X broadcast URLs that can be shown in an iframe embed. */
function isTwitterUrl(raw: string | null | undefined): boolean {
  const v = (raw || "").trim().toLowerCase();
  return (
    v.includes("twitter.com") ||
    v.includes("x.com") ||
    v.includes("t.co/")
  );
}

/**
 * Convert a Twitter/X broadcast URL to an embeddable player URL.
 * Handles:
 *   - https://twitter.com/i/broadcasts/…
 *   - https://x.com/i/broadcasts/…
 *   - plain status tweet (audio-space / video)
 */
function normalizeTwitterEmbedUrl(raw: string): string {
  const v = raw.trim();
  // Already an embed URL
  if (v.includes("platform.twitter.com/embed") || v.includes("syndication.twitter.com")) {
    return v;
  }

  try {
    const url = new URL(v);
    const host = url.hostname.toLowerCase();
    const pathname = url.pathname;

    if (host.includes("twitter.com") || host.includes("x.com")) {
      // twitter.com/i/broadcasts/<broadcastId>
      const broadcastMatch = pathname.match(/\/i\/broadcasts\/([A-Za-z0-9_-]+)/);
      if (broadcastMatch?.[1]) {
        return `https://broadcast.twitter.com/live/${broadcastMatch[1]}`;
      }

      // twitter.com/<user>/status/<tweetId>
      const statusMatch = pathname.match(/\/([^/]+)\/status\/(\d+)/);
      if (statusMatch?.[2]) {
        return `https://platform.twitter.com/embed/Tweet.html?id=${statusMatch[2]}&partner=&hideCard=false&hideThread=false&lang=en`;
      }
    }
  } catch {
    // fall through
  }
  return v;
}

function normalizeEmbedUrl(raw: string | null | undefined): string {
  const v = (raw || "").trim();
  if (!v) return "";

  if (v.includes("youtube-nocookie.com/embed/")) return v;
  if (v.includes("youtube.com/embed/")) {
    return v.replace("youtube.com/embed/", "youtube-nocookie.com/embed/");
  }

  const shortMatch = v.match(/youtu\.be\/([A-Za-z0-9_-]{6,})/);
  if (shortMatch?.[1]) {
    return `https://www.youtube-nocookie.com/embed/${shortMatch[1]}`;
  }

  const watchMatch = v.match(/[?&]v=([A-Za-z0-9_-]{6,})/);
  if (watchMatch?.[1]) {
    return `https://www.youtube-nocookie.com/embed/${watchMatch[1]}`;
  }

  const pathMatch = v.match(
    /youtube\.com\/(?:live|shorts)\/([A-Za-z0-9_-]{6,})/,
  );
  if (pathMatch?.[1]) {
    return `https://www.youtube-nocookie.com/embed/${pathMatch[1]}`;
  }

  if (isTwitterUrl(v)) {
    return normalizeTwitterEmbedUrl(v);
  }

  if (isTikTokUrl(v)) {
    return normalizeTikTokEmbedUrl(v);
  }

  return v;
}

function withVideoParams(rawUrl: string): string {
  if (!rawUrl) return "";

  // Twitter embed URLs must not have params appended
  if (isTwitterUrl(rawUrl)) return rawUrl;

  if (isTikTokUrl(rawUrl)) {
    try {
      const url = new URL(rawUrl);
      if (
        url.hostname.includes("tiktok.com") &&
        url.pathname.startsWith("/player/v1/")
      ) {
        if (!url.searchParams.has("controls")) {
          url.searchParams.set("controls", "1");
        }
        if (!url.searchParams.has("autoplay")) {
          url.searchParams.set("autoplay", "0");
        }
        return url.toString();
      }
    } catch {
      /* keep raw */
    }
    return rawUrl;
  }

  try {
    const url = new URL(rawUrl);
    const host = url.hostname.toLowerCase();
    const isYoutube =
      host.includes("youtube.com") ||
      host.includes("youtube-nocookie.com") ||
      host.includes("youtu.be");

    if (!url.searchParams.has("playsinline")) {
      url.searchParams.set("playsinline", "1");
    }

    if (!url.searchParams.has("autoplay")) {
      url.searchParams.set("autoplay", "0");
    }

    if (isYoutube) {
      url.searchParams.set("rel", "0");
      url.searchParams.set("modestbranding", "1");
      url.searchParams.set("controls", "1");
    }

    return url.toString();
  } catch {
    return rawUrl;
  }
}

export function formatPaddleType(type: string | null | undefined) {
  if (!type) return "—";
  const v = String(type).toLowerCase();
  if (v.includes("premium")) return "مميز";
  if (v.includes("normal")) return "عادي";
  return type;
}

export default function YearlyAuctionLiveClient({
  groupAuction,
  initialRooms,

  videoUrl,
  maxDays,
  animalType,
}: YearlyAuctionLiveClientProps) {
  const session = useSession();
  const setLoginModal = useSetAtom(loginModalAtom);
  const toast = useAppToast();
  const t = useTranslations("YEARLY_AUCTION_LIVE");
  const tPaddle = useTranslations("YEARLY_PADDLE_MODAL");
  const tCountdown = useTranslations("COUNTDOWN");
  const locale = useLocale();
  const isRtl = (locale || "").toLowerCase().startsWith("ar");
  const { toLatinDigits } = useNumberFormatter();
  const queryClient = useQueryClient();

  usePaymentSuccess({
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["wallet-balances"] });
      queryClient.invalidateQueries({ queryKey: ["user-paddles"] });
      queryClient.invalidateQueries({
        queryKey: ["available-paddles", groupAuction.id, "annual"],
      });
    },
  });

  const catalogUrl = (groupAuction as any)?.catalog?.url as string | undefined;

  const isYoutubeVideo = useMemo(() => isYoutubeUrl(videoUrl), [videoUrl]);
  const youtubeWatchUrl = useMemo(
    () => buildYoutubeWatchUrl(videoUrl),
    [videoUrl],
  );
  const youtubeThumbnail = useMemo(
    () => buildYoutubeThumbnail(videoUrl),
    [videoUrl],
  );

  const normalizedVideoUrl = useMemo(() => {
    if (!videoUrl) return "";
    return withVideoParams(normalizeEmbedUrl(videoUrl));
  }, [videoUrl]);

  const [videoFallbackToYoutube, setVideoFallbackToYoutube] = useState(false);
  const [videoFrameLoaded, setVideoFrameLoaded] = useState(false);

  useEffect(() => {
    setVideoFallbackToYoutube(false);
    setVideoFrameLoaded(false);

    // سابقاً كنا نحاول كشف فشل تحميل iframe ثم نرجع إلى يوتيوب.
    // الآن نترك الفيديو يعمل داخل الموقع دائماً متى ما كان هناك normalizedVideoUrl.
  }, [videoUrl]);

  const getLocalizedTitle = (title: string) => {
    try {
      const parsed = JSON.parse(title);
      return parsed[locale] || parsed["en"] || title;
    } catch {
      return title;
    }
  };

  const groupAuctionId = groupAuction.id;
  const token = session?.access_token;

  const [paddleModalOpen, setPaddleModalOpen] = useState(false);
  const [forcePaddleModalOpen, setForcePaddleModalOpen] = useState(false);
  const [hasAvailablePaddle, setHasAvailablePaddle] = useState(false);
  const [userPaddleNumber, setUserPaddleNumber] = useState<string | null>(null);
  const [userPaddleType, setUserPaddleType] = useState<string | null>(null);

  /**
   * A bidder's paddle is constant for the whole session, but `auction_group:state`
   * snapshots from Redis often omit paddle fields on older bid rows and on the
   * highest-bidder summary. Cache paddle info keyed by `userId` so any later
   * render can recover the paddle that was already seen on `bid:created` or
   * derived from the local session. This prevents the rendered paddle from
   * flickering back to "—" after each bid.
   */
  const paddleByUserIdRef = useRef<
    Map<string, { paddle: number; paddleUniqueId: string | null }>
  >(new Map());

  const recordPaddleForUser = useCallback(
    (
      userId: string | number | null | undefined,
      paddleNumber: number | string | null | undefined,
      paddleUniqueId: string | null | undefined,
    ) => {
      const uid = userId != null ? String(userId).trim() : "";
      if (!uid) return;
      const num =
        typeof paddleNumber === "number"
          ? paddleNumber
          : Number(paddleNumber ?? 0);
      const uniqueId =
        paddleUniqueId != null && String(paddleUniqueId).trim() !== ""
          ? String(paddleUniqueId)
          : null;
      if (!num && !uniqueId) return;
      const existing = paddleByUserIdRef.current.get(uid);
      if (
        existing &&
        existing.paddle === num &&
        (existing.paddleUniqueId ?? null) === uniqueId
      ) {
        return;
      }
      paddleByUserIdRef.current.set(uid, {
        paddle: Number.isFinite(num) ? num : 0,
        paddleUniqueId: uniqueId,
      });
    },
    [],
  );

  const prices = {
    normalPrice: toMoneyNumber(groupAuction.normal_paddle_price, 0),
    premiumPrice: toMoneyNumber(groupAuction.premium_paddle_price, 0),
    premiumUseTimes: toMoneyNumber(groupAuction.premium_use_times, 0),
  };

  const { data: availablePaddles = [] } = useAvailablePaddlesForAuction(
    groupAuctionId,
    "annual",
  );
  const [sessionLoading, setSessionLoading] = useState(true);

  const isPaddleActive = (p: any) =>
    p?.active === "1" || p?.active === 1 || p?.active === true;

  const refreshPaddleFromAPI = useCallback(async () => {
    if (!session || !groupAuctionId) return;

    try {
      const paddles = await getUserPaddlesForGroupAuction(
        groupAuctionId,
        "annual",
      );

      const availablePaddle = paddles.find(
        (p) => p.is_available !== false && isPaddleActive(p),
      );

      const displayNumber =
        availablePaddle?.paddle_number ??
        (availablePaddle as any)?.unique_id ??
        null;

      const displayType =
        (availablePaddle as any)?.type ??
        (availablePaddle as any)?.paddle_type ??
        null;

      setHasAvailablePaddle(!!availablePaddle);
      setUserPaddleNumber(displayNumber);
      setUserPaddleType(displayType);

      if (session?.id != null && availablePaddle) {
        recordPaddleForUser(
          session.id,
          (availablePaddle as any)?.paddle_number,
          (availablePaddle as any)?.unique_id ?? displayNumber ?? null,
        );
      }

      queryClient.invalidateQueries({
        queryKey: ["available-paddles", groupAuctionId, "annual"],
      });
    } catch {
      setHasAvailablePaddle(false);
      setUserPaddleNumber(null);
      setUserPaddleType(null);
    }
  }, [session, groupAuctionId, queryClient, recordPaddleForUser]);

  useEffect(() => {
    if (!session) {
      setHasAvailablePaddle(false);
      setUserPaddleNumber(null);
      setUserPaddleType(null);
      const timeout = setTimeout(() => setSessionLoading(false), 500);
      return () => clearTimeout(timeout);
    }

    setSessionLoading(false);
    refreshPaddleFromAPI();
  }, [session, groupAuctionId, refreshPaddleFromAPI]);

  useEffect(() => {
    if (!session || availablePaddles.length === 0) return;

    const myPaddle = availablePaddles.find(
      (p) => p.is_available !== false && isPaddleActive(p),
    );

    if (myPaddle) {
      const displayNumber =
        (myPaddle as any)?.paddle_number ?? myPaddle.unique_id ?? null;
      const displayType =
        (myPaddle as any)?.type ?? (myPaddle as any)?.paddle_type ?? null;

      setHasAvailablePaddle(true);
      setUserPaddleNumber(displayNumber);
      setUserPaddleType(displayType);

      if (session?.id != null) {
        recordPaddleForUser(
          session.id,
          (myPaddle as any)?.paddle_number,
          (myPaddle as any)?.unique_id ?? displayNumber ?? null,
        );
      }
    }
  }, [session, availablePaddles, recordPaddleForUser]);

  useEffect(() => {
    if (!session || !groupAuctionId) return;

    const handleVisibilityChange = () => {
      if (document.visibilityState === "visible") {
        refreshPaddleFromAPI();
      }
    };

    document.addEventListener("visibilitychange", handleVisibilityChange);
    return () =>
      document.removeEventListener("visibilitychange", handleVisibilityChange);
  }, [session, groupAuctionId, refreshPaddleFromAPI]);

  const [currentAuctionGroupId, setCurrentAuctionGroupId] = useState<
    string | null
  >(null);
  const [currentPrice, setCurrentPrice] = useState<number>(0);
  const [startingPrice, setStartingPrice] = useState<number>(0);
  const [marketEntryPrice, setMarketEntryPrice] = useState<number | null>(null);
  const [highestBidder, setHighestBidder] = useState<{
    id: string;
    name: string;
  } | null>(null);
  const [bidders, setBidders] = useState<Bidder[]>([]);
  const [closeAtMs, setCloseAtMs] = useState<number | null>(null);
  const [endTimeMs, setEndTimeMs] = useState<number | null>(null);
  const [reserveTriggered, setReserveTriggered] = useState<boolean>(false);
  const [auctionGroupLabel, setAuctionGroupLabel] = useState<string | null>(
    null,
  );
  const [auctionStatus, setAuctionStatus] = useState<
    "active" | "closed" | "upcoming" | "paused" | "skipped"
  >("upcoming");
  const [auctionPaused, setAuctionPaused] = useState(false);
  const [pausedFromPayload, setPausedFromPayload] = useState(false);
  const [pauseReason, setPauseReason] = useState<string | null>(null);

  const [currentAnimalDetails, setCurrentAnimalDetails] = useState<{
    name?: string;
    father_name?: string;
    mother_name?: string;
    age?: string | number;
    breed?: string;
    color?: string;
  } | null>(null);

  const [firstSingleAuctionFallback, setFirstSingleAuctionFallback] = useState<{
    father_name?: string;
    mother_name?: string;
    age?: string | number;
  } | null>(null);

  const [currentAuctionGroupImage, setCurrentAuctionGroupImage] = useState<
    string | null
  >(null);

  const [timer, setTimer] = useState<number | null>(null);
  const [timerEndMs, setTimerEndMs] = useState<number | null>(null);
  const [timerRemaining, setTimerRemaining] = useState<string | null>(null);

  const [winnerOverlayOpen, setWinnerOverlayOpen] = useState(false);
  const [winnerData, setWinnerData] = useState<{
    name: string | null;
    paddleUniqueId: string | null;
    paddleNumber: number | null;
    finalPrice: number;
    reason: string;
    label?: string;
  } | null>(null);

  const [nextAuctionGroupId, setNextAuctionGroupId] = useState<string | null>(
    null,
  );
  const nextAuctionGroupIdRef = useRef<string | null>(null);

  const [closedSummariesByRoomId, setClosedSummariesByRoomId] = useState<
    Record<string, LiveClosedSummary>
  >({});

  const [announcements, setAnnouncements] = useState<
    AdminAnnouncementPayload[]
  >([]);

  /**
   * Admin announcements are scoped to the *currently active auction group*.
   *
   * Backend payloads are tagged with `groupAuctionId` (the parent yearly
   * session) but not with the per-auction-group id, so we enforce the
   * "one window per auction group" semantics on the client by clearing the
   * panel whenever the active auction group id changes. This covers:
   *   - normal forward transitions via `handleWinnerOverlayClose`
   *   - jumps triggered by `auction_group:started`
   *   - REST snapshot resyncs that land on a different active group
   *   - manual `__manualSetAuctionGroup` overrides
   *
   * The first assignment from `null` to the initial group id is a no-op
   * because `announcements` is already `[]`.
   *
   * Within the same group, announcements continue to accumulate normally
   * (this effect only runs when the dep actually changes), and reconnects
   * that resolve to the same group do *not* clear the panel.
   */
  useEffect(() => {
    setAnnouncements([]);
  }, [currentAuctionGroupId]);

  const resolvedCurrentRoom = useMemo(
    () => findRoomByAuctionGroupId(initialRooms, currentAuctionGroupId),
    [initialRooms, currentAuctionGroupId],
  );

  const [liveCamelGroupSinglesCount, setLiveCamelGroupSinglesCount] = useState<
    number | null
  >(null);

  useEffect(() => {
    if (animalType !== "camel" || !currentAuctionGroupId) {
      setLiveCamelGroupSinglesCount(null);
      return;
    }
    let cancelled = false;
    setLiveCamelGroupSinglesCount(null);
    (async () => {
      try {
        const details = await getAuctionGroupById(
          groupAuctionId,
          currentAuctionGroupId,
        );
        if (cancelled || !details) return;
        const nested =
          (details as any)?.single_auctions ?? (details as any)?.items;
        if (Array.isArray(nested)) {
          setLiveCamelGroupSinglesCount(nested.length);
          return;
        }
        const c = Number((details as any)?.single_auctions_count);
        setLiveCamelGroupSinglesCount(Number.isFinite(c) ? c : null);
      } catch {
        if (!cancelled) setLiveCamelGroupSinglesCount(null);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [animalType, currentAuctionGroupId, groupAuctionId]);

  const broadcastPosterUrl = useMemo(() => {
    return (
      posterUrlFromRoom(resolvedCurrentRoom) ?? currentAuctionGroupImage ?? null
    );
  }, [resolvedCurrentRoom, currentAuctionGroupImage]);

  /**
   * Camel yearly auctions price the auction group as a whole, but the user
   * thinks of bids on a *per-camel* basis. Every entered increment (custom
   * input or quick chip) is multiplied by the live camel count of the
   * currently active room before validation and socket emission.
   *
   * Source priority (matches `CurrentAuctionCard.displayedCamelSingleAuctionsCount`):
   *   1. `liveCamelGroupSinglesCount` (fresh from `getAuctionGroupById`)
   *   2. `currentRoom.single_auctions.length` (embedded list)
   *   3. `currentRoom.single_auctions_count` (raw count)
   *   4. fallback `1` (so behavior is unchanged when the count is unknown)
   *
   * Horse auctions always resolve to `1`, leaving the existing flow intact.
   */
  const camelBidMultiplier = useMemo(() => {
    if (animalType !== "camel") return 1;

    if (
      liveCamelGroupSinglesCount != null &&
      Number.isFinite(liveCamelGroupSinglesCount) &&
      liveCamelGroupSinglesCount > 0
    ) {
      return liveCamelGroupSinglesCount;
    }

    const embedded = resolvedCurrentRoom?.single_auctions;
    if (Array.isArray(embedded) && embedded.length > 0) {
      return embedded.length;
    }

    const raw = resolvedCurrentRoom?.single_auctions_count;
    const n =
      typeof raw === "number"
        ? raw
        : Number(String(raw ?? "").trim());
    if (Number.isFinite(n) && n > 0) {
      return n;
    }

    return 1;
  }, [animalType, liveCamelGroupSinglesCount, resolvedCurrentRoom]);

  useEffect(() => {
    nextAuctionGroupIdRef.current = nextAuctionGroupId;
  }, [nextAuctionGroupId]);

  const hasCheckedActiveRef = useRef(false);
  const [isResolvingInitialAuctionState, setIsResolvingInitialAuctionState] =
    useState(true);
  const toastDedupMapRef = useRef<Map<string, number>>(new Map());

  const effectivePaused = useMemo(
    () =>
      auctionPaused ||
      auctionStatus === "paused" ||
      pausedFromPayload,
    [auctionPaused, auctionStatus, pausedFromPayload],
  );

  const isBidBlockedRef = useRef(false);
  useEffect(() => {
    isBidBlockedRef.current = effectivePaused;
  }, [effectivePaused]);

  /**
   * Stale watchdog flag passed into the socket hook. The watchdog should be
   * aggressive while there is an active auction group (we expect periodic
   * realtime activity) and quiet during the upcoming/closed phases.
   */
  const watchdogEnabledRef = useRef<boolean>(false);
  useEffect(() => {
    watchdogEnabledRef.current =
      currentAuctionGroupId != null &&
      (auctionStatus === "active" ||
        auctionStatus === "paused" ||
        auctionStatus === "upcoming");
  }, [currentAuctionGroupId, auctionStatus]);

  const showToastOnce = useCallback(
    (kind: ToastKind, key: string, message: string, dedupeMs = 1800) => {
      if (!message) return;
      const now = Date.now();
      const lastAt = toastDedupMapRef.current.get(key) ?? 0;
      if (now - lastAt < dedupeMs) return;
      toastDedupMapRef.current.set(key, now);
      toast[kind](message);
    },
    [toast],
  );

  useEffect(() => {
    const checkManualOverride = () => {
      const manualId = (window as any).__manualSetAuctionGroup;
      if (manualId && !currentAuctionGroupId) {
        setCurrentAuctionGroupId(manualId);
        setAuctionStatus("active");
      }
    };

    const timeout = setTimeout(checkManualOverride, 2000);
    return () => clearTimeout(timeout);
  }, [currentAuctionGroupId]);

  /**
   * Apply a REST snapshot of the currently active auction group to local state.
   * Used both on initial socket-connected mount and after silent recovery
   * (visibilitychange / online / reconnect / stale-watchdog).
   */
  const applyActiveAuctionSnapshot = useCallback(async (): Promise<boolean> => {
    try {
      const activeData = await getActiveAuctionGroup(groupAuctionId);
      if (!activeData?.activeAuctionGroupId) return false;

      setCurrentAuctionGroupId(activeData.activeAuctionGroupId);
      const pauseFromRest = extractYearlyPauseFromPayload(activeData);
      setPausedFromPayload(pauseFromRest.paused);
      setAuctionPaused(pauseFromRest.paused);
      setPauseReason(
        pauseFromRest.paused ? pauseFromRest.pauseReason : null,
      );
      if (activeData.status) {
        if (pauseFromRest.paused) {
          setAuctionStatus("paused");
        } else {
          setAuctionStatus(activeData.status as any);
        }
      }

      const rawActive = activeData as Record<string, unknown>;
      const hasLiveSnapshot =
        rawActive.currentPrice != null ||
        rawActive.current_price != null ||
        Array.isArray(rawActive.bids) ||
        Array.isArray(rawActive.bid_history) ||
        rawActive.highestBidder != null ||
        rawActive.highest_bidder != null;

      if (hasLiveSnapshot) {
        const normalized = yearlyActiveRestPayloadToState(
          groupAuctionId,
          activeData.activeAuctionGroupId,
          rawActive,
        );
        const uiBids = mapYearlyStateBidsToUiBidders(normalized.bids);
        collectPaddlesByUserId(
          paddleByUserIdRef.current,
          uiBids,
          normalized.highestBidder,
        );
        setCurrentPrice(normalized.currentPrice);
        setStartingPrice(normalized.startingPrice);
        setHighestBidder((prev) =>
          mergeYearlyHighestBidderPreservingPaddle(
            prev,
            normalized.highestBidder,
            paddleByUserIdRef.current,
          ),
        );
        setBidders((prev) =>
          mergeYearlyUiBiddersPreservingPaddles(
            prev,
            uiBids,
            paddleByUserIdRef.current,
          ),
        );
        if (normalized.marketEntryPrice != null) {
          setMarketEntryPrice(normalized.marketEntryPrice);
        }
        setCloseAtMs(normalized.closeAtMs);
        setEndTimeMs(normalized.endTimeMs);
        setReserveTriggered(normalized.reserveTriggered);
        if (normalized.label) {
          setAuctionGroupLabel(normalized.label);
        }
        if (normalized.timer !== undefined) {
          setTimer(normalized.timer);
        }
        if (normalized.timerEndMs !== undefined) {
          setTimerEndMs(normalized.timerEndMs);
        }
        if (normalized.timerRemaining !== undefined) {
          setTimerRemaining(normalized.timerRemaining);
        }
      }

      return true;
    } catch (err) {
      console.warn("Failed to apply active auction snapshot", err);
      return false;
    }
  }, [groupAuctionId]);

  /**
   * Resync handler: invalidate cached queries and refresh the live snapshot.
   * Triggered by the socket hook on reconnect, tab return, network online, or
   * when the stale-event watchdog fires. Also clears `hasCheckedActiveRef` so
   * the initial-mount REST sweep can run again if the page lost its
   * `currentAuctionGroupId`.
   */
  const handleSocketRecover = useCallback(async () => {
    queryClient.invalidateQueries({
      queryKey: ["available-paddles", groupAuctionId, "annual"],
    });
    queryClient.invalidateQueries({ queryKey: ["user-paddles"] });
    queryClient.invalidateQueries({ queryKey: ["wallet-balances"] });

    hasCheckedActiveRef.current = false;
    const ok = await applyActiveAuctionSnapshot();
    if (ok) {
      hasCheckedActiveRef.current = true;
      setIsResolvingInitialAuctionState(false);
    }

    if (session) {
      refreshPaddleFromAPI();
    }
  }, [
    applyActiveAuctionSnapshot,
    groupAuctionId,
    queryClient,
    refreshPaddleFromAPI,
    session,
  ]);

  const handleSocketRecoverRef = useRef(handleSocketRecover);
  useEffect(() => {
    handleSocketRecoverRef.current = handleSocketRecover;
  }, [handleSocketRecover]);

  const {
    socket,
    placeBid,
    requestRecovery,
    connectionStatus,
    recoveryFailureCount,
  } = useYearlyAuctionSocket({
    auctionGroupId: currentAuctionGroupId,
    groupAuctionId,
    token,
    isBidBlockedRef,
    watchdogEnabledRef,
    onRecover: () => {
      void handleSocketRecoverRef.current();
    },

    onStarted: (payload) => {
      playStartGroupAuctionSound();
      setIsResolvingInitialAuctionState(false);
      refreshPaddleFromAPI();

      // Do not close the winner overlay or clear winner data here: `onStarted` can fire
      // right after `onClosed`, which made the modal empty or disappear instantly.

      setCurrentAuctionGroupId(payload.auctionGroupId);

      const restartedId = String(payload.auctionGroupId);
      setClosedSummariesByRoomId((prev) => {
        if (!prev[restartedId]) return prev;
        const next = { ...prev };
        delete next[restartedId];
        return next;
      });

      const startPrice =
        payload.snapshot?.currentPrice ??
        payload.snapshot?.startingPrice ??
        payload.startingPrice ??
        0;

      const marketPrice =
        payload.snapshot?.marketEntryPrice ?? payload.marketEntryPrice;
      const label = payload.snapshot?.label ?? payload.label;

      setStartingPrice(startPrice);
      setCurrentPrice(startPrice);
      setMarketEntryPrice(marketPrice ?? null);
      setAuctionGroupLabel(label ?? null);
      setAuctionStatus("active");
      setBidders([]);
      setHighestBidder(null);
      setReserveTriggered(false);
      setCloseAtMs(null);
      setEndTimeMs(null);
      setTimer(null);
      setTimerEndMs(null);
      setTimerRemaining(null);
      setCurrentAnimalDetails(null);
      setFirstSingleAuctionFallback(null);
      setTimeRemaining("");

      const animal =
        (payload as any).animal || (payload.snapshot as any)?.animal;

      if (animal) {
        setCurrentAnimalDetails({
          name: animal.name,
          father_name: animal.father_name,
          mother_name: animal.mother_name,
          age: animal.age,
          breed: animal.breed,
          color: animal.color,
        });
      }

      if (payload.snapshot) {
        setAuctionStatus(payload.snapshot.status);
        setCurrentPrice(payload.snapshot.currentPrice);
        setReserveTriggered(payload.snapshot.reserveTriggered);
        setCloseAtMs(payload.snapshot.closeAtMs);

        const snapshotEndTimeMs = (payload.snapshot as any).endTimeMs;
        if (snapshotEndTimeMs !== undefined) setEndTimeMs(snapshotEndTimeMs);

        if (payload.snapshot.timer !== undefined) {
          setTimer(payload.snapshot.timer);
        }
        if (payload.snapshot.timerEndMs !== undefined) {
          setTimerEndMs(payload.snapshot.timerEndMs);
        }
        if (payload.snapshot.timerRemaining !== undefined) {
          setTimerRemaining(payload.snapshot.timerRemaining);
        }

        const snapshotBids = coerceYearlyBidsArray(
          payload.snapshot.bids ??
            (payload.snapshot as Record<string, unknown>).bid_history,
        );
        const uiSnapshotBids = mapYearlyStateBidsToUiBidders(snapshotBids);
        collectPaddlesByUserId(
          paddleByUserIdRef.current,
          uiSnapshotBids,
          payload.snapshot.highestBidder,
        );
        setHighestBidder((prev) =>
          mergeYearlyHighestBidderPreservingPaddle(
            prev,
            payload.snapshot!.highestBidder,
            paddleByUserIdRef.current,
          ),
        );
        setBidders((prev) =>
          mergeYearlyUiBiddersPreservingPaddles(
            prev,
            uiSnapshotBids,
            paddleByUserIdRef.current,
          ),
        );
      }

      const pauseFromStarted = mergeYearlyPauseFromSources(
        payload.snapshot,
        payload,
      );
      setPausedFromPayload(pauseFromStarted.paused);
      setAuctionPaused(pauseFromStarted.paused);
      setPauseReason(
        pauseFromStarted.paused ? pauseFromStarted.pauseReason : null,
      );
      if (pauseFromStarted.paused) {
        setAuctionStatus("paused");
      }
    },

    onState: (payload) => {
      setIsResolvingInitialAuctionState(false);
      setPausedFromPayload(payload.paused === true);
      const pauseFromState = extractYearlyPauseFromPayload(payload);
      setAuctionPaused(pauseFromState.paused);
      setPauseReason(
        pauseFromState.paused ? pauseFromState.pauseReason : null,
      );
      if (pauseFromState.paused) {
        setAuctionStatus("paused");
      } else {
        const st = payload.status;
        if (st === "skipped") {
          setAuctionStatus("closed");
        } else if (st === "active" || st === "closed" || st === "upcoming") {
          setAuctionStatus(st);
        }
      }
      const uiStateBids = mapYearlyStateBidsToUiBidders(payload.bids);
      collectPaddlesByUserId(
        paddleByUserIdRef.current,
        uiStateBids,
        payload.highestBidder,
      );

      setCurrentPrice(payload.currentPrice);
      setHighestBidder((prev) =>
        mergeYearlyHighestBidderPreservingPaddle(
          prev,
          payload.highestBidder,
          paddleByUserIdRef.current,
        ),
      );

      if (payload.marketEntryPrice != null) {
        setMarketEntryPrice(payload.marketEntryPrice);
      }

      setCloseAtMs(payload.closeAtMs);
      setEndTimeMs(payload.endTimeMs);
      setReserveTriggered(payload.reserveTriggered);

      if (payload.label) {
        setAuctionGroupLabel(payload.label);
      }

      if (payload.timer !== undefined) setTimer(payload.timer);

      if (payload.timerEndMs !== undefined) {
        if (payload.timerEndMs === null) {
          setTimerEndMs(null);
        } else {
          setTimerEndMs(payload.timerEndMs);
        }
      }

      if (payload.timerRemaining !== undefined) {
        if (payload.timerRemaining === null || payload.timerRemaining === "") {
          setTimerRemaining(null);
        } else {
          setTimerRemaining(payload.timerRemaining);
        }
      }

      if (payload.reserveTriggered) {
        setTimer(null);
        setTimerEndMs(null);
        setTimerRemaining(null);
      }

      setBidders((prev) =>
        mergeYearlyUiBiddersPreservingPaddles(
          prev,
          uiStateBids,
          paddleByUserIdRef.current,
        ),
      );
    },

    onBidCreated: (payload) => {
      recordPaddleForUser(
        payload.bidder?.id,
        payload.bidder?.paddleNumber,
        (payload.bidder as any)?.paddleUniqueId,
      );

      setCurrentPrice(payload.amount);
      setHighestBidder((prev) =>
        mergeYearlyHighestBidderPreservingPaddle(
          prev,
          payload.bidder,
          paddleByUserIdRef.current,
        ),
      );

      if (payload.closeAtMs != null) setCloseAtMs(payload.closeAtMs);
      if (payload.endTimeMs != null) setEndTimeMs(payload.endTimeMs);

      const newBidder: Bidder = {
        name: payload.bidder.name,
        paddle: payload.bidder.paddleNumber || 0,
        paddleUniqueId: (payload.bidder as any).paddleUniqueId || null,
        amount: payload.amount,
        timestamp: payload.timestamp,
        userId: payload.bidder.id,
      };

      setBidders((prev) => {
        if (
          prev.length === 1 &&
          prev[0].userId != null &&
          String(prev[0].userId) === String(payload.bidder.id)
        ) {
          return [newBidder];
        }
        return [newBidder, ...prev];
      });
      playAuctionSound(animalType);

      const isMyBid =
        session?.id != null && String(session.id) === String(payload.bidder.id);

      if (isMyBid) {
        showToastOnce(
          "success",
          `my-bid:${payload.auctionGroupId}:${payload.amount}:${payload.timestamp || ""}`,
          t("bid_success"),
          2000,
        );
      }
    },

    onReserveTriggered: (payload) => {
      setReserveTriggered(true);
      setCloseAtMs(payload.closeAtMs);
      setTimer(null);
      setTimerEndMs(null);
      setTimerRemaining(null);

      // showToastOnce(
      //   "warning",
      //   `reserve-triggered:${payload.auctionGroupId}:${payload.closeAtMs ?? ""}`,
      //   t("reserve_triggered", { label: payload.label || "" }),
      //   8000,
      // );
    },

    onClosed: async (payload) => {
      setIsResolvingInitialAuctionState(false);
      setAuctionPaused(false);
      setPausedFromPayload(false);
      setPauseReason(null);
      setAuctionStatus("closed");
      setTimer(null);
      setTimerEndMs(null);
      setTimerRemaining(null);
      // Clear admin announcements as soon as the current auction ends so the
      // notifications panel does not show stale messages while the winner
      // overlay is up or while we wait for the next auction to start.
      setAnnouncements([]);

      const pl = payload as any;
      const auctionGroupId =
        payload.auctionGroupId ?? pl.auction_group_id ?? currentAuctionGroupId;

      const winnerId =
        payload.winnerId ??
        pl.winner_id ??
        pl.winnerUser?.id ??
        pl.winner_user?.id ??
        pl.winner?.id ??
        pl.highestBidder?.id ??
        pl.highest_bidder?.id ??
        highestBidder?.id ??
        null;

      const winnerName =
        payload.winnerName ??
        pl.winner_name ??
        (pl.winnerUser?.name || pl.winner_user?.name || pl.winner?.name) ??
        (pl.highestBidder?.name || pl.highest_bidder?.name) ??
        highestBidder?.name ??
        null;

      const finalPrice =
        payload.finalPrice ?? pl.final_price ?? currentPrice ?? 0;

      const winnerPaddleUniqueId =
        payload.winnerPaddleUniqueId ??
        pl.winner_paddle_unique_id ??
        (highestBidder as any)?.paddleUniqueId ??
        null;

      const winnerPaddleNumber =
        payload.winnerPaddleNumber ??
        pl.winner_paddle_number ??
        (highestBidder as any)?.paddleNumber ??
        null;

      setWinnerData({
        name: winnerName,
        paddleUniqueId: winnerPaddleUniqueId,
        paddleNumber: winnerPaddleNumber,
        finalPrice,
        reason: payload.reason ?? pl.reason ?? "time",
        label: auctionGroupLabel || undefined,
      });

      const closePayload: Record<string, unknown> = {
        ...pl,
        reason: payload.reason ?? pl.reason ?? "time",
      };
      const outcome = extractCloseOutcomeFromPayload(closePayload, {
        winnerId,
        winnerName,
        finalPrice,
      });

      const summary = {
        auctionGroupId: auctionGroupId ?? currentAuctionGroupId ?? "",
        finalPrice,
        winnerName,
        winnerPaddleUniqueId: winnerPaddleUniqueId || null,
        winnerPaddleNumber: winnerPaddleNumber ?? null,
        outcome,
      };

      const roomId = auctionGroupId ?? currentAuctionGroupId ?? "";
      if (roomId) {
        setClosedSummariesByRoomId((prev) => ({ ...prev, [roomId]: summary }));
      }

      setNextAuctionGroupId(
        payload.nextAuctionGroupId ?? (pl as any).next_auction_group_id ?? null,
      );
      setWinnerOverlayOpen(true);

      if (session) {
        refreshPaddleFromAPI();
      }
    },

    onBidRejected: (payload) => {
      const msg = payload.message || payload.reason || "";
      const errorMessage = msg || t("bid_rejected");

      showToastOnce(
        "error",
        `auction-error:${errorMessage}`,
        errorMessage,
        2000,
      );

      const reason = String(msg).toLowerCase();
      const reasonAr = String(msg);

      const isPaddleSubscriptionError =
        reason.includes("no active paddle subscription") ||
        reason.includes("paddle subscription") ||
        reason.includes("active paddle") ||
        reason.includes("no active paddle") ||
        (reason.includes("paddle") && reason.includes("subscription")) ||
        (reasonAr.includes("اشتراك") && reasonAr.includes("مضرب"));

      if (isPaddleSubscriptionError) {
        setHasAvailablePaddle(false);
        setUserPaddleNumber(null);
        setUserPaddleType(null);
        setPaddleModalOpen(true);
        setForcePaddleModalOpen(true);
      }
    },

    onPaddleDeactivated: async (payload) => {
      setHasAvailablePaddle(false);
      setUserPaddleNumber(null);
      setUserPaddleType(null);

      queryClient.invalidateQueries({
        queryKey: ["available-paddles", groupAuctionId, "annual"],
      });
      queryClient.invalidateQueries({ queryKey: ["user-paddles"] });

      if (session) {
        refreshPaddleFromAPI();
      }

      const deactivatedMessage =
        payload.message ||
        t("paddle_deactivated", {
          wonCount: payload.won_count,
          premiumUseTimes: payload.premium_use_times,
        }) ||
        `You have used ${payload.won_count} of ${payload.premium_use_times} allowed wins. Your paddle subscription has been deactivated.`;

      showToastOnce(
        "info",
        `paddle-deactivated:${payload.won_count}:${payload.premium_use_times}`,
        deactivatedMessage,
        10000,
      );
    },

    onAnnouncement: (payload) => {
      // Skip announcements that arrive while no auction group is active
      // (between groups or before the first group has started). Announcement
      // payloads are tagged with the parent `groupAuctionId` only, so we
      // anchor the panel's lifecycle to the per-auction-group window here.
      if (!currentAuctionGroupId) return;
      // Skip closed/winner-overlay window: keep the panel quiet between groups.
      if (auctionStatus === "closed") return;
      setAnnouncements((prev) => [payload, ...prev].slice(0, 20));
    },

    onPaused: (payload) => {
      setAuctionPaused(true);
      setPausedFromPayload(true);
      const r =
        payload.pauseReason ??
        payload.pause_reason ??
        payload.reason ??
        null;
      setPauseReason(
        r != null && String(r).trim() ? String(r).trim() : null,
      );
      setAuctionStatus("paused");
    },

    onResumed: () => {
      setAuctionPaused(false);
      setPausedFromPayload(false);
      setPauseReason(null);
      setAuctionStatus((s) => (s === "paused" ? "active" : s));
    },

    onError: (payload) => {
      const msg = payload.message || "";
      const errorMessage = msg || t("socket_error");

      showToastOnce(
        "error",
        `auction-error:${errorMessage}`,
        errorMessage,
        2000,
      );

      const reason = String(msg).toLowerCase();
      const reasonAr = String(msg);

      const isPaddleSubscriptionError =
        reason.includes("no active paddle subscription") ||
        reason.includes("paddle subscription") ||
        reason.includes("active paddle") ||
        reason.includes("no active paddle") ||
        (reason.includes("paddle") && reason.includes("subscription")) ||
        (reasonAr.includes("اشتراك") && reasonAr.includes("مضرب"));

      if (isPaddleSubscriptionError) {
        setHasAvailablePaddle(false);
        setUserPaddleNumber(null);
        setUserPaddleType(null);
        setPaddleModalOpen(true);
        setForcePaddleModalOpen(true);
      }
    },
  });

  useEffect(() => {
    if (currentAuctionGroupId) {
      setIsResolvingInitialAuctionState(false);
    }
  }, [currentAuctionGroupId]);

  useEffect(() => {
    if (
      !socket?.connected ||
      hasCheckedActiveRef.current ||
      currentAuctionGroupId
    ) {
      return;
    }

    const checkActiveAuction = async () => {
      hasCheckedActiveRef.current = true;
      try {
        const ok = await applyActiveAuctionSnapshot();
        if (ok) {
          refreshPaddleFromAPI();
        }
      } finally {
        setIsResolvingInitialAuctionState(false);
      }
    };

    const timeout = setTimeout(checkActiveAuction, 1500);
    return () => clearTimeout(timeout);
  }, [
    socket?.connected,
    currentAuctionGroupId,
    groupAuctionId,
    refreshPaddleFromAPI,
    applyActiveAuctionSnapshot,
  ]);

  useEffect(() => {
    if (!isResolvingInitialAuctionState) return;

    const timeout = window.setTimeout(() => {
      setIsResolvingInitialAuctionState(false);
    }, 5000);

    return () => window.clearTimeout(timeout);
  }, [isResolvingInitialAuctionState]);

  useEffect(() => {
    if (!currentAuctionGroupId || !groupAuctionId) {
      setFirstSingleAuctionFallback(null);
      setCurrentAuctionGroupImage(null);
      return;
    }

    setCurrentAuctionGroupImage(null);

    const url = `/api/group-single-auctions?groupId=${encodeURIComponent(groupAuctionId)}&auction_group_id=${encodeURIComponent(currentAuctionGroupId)}`;

    let cancelled = false;

    fetch(url, { credentials: "include", cache: "no-store" })
      .then((r) => r.json())
      .then((json: SingleAuctionListResponse) => {
        if (cancelled) return;

        const items = parseSingleAuctionsResponse(json);
        const first = items[0] ?? null;

        if (!first) {
          setFirstSingleAuctionFallback(null);
          setCurrentAuctionGroupImage(null);
          return;
        }

        const imageUrl = posterUrlFromSingleAuctionRow(first);
        setCurrentAuctionGroupImage(imageUrl);

        const animal = (first as any)?.animal || first;
        const father = animal?.father_name ?? (first as any)?.father_name;
        const mother = animal?.mother_name ?? (first as any)?.mother_name;
        const dob = animal?.date_of_birth ?? (first as any)?.date_of_birth;

        let age: string | number | undefined;

        if (animal?.age != null || (first as any)?.age != null) {
          age = animal?.age ?? (first as any)?.age;
        } else if (dob) {
          const birth = new Date(dob);
          if (!Number.isNaN(birth.getTime())) {
            const today = new Date();
            age = today.getFullYear() - birth.getFullYear();
          }
        }

        if (father || mother || age != null) {
          setFirstSingleAuctionFallback({
            father_name: father ?? undefined,
            mother_name: mother ?? undefined,
            age,
          });
        } else {
          setFirstSingleAuctionFallback(null);
        }
      })
      .catch(() => {
        if (cancelled) return;
        setFirstSingleAuctionFallback(null);
        setCurrentAuctionGroupImage(null);
      });

    return () => {
      cancelled = true;
    };
  }, [currentAuctionGroupId, groupAuctionId]);

  const handleWinnerOverlayClose = useCallback(() => {
    setWinnerOverlayOpen(false);
    setWinnerData(null);

    if (nextAuctionGroupId) {
      const sameAsCurrent =
        String(nextAuctionGroupId) === String(currentAuctionGroupId);

      if (!sameAsCurrent) {
        setAuctionPaused(false);
        setPausedFromPayload(false);
        setPauseReason(null);
        setAuctionStatus("upcoming");
        setCurrentPrice(0);
        setStartingPrice(0);
        setMarketEntryPrice(null);
        setHighestBidder(null);
        setBidders([]);
        setCloseAtMs(null);
        setEndTimeMs(null);
        setReserveTriggered(false);
        setAuctionGroupLabel(null);
        setTimer(null);
        setTimerEndMs(null);
        setTimerRemaining(null);
        setCurrentAuctionGroupId(nextAuctionGroupId);
      }
      setNextAuctionGroupId(null);
    } else {
      showToastOnce(
        "info",
        `session-ended:${groupAuctionId}`,
        t("session_ended"),
        8000,
      );
      setAuctionPaused(false);
      setPausedFromPayload(false);
      setPauseReason(null);
      setAuctionStatus("closed");
    }
  }, [
    nextAuctionGroupId,
    currentAuctionGroupId,
    showToastOnce,
    t,
    groupAuctionId,
  ]);

  const handleBidClick = (amount: number) => {
    if (!session) {
      setLoginModal(true);
      return;
    }

    if (effectivePaused) {
      showToastOnce(
        "warning",
        "auction-paused-bid",
        t("bidding_disabled_paused"),
        1600,
      );
      return;
    }

    if (auctionStatus !== "active") {
      showToastOnce(
        "warning",
        "auction-not-active",
        t("auction_not_active"),
        1200,
      );
      return;
    }

    // For camel yearly auctions the input is treated as a *per-camel*
    // increment: multiply by the live camel count to get the real total
    // increment that the backend (and validator) operate on.
    // Horse / single-camel rooms keep `multiplier === 1` and behavior is
    // identical to before.
    const multiplier =
      camelBidMultiplier > 0 ? Math.floor(camelBidMultiplier) : 1;
    const totalDelta = amount * multiplier;

    const validation = validateAnnualBidDelta(totalDelta, currentPrice);
    if (!validation.valid) {
      const totalMinInc = validation.minRequiredIncrement;
      // Translate the total minimum back to a per-camel minimum so the error
      // message matches what the user is actually typing in the input.
      const perUnitMinInc =
        multiplier > 1 ? Math.ceil(totalMinInc / multiplier) : totalMinInc;
      const minStr = toLatinDigits(
        Number(perUnitMinInc).toLocaleString(locale),
      );
      const validationMessage =
        !Number.isFinite(amount) || amount <= 0
          ? t("invalid_bid")
          : t("bid_increment_below_minimum", { min: minStr });
      showToastOnce(
        "error",
        `validation:annual-bid:${perUnitMinInc}:${amount}:${multiplier}`,
        validationMessage,
        3200,
      );
      return;
    }

    placeBid(currentPrice + totalDelta);
  };

  const handleOpenPaddleModal = () => {
    if (!session) {
      setLoginModal(true);
      return;
    }

    const currentRoom = resolvedCurrentRoom;

    const backendPaddleNumber =
      (groupAuction as any)?.paddle_number != null
        ? String((groupAuction as any).paddle_number)
        : currentRoom?.paddle_number != null
          ? String(currentRoom.paddle_number)
          : null;

    const effectivePaddleNumber = backendPaddleNumber || userPaddleNumber;
    const canBuyPaddle =
      (groupAuction as any)?.can_buy_paddle ?? currentRoom?.can_buy_paddle;

    if (effectivePaddleNumber || canBuyPaddle === false) return;

    setPaddleModalOpen(true);
  };

  const refetchPaddleDisplay = useCallback(async () => {
    queryClient.invalidateQueries({ queryKey: ["wallet-balances"] });
    queryClient.invalidateQueries({ queryKey: ["user-paddles"] });

    await queryClient.refetchQueries({
      queryKey: ["available-paddles", groupAuctionId, "annual"],
    });

    const paddles = await getUserPaddlesForGroupAuction(
      groupAuctionId,
      "annual",
    );

    const availablePaddle = paddles.find((p) => {
      const active = (p as any)?.active;
      const isActive = active === "1" || active === 1 || active === true;
      return p.is_available !== false && isActive;
    });

    const displayNumber =
      availablePaddle?.paddle_number ??
      (availablePaddle as any)?.unique_id ??
      null;

    const displayType =
      (availablePaddle as any)?.type ??
      (availablePaddle as any)?.paddle_type ??
      null;

    setHasAvailablePaddle(!!availablePaddle);
    setUserPaddleNumber(displayNumber);
    setUserPaddleType(displayType);
  }, [groupAuctionId, queryClient]);

  const handlePaddleJoined = useCallback(async () => {
    showToastOnce("success", "paddle-joined", t("paddle_joined"), 2000);
    await refetchPaddleDisplay();
    setTimeout(refetchPaddleDisplay, 600);
  }, [refetchPaddleDisplay, showToastOnce, t]);

  const [timeRemaining, setTimeRemaining] = useState("");

  useEffect(() => {
    const isInMarket =
      reserveTriggered ||
      (marketEntryPrice != null && closeAtMs != null) ||
      closeAtMs != null;

    if (isInMarket) {
      const targetMs = closeAtMs || endTimeMs;

      if (targetMs) {
        const tick = () => {
          const now = Date.now();
          const diff = Math.max(0, targetMs - now);
          const minutes = Math.floor(diff / 60000);
          const seconds = Math.floor((diff % 60000) / 1000);
          setTimeRemaining(`${minutes}:${seconds.toString().padStart(2, "0")}`);
        };

        tick();
        const interval = setInterval(tick, 1000);
        return () => clearInterval(interval);
      }

      setTimeRemaining("");
      return;
    }

    if (timerEndMs) {
      const tick = () => {
        const now = Date.now();
        const diff = Math.max(0, timerEndMs - now);
        const minutes = Math.floor(diff / 60000);
        const seconds = Math.floor((diff % 60000) / 1000);
        setTimeRemaining(`${minutes}:${seconds.toString().padStart(2, "0")}`);
      };

      tick();
      const interval = setInterval(tick, 1000);
      return () => clearInterval(interval);
    }

    if (timerRemaining) {
      setTimeRemaining(timerRemaining);
      return;
    }

    setTimeRemaining("");
  }, [
    closeAtMs,
    endTimeMs,
    timerRemaining,
    timerEndMs,
    reserveTriggered,
    marketEntryPrice,
  ]);

  const isInMarketPhase =
    reserveTriggered ||
    (marketEntryPrice != null && closeAtMs != null) ||
    closeAtMs != null;

  const workStartTime = (groupAuction as any)?.work_start_time || "00:00";
  const workEndTime = (groupAuction as any)?.work_end_time || "00:00";

  const toIsoDateTime = (value?: string | null): string | null => {
    if (!value) return null;
    const v = String(value).trim();
    if (!v) return null;
    if (v.includes("T")) return v;

    const m = v.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})(?::(\d{2}))?$/);
    if (m) {
      return `${m[1]}T${m[2]}:${m[3] ?? "00"}`;
    }

    if (/^\d{4}-\d{2}-\d{2}$/.test(v)) {
      return `${v}T00:00:00`;
    }

    return v;
  };

  const parseDateSafe = (value?: string | null): Date | null => {
    const iso = toIsoDateTime(value);
    if (!iso) return null;
    const d = new Date(iso);
    return Number.isNaN(d.getTime()) ? null : d;
  };

  const auctionStartDateTime = useMemo(() => {
    const v =
      (groupAuction as any)?.auction_start_datetime ??
      (groupAuction as any)?.auction_start_date ??
      null;

    return parseDateSafe(v);
  }, [groupAuction]);

  const parseTimeString = (
    timeStr: string,
  ): { hours: number; minutes: number } => {
    const parts = timeStr.split(":");
    return {
      hours: parseInt(parts[0] || "0", 10),
      minutes: parseInt(parts[1] || "0", 10),
    };
  };

  const getNextWorkStartTime = useCallback(() => {
    const now = new Date();

    if (auctionStartDateTime && now < auctionStartDateTime) {
      return auctionStartDateTime;
    }

    const { hours, minutes } = parseTimeString(workStartTime);
    const todayWorkStart = new Date();
    todayWorkStart.setHours(hours, minutes, 0, 0);

    if (now >= todayWorkStart) {
      todayWorkStart.setDate(todayWorkStart.getDate() + 1);
    }

    return todayWorkStart;
  }, [workStartTime, auctionStartDateTime]);

  const [countdownToWorkStart, setCountdownToWorkStart] = useState<{
    hours: number;
    minutes: number;
    seconds: number;
  } | null>(null);

  useEffect(() => {
    const tick = () => {
      const nextWorkStart = getNextWorkStartTime();
      const now = new Date();
      const diff = Math.max(0, nextWorkStart.getTime() - now.getTime());

      setCountdownToWorkStart({
        hours: Math.floor(diff / (1000 * 60 * 60)),
        minutes: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)),
        seconds: Math.floor((diff % (1000 * 60)) / 1000),
      });
    };

    tick();
    const interval = setInterval(tick, 1000);
    return () => clearInterval(interval);
  }, [getNextWorkStartTime]);

  const formatTime = (timeStr: string): string => {
    const { hours, minutes } = parseTimeString(timeStr);
    return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
  };

  const backendPaddleNumber =
    (groupAuction as any)?.paddle_number != null
      ? String((groupAuction as any).paddle_number)
      : resolvedCurrentRoom?.paddle_number != null
        ? String(resolvedCurrentRoom.paddle_number)
        : null;

  const backendPaddleType =
    (groupAuction as any)?.paddle_type ??
    (groupAuction as any)?.type ??
    (resolvedCurrentRoom as any)?.paddle_type ??
    null;

  const effectivePaddleNumber = backendPaddleNumber || userPaddleNumber || null;
  const effectivePaddleType = userPaddleType || backendPaddleType || null;

  const canBuyPaddle =
    (groupAuction as any)?.can_buy_paddle ??
    resolvedCurrentRoom?.can_buy_paddle;

  const shouldShowOwnedPaddle =
    !!effectivePaddleNumber || canBuyPaddle === false;

  if (isResolvingInitialAuctionState) {
    return (
      <div className="bg-[#FBF9F6] text-slate-800 min-h-screen">
        <header className="bg-[#0F5132] text-white py-4">
          <div className="max-w-7xl mx-auto px-6">
            <h1 className="text-2xl md:text-3xl font-extrabold">
              {getLocalizedTitle(groupAuction.title) || t("title")}
            </h1>
            <p className="text-sm md:text-base text-white/80 mt-1">
              {t("loading_auction")}
            </p>
          </div>
        </header>

        <main className="max-w-7xl mx-auto px-6 py-6">
          <section className="bg-white rounded-2xl shadow-lg border border-gray-100 p-6 md:p-8">
            <div className="flex flex-col items-center justify-center text-center gap-4 py-6">
              <span className="h-12 w-12 rounded-full border-4 border-emerald-100 border-t-emerald-700 animate-spin" />
              <p className="text-base md:text-lg font-semibold text-slate-700">
                {t("loading_auction")}
              </p>
              <p className="text-sm text-slate-500">
                {t("waiting_for_auction")}
              </p>
            </div>
          </section>
        </main>
      </div>
    );
  }

  if (!currentAuctionGroupId || auctionStatus === "upcoming") {
    return (
      <div className="bg-[#FBF9F6] text-slate-800 min-h-screen">
        <header className="bg-[#0F5132] text-white py-4">
          <div className="max-w-7xl mx-auto px-6 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
            <div className="min-w-0">
              <h1 className="text-2xl md:text-3xl font-extrabold">
                {getLocalizedTitle(groupAuction.title) || t("title")}
              </h1>
              <p className="text-sm md:text-base text-white/80 mt-1">
                {t("waiting_for_auction")}
              </p>
            </div>
            <YearlyVoiceStreamControl
              streamToken={groupAuction.stream_token ?? null}
              auctionState={groupAuction.auction_state}
              className="shrink-0"
            />
          </div>
        </header>

        <main className="max-w-7xl mx-auto px-6 py-6 space-y-6">
          <YearlyLiveConnectionBanner
            connectionStatus={connectionStatus}
            recoveryFailureCount={recoveryFailureCount}
            onRequestRecovery={requestRecovery}
          />

          <section className="bg-white rounded-2xl shadow-lg overflow-hidden">
            <div className="bg-gradient-to-r from-amber-500 to-orange-500 px-4 sm:px-6 py-3 flex items-center justify-center gap-2">
              <span className="w-2 h-2 rounded-full bg-white animate-pulse" />
              <span className="text-white font-bold text-sm sm:text-base">
                {t("no_active_auction")}
              </span>
            </div>

            <div className="p-4 sm:p-6 text-center">
              <div className="grid grid-cols-2 gap-3 sm:gap-4 mb-4 sm:mb-6">
                <div className="bg-slate-50 rounded-xl p-3 sm:p-4">
                  <p className="text-[10px] sm:text-xs text-slate-500 mb-1">
                    {t("work_start_time")}
                  </p>
                  <p className="text-base sm:text-xl font-bold text-slate-800">
                    {formatTime(workStartTime)}
                  </p>
                </div>

                <div className="bg-slate-50 rounded-xl p-3 sm:p-4">
                  <p className="text-[10px] sm:text-xs text-slate-500 mb-1">
                    {t("work_end_time")}
                  </p>
                  <p className="text-base sm:text-xl font-bold text-slate-800">
                    {formatTime(workEndTime)}
                  </p>
                </div>
              </div>

              {countdownToWorkStart && (
                <div className="mb-4 sm:mb-6 p-4 sm:p-5 rounded-2xl border border-slate-200 bg-slate-50/80 shadow-sm">
                  <div className="flex items-center gap-1.5 mb-3">
                    <span className="w-1.5 h-1.5 rounded-full bg-[#0f5132] animate-pulse" />
                    <p className="text-[11px] sm:text-sm font-semibold text-[#0f5132]">
                      {t("next_work_start_in")}
                    </p>
                  </div>

                  <div className="grid grid-cols-3 gap-1.5 sm:gap-2">
                    {[
                      {
                        value: countdownToWorkStart.hours,
                        label: tCountdown("hours"),
                      },
                      {
                        value: countdownToWorkStart.minutes,
                        label: tCountdown("minutes"),
                      },
                      {
                        value: countdownToWorkStart.seconds,
                        label: tCountdown("seconds"),
                      },
                    ].map((item, idx) => (
                      <div
                        key={idx}
                        className="relative overflow-hidden rounded-xl border border-[#d4e0d9] bg-white text-center shadow-[0_3px_10px_rgba(15,81,50,0.12)] px-2 py-2.5 sm:px-3 sm:py-3"
                      >
                        <div className="mb-0.5 text-[9px] sm:text-[10px] text-[#5f7e71] font-medium">
                          {item.label}
                        </div>
                        <div className="text-base sm:text-lg font-extrabold tabular-nums text-[#0f5132] leading-tight">
                          {toLatinDigits(String(item.value).padStart(2, "0"))}
                        </div>
                      </div>
                    ))}
                  </div>
                </div>
              )}

              {!shouldShowOwnedPaddle &&
                !hasAvailablePaddle &&
                (groupAuction as any)?.can_buy_paddle !== false &&
                !sessionLoading && (
                  <DynamicButton
                    onClick={handleOpenPaddleModal}
                    className="w-full sm:w-auto px-6 sm:px-8 py-2.5 sm:py-3 text-sm sm:text-base"
                  >
                    {tPaddle("buy_paddle_button")}
                  </DynamicButton>
                )}
            </div>
          </section>

          {catalogUrl ? <BookletDownloadCard catalogUrl={catalogUrl} /> : null}

          <section className="space-y-3 sm:space-y-4">
            <h2 className="text-lg sm:text-xl md:text-2xl font-extrabold">
              {animalType === "horse"
                ? isRtl
                  ? "المجموعة"
                  : "Auction Group"
                : t("upcoming_schedule")}
            </h2>

            <LiveRoomsTable
              groupId={groupAuctionId}
              initialRooms={initialRooms}
              maxDays={maxDays}
              animalType={animalType}
              closedSummariesByRoomId={closedSummariesByRoomId}
              hideActions
            />
          </section>

          <GroupAuctionLocationMapSection
            lat={groupAuction.lat}
            lng={groupAuction.lng}
          />
        </main>

        <YearlyPaddleModal
          open={paddleModalOpen}
          onClose={() => {
            setPaddleModalOpen(false);
            refetchPaddleDisplay();
          }}
          groupAuctionId={groupAuctionId}
          yearlyAuctionId={groupAuctionId}
          yearlyAuctionType="group"
          prices={prices}
          availablePaddles={availablePaddles}
          prefetchedAuctionTerms={getPrefetchedGroupAuctionTerms(groupAuction)}
          twoStepAuctionTerms
          onJoined={handlePaddleJoined}
        />
      </div>
    );
  }

  return (
    <div className="bg-[#FBF9F6] text-slate-800 min-h-screen">
      <header className="bg-[#0F5132] text-white py-4">
        <div className="max-w-7xl mx-auto px-6 flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
          <div className="min-w-0">
            <h1 className="text-2xl md:text-3xl font-extrabold">
              {getLocalizedTitle(groupAuction.title) || t("title")}
            </h1>
            {auctionGroupLabel && (
              <p className="text-sm md:text-base text-white/80 mt-1">
                {auctionGroupLabel}
              </p>
            )}
          </div>

          <div className="flex flex-wrap items-center gap-2 sm:gap-3">
            <YearlyVoiceStreamControl
              streamToken={groupAuction.stream_token ?? null}
              auctionState={groupAuction.auction_state}
            />
            {auctionPaused && currentAuctionGroupId ? (
              <div className="flex items-center gap-2 bg-amber-500 px-3 py-1 rounded-full text-sm font-bold text-amber-950 shadow">
                <span className="w-2 h-2 rounded-full bg-amber-950/80 animate-pulse" />
                <span>{t("auction_paused_title")}</span>
              </div>
            ) : (
              <div className="flex items-center gap-2 bg-red-600/90 px-3 py-1 rounded-full text-sm font-bold shadow">
                <span className="w-2 h-2 rounded-full bg-white animate-pulse" />
                <span>{t("live_now")}</span>
              </div>
            )}
          </div>
        </div>
      </header>

      <main className="max-w-7xl mx-auto px-6 py-6 space-y-6">
        <YearlyLiveConnectionBanner
          connectionStatus={connectionStatus}
          recoveryFailureCount={recoveryFailureCount}
          onRequestRecovery={requestRecovery}
        />

        <div className="bg-black rounded-xl overflow-hidden relative aspect-video shadow-md">
          <div className="absolute top-3 left-3 flex items-center gap-2 z-10">
            <span className="w-2 h-2 rounded-full bg-red-500 animate-pulse" />
            <span className="bg-red-600 text-white text-xs px-2 py-1 rounded-full font-bold">
              {t("live")}
            </span>
          </div>

          {normalizedVideoUrl ? (
            <iframe
              src={normalizedVideoUrl}
              className="w-full h-full"
              title={t("live")}
              allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
              allowFullScreen
              referrerPolicy="strict-origin-when-cross-origin"
              onLoad={() => setVideoFrameLoaded(true)}
            />
          ) : broadcastPosterUrl ? (
            <div className="relative w-full h-full flex items-center justify-center bg-black">
              {/* eslint-disable-next-line @next/next/no-img-element */}
              <img
                src={broadcastPosterUrl}
                alt=""
                className="w-full h-full object-contain"
              />
            </div>
          ) : (
            <div className="flex h-full items-center justify-center bg-black text-white/70">
              لا يوجد رابط بث متاح حالياً
            </div>
          )}
        </div>

        {effectivePaused && currentAuctionGroupId ? (
          <div
            className="rounded-xl border-2 border-amber-400 bg-amber-50 px-4 py-3 text-amber-950 shadow-sm"
            role="status"
            aria-live="polite"
          >
            <p className="font-extrabold text-base">
              {t("auction_paused_title")}
            </p>
            {pauseReason ? (
              <p className="mt-1.5 text-sm leading-relaxed">
                <span className="font-semibold">
                  {t("auction_paused_reason_label")}:{" "}
                </span>
                {pauseReason}
              </p>
            ) : (
              <p className="mt-1.5 text-sm text-amber-900/85">
                {t("auction_paused_no_reason_detail")}
              </p>
            )}
            <p className="mt-2 text-xs text-amber-900/70">
              {t("bidding_disabled_because_paused")}
            </p>
          </div>
        ) : null}

        {catalogUrl ? <BookletDownloadCard catalogUrl={catalogUrl} /> : null}

        {(() => {
          const currentRoom = resolvedCurrentRoom;

          const backendPaddleNumberLocal =
            (groupAuction as any)?.paddle_number != null
              ? String((groupAuction as any).paddle_number)
              : currentRoom?.paddle_number != null
                ? String(currentRoom.paddle_number)
                : null;

          const availPaddle = availablePaddles.find(
            (p) => p.is_available && p.active,
          );

          const fromAvailablePaddles =
            (availPaddle as any)?.paddle_number ??
            availPaddle?.unique_id ??
            null;

          const effectiveUserPaddleNumberLocal =
            backendPaddleNumberLocal ||
            userPaddleNumber ||
            fromAvailablePaddles;

          const canBuyPaddleLocal =
            (groupAuction as any)?.can_buy_paddle ??
            currentRoom?.can_buy_paddle;

          const effectiveHasAvailablePaddle =
            canBuyPaddleLocal === false ? true : hasAvailablePaddle;

          const roomAnimal: any = (currentRoom as any)?.animal || null;

          const fallbackAnimalDetails =
            roomAnimal || currentRoom
              ? {
                  name:
                    roomAnimal?.name ??
                    (currentRoom?.name as string | undefined) ??
                    undefined,
                  father_name: roomAnimal?.father_name ?? undefined,
                  mother_name: roomAnimal?.mother_name ?? undefined,
                  age:
                    roomAnimal?.age ??
                    (roomAnimal as any)?.ageYears ??
                    undefined,
                  breed: roomAnimal?.breed ?? undefined,
                  color: roomAnimal?.color ?? undefined,
                  owner: (currentRoom as any)?.owner_name ?? undefined,
                }
              : null;

          const baseDetails = currentAnimalDetails || fallbackAnimalDetails;

          const effectiveAnimalDetails =
            baseDetails || firstSingleAuctionFallback
              ? {
                  ...(baseDetails || {}),
                  father_name:
                    baseDetails?.father_name ??
                    firstSingleAuctionFallback?.father_name ??
                    undefined,
                  mother_name:
                    baseDetails?.mother_name ??
                    firstSingleAuctionFallback?.mother_name ??
                    undefined,
                  age:
                    baseDetails?.age ??
                    firstSingleAuctionFallback?.age ??
                    undefined,
                }
              : null;

          return (
            <CurrentAuctionCard
              key={currentAuctionGroupId || "waiting"}
              groupAuction={groupAuction}
              currentRoom={currentRoom}
              currentPrice={currentPrice}
              timeRemaining={timeRemaining}
              animalType={animalType}
              onBidClick={handleBidClick}
              onBuyPaddle={handleOpenPaddleModal}
              hasPaddle={availablePaddles.length > 0}
              hasAvailablePaddle={effectiveHasAvailablePaddle}
              userPaddleNumber={effectiveUserPaddleNumberLocal}
              isActive={
                auctionStatus === "active" &&
                currentAuctionGroupId !== null &&
                !effectivePaused
              }
              marketEntryPrice={marketEntryPrice}
              isInMarketPhase={isInMarketPhase}
              animalDetails={effectiveAnimalDetails}
              highestBidderPaddle={(() => {
                const fromState =
                  (highestBidder as any)?.paddleUniqueId ||
                  (bidders[0]?.paddleUniqueId ??
                    (bidders[0]?.paddle ? `A-${bidders[0].paddle}` : null));
                if (fromState) return fromState;

                const candidateUserIds = [
                  (highestBidder as any)?.id,
                  bidders[0]?.userId,
                ];
                for (const uid of candidateUserIds) {
                  if (uid == null) continue;
                  const cached = paddleByUserIdRef.current.get(
                    String(uid).trim(),
                  );
                  if (cached?.paddleUniqueId) return cached.paddleUniqueId;
                  if (cached?.paddle) return `A-${cached.paddle}`;
                }

                const hbId = (highestBidder as any)?.id;
                if (
                  session?.id != null &&
                  hbId != null &&
                  String(session.id) === String(hbId) &&
                  effectivePaddleNumber
                ) {
                  return effectivePaddleNumber;
                }
                return null;
              })()}
              isVipBidder={false}
              sessionLoading={sessionLoading}
              shouldShowOwnedPaddle={shouldShowOwnedPaddle}
              effectivePaddleNumber={effectivePaddleNumber}
              effectivePaddleType={effectivePaddleType}
              paddlePrices={prices}
              liveAuctionGroupSinglesCount={liveCamelGroupSinglesCount}
              bidMultiplier={camelBidMultiplier}
            />
          );
        })()}

        <div
          className="grid grid-cols-1 lg:grid-cols-2 gap-4 items-stretch"
          dir="rtl"
        >
          <div
            className="bg-white rounded-2xl p-6 shadow-md border border-gray-100 w-full order-2 lg:order-2"
            dir="rtl"
          >
            <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-4 gap-2">
              <h3 className="text-lg font-extrabold text-slate-900">
                {t("recent_bids")}
              </h3>
              <p className="text-xs text-slate-500">
                {animalType === "camel"
                  ? t("camel_on_platform")
                  : t("horse_on_platform")}
              </p>
            </div>

            {bidders.length > 0 ? (
              <>
                {highestBidder ? (
                  <div className="text-sm text-emerald-700 font-semibold mb-3">
                    {t("highest_bidder")}:{" "}
                    {(highestBidder as any).paddleUniqueId ||
                      ((highestBidder as any).paddleNumber
                        ? `#${(highestBidder as any).paddleNumber}`
                        : highestBidder.name)}
                  </div>
                ) : null}

                <div className="overflow-x-auto max-h-64 overflow-y-auto">
                  <table className="w-full text-sm">
                    <thead className="sticky top-0 bg-white">
                      <tr className="border-b border-slate-200">
                        <th className="text-start p-3 font-semibold text-slate-600">
                          #
                        </th>
                        <th className="text-center p-3 font-semibold text-slate-600">
                          {t("bidder")}
                        </th>
                        <th className="text-center p-3 font-semibold text-slate-600">
                          {t("paddle_number")}
                        </th>
                        <th className="text-end p-3 font-semibold text-slate-600">
                          {t("bid_amount")}
                        </th>
                      </tr>
                    </thead>

                    <tbody>
                      {bidders.map((b, i) => (
                        <tr
                          key={i}
                          className="border-b border-slate-100 hover:bg-slate-50"
                        >
                          <td className="p-3 text-slate-700">{i + 1}</td>
                          <td className="p-3 text-center text-slate-600">
                            {b.name}
                          </td>
                          <td className="p-3 text-center text-slate-600">
                            {(() => {
                              if (b.paddleUniqueId) return b.paddleUniqueId;
                              if (b.paddle) return `#${b.paddle}`;
                              if (b.userId != null) {
                                const cached =
                                  paddleByUserIdRef.current.get(
                                    String(b.userId).trim(),
                                  );
                                if (cached?.paddleUniqueId)
                                  return cached.paddleUniqueId;
                                if (cached?.paddle)
                                  return `#${cached.paddle}`;
                                if (
                                  session?.id != null &&
                                  String(session.id) === String(b.userId) &&
                                  effectivePaddleNumber
                                ) {
                                  return formatPaddleDisplay(
                                    effectivePaddleNumber,
                                  );
                                }
                              }
                              return "-";
                            })()}
                          </td>
                          <td className="p-3 text-end font-bold text-[#0F5132]">
                            {toLatinDigits(b.amount.toLocaleString())}{" "}
                            {t("currency")}
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              </>
            ) : (
              <p className="text-sm text-slate-400 text-center py-4">
                {t("no_bids_yet")}
              </p>
            )}
          </div>

          <div className="order-1 lg:order-1">
            <AdminAnnouncementsCard announcements={announcements} />
          </div>
        </div>

        <section className="space-y-4">
          <h2 className="text-xl md:text-2xl font-extrabold">
            {animalType === "horse"
              ? isRtl
                ? "المجموعة"
                : "Auction Group"
              : t("camels_title")}
          </h2>

          <LiveRoomsTable
            groupId={groupAuctionId}
            initialRooms={initialRooms}
            maxDays={maxDays}
            animalType={animalType}
            auctionType={groupAuction.auction_type as "public" | "private"}
            closedSummariesByRoomId={closedSummariesByRoomId}
            activeAuctionGroupId={currentAuctionGroupId}
            activeRoomLivePrice={
              currentAuctionGroupId != null ? currentPrice : null
            }
            hideActions
          />
        </section>

        <GroupAuctionLocationMapSection
          lat={groupAuction.lat}
          lng={groupAuction.lng}
        />
      </main>

      <YearlyPaddleModal
        open={(() => {
          const currentRoom = resolvedCurrentRoom;

          const backendPaddleNumber =
            (groupAuction as any)?.paddle_number != null
              ? String((groupAuction as any).paddle_number)
              : currentRoom?.paddle_number != null
                ? String(currentRoom.paddle_number)
                : null;

          const effectivePaddleNumber = backendPaddleNumber || userPaddleNumber;
          const canBuyPaddle =
            (groupAuction as any)?.can_buy_paddle ??
            currentRoom?.can_buy_paddle;

          if (forcePaddleModalOpen) return true;

          return (
            canBuyPaddle !== false &&
            !!paddleModalOpen &&
            !effectivePaddleNumber
          );
        })()}
        onClose={() => {
          setPaddleModalOpen(false);
          setForcePaddleModalOpen(false);
          refetchPaddleDisplay();
        }}
        groupAuctionId={groupAuctionId}
        yearlyAuctionId={groupAuctionId}
        yearlyAuctionType="group"
        prices={prices}
        availablePaddles={availablePaddles}
        prefetchedAuctionTerms={getPrefetchedGroupAuctionTerms(groupAuction)}
        twoStepAuctionTerms
        onJoined={handlePaddleJoined}
      />

      <WinnerOverlay
        open={winnerOverlayOpen}
        winnerName={winnerData?.name || null}
        winnerPaddleUniqueId={winnerData?.paddleUniqueId || null}
        winnerPaddleNumber={winnerData?.paddleNumber || null}
        finalPrice={winnerData?.finalPrice || 0}
        reason={winnerData?.reason || ""}
        label={winnerData?.label}
        onClose={handleWinnerOverlayClose}
      />
    </div>
  );
}
