'use client';

/**
 * DealDetail - "The DTC Brand" layout for the deal detail page.
 *
 * Layout concept: Trust-first / DTC brand.
 * Desktop:
 *  - Vendor hero banner (full-width, h-40) with logo circle, vendor name, city + rating
 *  - Two-column: START = deal content + gallery + details + BusinessProfile + related deals
 *                END (sticky w-96) = purchase card
 *  - Sticky bottom bar (fixed, desktop only) for persistent purchase CTA
 * Mobile: identical to DealDetail.tsx mobile section.
 */

import { useEffect, useMemo, useRef, useState } from 'react';
import { useTrackRecentlyViewed } from '@/features/recently-viewed/useTrackRecentlyViewed';
import { writeVisitedDealSnapshot } from '@/lib/offline/offlineContinuity';
import { dealTypeMeta } from '@/lib/deal-types';
import { HydratedIsland } from '@/components/HydratedIsland';
import { AppShell } from '@/components/ui/layout/AppShell';
import { BottomNav, useCustomerNavItems } from '@/components/ui/layout/BottomNav';
import { Container } from '@/components/ui/layout/Container';
import { DealImageGallery } from '@/components/ui/domain/DealImageGallery';
import { ReportButton } from '@/components/ui/domain/ReportButton';
import { ShareButton } from '@/components/ui/domain/ShareButton';
import { Badge } from '@/components/ui/primitives/Badge';
import { StickyCTA } from '@/components/ui/layout/StickyCTA';
import { SiteNav } from '@/components/ui/layout/SiteNav';
import { GlobalCartDrawer } from '@/components/ui/domain/cart/GlobalCartDrawer';
import { useCheckoutModalStore } from '@/features/checkout-modal/checkoutModalStore';
import { CheckoutModal } from '@/features/checkout-modal/CheckoutModal';
import { useT, useLocale } from '@/lib/i18n/react';
import { formatImageAlt } from '@/lib/format';
import type { DealDetailData, GroupGroupProp } from './DealDetailDataLoader';
import type { Locale } from '@/lib/i18n';
import type { DealGalleryImage } from '@/components/ui/domain/DealImageGallery';
import { captureCaught } from '@/lib/observability';
import { WishlistProvider } from '@/features/wishlist/WishlistContext';
import { TagPills } from '@/components/ui/domain/TagPills';
import { RelatedDealsSection } from '@/components/ui/domain/RelatedDealsSection';
import type { RelatedDeals } from '@/server/catalog/related';
import type { DealWithSkus } from '@/server/domain/variants/read';

import { BusinessProfile } from '@/components/ui/domain/BusinessProfile';
import { Icon } from '@/components/ui/icons/Icon';
import { DealCTA } from './DealCTA';
import type { DealCTAProps, PersonalRequestState } from './DealCTA';
import { DealReviews } from './DealReviews';
import { VendorHeroBanner } from './VendorHeroBanner';
import { StickyDesktopBar } from './StickyDesktopBar';
import { MobileDealDetails } from './MobileDealDetails';

import { DealPurchaseCard } from './DealPurchaseCard';
import type { ReactNode, RefObject } from 'react';
import { getCsrfToken } from '@/lib/csrf';
import { useAuthGateStore } from '@/lib/stores/auth-gate';
import { AuthGateModal } from '@/features/auth-flow/AuthGateModal';
import type { CheckoutModalPreview } from '@/features/checkout-modal/checkoutModalStore';
import { localizeCommandPath } from '@/components/ui/overlays/CommandPalette/CommandPalette';

export interface DealDetailProps {
  deal: DealDetailData;
  /** Hebrew slug from translation.slug in [...path].astro */
  dealSlug: string;
  locale: Locale;
  siteUrl: string;
  isGuest?: boolean;
  isAdmin?: boolean;
  isVendor?: boolean;
  userName?: string;
  csrfToken?: string | null;
  buyerName?: string | null;
  buyerEmail?: string | null;
  /** Variant axes + SKUs for the deal. When axes is empty, single-SKU mode. */
  variantData: DealWithSkus;
  relatedSections?: RelatedDeals;
}

