import prisma from "@/lib/prisma";
import { cache, Suspense } from "react";
import { notFound } from "next/navigation";
import Link from "next/link";
import {
  avatarUrl,
  photoUrl,
  getUserAvatarMedia,
  getUserCoverMedia,
  getUserImagesMedia,
  mediaRecordUrl,
  mediaRecordUrlCandidates,
  AVATAR_CONVERSIONS,
  COVER_CONVERSIONS,
  GALLERY_CONVERSIONS,
} from "@/lib/media";
import MediaImage from "@/components/shared/media-image";
import type { Metadata } from "next";

// Both generateMetadata() and the page body need user data; without cache()
// each call hits Postgres independently. Returning the SUPERSET dedups them
// to a single query per request.
const getProfile = cache(async (idAw: string) => {
  const user = await prisma.user.findFirst({
    where: { id_aw: idAw },
    include: {
      country: true,
      city: true,
      // Cap photos and videos: an escort with 500 photos previously dragged
      // the whole array through the SSR + serialization. UI only needs the
      // first ~24; "Load more" can lazy-fetch beyond that if it's added.
      photos: { where: { private: false }, orderBy: { sort_order: "asc" }, take: 24 },
      galleries: { orderBy: { created_at: "desc" }, select: { id: true, name: true, credits: true } },
      videos: { where: { private: false }, orderBy: { created_at: "desc" }, take: 12 },
      // include: { rate: true } pulled every column on rates; only `name` is used.
      rateUsers: { select: { id: true, in_call: true, out_call: true, rate: { select: { name: true } } } },
      enjoyUsers: { select: { id: true, enjoy: { select: { name: true } } } },
      reviews: { orderBy: { created_at: "desc" }, take: 10 },
      characteristic: {
        include: {
          gender: true,
          ethnicity: true,
          eye_color: true,
          hair_color: true,
          hair_length: true,
          nationality: true,
          orientation: true,
          age: true,
          height: true,
          weight: true,
        },
      },
      languageLinks: true,
    },
  });
  if (!user) return user;
  const [avatarMedia, coverMedia, imageMedia] = await Promise.all([
    getUserAvatarMedia(user.id),
    getUserCoverMedia(user.id),
    getUserImagesMedia(user.id),
  ]);
  return { ...user, avatarMedia, coverMedia, imageMedia };
});

function resolveAvatarSrc(user: {
  avatarMedia: Awaited<ReturnType<typeof getUserAvatarMedia>>;
  profile_photo: string | null;
  id_aw: string | null;
}): string | null {
  if (user.avatarMedia) return mediaRecordUrl(user.avatarMedia, AVATAR_CONVERSIONS);
  if (user.profile_photo) return avatarUrl(user.profile_photo, user.id_aw);
  return null;
}

function resolveBannerSrc(user: {
  coverMedia: Awaited<ReturnType<typeof getUserCoverMedia>>;
  avatarMedia: Awaited<ReturnType<typeof getUserAvatarMedia>>;
  profile_photo: string | null;
  id_aw: string | null;
  photos?: { photo: string | null }[];
}): string | null {
  if (user.coverMedia) return mediaRecordUrl(user.coverMedia, COVER_CONVERSIONS);
  const avatarSrc = resolveAvatarSrc(user);
  if (avatarSrc) return avatarSrc;
  const firstPhoto = user.photos?.[0]?.photo;
  return firstPhoto ? photoUrl(firstPhoto, user.id_aw) : null;
}

/** Candidate URLs for <MediaImage> — conversion first, then original, then legacy fallback. */
function resolveAvatarSrcs(user: {
  avatarMedia: Awaited<ReturnType<typeof getUserAvatarMedia>>;
  profile_photo: string | null;
  id_aw: string | null;
}): string[] {
  if (user.avatarMedia) return mediaRecordUrlCandidates(user.avatarMedia, AVATAR_CONVERSIONS);
  if (user.profile_photo) return [avatarUrl(user.profile_photo, user.id_aw)];
  return [];
}