function getDealHeroViewTransitionName(dealId: string): string {
  return `deal-hero-${dealId}`;
}

// ─── Hooks ───────────────────────────────────────────────────────────────────

/**
 * Tracks visibility of two CTA ref elements via IntersectionObserver.
 * Returns booleans that are true when the element has scrolled out of view.
 */
function useCtaVisibility(): {
  mobileCtaRef: RefObject<HTMLDivElement | null>;
  desktopCtaRef: RefObject<HTMLDivElement | null>;
  mobileTopCtaHidden: boolean;
  desktopTopCtaHidden: boolean;
} {
  const mobileCtaRef = useRef<HTMLDivElement>(null);
  const desktopCtaRef = useRef<HTMLDivElement>(null);
  const [mobileTopCtaHidden, setMobileTopCtaHidden] = useState(false);
  const [desktopTopCtaHidden, setDesktopTopCtaHidden] = useState(false);

  useEffect(() => {
    const mobileObs = new IntersectionObserver(
      (entries) => setMobileTopCtaHidden(!(entries[0]?.isIntersecting ?? true)),
      { threshold: 0 },
    );
    const desktopObs = new IntersectionObserver(
      (entries) => setDesktopTopCtaHidden(!(entries[0]?.isIntersecting ?? true)),
      { threshold: 0 },
    );
    if (mobileCtaRef.current) mobileObs.observe(mobileCtaRef.current);
    if (desktopCtaRef.current) desktopObs.observe(desktopCtaRef.current);
    return () => {
      mobileObs.disconnect();
      desktopObs.disconnect();
    };
  }, []);

  return { mobileCtaRef, desktopCtaRef, mobileTopCtaHidden, desktopTopCtaHidden };
}

/**
 * Manages the personal deal request flow (async POST + state).
 */
function useDealActions(
  dealId: string,
  isGuest: boolean,
): {
  personalRequestState: PersonalRequestState;
  handleRequestPersonalDeal: () => void;
} {
  const [personalRequestState, setPersonalRequestState] = useState<PersonalRequestState>('idle');

  useEffect(() => {
    if (isGuest) return;
    let cancelled = false;
    fetch(`/api/personal-deals?sourceDealId=${encodeURIComponent(dealId)}`, {
      credentials: 'same-origin',
    })
      .then(async (response) => {
        if (!response.ok) return null;
        return response.json() as Promise<{ status?: string | null }>;
      })
      .then((result) => {
        if (cancelled || !result?.status) return;
        const stateByStatus: Record<string, PersonalRequestState> = {
          PENDING: 'sent',
          ACCEPTED: 'accepted',
          REJECTED: 'rejected',
          EXPIRED: 'expired',
        };
        const state = stateByStatus[result.status];
        if (state) setPersonalRequestState(state);
      })
      .catch((error) => {
        captureCaught(error, {
          scope: 'features.deal-detail.personal-deal-status',
          severity: 'info',
        });
      });
    return () => {
      cancelled = true;
    };
  }, [dealId, isGuest]);

  const handleRequestPersonalDeal = () => {
    setPersonalRequestState('pending');
    fetch('/api/personal-deals', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'x-csrf-token': getCsrfToken() },
      body: JSON.stringify({ sourceDealId: dealId }),
      credentials: 'same-origin',
    })
      .then(async (res) => {
        if (res.ok) {
          setPersonalRequestState('sent');
        } else {
          const json = (await res.json()) as { code?: string };
          if (json.code === 'AUTH_REQUIRED') {
            useAuthGateStore.getState().triggerAuth(handleRequestPersonalDeal);
          } else {
            setPersonalRequestState('error');
          }
        }
      })
      .catch((err) => {
        captureCaught(err, { scope: 'features.deal-detail.DealDetail', severity: 'warning' });
        setPersonalRequestState('error');
      });
  };

  return { personalRequestState, handleRequestPersonalDeal };
}

// ─── Sub-components ───────────────────────────────────────────────────────────

interface DesktopDealContentProps {
  deal: DealDetailData;
  galleryImages: DealGalleryImage[];
  activeImageIndex: number;
  onChangeImageIndex: (i: number) => void;
  sharedElementName: string;
}