function resolveBannerSrcs(user: {
  coverMedia: Awaited<ReturnType<typeof getUserCoverMedia>>;
  avatarMedia: Awaited<ReturnType<typeof getUserAvatarMedia>>;
  profile_photo: string | null;
  id_aw: string | null;
  photos?: { photo: string | null }[];
}): string[] {
  if (user.coverMedia) return mediaRecordUrlCandidates(user.coverMedia, COVER_CONVERSIONS);
  const avatarSrcs = resolveAvatarSrcs(user);
  if (avatarSrcs.length > 0) return avatarSrcs;
  const firstPhoto = user.photos?.[0]?.photo;
  return firstPhoto ? [photoUrl(firstPhoto, user.id_aw)] : [];
}
import ProfileActions from "./ProfileActions";
import SimilarEscorts from "./SimilarEscorts";
import ReviewForm from "./ReviewForm";
import ViewTracker from "./ViewTracker";
import TourDisplay from "./TourDisplay";
import TipButton from "@/components/shared/tip-button";
import RateCalculator from "@/components/shared/rate-calculator";
import ProfileBadges from "@/components/shared/profile-badges";
import AvailabilityCalendar from "@/components/shared/availability-calendar";
import AvailabilityPrediction from "@/components/shared/availability-prediction";
import {
  StoryViewerLazy,
  ReviewAiSummaryLazy,
  QRCodeShareLazy,
  ShareDropdownLazy,
} from "./lazy-below-fold";
import RequestContentButton from "@/components/shared/request-content-button";
import BookingButton from "@/components/shared/booking-button";
import LetsChatButton from "@/components/shared/lets-chat-button";
import MicroTip from "@/components/shared/micro-tip";
import FindSimilarButton from "@/components/shared/find-similar-button";
import PricingChart from "@/components/shared/pricing-chart";
import AvailabilityHeatmap from "@/components/shared/availability-heatmap";
import HappyHourBadge from "@/components/shared/happy-hour-badge";
import ReportButton from "@/components/shared/report-button";
import LiveCamRequestButton from "@/components/shared/livecam-request-button";
import BackToResults from "@/components/shared/back-to-results";
import CopyProfileLink from "@/components/shared/copy-profile-link";
import LastSeen from "@/components/shared/last-seen";

export async function generateMetadata({
  params,
}: {
  params: Promise<{ idAw: string }>;
}): Promise<Metadata> {
  const { idAw } = await params;
  const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://www.adultworld.ai";
  const user = await getProfile(idAw);
  if (!user) return {};
  const city = user.city?.name;
  const country = user.country?.name;
  const location = [city, country].filter(Boolean).join(", ");
  const services = user.enjoyUsers
    ?.map((eu) => eu.enjoy?.name)
    .filter(Boolean)
    .slice(0, 5)
    .join(", ");
  const descParts = [`${user.username} is an escort`];
  if (location) descParts[0] += ` in ${location}`;
  if (services) descParts.push(`Services: ${services}.`);
  descParts.push("View photos, rates, reviews and availability on AdultWorld.");
  const description = descParts.join(". ");
  const titleLocation = city && country ? `${city}, ${country}` : location;
  const profileUrl = `${baseUrl}/view/${user.id_aw}`;
  const avatarSrc = resolveAvatarSrc(user);
  const ogImage = avatarSrc ? [{ url: avatarSrc }] : [];
  return {
    title: `${user.username}${titleLocation ? ` - Escort in ${titleLocation}` : ""} | AdultWorld`,
    description,
    alternates: {
      canonical: profileUrl,
      languages: {
        en: profileUrl,
        es: profileUrl,
        de: profileUrl,
        fr: profileUrl,
        "x-default": profileUrl,
      },
    },
    openGraph: {
      title: `${user.username} - Escort in ${titleLocation || "AdultWorld"} | AdultWorld`,
      description,
      url: profileUrl,
      images: ogImage,
    },
    twitter: {
      card: "summary_large_image",
      title: `${user.username}${titleLocation ? ` - Escort in ${titleLocation}` : ""} | AdultWorld`,
      description,
      images: ogImage.map((i) => i.url),
    },
  };
}

export default async function ProfileViewPage({
  params,
}: {
  params: Promise<{ idAw: string }>;
}) {
  const { idAw } = await params;
  const user = await getProfile(idAw);

  if (!user) notFound();

  const avatarSrcs = resolveAvatarSrcs(user);
  const bannerSrcs = resolveBannerSrcs(user);
  const mediaGalleryImages = user.imageMedia.map((media) => ({
    key: `media-${media.id}`,
    srcs: mediaRecordUrlCandidates(media, GALLERY_CONVERSIONS),
  }));
  const knownMediaUrls = new Set(mediaGalleryImages.flatMap((image) => image.srcs));
  const directGalleryImages = user.photos
    .filter((photo) => {
      const path = photo.photo?.trim() ?? "";
      return (
        path.startsWith("http://") ||
        path.startsWith("https://") ||
        path.startsWith("/uploads/")
      );
    })
    .map((photo) => ({
      key: `photo-${photo.id}`,
      srcs: [photoUrl(photo.photo, user.id_aw)],
    }))
    .filter((image) => !knownMediaUrls.has(image.srcs[0]));
  const galleryImages = [...mediaGalleryImages, ...directGalleryImages];

  // D.7: gate the review form to viewers who have a completed booking with
  // this escort. The /api/reviews POST already enforces this (Round 7), but
  // the form was rendered for everyone — so non-eligible viewers filled it
  // out and got a 403 only on submit. Now we hide the form entirely until a
  // completed booking exists, and show a "Book to leave a review" placeholder.
  let canReview = false;
  try {
    const { auth } = await import("@/lib/auth");
    const session = await auth();
    const viewerId = session?.user?.id ? Number(session.user.id) : null;
    if (viewerId && viewerId !== user.id) {
      const completed = await prisma.$queryRawUnsafe<{ id: number }[]>(
        `SELECT id FROM booking_requests
         WHERE client_id = $1 AND escort_id = $2 AND status = 'completed'
         LIMIT 1`,
        viewerId,
        user.id,
      );
      canReview = completed.length > 0;
    }
  } catch (err) {
    console.error("review-gate check failed:", err);
  }

  // Find first video with a file for hero intro
  const heroVideo = user.videos?.find((v) => v.video);

  // Compute unique review authors before the parallel block (needed for one of the queries)
  const reviewsRaw = user.reviews ?? [];
  const uniqueAuthors = [...new Set(reviewsRaw.map((r) => r.author).filter(Boolean))];

  // Fan out 7 supplementary queries in parallel — each gets its own .catch so
  // a single failure doesn't drop the page. Was previously sequential awaits.
  const [
    tours,
    availabilities,
    viewsToday,
    interviewAnswers,
    authorReviewCounts,
    messageCost,
    contactMethods,
  ] = await Promise.all([
    prisma
      .$queryRawUnsafe<
        { id: number; city: string; start_date: string; end_date: string; notes: string | null }[]
      >(
        `SELECT id, city, start_date, end_date, notes
         FROM tours
         WHERE user_id = $1 AND end_date >= CURRENT_DATE
         ORDER BY start_date ASC`,
        user.id
      )
      .catch(
        () => [] as { id: number; city: string; start_date: string; end_date: string; notes: string | null }[]
      ),

    prisma
      .$queryRawUnsafe<
        { day: string; start_at: string; end_at: string; is_all_day: boolean }[]
      >(
        `SELECT day, start_at, end_at, is_all_day
         FROM availabilities
         WHERE user_id = $1
         ORDER BY CASE day
           WHEN 'Monday' THEN 1 WHEN 'Tuesday' THEN 2 WHEN 'Wednesday' THEN 3
           WHEN 'Thursday' THEN 4 WHEN 'Friday' THEN 5 WHEN 'Saturday' THEN 6
           WHEN 'Sunday' THEN 7 END`,
        user.id
      )
      .catch(() => [] as { day: string; start_at: string; end_at: string; is_all_day: boolean }[]),

    prisma
      .$queryRawUnsafe<{ count: bigint }[]>(
        `SELECT COUNT(DISTINCT user_id) as count FROM recently_viewed
         WHERE viewed_user_id = $1 AND created_at >= CURRENT_DATE`,
        user.id
      )
      .then((rows) => Number(rows[0]?.count ?? 0))
      .catch(() => 0),

    prisma
      .$queryRawUnsafe<{ question: string; answer: string }[]>(
        `SELECT question, answer FROM interview_answers
         WHERE user_id = $1 AND answer != '' ORDER BY sort_order ASC, id ASC`,
        user.id
      )
      .catch(() => [] as { question: string; answer: string }[]),

    uniqueAuthors.length > 0
      ? prisma
          .$queryRawUnsafe<{ author: string; cnt: bigint }[]>(
            `SELECT author, COUNT(*) as cnt FROM reviews WHERE author = ANY($1) GROUP BY author`,
            uniqueAuthors
          )
          .then((rows) =>
            Object.fromEntries(rows.map((r) => [r.author, Number(r.cnt)] as const))
          )
          .catch(() => ({} as Record<string, number>))
      : Promise.resolve({} as Record<string, number>),

    prisma
      .$queryRawUnsafe<{ per_message_price: number; enabled: boolean }[]>(
        `SELECT per_message_price, enabled FROM messaging_settings WHERE user_id = $1 AND enabled = true LIMIT 1`,
        user.id
      )
      .then((rows) => (rows.length > 0 ? Number(rows[0].per_message_price) : null))
      .catch(() => null as number | null),

    prisma
      .$queryRawUnsafe<{ number: string; app_name: string | null }[]>(
        `SELECT p.number, ca.name as app_name
         FROM phones p
         LEFT JOIN contact_apps ca ON ca.id = p.contact_app_id
         WHERE p.user_id = $1 AND p.number IS NOT NULL AND p.number != ''`,
        user.id
      )
      .then((rows) =>
        rows.map((r) => ({ app: r.app_name ?? "Phone", number: r.number }))
      )
      .catch(() => [] as { app: string; number: string }[]),
  ]);

  const char = user.characteristic;
  const reviews = reviewsRaw;

  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "Person",
    name: user.username,
    image: avatarSrcs[0] ?? undefined,
    url: `${process.env.NEXT_PUBLIC_APP_URL || "https://www.adultworld.ai"}/view/${user.id_aw}`,
    description: user.status?.slice(0, 500) || undefined,
    address: {
      "@type": "PostalAddress",
      addressLocality: user.city?.name,
      addressCountry: user.country?.name,
    },
    knowsAbout: user.enjoyUsers
      ?.map((eu) => eu.enjoy?.name)
      .filter((s): s is string => !!s)
      .slice(0, 20),
    knowsLanguage: user.languageLinks?.map((l) => l.name).filter(Boolean),
    ...(reviews.length > 0
      ? {
          aggregateRating: {
            "@type": "AggregateRating",
            ratingValue: (
              reviews.reduce((sum, r) => sum + (r.stars || 0), 0) /
              reviews.length
            ).toFixed(1),
            reviewCount: reviews.length,
            bestRating: 5,
            worstRating: 1,
          },
          // Up to 5 individual Review items so AI crawlers can quote real
          // testimonials instead of just the aggregate.
          review: reviews.slice(0, 5).map((r) => ({
            "@type": "Review",
            author: { "@type": "Person", name: r.author || "Anonymous" },
            reviewRating: {
              "@type": "Rating",
              ratingValue: r.stars ?? 5,
              bestRating: 5,
              worstRating: 1,
            },
            reviewBody: r.review_text || "",
            datePublished: r.created_at?.toISOString(),
          })),
        }
      : {}),
  };

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <ViewTracker viewedUserId={user.id} />
    <div className="max-w-5xl mx-auto space-y-6">
      {/* Back to results */}
      <BackToResults />
      {/* Video Hero Intro */}
      {heroVideo && (
        <div className="bg-surface rounded-lg overflow-hidden">
          <video
            src={heroVideo.video?.startsWith("http") ? heroVideo.video : `${process.env.NEXT_PUBLIC_MEDIA_URL || "https://www.adultworld.ai"}/storage/${heroVideo.video}`}
            controls
            autoPlay
            muted
            playsInline
            className="w-full rounded-lg max-h-[400px] object-cover"
          />
        </div>
      )}

      {/* Profile Badges */}
      <ProfileBadges userId={user.id} lastonlineAt={user.lastonline_at} hits={user.hits ?? 0} createdAt={user.created_at} />
      <AvailabilityPrediction lastOnlineAt={user.lastonline_at} />
      <HappyHourBadge userId={user.id} />

      {/* Header */}
      <div className="bg-surface rounded-lg overflow-hidden">
        {/* D.2: blurred cover-photo fallback. The site has no cover_photo
            schema field; use photos[0] (which already loads) as a tasteful
            blurred banner so the profile no longer opens with a flat grey
            rectangle. */}
        <div className="h-48 bg-surface-light relative overflow-hidden">
          <MediaImage
            srcs={bannerSrcs}
            alt=""
            fill
            priority
            sizes="100vw"
            className="object-cover blur-sm scale-105 brightness-50"
          />
        </div>

        <div className="p-6 flex flex-col md:flex-row gap-6 -mt-16 relative">
          {/* C.2: hero avatar is the profile-page LCP candidate. priority +
              explicit dimensions tell the browser to discover it early and
              reserves layout space. */}
          <div className="w-32 h-32 rounded-lg overflow-hidden border-4 border-surface bg-surface-light shrink-0 relative">
            <MediaImage
              srcs={avatarSrcs}
              alt={user.username ?? ""}
              width={128}
              height={128}
              priority
              className="w-full h-full object-cover"
              fallback={
                <div className="w-full h-full flex items-center justify-center text-text-muted">
                  <svg className="w-16 h-16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
                  </svg>
                </div>
              }
            />
          </div>

          <div className="flex-1 pt-16 md:pt-0">
            <div className="flex items-center gap-3 flex-wrap">
              <h1 className="text-3xl font-bold">{user.username}</h1>
              <LastSeen lastonlineAt={user.lastonline_at} />
              {reviews.length > 0 && (
                <a
                  href="#reviews"
                  className="inline-flex items-center gap-1 text-sm text-yellow-400 hover:text-yellow-300 transition-colors"
                >
                  <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
                    <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                  </svg>
                  {reviews.length} {reviews.length === 1 ? "review" : "reviews"}
                </a>
              )}
            </div>
            <p className="text-text-muted mt-1">
              {user.city?.slug && user.country?.slug ? (
                <Link
                  href={`/escorts/${user.country.slug}/${user.city.slug}`}
                  className="hover:text-primary transition-colors underline-offset-2 hover:underline"
                  title={`More escorts in ${user.city.name}`}
                >
                  {user.city.name}, {user.country.name}
                </Link>
              ) : (
                <>
                  {user.city?.name}
                  {user.city && user.country ? ", " : ""}
                  {user.country?.name}
                </>
              )}
            </p>
            {user.created_at && (
              <span className="text-xs text-text-muted">
                Member since {new Date(user.created_at).toLocaleDateString("en-US", { month: "long", year: "numeric" })}
              </span>
            )}
            <div className="flex items-center gap-3 flex-wrap mt-1">
              {(user.hits ?? 0) > 100 && (
                <span className="inline-flex items-center gap-1 text-xs font-semibold bg-primary/10 text-primary px-2.5 py-1 rounded-full">
                  <svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20">
                    <path d="M12.395 2.553a1 1 0 00-1.45-.385c-.345.23-.614.558-.822.88-.214.33-.403.713-.57 1.116-.334.804-.614 1.768-.84 2.734a31.365 31.365 0 00-.613 3.58 2.64 2.64 0 01-.945-1.067c-.328-.68-.398-1.534-.398-2.654A1 1 0 005.05 6.05 6.981 6.981 0 003 11a7 7 0 1011.95-4.95c-.592-.591-.98-.985-1.348-1.467-.363-.476-.724-1.063-1.207-2.03zM12.12 15.12A3 3 0 017 13s.879.5 2.5.5c0-1 .5-2 1-3 .5 1.5 1 2 2 3a3 3 0 01-.38 1.62z" />
                  </svg>
                  Popular Profile
                </span>
              )}
              {viewsToday > 0 && (
                <span className="text-text-muted text-sm flex items-center gap-1.5">
                  <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
                  </svg>
                  {viewsToday} {viewsToday === 1 ? "person" : "people"} viewed this profile today
                </span>
              )}
            </div>
            {user.status && (
              <p className="text-text-muted mt-2 italic">{user.status}</p>
            )}
            <ProfileActions userId={user.id} idAw={user.id_aw ?? String(user.id)} />
            <div className="mt-3 flex items-center gap-3 flex-wrap">
              <LetsChatButton
                recipientId={user.id}
                recipientIdAw={user.id_aw ?? String(user.id)}
                messageCost={messageCost}
              />
              <BookingButton escortId={user.id} escortName={user.username ?? "this escort"} />
              <TipButton receiverId={user.id} receiverName={user.username ?? "this user"} />
              <RequestContentButton escortId={user.id} escortName={user.username ?? "this escort"} />
              <QRCodeShareLazy url={`${process.env.NEXT_PUBLIC_APP_URL || "https://www.adultworld.ai"}/view/${user.id_aw}`} />
              <CopyProfileLink idAw={user.id_aw ?? String(user.id)} />
              <ShareDropdownLazy
                url={`${process.env.NEXT_PUBLIC_APP_URL || "https://www.adultworld.ai"}/view/${user.id_aw}`}
                title={`${user.username} - Escort on AdultWorld`}
              />
              <LiveCamRequestButton escortId={user.id} escortName={user.username ?? "this escort"} />
              <ReportButton reportedUserId={user.id} />
            </div>
          </div>
        </div>
      </div>

      {/* Quick Contact Methods */}
      {contactMethods.length > 0 && (
        <div className="bg-surface rounded-lg p-6">
          <h2 className="text-lg font-semibold mb-4">Contact Methods</h2>
          <div className="flex flex-wrap gap-3">
            {contactMethods.map((cm, i) => {
              const appLower = cm.app.toLowerCase();
              let icon: string;
              let colorClass: string;
              let href: string | null = null;

              if (appLower.includes("whatsapp")) {
                icon = "M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347z";
                colorClass = "bg-green-500/10 text-green-400 hover:bg-green-500/20";
                href = `https://wa.me/${cm.number.replace(/\D/g, "")}`;
              } else if (appLower.includes("telegram")) {
                icon = "M11.944 0A12 12 0 000 12a12 12 0 0012 12 12 12 0 0012-12A12 12 0 0012 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 01.171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.479.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z";
                colorClass = "bg-blue-400/10 text-blue-400 hover:bg-blue-400/20";
                href = `https://t.me/${cm.number}`;
              } else if (appLower.includes("signal")) {
                icon = "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z";
                colorClass = "bg-blue-500/10 text-blue-500 hover:bg-blue-500/20";
                // Signal deeplink — falls back to tel: if Signal isn't installed.
                href = `sgnl://signal.me/#p/${cm.number.replace(/\D/g, "")}`;
              } else {
                icon = "M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z";
                colorClass = "bg-zinc-500/10 text-zinc-400 hover:bg-zinc-500/20";
                href = `tel:${cm.number}`;
              }

              const content = (
                <span className={`inline-flex items-center gap-2 px-4 py-2.5 rounded-lg transition-colors ${colorClass}`}>
                  <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
                    <path d={icon} />
                  </svg>
                  <span className="text-sm font-medium">{cm.app}</span>
                </span>
              );

              return href ? (
                <a key={i} href={href} target="_blank" rel="noopener noreferrer">
                  {content}
                </a>
              ) : (
                <span key={i}>{content}</span>
              );
            })}
          </div>
        </div>
      )}

      {/* Characteristics */}
      {char && (
        <div className="bg-surface rounded-lg p-6">
          <h2 className="text-lg font-semibold mb-4">Characteristics</h2>
          <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
            {char.gender && (
              <div>
                <p className="text-text-muted text-sm">Gender</p>
                <p>{char.gender.name}</p>
              </div>
            )}
            {char.age && (
              <div>
                <p className="text-text-muted text-sm">Age</p>
                <p>{char.age.name}</p>
              </div>
            )}
            {char.ethnicity && (
              <div>
                <p className="text-text-muted text-sm">Ethnicity</p>
                <p>{char.ethnicity.name}</p>
              </div>
            )}
            {char.nationality && (
              <div>
                <p className="text-text-muted text-sm">Nationality</p>
                <p>{char.nationality.name}</p>
              </div>
            )}
            {char.orientation && (
              <div>
                <p className="text-text-muted text-sm">Orientation</p>
                <p>{char.orientation.name}</p>
              </div>
            )}
            {char.eye_color && (
              <div>
                <p className="text-text-muted text-sm">Eye Color</p>
                <p>{char.eye_color.name}</p>
              </div>
            )}
            {char.hair_color && (
              <div>
                <p className="text-text-muted text-sm">Hair Color</p>
                <p>{char.hair_color.name}</p>
              </div>
            )}
            {char.hair_length && (
              <div>
                <p className="text-text-muted text-sm">Hair Length</p>
                <p>{char.hair_length.name}</p>
              </div>
            )}
            {char.height && (
              <div>
                <p className="text-text-muted text-sm">Height</p>
                <p>{char.height.name}</p>
              </div>
            )}
            {char.weight && (
              <div>
                <p className="text-text-muted text-sm">Weight</p>
                <p>{char.weight.name}</p>
              </div>
            )}
          </div>

          {/* Languages */}
          {user.languageLinks && user.languageLinks.length > 0 && (
            <div className="mt-4">
              <p className="text-text-muted text-sm mb-1">Languages</p>
              <div className="flex flex-wrap gap-2">
                {user.languageLinks.map((lang) => (
                  <span
                    key={lang.id}
                    className="bg-surface-light px-2 py-1 rounded text-sm"
                  >
                    {lang.name}
                  </span>
                ))}
              </div>
            </div>
          )}
        </div>
      )}

      {/* Get to Know Me — Interview Q&A */}
      {interviewAnswers.length > 0 && (
        <div className="bg-surface rounded-lg p-6">
          <h2 className="text-lg font-semibold mb-4 flex items-center gap-2">
            <svg className="w-5 h-5 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
            </svg>
            Get to Know Me
          </h2>
          <div className="space-y-4">
            {interviewAnswers.map((qa, idx) => (
              <div key={idx}>
                <p className="text-sm font-medium text-primary mb-1">{qa.question}</p>
                <p className="text-text-muted text-sm leading-relaxed">{qa.answer}</p>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* R15 C.2: Suspense streams the hero/photos shell first; below-fold
          panels resolve on their own RSC walk and pop in as they finish. */}
      <Suspense fallback={<div className="bg-surface rounded-lg h-32 animate-pulse" />}>
        <StoryViewerLazy userId={user.id} username={user.username ?? "Anonymous"} />
      </Suspense>

      <Suspense fallback={null}>
        <TourDisplay tours={tours} />
      </Suspense>

      {/* Photos */}
      {galleryImages.length > 0 && (
        <div className="bg-surface rounded-lg p-6">
          <h2 className="text-lg font-semibold mb-4">Photos</h2>
          <div className="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-2">
            {galleryImages.map((photo, idx) => (
              <div
                key={photo.key}
                className="relative aspect-square rounded-lg overflow-hidden bg-surface-light"
              >
                <MediaImage
                  srcs={photo.srcs}
                  alt=""
                  fill
                  sizes="(max-width:768px) 33vw, (max-width:1024px) 25vw, 16vw"
                  loading={idx < 6 ? "eager" : "lazy"}
                  className="object-cover"
                  fallback={
                    <div className="flex h-full items-center justify-center text-xs text-text-muted">
                      Photo unavailable
                    </div>
                  }
                />
              </div>
            ))}
          </div>
          <div className="mt-3 flex items-center gap-2">
            <span className="text-text-muted text-sm">React:</span>
            <MicroTip receiverId={user.id} />
            <FindSimilarButton userId={user.id} />
          </div>
        </div>
      )}

      {/* Galleries */}
      {user.galleries && user.galleries.length > 0 && (
        <div className="bg-surface rounded-lg p-6">
          <h2 className="text-lg font-semibold mb-4">Galleries</h2>
          <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
            {user.galleries.map((gallery) => (
              <Link
                key={gallery.id}
                href={`/gallery/show/${gallery.id}`}
                className="bg-surface-light rounded-lg p-4 hover:ring-1 hover:ring-primary transition-all"
              >
                <p className="font-medium">{gallery.name}</p>
                <p className="text-text-muted text-sm mt-1">
                  {gallery.credits ? `${gallery.credits} credits` : "Free"}
                </p>
              </Link>
            ))}
          </div>
        </div>
      )}

      {/* Videos */}
      {user.videos && user.videos.length > 0 && (
        <div className="bg-surface rounded-lg p-6">
          <h2 className="text-lg font-semibold mb-4">Videos</h2>
          <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4">
            {user.videos.map((video) => (
              <div
                key={video.id}
                className="bg-surface-light rounded-lg p-4"
              >
                <div className="aspect-video bg-background rounded mb-2 flex items-center justify-center">
                  <svg className="w-12 h-12 text-text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                  </svg>
                </div>
                <p className="font-medium truncate">{video.name}</p>
                <p className="text-text-muted text-sm">
                  {video.credits ? `${video.credits} credits` : "Free"}
                </p>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Rates */}
      {user.rateUsers && user.rateUsers.length > 0 && (
        <div className="bg-surface rounded-lg p-6">
          <h2 className="text-lg font-semibold mb-4">Rates</h2>
          <div className="overflow-x-auto">
            <table className="w-full text-left">
              <thead>
                <tr className="border-b border-surface-light">
                  <th className="pb-2 text-text-muted font-medium">Duration</th>
                  <th className="pb-2 text-text-muted font-medium">Incall</th>
                  <th className="pb-2 text-text-muted font-medium">Outcall</th>
                </tr>
              </thead>
              <tbody>
                {user.rateUsers.map((ru) => (
                  <tr
                    key={ru.id}
                    className="border-b border-surface-light last:border-0"
                  >
                    <td className="py-2">{ru.rate?.name ?? "N/A"}</td>
                    <td className="py-2">
                      {ru.in_call ? ru.in_call : "-"}
                    </td>
                    <td className="py-2">
                      {ru.out_call ? ru.out_call : "-"}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {/* Rate Calculator */}
      {user.rateUsers && user.rateUsers.length > 0 && (
        <RateCalculator
          rates={user.rateUsers.map((ru) => ({
            name: ru.rate?.name ?? "N/A",
            in_call: ru.in_call,
            out_call: ru.out_call,
          }))}
        />
      )}

      {/* Pricing Visualization */}
      {user.rateUsers && user.rateUsers.length > 0 && (
        <PricingChart
          rates={user.rateUsers.map((ru) => ({
            name: ru.rate?.name ?? "N/A",
            in_call: ru.in_call,
            out_call: ru.out_call,
          }))}
        />
      )}

      {/* Services */}
      {user.enjoyUsers && user.enjoyUsers.length > 0 && (
        <div className="bg-surface rounded-lg p-6">
          <h2 className="text-lg font-semibold mb-4">Services</h2>
          <div className="flex flex-wrap gap-2">
            {user.enjoyUsers.map((eu) => (
              <span
                key={eu.id}
                className="bg-surface-light px-3 py-1 rounded-full text-sm"
              >
                {eu.enjoy?.name ?? ""}
              </span>
            ))}
          </div>
        </div>
      )}

      {/* Reviews */}
      {user.reviews && user.reviews.length > 0 && (
        <div id="reviews" className="bg-surface rounded-lg p-6 scroll-mt-20">
          <h2 className="text-lg font-semibold mb-4">
            Reviews ({user.reviews.length})
          </h2>
          <Suspense fallback={<div className="h-16 animate-pulse bg-surface-light rounded" />}>
            <ReviewAiSummaryLazy userId={user.id} reviewCount={user.reviews.length} />
          </Suspense>
          <div className="space-y-4">
            {user.reviews.map((review) => (
              <div
                key={review.id}
                className="border-b border-surface-light pb-4 last:border-0"
              >
                <div className="flex items-center justify-between mb-2">
                  <p className="font-medium">
                    {review.author ?? "Anonymous"}
                    {review.author && authorReviewCounts[review.author] && (
                      <span className="text-text-muted text-xs font-normal ml-2">
                        ({authorReviewCounts[review.author]} reviews written)
                      </span>
                    )}
                  </p>
                  <div className="flex items-center gap-1">
                    {[1, 2, 3, 4, 5].map((star) => (
                      <svg
                        key={star}
                        className={`w-4 h-4 ${
                          star <= (review.stars ?? 0)
                            ? "text-yellow-400"
                            : "text-surface-light"
                        }`}
                        fill="currentColor"
                        viewBox="0 0 20 20"
                      >
                        <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                      </svg>
                    ))}
                  </div>
                </div>
                <p className="text-text-muted">{review.review_text}</p>
                <p className="text-text-muted text-sm mt-1">
                  {new Date(review.created_at).toLocaleDateString()}
                </p>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Review Form — D.7: only shown when the viewer has a completed
          booking with this escort. Non-eligible viewers see a friendly
          placeholder rather than a form that 403s on submit. */}
      {canReview ? (
        <ReviewForm targetUserId={user.id} />
      ) : (
        <div className="bg-surface rounded-lg p-6 text-center text-text-muted text-sm">
          Reviews are open to clients with a completed booking. Book a session
          with {user.username} to share your experience.
        </div>
      )}

      {/* Availability Calendar */}
      {availabilities.length > 0 && (
        <AvailabilityCalendar availabilities={availabilities} />
      )}

      {/* Availability Heatmap — historical online patterns */}
      <Suspense fallback={<div className="bg-surface rounded-lg h-48 animate-pulse" />}>
        <AvailabilityHeatmap userId={user.id} />
      </Suspense>

      {/* Similar Escorts */}
      <Suspense fallback={<div className="bg-surface rounded-lg h-64 animate-pulse" />}>
        <SimilarEscorts
          userId={user.id}
          cityId={user.city_id}
          countryId={user.country_id}
        />
      </Suspense>
    </div>
    </>
  );
}