/**
 * Desktop START content column: badge, title, category/tags, description,
 * image gallery, instructions, pickup info, reviews, related deals.
 */
function DesktopDealContent({
  deal,
  galleryImages,
  activeImageIndex,
  onChangeImageIndex,
  sharedElementName,
}: DesktopDealContentProps) {
  const t = useT('deal_detail');
  const tDealType = useT('domain_deal_type');
  const { locale } = useLocale();
  const dealTypeKey = dealTypeMeta(deal.dealType).labelKey;

  return (
    <div className="flex min-w-0 flex-1 flex-col gap-6">
      {/* Deal header */}
      <div className="flex flex-col gap-3">
        <Badge tone="solid-dark" className="w-fit">
          {tDealType(dealTypeKey)}
        </Badge>
        <h1
          data-user-content
          className="text-text-primary text-[length:var(--font-size-display)] leading-tight font-[var(--font-weight-extrabold)] break-words"
        >
          {deal.title}
        </h1>
        {(deal.categoryNameHe || (deal.tags && deal.tags.length > 0)) && (
          <div className="mt-2 flex flex-wrap items-center gap-2">
            {deal.categoryNameHe && (
              <Badge tone="neutral" size="sm" data-user-content>
                {locale === 'he' ? deal.categoryNameHe : deal.categoryNameEn}
              </Badge>
            )}
            {deal.tags && deal.tags.length > 0 && (
              <>
                <span className="text-text-muted text-xs font-medium">{t('tags_label')}</span>
                <TagPills tags={deal.tags} locale={locale} />
              </>
            )}
          </div>
        )}
        {deal.description && (
          <p data-user-content className="text-text-secondary text-lg leading-relaxed">
            {deal.description}
          </p>
        )}
      </div>

      {/* Image gallery */}
      {galleryImages.length > 0 && (
        <DealImageGallery
          images={galleryImages}
          activeIndex={activeImageIndex}
          onChangeIndex={onChangeImageIndex}
          prioritizeFirst
          sharedElementName={sharedElementName}
        />
      )}

      {/* Special instructions */}
      {deal.specialInstructions && (
        <div className="bg-surface-inset border-border-subtle rounded-xl border px-5 py-4">
          <p className="text-text-muted mb-1.5 text-xs font-semibold tracking-wide uppercase">
            {t('special_instructions')}
          </p>
          <p data-user-content className="text-text-secondary text-base">
            {deal.specialInstructions}
          </p>
        </div>
      )}

      {/* Pickup address + hours (same row on desktop) */}
      {(deal.pickupAddress || deal.pickupStart || deal.pickupEnd) && (
        <div className="flex flex-wrap gap-6">
          {deal.pickupAddress && (
            <div className="flex items-start gap-3">
              <Icon name="MapPin" size="md" color="muted" className="mt-0.5 shrink-0" />
              <div>
                <p className="text-text-muted text-xs font-medium">{t('pickup_address')}</p>
                <p data-user-content className="text-text-primary text-base">
                  {deal.pickupAddress}
                </p>
              </div>
            </div>
          )}
          {(deal.pickupStart ?? deal.pickupEnd) && (
            <div className="flex items-start gap-3">
              <Icon name="Clock" size="md" color="muted" className="mt-0.5 shrink-0" />
              <div>
                <p className="text-text-muted text-xs font-medium">{t('pickup_hours')}</p>
                <p className="text-text-primary text-base" data-user-content>
                  {deal.pickupStart?.slice(0, 5)} - {deal.pickupEnd?.slice(0, 5)}
                </p>
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ─── Additional hooks ─────────────────────────────────────────────────────────

interface UseDealCTAPropsArgs {
  deal: DealDetailData;
  dealSlug: string;
  isSoldOut: boolean;
  isPaused: boolean;
  isGuest: boolean;
  hasVariants: boolean;
  selectedSkuId: string | null;
  isSelectedSkuOOS: boolean;
  notifySkuId: string | null;
  personalRequestState: PersonalRequestState;
  variantData: DealWithSkus;
  setSelectedSkuId: (id: string | null) => void;
  setNotifySkuId: (id: string | null) => void;
  handleRequestPersonalDeal: () => void;
}

/** Assembles the shared DealCTA props object from coordinator state. */
function useDealCTAProps({
  deal,
  dealSlug,
  isSoldOut,
  isPaused,
  isGuest,
  hasVariants,
  selectedSkuId,
  isSelectedSkuOOS,
  notifySkuId,
  personalRequestState,
  variantData,
  setSelectedSkuId,
  setNotifySkuId,
  handleRequestPersonalDeal,
}: UseDealCTAPropsArgs): DealCTAProps {
  return useMemo(() => {
    const selectedSku = selectedSkuId
      ? variantData.skus.find((s) => s.id === selectedSkuId)
      : hasVariants
        ? undefined
        : variantData.skus.find((s) => s.isActive);

    const preview: CheckoutModalPreview = {
      thumbnailUrl: deal.images.dealMain[0]?.url ?? null,
      title: deal.title,
      businessName: deal.vendor.displayName,
      skuLabel: null,
      discountedPriceAgorot: selectedSku
        ? Math.round(parseFloat(selectedSku.discountedPrice) * 100)
        : null,
      originalPriceAgorot: selectedSku
        ? Math.round(parseFloat(selectedSku.originalPrice) * 100)
        : null,
    };

    return {
      dealId: deal.id,
      heSlug: dealSlug,
      dealType: deal.dealType,
      dealTitle: deal.title,
      isSoldOut,
      isPaused,
      isGuest,
      hasVariants,
      selectedSkuId,
      isSelectedSkuOOS,
      notifySkuId,
      personalRequestState,
      variantData,
      preview,
      onSelectSku: setSelectedSkuId,
      onNotifyOosSku: (skuId: string) => setNotifySkuId(skuId),
      onDismissNotify: () => setNotifySkuId(null),
      onRequestPersonalDeal: handleRequestPersonalDeal,
    };
  }, [
    deal.id,
    deal.dealType,
    deal.title,
    deal.images.dealMain,
    deal.vendor.displayName,
    dealSlug,
    isSoldOut,
    isPaused,
    isGuest,
    hasVariants,
    selectedSkuId,
    isSelectedSkuOOS,
    notifySkuId,
    personalRequestState,
    variantData,
    setSelectedSkuId,
    setNotifySkuId,
    handleRequestPersonalDeal,
  ]);
}

// ─── Mobile/Sticky sub-components ────────────────────────────────────────────

interface MobileDealContentProps {
  deal: DealDetailData;
  dealSlug: string;
  galleryImages: DealGalleryImage[];
  activeImageIndex: number;
  onChangeImageIndex: (i: number) => void;
  isSoldOut: boolean;
  isGuest: boolean;
  stockRemaining: number;
  group: GroupGroupProp | null;
  selectedSkuQtyTiers: { minQty: number; discountPercent: number }[];
  qtyLadderText: string;
  ctaRef: RefObject<HTMLDivElement | null>;
  ctaProps: DealCTAProps;
  locale: Locale;
  relatedSections?: RelatedDeals;
}

/** Mobile-only deal content: gallery + details + business profile + related. */
function MobileDealContent({
  deal,
  dealSlug,
  galleryImages,
  activeImageIndex,
  onChangeImageIndex,
  isSoldOut,
  isGuest,
  stockRemaining,
  group,
  selectedSkuQtyTiers,
  qtyLadderText,
  ctaRef,
  ctaProps,
  locale,
  relatedSections,
}: MobileDealContentProps) {
  const sharedElementName = getDealHeroViewTransitionName(deal.id);

  return (
    <div className="lg:hidden">
      <DealImageGallery
        images={galleryImages}
        activeIndex={activeImageIndex}
        onChangeIndex={onChangeImageIndex}
        prioritizeFirst
        sharedElementName={sharedElementName}
      />
      <div className="flex flex-col gap-4 px-4 py-4">
        <MobileDealDetails
          dealId={deal.id}
          dealSlug={dealSlug}
          dealTitle={deal.title}
          dealDescription={deal.description}
          dealSpecialInstructions={deal.specialInstructions}
          dealPickupAddress={deal.pickupAddress}
          dealPickupStart={deal.pickupStart}
          dealPickupEnd={deal.pickupEnd}
          dealWindowEnd={deal.windowEnd}
          originalPrice={deal.originalPrice}
          discountedPrice={deal.discountedPrice}
          isSoldOut={isSoldOut}
          isGuest={isGuest}
          stockRemaining={stockRemaining}
          quantityTotal={deal.quantityTotal}
          group={group}
          categoryNameHe={deal.categoryNameHe}
          categoryNameEn={deal.categoryNameEn}
          tags={deal.tags}
          selectedSkuQtyTiers={selectedSkuQtyTiers}
          qtyLadderText={qtyLadderText}
          ctaRef={ctaRef}
          ctaProps={ctaProps}
          locale={locale}
        />
        <BusinessProfile vendor={deal.vendor} />
      </div>
      {relatedSections && (
        <RelatedDealsSection
          alsoBought={relatedSections.alsoBought}
          similar={relatedSections.similar}
          moreFromVendor={relatedSections.moreFromVendor}
          vendorName={deal.vendor.displayName}
        />
      )}
    </div>
  );
}

interface DesktopDealLayoutProps {
  deal: DealDetailData;
  dealSlug: string;
  siteUrl: string;
  galleryImages: DealGalleryImage[];
  activeImageIndex: number;
  onChangeImageIndex: (i: number) => void;
  savingsAmount: number;
  stockRemaining: number;
  stockFillPct: number;
  isSoldOut: boolean;
  group: GroupGroupProp | null;
  selectedSkuQtyTiers: { minQty: number; discountPercent: number }[];
  qtyLadderText: string;
  ctaRef: RefObject<HTMLDivElement | null>;
  ctaProps: DealCTAProps;
  relatedSections?: RelatedDeals;
}

/** Desktop two-column layout: vendor hero + content column + purchase card. */
function DesktopDealLayout({
  deal,
  dealSlug,
  siteUrl,
  galleryImages,
  activeImageIndex,
  onChangeImageIndex,
  savingsAmount,
  stockRemaining,
  stockFillPct,
  isSoldOut,
  group,
  selectedSkuQtyTiers,
  qtyLadderText,
  ctaRef,
  ctaProps,
  relatedSections,
}: DesktopDealLayoutProps) {
  const sharedElementName = getDealHeroViewTransitionName(deal.id);

  return (
    <div className="hidden lg:block">
      <VendorHeroBanner vendor={deal.vendor} dealTitle={deal.title} dealType={deal.dealType} />
      <Container maxWidth="4xl" px="8" className="pt-6">
        <div className="flex items-start gap-8">
          <DesktopDealContent
            deal={deal}
            galleryImages={galleryImages}
            activeImageIndex={activeImageIndex}
            onChangeImageIndex={onChangeImageIndex}
            sharedElementName={sharedElementName}
          />
          <div className="w-96 shrink-0">
            <DealPurchaseCard
              deal={deal}
              dealSlug={dealSlug}
              siteUrl={siteUrl}
              savingsAmount={savingsAmount}
              stockRemaining={stockRemaining}
              stockFillPct={stockFillPct}
              isSoldOut={isSoldOut}
              group={group}
              selectedSkuQtyTiers={selectedSkuQtyTiers}
              qtyLadderText={qtyLadderText}
              ctaRef={ctaRef}
              ctaProps={ctaProps}
            />
          </div>
        </div>
        {/* Reviews + related deals span full container width below the two-column section */}
        <div className="mt-6 flex flex-col gap-6">
          <DealReviews reviews={deal.reviews} />
          {relatedSections && (
            <RelatedDealsSection
              alsoBought={relatedSections.alsoBought}
              similar={relatedSections.similar}
              moreFromVendor={relatedSections.moreFromVendor}
              vendorName={deal.vendor.displayName}
            />
          )}
        </div>
      </Container>
    </div>
  );
}

interface DealAppShellProps {
  deal: DealDetailData;
  dealSlug: string;
  siteUrl: string;
  isGuest: boolean;
  isAdmin: boolean;
  isVendor: boolean;
  userName?: string;
  navItems: ReturnType<typeof useCustomerNavItems>;
  children: ReactNode;
}

/** AppShell wrapper with deal-specific SiteNav, BottomNav, and overlays. */
function DealAppShell({
  deal,
  dealSlug,
  siteUrl,
  isGuest,
  isAdmin,
  isVendor,
  userName,
  navItems,
  children,
}: DealAppShellProps) {
  return (
    <AppShell
      mode="customer"
      desktopTopBar={
        <SiteNav
          variant="desktop"
          currentPath={`/deals/${dealSlug}`}
          isGuest={isGuest}
          isAdmin={isAdmin}
          isVendor={isVendor}
          userName={userName}
        />
      }
      topBar={
        <SiteNav
          variant="mobile"
          title={deal.title}
          currentPath={`/deals/${dealSlug}`}
          isGuest={isGuest}
          isAdmin={isAdmin}
          isVendor={isVendor}
          endExtra={
            <>
              <ReportButton
                targetId={deal.id}
                targetType="DEAL"
                className="text-text-inverse hover:text-text-inverse/70"
              />
              <ShareButton
                shareParams={{ targetUrl: `${siteUrl}/deals/${dealSlug}`, dealId: deal.id }}
                title={deal.title}
                className="text-text-inverse hover:text-text-inverse/70"
              />
            </>
          }
        />
      }
      bottomNav={<BottomNav mode="customer" items={navItems} />}
      pageOverlays={
        <>
          <GlobalCartDrawer />
          <AuthGateModal />
          <CheckoutModal />
        </>
      }
    >
      {children}
    </AppShell>
  );
}

interface StickyCTABarsProps {
  isSoldOut: boolean;
  mobileTopCtaHidden: boolean;
  ctaProps: DealCTAProps;
  deal: DealDetailData;
  selectedSkuId: string | null;
  desktopTopCtaHidden: boolean;
  stockRemaining: number;
  group: GroupGroupProp | null;
  onBuy: () => void;
}

/** Mobile sticky CTA + desktop sticky bottom bar. */
function StickyCTABars({
  isSoldOut,
  mobileTopCtaHidden,
  ctaProps,
  deal,
  selectedSkuId,
  desktopTopCtaHidden,
  stockRemaining,
  group,
  onBuy,
}: StickyCTABarsProps) {
  const t = useT('deal_detail');
  const tg = useT('group_deal');
  const isGroupFull = group != null && group.currentReservationCount >= group.maxGroupSize;
  const soldOut = isSoldOut || isGroupFull;
  const showMobileSticky = mobileTopCtaHidden && (!soldOut || isGroupFull);
  return (
    <>
      <div className="lg:hidden">
        <StickyCTA visible={showMobileSticky} ariaLabel={t('urgency_banner_aria')}>
          <div className="flex items-center gap-2">
            {isGroupFull && (
              <Badge tone="success" size="sm" className="shrink-0">
                {tg('group_full')}
              </Badge>
            )}
            {/* aria-hidden duplicates the inline CTA; focusable children must be inert */}
            <div aria-hidden="true" inert className="min-w-0 flex-1">
              <DealCTA {...ctaProps} isSoldOut={soldOut} />
            </div>
          </div>
        </StickyCTA>
      </div>
      <StickyDesktopBar
        dealId={deal.id}
        dealTitle={deal.title}
        originalPrice={deal.originalPrice}
        discountedPrice={deal.discountedPrice}
        windowEnd={deal.windowEnd}
        isSoldOut={isSoldOut}
        stockRemaining={stockRemaining}
        quantityTotal={deal.quantityTotal}
        group={group}
        selectedSkuId={selectedSkuId}
        desktopTopCtaHidden={desktopTopCtaHidden}
        onBuy={onBuy}
      />
    </>
  );
}

// ─── DealDetailInner ──────────────────────────────────────────────────────────

function DealDetailInner({
  deal,
  dealSlug,
  siteUrl,
  isGuest = true,
  isAdmin = false,
  isVendor = false,
  userName,
  variantData,
  relatedSections,
}: Omit<DealDetailProps, 'locale'>) {
  const tImage = useT('image');
  const tV = useT('variants');
  const { locale } = useLocale();

  useTrackRecentlyViewed(deal.id, isGuest);

  useEffect(() => {
    writeVisitedDealSnapshot({
      dealId: deal.id,
      title: deal.title,
      vendorName: deal.vendor.displayName,
      href: visitedDealHref(dealSlug ? dealSlug : deal.id, locale),
      updatedAt: Date.now(),
    });
  }, [deal.id, deal.title, deal.vendor.displayName, dealSlug, locale]);

  const isSoldOut = deal.dealState === 'SOLD_OUT';
  const isPaused = deal.dealState === 'PAUSED';

  const hasVariants = useMemo(
    () => variantData.axes.filter((a) => a.isActive).length > 0,
    [variantData.axes],
  );

  // When no variant axes: auto-select the first (only) active SKU.
  // When variant axes exist: null until user picks from all axes.
  const [selectedSkuId, setSelectedSkuId] = useState<string | null>(
    !hasVariants ? (variantData.skus.find((s) => s.isActive)?.id ?? null) : null,
  );

  const [notifySkuId, setNotifySkuId] = useState<string | null>(null);

  const isSelectedSkuOOS = useMemo(() => {
    if (!selectedSkuId) return false;
    const sku = variantData.skus.find((s) => s.id === selectedSkuId);
    if (!sku) return false;
    return sku.quantityTotal - sku.quantitySold <= 0;
  }, [selectedSkuId, variantData.skus]);

  // Resolve qty tiers for the currently selected SKU.
  const selectedSkuQtyTiers = useMemo(() => {
    const sku = selectedSkuId
      ? variantData.skus.find((s) => s.id === selectedSkuId)
      : variantData.skus.find((s) => s.isActive);
    return sku?.qtyTiers ?? [];
  }, [selectedSkuId, variantData.skus]);

  const qtyLadderText = useMemo(
    () =>
      [...selectedSkuQtyTiers]
        .sort((a, b) => a.minQty - b.minQty)
        .map((tier) =>
          tV('qty_tier_ladder')
            .replace('{minQty}', String(tier.minQty))
            .replace('{percent}', String(tier.discountPercent)),
        )
        .join(' · '),
    [selectedSkuQtyTiers, tV],
  );

  const [activeImageIndex, setActiveImageIndex] = useState(0);

  // Refs for the "top" CTA areas — used to show/hide the sticky bottom bars
  // only when the inline CTAs are scrolled out of view.
  // The IntersectionObservers remain in this coordinator because their output
  // (mobileTopCtaHidden, desktopTopCtaHidden) is consumed by both the sticky bars
  // and passed as props — keeping observers here avoids prop-drilling refs back up.
  const { mobileCtaRef, desktopCtaRef, mobileTopCtaHidden, desktopTopCtaHidden } =
    useCtaVisibility();

  const { personalRequestState, handleRequestPersonalDeal } = useDealActions(deal.id, isGuest);

  const stockRemaining = deal.quantityTotal - deal.quantitySold;
  const savingsAmount = Math.round(deal.originalPrice - deal.discountedPrice);
  const stockFillPct =
    deal.quantityTotal > 0
      ? Math.max(0, Math.min(100, (deal.quantitySold / deal.quantityTotal) * 100))
      : 0;

  const galleryImages = useMemo((): DealGalleryImage[] => {
    const source =
      selectedSkuId && deal.images.bySkuId[selectedSkuId]?.length
        ? deal.images.bySkuId[selectedSkuId]
        : deal.images.dealMain;
    return source.map((img, index) => ({
      id: img.id,
      src: img.url,
      alt: formatImageAlt(deal.title, index, tImage('image_of_deal')),
    }));
  }, [selectedSkuId, deal.images, deal.title, tImage]);

  // Reset to first image whenever the gallery source changes (SKU switch).
  useEffect(() => {
    const timer = setTimeout(() => setActiveImageIndex(0), 0);
    return () => clearTimeout(timer);
  }, [galleryImages]);

  const navItems = useCustomerNavItems('');
  const openCheckout = useCheckoutModalStore((s) => s.open);

  /** Shared CTA props — same state threaded to all 3 DealCTA slots */
  const ctaProps = useDealCTAProps({
    deal,
    dealSlug,
    isSoldOut,
    isPaused,
    isGuest,
    hasVariants,
    selectedSkuId,
    isSelectedSkuOOS,
    notifySkuId,
    personalRequestState,
    variantData,
    setSelectedSkuId,
    setNotifySkuId,
    handleRequestPersonalDeal,
  });

  return (
    <DealAppShell
      deal={deal}
      dealSlug={dealSlug}
      siteUrl={siteUrl}
      isGuest={isGuest}
      isAdmin={isAdmin}
      isVendor={isVendor}
      userName={userName}
      navItems={navItems}
    >
      {/* ── Main content ─────────────────────────────────────────── */}
      <div className="pb-16">
        {/* ── Mobile layout ──────────────────────────────────────── */}
        <MobileDealContent
          deal={deal}
          dealSlug={dealSlug}
          galleryImages={galleryImages}
          activeImageIndex={activeImageIndex}
          onChangeImageIndex={setActiveImageIndex}
          isSoldOut={isSoldOut}
          isGuest={isGuest}
          stockRemaining={stockRemaining}
          group={deal.group}
          selectedSkuQtyTiers={selectedSkuQtyTiers}
          qtyLadderText={qtyLadderText}
          ctaRef={mobileCtaRef}
          ctaProps={ctaProps}
          locale={locale}
          relatedSections={relatedSections}
        />

        {/* ── Desktop layout ─────────────────────────────────────── */}
        <DesktopDealLayout
          deal={deal}
          dealSlug={dealSlug}
          siteUrl={siteUrl}
          galleryImages={galleryImages}
          activeImageIndex={activeImageIndex}
          onChangeImageIndex={setActiveImageIndex}
          savingsAmount={savingsAmount}
          stockRemaining={stockRemaining}
          stockFillPct={stockFillPct}
          isSoldOut={isSoldOut}
          group={deal.group}
          selectedSkuQtyTiers={selectedSkuQtyTiers}
          qtyLadderText={qtyLadderText}
          ctaRef={desktopCtaRef}
          ctaProps={ctaProps}
          relatedSections={relatedSections}
        />
      </div>

      <StickyCTABars
        isSoldOut={isSoldOut || isPaused}
        mobileTopCtaHidden={mobileTopCtaHidden}
        ctaProps={ctaProps}
        deal={deal}
        selectedSkuId={selectedSkuId}
        desktopTopCtaHidden={desktopTopCtaHidden}
        stockRemaining={stockRemaining}
        group={deal.group}
        onBuy={() => openCheckout({ dealId: deal.id, skuId: selectedSkuId })}
      />
    </DealAppShell>
  );
}

export function visitedDealHref(dealSlug: string, locale: Locale): string {
  return localizeCommandPath(`/deals/${dealSlug}`, locale);
}

/**
 * DealDetail - exported island component.
 * Self-wraps with HydratedIsland so that QueryProgress (inside AppShell) always
 * has a QueryClientProvider in scope — even during Astro SSR slot serialization
 * with client:only="react", where outer wrapper context is not available.
 */
export function DealDetail({
  deal,
  dealSlug,
  locale,
  siteUrl,
  isGuest,
  isAdmin,
  isVendor,
  userName,
  csrfToken,
  buyerName,
  buyerEmail,
  variantData,
  relatedSections,
}: DealDetailProps) {
  return (
    <HydratedIsland locale={locale}>
      <WishlistProvider isAuthenticated={!isGuest}>
        <DealDetailInner
          deal={deal}
          dealSlug={dealSlug}
          siteUrl={siteUrl}
          isGuest={isGuest}
          isAdmin={isAdmin}
          isVendor={isVendor}
          userName={userName}
          csrfToken={csrfToken}
          buyerName={buyerName}
          buyerEmail={buyerEmail}
          variantData={variantData}
          relatedSections={relatedSections}
        />
      </WishlistProvider>
    </HydratedIsland>
  );
}
