/* ============================================================
   Paella — Restaurants list + Restaurant detail
   ============================================================ */

const CUISINE_EMOJI = {
  "Ресторан": "🍽️", "Бар": "🍷", "Суши": "🍣", "Пицца": "🍕",
  "Кофе": "☕", "Кафе": "🥐", "Десерты": "🍰", "Завтраки": "🍳", "Бизнес-ланч": "🍱",
};
const CUISINE_GRAD = {
  "Ресторан": ["oklch(0.7 0.12 50)","oklch(0.55 0.12 35)"],
  "Бар": ["oklch(0.55 0.15 20)","oklch(0.42 0.13 15)"],
  "Суши": ["oklch(0.72 0.11 200)","oklch(0.6 0.12 220)"],
  "Пицца": ["oklch(0.74 0.15 50)","oklch(0.64 0.16 30)"],
};

/* ============================================================
   Скоринг — Paella Score 2.0, единая шкала 0–100.
   Модель, формула смешивания и цвет живут в paella-data.js:
   window.paellaAI (прогноз с аспектами), window.paellaScore
   (единый балл + источник + ранг), window.scoreColor.
   Здесь — только рендер.
   ============================================================ */

/* единая оценка блюда (обёртка над window.paellaScore для сортировок) */
function dishScore(d, rest) {
  const u = paellaScore(d, rest);
  return { q: u.q, kind: u.kind, ai: u.ai, u };
}
/* топ-N «хитов» заведения (с фото в приоритете, по реальной оценке) */
function topDishes(rest, n) {
  return (rest.menu || []).map(d => ({ d, ...dishScore(d, rest) })).sort((a, b) => b.q - a.q).slice(0, n || 3);
}

/* ---------- Dish photo or "нет фото" placeholder (логотип Paella) ---------- */
function DishPhoto({ src, alt, size }) {
  const [err, setErr] = useState(false);
  if (src && !err) {
    return <img src={src} alt={alt || ""} loading="lazy" onError={() => setErr(true)}
      style={{ width: "100%", height: "100%", objectFit: "cover", position: "absolute", inset: 0 }} />;
  }
  const big = size === "lg";
  return (
    <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: big ? 8 : 5, background: "var(--bg-2)" }}>
      <img src="assets/logo-mark.png" alt="" style={{ width: big ? 60 : 38, height: big ? 60 : 38, opacity: .45 }} />
      <span style={{ fontSize: big ? 13 : 10.5, color: "var(--ink-3)", fontWeight: 700, letterSpacing: ".02em" }}>нет фото</span>
    </div>
  );
}

/* ---------- Lightbox: полноэкранный просмотр фото с зумом и стрелками ---------- */
function Lightbox({ photos, index, onClose, onNav, caption }) {
  const [zoom, setZoom] = useState(false);
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); else if (e.key === "ArrowRight") onNav(1); else if (e.key === "ArrowLeft") onNav(-1); };
    window.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = ""; };
  }, []);
  useEffect(() => { setZoom(false); }, [index]);
  return (
    <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 320, background: "rgba(10,8,6,.94)", display: "flex", alignItems: "center", justifyContent: "center", padding: "3vh 2vw" }}>
      <button onClick={onClose} aria-label="Закрыть" style={{ position: "absolute", top: 18, right: 18, zIndex: 4, width: 42, height: 42, borderRadius: 999, background: "rgba(255,255,255,.14)", color: "#fff", display: "grid", placeItems: "center" }}><Icon name="x" size={20} /></button>
      {photos.length > 1 && <button onClick={(e) => { e.stopPropagation(); onNav(-1); }} aria-label="Назад" style={{ position: "absolute", left: 14, top: "50%", transform: "translateY(-50%)", zIndex: 4, width: 46, height: 46, borderRadius: 999, background: "rgba(255,255,255,.14)", color: "#fff", display: "grid", placeItems: "center" }}><Icon name="chevronLeft" size={26} /></button>}
      <img src={photos[index]} alt="" referrerPolicy="no-referrer" onClick={(e) => { e.stopPropagation(); setZoom(z => !z); }}
        style={{ maxWidth: zoom ? "none" : "92vw", maxHeight: zoom ? "none" : "86vh", width: zoom ? "auto" : undefined, height: zoom ? "130vh" : undefined, objectFit: "contain", borderRadius: 14, boxShadow: "0 20px 60px rgba(0,0,0,.5)", cursor: zoom ? "zoom-out" : "zoom-in", transition: "transform .25s ease", transform: zoom ? "scale(1)" : "scale(1)" }} />
      {photos.length > 1 && <button onClick={(e) => { e.stopPropagation(); onNav(1); }} aria-label="Вперёд" style={{ position: "absolute", right: 14, top: "50%", transform: "translateY(-50%)", zIndex: 4, width: 46, height: 46, borderRadius: 999, background: "rgba(255,255,255,.14)", color: "#fff", display: "grid", placeItems: "center" }}><Icon name="chevronRight" size={26} /></button>}
      <div style={{ position: "absolute", bottom: 18, left: 0, right: 0, textAlign: "center", color: "rgba(255,255,255,.85)", fontSize: 13, fontWeight: 600, pointerEvents: "none" }}>
        {caption ? caption + " · " : ""}{index + 1} / {photos.length} · нажмите на фото, чтобы приблизить
      </div>
    </div>
  );
}

/* ---------- Галерея заведения: авто-прокрутка влево + стрелки + лайтбокс ---------- */
function VenueGallery({ photos, name }) {
  const railRef = useRef(null);
  const pausedRef = useRef(false);
  const [lb, setLb] = useState(-1);
  const doubled = photos.length > 2 ? [...photos, ...photos] : photos;

  useEffect(() => {
    const el = railRef.current; if (!el || photos.length <= 2) return;
    let raf;
    const step = () => {
      if (!pausedRef.current && lb < 0) {
        el.scrollLeft += 0.4;
        const half = el.scrollWidth / 2;
        if (half > 0 && el.scrollLeft >= half) el.scrollLeft -= half;
      }
      raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [lb, photos.length]);

  const arrow = (dir) => {
    const el = railRef.current; if (!el) return;
    const amount = dir * 280;
    const start = el.scrollLeft, target = start + amount, t0 = performance.now(), dur = 360;
    const ease = (t) => 1 - Math.pow(1 - t, 3);
    const tick = (now) => { const p = Math.min(1, (now - t0) / dur); el.scrollLeft = start + (target - start) * ease(p); if (p < 1) requestAnimationFrame(tick); };
    requestAnimationFrame(tick);
  };

  return (
    <section className="sec" style={{ marginTop: 22 }}>
      <div className="row" style={{ marginBottom: 12, gap: 9, alignItems: "baseline" }}>
        <span className="eyebrow">Фото заведения</span>
        <span className="muted" style={{ fontSize: 12.5 }}>{photos.length} реальных фото · 2ГИС / Яндекс · нажмите, чтобы увеличить</span>
      </div>
      <div style={{ position: "relative" }}>
        {photos.length > 2 && <button className="vgal-arrow" onClick={() => arrow(-1)} aria-label="Назад" style={{ position: "absolute", left: 6, top: "50%", transform: "translateY(-50%)", zIndex: 3, width: 38, height: 38, borderRadius: 999, background: "var(--surface)", boxShadow: "var(--sh-md)", display: "grid", placeItems: "center" }}><Icon name="chevronLeft" size={20} /></button>}
        <div className="vgal-rail" ref={railRef}
          onMouseEnter={() => (pausedRef.current = true)} onMouseLeave={() => (pausedRef.current = false)}
          onTouchStart={() => (pausedRef.current = true)}
          style={{ display: "flex", gap: 10, overflowX: "auto", paddingBottom: 4, scrollbarWidth: "none" }}>
          {doubled.map((src, i) => (
            <div key={i} onClick={() => setLb(i % photos.length)} style={{ width: 230, height: 158, flex: "none", borderRadius: 14, overflow: "hidden", background: "var(--bg-2)", cursor: "zoom-in", position: "relative" }}>
              <img src={src} alt="" loading="lazy" referrerPolicy="no-referrer" style={{ width: "100%", height: "100%", objectFit: "cover" }} />
            </div>
          ))}
        </div>
        {photos.length > 2 && <button className="vgal-arrow" onClick={() => arrow(1)} aria-label="Вперёд" style={{ position: "absolute", right: 6, top: "50%", transform: "translateY(-50%)", zIndex: 3, width: 38, height: 38, borderRadius: 999, background: "var(--surface)", boxShadow: "var(--sh-md)", display: "grid", placeItems: "center" }}><Icon name="chevronRight" size={20} /></button>}
      </div>
      {lb >= 0 && <Lightbox photos={photos} index={lb} onClose={() => setLb(-1)} onNav={(d) => setLb((lb + d + photos.length) % photos.length)} caption={name} />}
    </section>
  );
}

/* ---------- Real (2ГИС) restaurant card ---------- */
function RealRestaurantCard({ rest, onOpen }) {
  const grad = CUISINE_GRAD[rest.cuisine] || CUISINE_GRAD["Ресторан"];
  const emoji = CUISINE_EMOJI[rest.cuisine] || "🍽️";
  const hasMenu = rest.menuCount > 0;
  const prices = hasMenu ? rest.menu.map((d) => d.price).filter((p) => p) : [];
  const minP = prices.length ? Math.min(...prices) : null;
  // хиты для сетки: топ-3 блюда С ФОТО (чтобы миниатюры были визуальными, без плейсхолдеров)
  const hits = useMemo(() => {
    if (!hasMenu) return [];
    return (rest.menu || []).filter(d => d.photo).map(d => ({ d, ...dishScore(d, rest) })).sort((a, b) => b.q - a.q).slice(0, 3);
  }, [rest.id]);
  return (
    <article className="rcard" style={{ cursor: "pointer" }} onClick={() => onOpen(rest)}>
      <div className="rcard-cover">
        <SmartImage src={rest.imageUrl} emoji={emoji} grad={grad} alt={rest.name} />
        {rest.rating != null && (
          <span className="score float"><Icon name="star" size={13} fill="currentColor" sw={0} style={{ color: "var(--gold)" }} />{Number(rest.rating).toFixed(1)}</span>
        )}
        <span className="badge b-olive" style={{ position: "absolute", left: 12, top: 12, zIndex: 3 }}>2ГИС · реальные данные</span>
        {hasMenu && (
          <span className="badge b-coral" style={{ position: "absolute", right: 12, bottom: 12, zIndex: 3 }}><Icon name="utensils" size={12} />{rest.menuCount} блюд · меню</span>
        )}
      </div>
      <div className="rcard-body">
        <div className="rcard-name">{rest.name}</div>
        <div className="rcard-meta">
          <span>{rest.cuisine}</span><span className="dot" style={{ width: 3, height: 3, borderRadius: 9, background: "var(--ink-3)" }}></span>
          <span>{rest.district}</span>
          {rest.is24x7 && (<><span className="dot" style={{ width: 3, height: 3, borderRadius: 9, background: "var(--ink-3)" }}></span><span>круглосуточно</span></>)}
        </div>
        <div className="rcard-meta" style={{ marginTop: 6 }}>
          <Icon name="pin" size={14} /><span style={{ minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{rest.address || "—"}</span>
          {rest.branchCount > 1 && (<><span className="dot" style={{ width: 3, height: 3, borderRadius: 9, background: "var(--ink-3)" }}></span><span style={{ fontWeight: 700, color: "var(--coral-ink)", flex: "none" }}>сеть · {rest.branchCount} точ.</span></>)}
        </div>
        {hits.length >= 2 ? (
          <div className="rcard-best" style={{ flexDirection: "column", alignItems: "stretch", gap: 8 }}>
            <div style={{ fontSize: 11, fontWeight: 700, color: "var(--coral-ink)", letterSpacing: ".02em", display: "flex", alignItems: "center", gap: 5 }}>🔥 ХИТЫ ЗАВЕДЕНИЯ</div>
            <div style={{ display: "flex", gap: 6 }}>
              {hits.map((it, i) => (
                <div key={i} style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ position: "relative", width: "100%", aspectRatio: "1/1", borderRadius: 9, overflow: "hidden", background: "var(--bg-2)" }}>
                    <DishPhoto src={it.d.photo} alt={it.d.name} />
                    <span style={{ position: "absolute", right: 3, bottom: 3, fontSize: 9.5, fontWeight: 800, padding: "1px 5px", borderRadius: 6, background: scoreColor(it.u.score), color: "#fff" }}>{it.u.approx ? "~" : ""}{it.u.score}</span>
                  </div>
                  <div style={{ fontSize: 10.5, fontWeight: 600, marginTop: 3, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", color: "var(--ink-2)" }}>{it.d.name}</div>
                </div>
              ))}
            </div>
          </div>
        ) : hasMenu ? (
          <div className="rcard-best" style={{ alignItems: "center" }}>
            <Icon name="utensils" size={16} style={{ color: "var(--coral-ink)", flex: "none" }} />
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: 13.5, fontWeight: 700 }}>Меню: {rest.menuCount} блюд{minP ? ` · от ${minP} ₽` : ""}</div>
              <div style={{ fontSize: 11.5, color: "var(--ink-3)" }}>Реальное меню с ценами · {rest.menuSource}</div>
            </div>
          </div>
        ) : (
          <div className="rcard-best" style={{ alignItems: "center" }}>
            <Icon name="message" size={16} style={{ color: "var(--ink-2)", flex: "none" }} />
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: 13.5, fontWeight: 700 }}>{rest.reviewCount.toLocaleString("ru-RU")} отзывов в 2ГИС</div>
              <div style={{ fontSize: 11.5, color: "var(--ink-3)" }}>Меню — на следующем этапе сбора данных</div>
            </div>
          </div>
        )}
        <div style={{ marginTop: 13, display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8 }}>
          <span className="sec-link" style={{ fontWeight: 700 }}>{hasMenu ? "Открыть ресторан и меню" : "Открыть ресторан"} <Icon name="arrowRight" size={15} /></span>
          {hasMenu && <span className="muted" style={{ fontSize: 12, fontWeight: 600, flex: "none" }}>{rest.menuCount} блюд</span>}
        </div>
      </div>
    </article>
  );
}

/* ---------- Real dish card (in restaurant menu) ---------- */
function RealDishCard({ dish, rest, onOpen }) {
  const u = paellaScore(dish, rest);
  return (
    <article className="dcard" onClick={() => onOpen(dish)}>
      <div className="dcard-media">
        <DishPhoto src={dish.photo} alt={dish.name} />
        <ScoreTag u={u} float />
      </div>
      <div className="dcard-body">
        <div className="dcard-name" style={{ fontSize: 15.5 }}>{dish.name}</div>
        {dish.grams || dish.kcal ? <div className="dcard-reason" style={{ flex: 1, color: "var(--ink-3)" }}>{[dish.grams && `${dish.grams} г`, dish.kcal && `${dish.kcal} ккал`].filter(Boolean).join(" · ")}</div> : (dish.desc ? <div className="dcard-reason" style={{ flex: 1 }}>{dish.desc}</div> : <div style={{ flex: 1 }} />)}
        <div className="dcard-foot">
          <span className="dcard-price">{dish.price ? dish.price : "—"} <span className="cur">₽</span></span>
          <span className="muted" style={{ fontSize: 12, fontWeight: 600, display: "flex", alignItems: "center", gap: 4 }}><Icon name={KIND_ICON[u.kind]} size={12} />{scoreCaption(u)}</span>
        </div>
      </div>
    </article>
  );
}

/* ---------- Compact dish row (для блюд без фото) ---------- */
function RealDishRow({ dish, rest, onOpen }) {
  const u = paellaScore(dish, rest);
  return (
    <div onClick={() => onOpen(dish)} style={{ display: "flex", alignItems: "center", gap: 12, padding: "11px 14px", borderBottom: "1px solid var(--hairline)", cursor: "pointer" }}>
      <div style={{ minWidth: 0, flex: 1 }}>
        <div style={{ fontSize: 14, fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{dish.name}</div>
        {dish.desc ? <div style={{ fontSize: 12, color: "var(--ink-3)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{dish.desc}</div> : null}
      </div>
      {dish.reviewMentions >= 3 && <span className="muted" style={{ fontSize: 11.5, fontWeight: 600, display: "flex", alignItems: "center", gap: 3, flex: "none" }}><Icon name="message" size={11} />{dish.reviewMentions}</span>}
      <span style={{ fontSize: 12.5, fontWeight: 700, color: scoreColor(u.score), flex: "none" }}>{u.approx ? "~" : ""}{u.score}</span>
      <span style={{ fontSize: 14.5, fontWeight: 800, flex: "none", whiteSpace: "nowrap" }}>{dish.price ? `${dish.price} ₽` : "—"}</span>
    </div>
  );
}

/* ---------- Галерея блюда: главное фото + ракурсы от гостей (листание) ---------- */
function DishGallery({ dish }) {
  const list = (dish.photos && dish.photos.length ? dish.photos : (dish.photo ? [dish.photo] : []));
  const [i, setI] = useState(0);
  useEffect(() => { setI(0); }, [dish && dish.name]);
  if (!list.length) return <DishPhoto src={null} alt={dish.name} size="lg" />;
  const go = (d, e) => { e.stopPropagation(); setI((i + d + list.length) % list.length); };
  return (
    <>
      <img src={list[i]} alt={dish.name} referrerPolicy="no-referrer"
        style={{ width: "100%", height: "100%", objectFit: "cover", position: "absolute", inset: 0 }} />
      {list.length > 1 && (
        <>
          <button onClick={(e) => go(-1, e)} aria-label="Предыдущее фото"
            style={{ position: "absolute", left: 10, top: "50%", transform: "translateY(-50%)", zIndex: 3, width: 34, height: 34, borderRadius: 999, background: "rgba(255,255,255,.92)", display: "grid", placeItems: "center" }}>
            <Icon name="chevronLeft" size={18} />
          </button>
          <button onClick={(e) => go(1, e)} aria-label="Следующее фото"
            style={{ position: "absolute", right: 10, top: "50%", transform: "translateY(-50%)", zIndex: 3, width: 34, height: 34, borderRadius: 999, background: "rgba(255,255,255,.92)", display: "grid", placeItems: "center" }}>
            <Icon name="chevronRight" size={18} />
          </button>
          <span style={{ position: "absolute", right: 12, top: 12, zIndex: 3, fontSize: 11.5, fontWeight: 700, padding: "3px 9px", borderRadius: 999, background: "rgba(20,16,10,.55)", color: "#fff" }}>
            {i + 1}/{list.length}{i > 0 ? " · фото гостей" : ""}
          </span>
        </>
      )}
    </>
  );
}

/* ---------- Real dish detail (AI Paella Score) ---------- */
function RealDishDetail({ dish, rest, onClose, onOpenRest }) {
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.body.style.overflow = "hidden"; window.addEventListener("keydown", onKey);
    return () => { document.body.style.overflow = ""; window.removeEventListener("keydown", onKey); };
  }, []);
  const u = paellaScore(dish, rest);
  const ai = u.ai;
  const V = useVisited();
  const [rated, setRated] = useState(null);
  const [rnote, setRnote] = useState("");
  const [rsaved, setRsaved] = useState(false);
  const submitRate = () => {
    if (!rated) return;
    V.addReview(rest, { dish: dish.name, dishPhoto: dish.photo || null, price: dish.price || null, rating: rated, text: rnote.trim(), date: new Date().toISOString().slice(0, 10) });
    setRsaved(true);
  };
  return (
    <div className="modal-overlay" onClick={onClose}
      style={{ position: "fixed", inset: 0, zIndex: 240, background: "rgba(30,26,20,.6)", display: "flex", alignItems: "flex-start", justifyContent: "center", padding: "4vh 16px", overflowY: "auto" }}>
      <div onClick={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: 560, background: "var(--surface)", borderRadius: 22, overflow: "hidden", boxShadow: "var(--sh-lg)" }}>
        <div style={{ position: "relative", height: 230, background: "var(--bg-2)" }}>
          <DishGallery dish={dish} />
          <button onClick={onClose} aria-label="Закрыть" style={{ position: "absolute", top: 14, right: 14, zIndex: 3, width: 36, height: 36, borderRadius: 999, background: "rgba(255,255,255,.92)", display: "grid", placeItems: "center" }}><Icon name="x" size={18} /></button>
          <ScoreTag u={u} lg style={{ position: "absolute", left: 16, bottom: 16, zIndex: 3 }} />
        </div>
        <div style={{ padding: "18px 22px 24px" }}>
          <div style={{ fontSize: 23, fontWeight: 800, letterSpacing: "-.02em", lineHeight: 1.15 }}>{dish.name}</div>
          <div className="row" style={{ gap: 8, marginTop: 6, fontSize: 13.5, color: "var(--ink-2)", flexWrap: "wrap" }}>
            <span>{rest.name}</span><span className="dot" style={{ width: 3, height: 3, borderRadius: 9, background: "var(--ink-3)" }} /><span>{rest.district}</span>
            {(dish.grams || dish.kcal) && <><span className="dot" style={{ width: 3, height: 3, borderRadius: 9, background: "var(--ink-3)" }} /><span>{[dish.grams && `${dish.grams} г`, dish.kcal && `${dish.kcal} ккал`].filter(Boolean).join(" · ")}</span></>}
          </div>
          {dish.desc && <p style={{ fontSize: 13.5, color: "var(--ink-2)", lineHeight: 1.5, marginTop: 10 }}>{dish.desc}</p>}
          <div className="row" style={{ marginTop: 12, alignItems: "center", gap: 12, justifyContent: "space-between" }}>
            <span style={{ fontSize: 26, fontWeight: 800 }}>{dish.price ? `${dish.price} ₽` : "—"}</span>
            {onOpenRest && <button className="btn btn-soft btn-sm" onClick={() => { onClose(); onOpenRest(rest); }}>Открыть ресторан <Icon name="arrowRight" size={15} /></button>}
          </div>

          {/* Реальная оценка гостей (Яндекс.Еда) */}
          {dish.ratingPct != null && (
            <div style={{ marginTop: 16, padding: 16, background: "var(--green-soft, #e9efde)", borderRadius: 16, border: "1px solid var(--hairline)" }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <Icon name="heart" size={16} fill="var(--olive)" sw={0} style={{ color: "var(--olive)" }} />
                <span style={{ fontWeight: 800, fontSize: 14 }}>Оценка гостей</span>
                <span style={{ marginLeft: "auto", fontWeight: 800, fontSize: 22, color: "var(--olive)" }}>{dish.ratingPct}%</span>
              </div>
              <p style={{ fontSize: 12.5, color: "var(--ink-2)", margin: "6px 0 0" }}>{dish.ratingPct}% гостей рекомендуют это блюдо{dish.ratingCount ? ` · ${dish.ratingCount} реальных оценок` : ""} · источник Яндекс.Еда</p>
            </div>
          )}

          {/* Из отзывов гостей по этому блюду (обработка реальных отзывов 2ГИС) */}
          {dish.reviewMentions >= 1 && (
            <div style={{ marginTop: 16, padding: 16, background: "var(--surface)", border: "1px solid var(--hairline)", borderRadius: 16 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }}>
                <Icon name="message" size={15} style={{ color: "var(--olive)" }} />
                <span style={{ fontWeight: 800, fontSize: 14 }}>Из отзывов о блюде</span>
                <span style={{ marginLeft: "auto", fontSize: 12.5, color: "var(--ink-2)", fontWeight: 600 }}>упоминается {dish.reviewMentions} раз{dish.reviewPosPct != null ? ` · ${dish.reviewPosPct}% хвалят` : ""}</span>
              </div>
              {(dish.reviewExcerpts || (dish.reviewSnippet ? [dish.reviewSnippet] : [])).slice(0, 3).map((e, i) => (
                <p key={i} style={{ fontSize: 13.5, color: "var(--ink)", margin: i ? "6px 0 0" : 0, lineHeight: 1.5, fontStyle: "italic" }}>«{e}»</p>
              ))}
              <div style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 6 }}>ИИ выделил из реальных отзывов гостей (2ГИС)</div>
            </div>
          )}

          {/* Paella Score 2.0 — из чего сложился балл (прозрачность = доверие) */}
          <div style={{ marginTop: 16, padding: 16, background: "var(--bg-2)", borderRadius: 16 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <Icon name={KIND_ICON[u.kind]} size={15} style={{ color: "var(--ink-2)" }} />
              <span style={{ fontWeight: 800, fontSize: 14 }}>Paella Score</span>
              <span style={{ marginLeft: "auto", fontWeight: 800, fontSize: 20, color: scoreColor(u.score) }}>{u.approx ? "~" : ""}{u.score}<span style={{ fontSize: 12, color: "var(--ink-3)" }}>/100</span></span>
            </div>
            <p style={{ fontSize: 12.5, color: "var(--ink-2)", margin: "6px 0 0", lineHeight: 1.45 }}>
              {u.kind === "guest" ? `Основа балла — реальные оценки гостей; доля прогноза модели ${u.priorShare}%.`
                : u.kind === "review" ? `Балл опирается на упоминания блюда в реальных отзывах; доля прогноза модели ${u.priorShare}%.`
                : "Реальных оценок пока мало — это прогноз модели Paella (знак «~»). Балл уточнится оценками гостей."}
            </p>
            <div style={{ marginTop: 12, display: "flex", flexDirection: "column", gap: 7 }}>
              {u.votes > 0 && (
                <div style={{ display: "flex", justifyContent: "space-between", gap: 10, fontSize: 12.5 }}>
                  <span style={{ color: "var(--ink-2)" }}>Оценки гостей · Яндекс.Еда</span>
                  <b style={{ whiteSpace: "nowrap" }}>{dish.ratingPct} · {u.votes} оценок</b>
                </div>
              )}
              {u.mentions >= 2 && dish.reviewPosPct != null && (
                <div style={{ display: "flex", justifyContent: "space-between", gap: 10, fontSize: 12.5 }}>
                  <span style={{ color: "var(--ink-2)" }}>Отзывы гостей · 2ГИС</span>
                  <b style={{ whiteSpace: "nowrap" }}>{dish.reviewPosPct}% позитивных · {u.mentions} упом.</b>
                </div>
              )}
              <div style={{ display: "flex", justifyContent: "space-between", gap: 10, fontSize: 12.5 }}>
                <span style={{ color: "var(--ink-2)" }}>Прогноз модели Paella</span>
                <b style={{ whiteSpace: "nowrap" }}>{u.prior} · вес {u.priorShare}%</b>
              </div>
            </div>
            {u.kind === "ai" && (
              <div style={{ marginTop: 12 }}>
                <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginBottom: 8 }}>Из чего состоит прогноз (веса методики Paella):</div>
                {ai.aspects.map(([label, val, w], i) => (
                  <div key={i} style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 7 }}>
                    <span style={{ width: 120, fontSize: 12, color: "var(--ink-2)", flex: "none" }}>{label} <span style={{ color: "var(--ink-3)" }}>{w}%</span></span>
                    <span style={{ flex: 1, height: 6, background: "var(--surface)", borderRadius: 9, overflow: "hidden" }}>
                      <span style={{ display: "block", height: "100%", width: `${(val / 5) * 100}%`, background: "var(--olive)" }} />
                    </span>
                    <span style={{ width: 26, textAlign: "right", fontSize: 12, fontWeight: 700 }}>{val.toFixed(1)}</span>
                  </div>
                ))}
              </div>
            )}
          </div>

          {/* Вы заказывали это? Оценка → в ваш «Паспорт мест» */}
          <div style={{ marginTop: 16, padding: 16, background: "var(--bg-2)", borderRadius: 16, border: "1px dashed var(--hairline)" }}>
            {rsaved ? (
              <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                <span style={{ fontSize: 22 }}>✅</span>
                <div>
                  <div style={{ fontWeight: 800, fontSize: 14 }}>Отзыв сохранён в вашем «Паспорте мест»</div>
                  <div style={{ fontSize: 12.5, color: "var(--ink-2)" }}>{rest.name} отмечен как посещённый.</div>
                </div>
              </div>
            ) : (
              <>
                <div style={{ fontWeight: 800, fontSize: 14, marginBottom: 4 }}>Заказывали это блюдо?</div>
                <div style={{ fontSize: 12.5, color: "var(--ink-2)", marginBottom: 10 }}>Оцените — и оно попадёт в ваш «Паспорт мест» вместе с рестораном.</div>
                <div className="row" style={{ gap: 8, flexWrap: "wrap" }}>
                  {[["fire", "🔥", "Огонь"], ["ok", "🙂", "Норм"], ["no", "👎", "Не зашло"]].map(([k, e, l]) => (
                    <button key={k} onClick={() => setRated(k)} className="chip" style={{ fontWeight: 700, border: rated === k ? "1.5px solid var(--olive)" : "1px solid var(--hairline)", background: rated === k ? "var(--green-soft, #e9efde)" : "var(--surface)" }}>{e} {l}</button>
                  ))}
                </div>
                {rated && (
                  <div style={{ marginTop: 10 }}>
                    <input value={rnote} onChange={(e) => setRnote(e.target.value)} placeholder="Пара слов о блюде (необязательно)…"
                      style={{ width: "100%", fontSize: 13.5, padding: "10px 12px", borderRadius: 10, border: "1px solid var(--hairline)", background: "var(--surface)", color: "var(--ink)" }} />
                    <button className="btn btn-pri btn-sm" style={{ marginTop: 10 }} onClick={submitRate}><Icon name="flag" size={15} />Сохранить в Паспорт</button>
                  </div>
                )}
              </>
            )}
          </div>

          <p style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 12, lineHeight: 1.45 }}>
            {"Paella Score — единый балл 0–100. Реальные оценки гостей и отзывы вытесняют прогноз модели по мере накопления данных; знак «~» означает, что реальных оценок пока мало. Ваша оценка ниже тоже уточняет балл. Подробнее — в разделе «Методика»."}
          </p>
        </div>
      </div>
    </div>
  );
}

/* ---------- Хит заведения (выделенная карточка топ-блюда) ---------- */
function RealHitCard({ item, rank, onOpen }) {
  const { d, u } = item;
  const medals = ["🥇", "🥈", "🥉"];
  return (
    <article className="dcard" onClick={() => onOpen(d)} style={{ border: "1.5px solid var(--olive)", boxShadow: "0 4px 16px oklch(0.45 0.08 130 / 0.13)" }}>
      <div className="dcard-media">
        <DishPhoto src={d.photo} alt={d.name} />
        <span style={{ position: "absolute", left: 10, top: 8, zIndex: 3, fontSize: 26, lineHeight: 1, filter: "drop-shadow(0 1px 2px rgba(0,0,0,.45))" }}>{medals[rank] || `#${rank + 1}`}</span>
        <ScoreTag u={u} float />
      </div>
      <div className="dcard-body">
        <div className="dcard-name" style={{ fontSize: 15.5 }}>{d.name}</div>
        {d.reviewSnippet ? <div className="dcard-reason" style={{ flex: 1, fontStyle: "italic" }}>«{d.reviewSnippet}»</div> : (d.kcal ? <div className="dcard-reason" style={{ flex: 1, color: "var(--ink-3)" }}>{[d.grams && `${d.grams} г`, d.kcal && `${d.kcal} ккал`].filter(Boolean).join(" · ")}</div> : <div style={{ flex: 1 }} />)}
        <div className="dcard-foot">
          <span className="dcard-price">{d.price ? d.price : "—"} <span className="cur">₽</span></span>
          <span className="muted" style={{ fontSize: 12, fontWeight: 600, display: "flex", alignItems: "center", gap: 4 }}><Icon name={KIND_ICON[u.kind]} size={12} />{scoreCaption(u)}</span>
        </div>
      </div>
    </article>
  );
}

/* ---------- Real restaurant detail (description + menu) ---------- */
function RealRestaurantDetail({ rest, onBack }) {
  const [dish, setDish] = useState(null);
  const [cat, setCat] = useState("Все");
  const [sort, setSort] = useState("score");
  const [showAllText, setShowAllText] = useState(false);
  const [revLimit, setRevLimit] = useState(6);
  const V = useVisited();
  const visited = V.isVisited(rest.id);
  useEffect(() => { window.scrollTo({ top: 0 }); setCat("Все"); setShowAllText(false); setRevLimit(6); }, [rest.id]);
  const grad = CUISINE_GRAD[rest.cuisine] || CUISINE_GRAD["Ресторан"];
  const emoji = CUISINE_EMOJI[rest.cuisine] || "🍽️";
  const hasMenu = rest.menuCount > 0;
  const siteUrl = rest.website ? ((rest.website.startsWith("http") ? "" : "https://") + rest.website) : null;
  // категории, реально присутствующие в меню (с counts), по убыванию
  const catCounts = {};
  (rest.menu || []).forEach((d) => { const c = d.cat || "Другое"; catCounts[c] = (catCounts[c] || 0) + 1; });
  const cats = ["Все", ...Object.keys(catCounts).sort((a, b) => catCounts[b] - catCounts[a])];
  let shownMenu = (rest.menu || []).filter((d) => cat === "Все" || (d.cat || "Другое") === cat);
  shownMenu = shownMenu.map((d) => ({ d, s: paellaScore(d, rest).q, hasPhoto: d.photo ? 1 : 0 }));
  const bySort = (a, b) => sort === "price" ? ((a.d.price || 1e9) - (b.d.price || 1e9)) : (b.s - a.s);
  // блюда с фото всегда впереди (кроме явной сортировки по цене) — убираем «поле пустых квадратов»
  if (sort === "price") shownMenu.sort(bySort);
  else shownMenu.sort((a, b) => (b.hasPhoto - a.hasPhoto) || bySort(a, b));
  shownMenu = shownMenu.map((x) => x.d);
  return (
    <div className="fadein">
      <div style={{ position: "relative", height: 280, overflow: "hidden" }}>
        <SmartImage src={rest.imageUrl} emoji={emoji} grad={grad} alt={rest.name} />
        <div style={{ position: "absolute", inset: 0, background: "linear-gradient(180deg, rgba(20,16,10,.15), rgba(20,16,10,.72))", zIndex: 2 }} />
        <button className="btn btn-soft btn-sm" style={{ position: "absolute", top: 16, left: 16, zIndex: 3 }} onClick={onBack}>
          <Icon name="chevronLeft" size={16} />Все рестораны
        </button>
        <div className="wrap" style={{ position: "absolute", left: 0, right: 0, bottom: 18, zIndex: 3, color: "#fff" }}>
          <div className="wrap-g" style={{ marginBottom: 10, display: "flex", gap: 8, flexWrap: "wrap" }}>
            <span className="badge b-olive">2ГИС · реальные данные</span>
            <span className="badge" style={{ background: "rgba(255,255,255,.85)", color: "var(--ink)" }}>{rest.cuisine}</span>
            {rest.rating != null && <span className="badge" style={{ background: "rgba(255,255,255,.85)", color: "var(--ink)" }}><Icon name="star" size={12} fill="var(--gold)" sw={0} />{Number(rest.rating).toFixed(1)} · {rest.reviewCount.toLocaleString("ru-RU")} отз.</span>}
            {rest.gmapsRating != null && <span className="badge" style={{ background: "rgba(255,255,255,.85)", color: "var(--ink)" }} title="Рейтинг Google Maps">G {Number(rest.gmapsRating).toFixed(1)}{rest.gmapsReviewCount ? ` · ${rest.gmapsReviewCount.toLocaleString("ru-RU")}` : ""}</span>}
          </div>
          <h1 style={{ fontSize: 34, fontWeight: 800, letterSpacing: "-0.03em", margin: 0 }}>{rest.name}</h1>
          <div className="row" style={{ gap: 8, marginTop: 8, fontSize: 14, opacity: .95, flexWrap: "wrap" }}>
            <span className="row" style={{ gap: 5 }}><Icon name="pin" size={15} />{rest.district} · {rest.address}</span>
            {rest.is24x7 && <span className="row" style={{ gap: 5 }}><Icon name="clock" size={14} />круглосуточно</span>}
          </div>
        </div>
      </div>

      <div className="wrap" style={{ marginTop: 22 }}>
        {rest.desc && (
          <div style={{ marginBottom: 18 }}>
            <span className="eyebrow">О месте</span>
            <p style={{ fontSize: 15.5, lineHeight: 1.55, maxWidth: 720, marginTop: 8, color: "var(--ink)" }}>{rest.desc}</p>
          </div>
        )}
        <div className="row" style={{ gap: 10, flexWrap: "wrap", marginBottom: 8 }}>
          <button className={"btn btn-sm " + (visited ? "btn-pri" : "btn-dark")} onClick={() => V.toggle(rest)}>
            <Icon name={visited ? "check" : "flag"} size={15} />{visited ? "Вы здесь были" : "Я здесь был"}
          </button>
          <a href={rest.dgisUrl} target="_blank" rel="noopener noreferrer" className="btn btn-soft btn-sm" style={{ textDecoration: "none" }}><Icon name="pin" size={15} />2ГИС</a>
          {siteUrl && <a href={siteUrl} target="_blank" rel="noopener noreferrer" className="btn btn-soft btn-sm" style={{ textDecoration: "none" }}><Icon name="share" size={15} />Сайт</a>}
          {rest.phone && <a href={`tel:${rest.phone}`} className="btn btn-soft btn-sm" style={{ textDecoration: "none" }}>{rest.phone}</a>}
        </div>

        {/* quick facts */}
        <div className="stat-grid" style={{ marginTop: 18, marginBottom: 4 }}>
          {rest.rating != null && <div className="stat"><div className="v">{Number(rest.rating).toFixed(1)}<Icon name="star" size={15} fill="var(--gold)" sw={0} style={{ marginLeft: 3, verticalAlign: "baseline" }} /></div><div className="l">{rest.branchCount > 1 ? "средний рейтинг" : "рейтинг 2ГИС"}</div></div>}
          {rest.reviewCount ? <div className="stat"><div className="v">{rest.reviewCount >= 1000 ? (rest.reviewCount / 1000).toFixed(1).replace(".0", "") + "K" : rest.reviewCount}</div><div className="l">отзывов{rest.branchCount > 1 ? " (все точки)" : ""}</div></div> : null}
          {hasMenu && <div className="stat"><div className="v">{rest.menuCount}</div><div className="l">блюд в меню</div></div>}
          {rest.branchCount > 1 ? <div className="stat"><div className="v">{rest.branchCount}</div><div className="l">{plural(rest.branchCount, "точка", "точки", "точек")} в центре</div></div> : <div className="stat"><div className="v">{rest.cuisine}</div><div className="l">{rest.is24x7 ? "круглосуточно" : "кухня"}</div></div>}
        </div>

        {/* Точки сети (бренд с несколькими адресами) */}
        {rest.branchCount > 1 && rest.locations && (
          <section className="sec" style={{ marginTop: 20 }}>
            <div className="row" style={{ marginBottom: 10, gap: 9, alignItems: "baseline" }}>
              <span className="eyebrow">🍴 Сеть · {rest.branchCount} {plural(rest.branchCount, "точка", "точки", "точек")}</span>
              <span className="muted" style={{ fontSize: 12.5 }}>оценки блюд усреднены по всем точкам · выбери ближайшую</span>
            </div>
            <div style={{ display: "grid", gap: 10, gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))" }}>
              {rest.locations.map((loc, i) => (
                <a key={i} href={loc.dgisUrl} target="_blank" rel="noopener noreferrer" style={{ textDecoration: "none", padding: 12, background: "var(--surface)", border: "1px solid var(--hairline)", borderRadius: 12, display: "flex", alignItems: "flex-start", gap: 10 }}>
                  <Icon name="pin" size={16} style={{ color: "var(--coral-ink)", flex: "none", marginTop: 2 }} />
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontSize: 13.5, fontWeight: 700, color: "var(--ink)" }}>{loc.address || loc.district}</div>
                    <div className="row" style={{ gap: 6, marginTop: 3, fontSize: 12, color: "var(--ink-2)" }}>
                      {loc.rating != null && <span><Icon name="star" size={11} fill="var(--gold)" sw={0} /> {Number(loc.rating).toFixed(1)}</span>}
                      {loc.reviewCount ? <><span className="dot" style={{ width: 3, height: 3, borderRadius: 9, background: "var(--ink-3)" }} /><span>{loc.reviewCount.toLocaleString("ru-RU")} отз.</span></> : null}
                      {loc.is24x7 && <><span className="dot" style={{ width: 3, height: 3, borderRadius: 9, background: "var(--ink-3)" }} /><span>24/7</span></>}
                    </div>
                  </div>
                </a>
              ))}
            </div>
          </section>
        )}

        {/* photo gallery — авто-прокрутка + стрелки + лайтбокс */}
        {rest.gallery && rest.gallery.length > 1 && <VenueGallery photos={rest.gallery} name={rest.name} />}

        {/* Хиты заведения — топ-3 лучших блюда */}
        {hasMenu && (() => {
          const hits = topDishes(rest, 3);
          if (hits.length < 2) return null;
          return (
            <section className="sec">
              <div className="row" style={{ marginBottom: 12, gap: 9, alignItems: "baseline" }}>
                <span className="eyebrow" style={{ color: "var(--coral-ink)" }}>🔥 Хиты заведения</span>
                <span className="muted" style={{ fontSize: 12.5 }}>топ-3 блюда по оценкам гостей и отзывам</span>
              </div>
              <div className="dgrid">
                {hits.map((it, i) => <RealHitCard key={i} item={it} rank={i} onOpen={setDish} />)}
              </div>
            </section>
          );
        })()}

        <section className="sec">
          <div className="sec-head">
            <div>
              <h2 className="sec-title">Всё меню</h2>
              <p className="sec-sub">{hasMenu ? `${rest.menuCount} блюд с ценами и Paella Score · источник ${rest.menuSource}` : "Меню пока не подтянуто из открытых источников"}</p>
            </div>
            {hasMenu && (
              <select className="chip" style={{ fontWeight: 600, paddingRight: 30 }} value={sort} onChange={(e) => setSort(e.target.value)}>
                <option value="score">По оценке</option>
                <option value="price">По цене</option>
              </select>
            )}
          </div>
          {hasMenu && cats.length > 2 && (
            <div className="chips" style={{ marginBottom: 18 }}>
              {cats.map((c) => (
                <button key={c} className={"chip" + (cat === c ? " on" : "")} onClick={() => setCat(c)}>
                  {c}{c !== "Все" && <span style={{ opacity: .55, marginLeft: 5 }}>{catCounts[c]}</span>}
                </button>
              ))}
            </div>
          )}
          {hasMenu ? (
            <>
              {shownMenu.filter((d) => d.photo).length > 0 && (
                <div className="dgrid">
                  {shownMenu.filter((d) => d.photo).map((d, i) => <RealDishCard key={i} dish={d} rest={rest} onOpen={setDish} />)}
                </div>
              )}
              {(() => {
                const textDishes = shownMenu.filter((d) => !d.photo);
                if (!textDishes.length) return null;
                const limit = 14;
                const shown = showAllText ? textDishes : textDishes.slice(0, limit);
                return (
                  <div style={{ marginTop: shownMenu.some((d) => d.photo) ? 22 : 0 }}>
                    {shownMenu.some((d) => d.photo) && <div className="muted" style={{ fontSize: 13, fontWeight: 700, margin: "0 0 10px" }}>Ещё {textDishes.length} блюд — фото пока нет</div>}
                    <div style={{ border: "1px solid var(--hairline)", borderRadius: 14, overflow: "hidden", background: "var(--surface)" }}>
                      {shown.map((d, i) => <RealDishRow key={i} dish={d} rest={rest} onOpen={setDish} />)}
                    </div>
                    {textDishes.length > limit && (
                      <button className="btn btn-soft btn-sm" style={{ marginTop: 12 }} onClick={() => setShowAllText((v) => !v)}>
                        {showAllText ? "Свернуть" : `Показать ещё ${textDishes.length - limit}`}<Icon name={showAllText ? "arrowDown" : "arrowDown"} size={15} style={{ transform: showAllText ? "rotate(180deg)" : "none" }} />
                      </button>
                    )}
                  </div>
                );
              })()}
            </>
          ) : (
            <EmptyState emoji="🍽️" title="Меню в работе" text="Для этого заведения меню не нашлось в открытых данных (Zoon / Яндекс.Карты). Появится на этапе живого UGC." />
          )}
        </section>

        {/* Гости о блюдах — LLM-выдержки из реальных отзывов (полные отзывы не публикуем) */}
        {(() => {
          const talked = (rest.menu || []).filter(d => d.reviewSnippet && d.reviewMentions >= 1)
            .sort((a, b) => (b.reviewMentions || 0) - (a.reviewMentions || 0));
          if (!talked.length) return null;
          return (
            <section className="sec">
              <div className="row" style={{ marginBottom: 12, gap: 9, alignItems: "baseline" }}>
                <span className="eyebrow">Гости о блюдах</span>
                <span className="muted" style={{ fontSize: 12.5 }}>{rest.reviewCount ? `${rest.reviewCount.toLocaleString("ru-RU")} отзывов обработано · ` : ""}ИИ выделил всё, что гости пишут о конкретных блюдах · 2ГИС</span>
              </div>
              <div className="m-compare" style={{ display: "grid", gap: 12, gridTemplateColumns: "1fr 1fr" }}>
                {talked.slice(0, revLimit).map((d, i) => (
                  <div key={i} onClick={() => setDish(d)} style={{ padding: 14, background: "var(--surface)", border: "1px solid var(--hairline)", borderRadius: 14, cursor: "pointer" }}>
                    <div className="row" style={{ justifyContent: "space-between", gap: 8, marginBottom: 6 }}>
                      <span style={{ fontSize: 13, fontWeight: 700, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{d.name}</span>
                      <span className="muted" style={{ fontSize: 12, fontWeight: 600, flex: "none" }}>{d.reviewMentions} упом.{d.reviewPosPct != null ? ` · ${d.reviewPosPct}% 👍` : ""}</span>
                    </div>
                    {(d.reviewExcerpts || [d.reviewSnippet]).slice(0, 2).map((e, j) => (
                      <p key={j} style={{ fontSize: 13.5, lineHeight: 1.5, margin: j ? "6px 0 0" : 0, color: "var(--ink)", fontStyle: "italic" }}>«{e}»</p>
                    ))}
                  </div>
                ))}
              </div>
              {talked.length > revLimit && (
                <div style={{ textAlign: "center", marginTop: 16 }}>
                  <button className="btn btn-soft" onClick={() => setRevLimit((l) => l + 8)}>Показать ещё <Icon name="arrowDown" size={16} /></button>
                </div>
              )}
            </section>
          );
        })()}
      </div>

      {dish && <RealDishDetail dish={dish} rest={rest} onClose={() => setDish(null)} />}
    </div>
  );
}

/* ============================================================
   ПОИСК БЛЮД ПО ВСЕЙ МОСКВЕ — главная фишка Paella
   «Поиск блюд, а не ресторанов»
   ============================================================ */
const DISH_CAT_EMOJI = {
  "Паста": "🍝", "Пицца": "🍕", "Суши": "🍣", "Бургеры": "🍔", "Стейки": "🥩",
  "Супы": "🍲", "Салаты": "🥗", "Десерты": "🍰", "Завтраки": "🍳", "Кофе": "☕",
  "Морепродукты": "🦐", "Грузинская кухня": "🫓", "Мясо": "🍖", "Другое": "🍽️",
};
const _normq = (s) => (s || "").toLowerCase().replace(/ё/g, "е").trim();

// единый индекс всех блюд по всем реальным ресторанам + оценка качества для ранжирования
function buildDishIndex(REAL) {
  const out = [];
  for (const r of REAL) {
    for (const d of (r.menu || [])) {
      if (!d.name) continue;
      const u = paellaScore(d, r);
      out.push({ d, r, u, ai: u.ai, q: u.q, kind: u.kind });
    }
  }
  return out;
}

/* ---------- карточка блюда в поиске (с контекстом ресторана) ---------- */
function RealDishSearchCard({ item, onOpen }) {
  const { d, r, u } = item;
  return (
    <article className="dcard" onClick={() => onOpen(item)}>
      <div className="dcard-media">
        <DishPhoto src={d.photo} alt={d.name} />
        <ScoreTag u={u} float />
      </div>
      <div className="dcard-body">
        <div className="dcard-name" style={{ fontSize: 15.5 }}>{d.name}</div>
        <div className="dcard-meta"><Icon name="pin" size={13} /><span style={{ minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontWeight: 600, color: "var(--ink)" }}>{r.name}</span><span className="dot" style={{ width: 3, height: 3, borderRadius: 9, background: "var(--ink-3)" }} /><span>{r.district}</span></div>
        {d.reviewSnippet ? <div className="dcard-reason" style={{ flex: 1, fontStyle: "italic" }}>«{d.reviewSnippet}»</div> : (d.kcal ? <div className="dcard-reason" style={{ flex: 1, color: "var(--ink-3)" }}>{[d.grams && `${d.grams} г`, d.kcal && `${d.kcal} ккал`].filter(Boolean).join(" · ")}</div> : <div style={{ flex: 1 }} />)}
        <div className="dcard-foot">
          <span className="dcard-price">{d.price ? d.price : "—"} <span className="cur">₽</span></span>
          <span className="muted" style={{ fontSize: 12, fontWeight: 600, display: "flex", alignItems: "center", gap: 4 }}><Icon name={KIND_ICON[u.kind]} size={12} />{scoreCaption(u)}</span>
        </div>
      </div>
    </article>
  );
}

/* ---------- экран поиска блюд ---------- */
function DishSearchScreen({ REAL, onOpenRest }) {
  const [q, setQ] = useState("");
  const [cat, setCat] = useState("Все");
  const [sort, setSort] = useState("best");
  const [limit, setLimit] = useState(48);
  const [picked, setPicked] = useState(null);
  const index = useMemo(() => buildDishIndex(REAL), [REAL]);
  // категории с количеством
  const catCounts = useMemo(() => { const m = {}; index.forEach(it => { const c = it.d.cat || "Другое"; m[c] = (m[c] || 0) + 1; }); return m; }, [index]);
  const cats = useMemo(() => ["Все", ...Object.keys(catCounts).sort((a, b) => catCounts[b] - catCounts[a])], [catCounts]);

  const results = useMemo(() => {
    const nq = _normq(q);
    const toks = nq.split(/\s+/).filter(Boolean);
    let res = index.filter(it => {
      if (cat !== "Все" && (it.d.cat || "Другое") !== cat) return false;
      if (!toks.length) return true;
      const hay = _normq(it.d.name + " " + (it.d.desc || ""));
      return toks.every(t => hay.includes(t));
    });
    if (sort === "price") res = res.slice().sort((a, b) => (a.d.price || 1e9) - (b.d.price || 1e9));
    else if (sort === "cheap") res = res.slice().sort((a, b) => (b.d.price || 0) - (a.d.price || 0));
    else res = res.slice().sort((a, b) => b.q - a.q); // лучшие
    return res;
  }, [index, q, cat, sort]);

  const restCount = useMemo(() => new Set(results.map(it => it.r.id)).size, [results]);
  useEffect(() => { setLimit(48); }, [q, cat, sort]);

  return (
    <div className="fadein">
      <div className="search" style={{ margin: "2px 0 14px" }}>
        <span className="sic"><Icon name="search" size={19} /></span>
        <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Найти блюдо: хачапури, тартар, паста карбонара, сырники…" autoFocus />
        {q && <button onClick={() => setQ("")} style={{ position: "absolute", right: 14, top: "50%", transform: "translateY(-50%)", color: "var(--ink-3)" }}><Icon name="x" size={18} /></button>}
      </div>

      <div className="chips" style={{ marginBottom: 14 }}>
        {cats.map((c) => (
          <button key={c} className={"chip" + (cat === c ? " on" : "")} onClick={() => setCat(c)}>
            {c !== "Все" && <span style={{ marginRight: 4 }}>{DISH_CAT_EMOJI[c] || "🍽️"}</span>}{c}<span style={{ opacity: .55, marginLeft: 5 }}>{c === "Все" ? index.length : catCounts[c]}</span>
          </button>
        ))}
      </div>

      <div className="row" style={{ justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 10, marginBottom: 16 }}>
        <span className="muted" style={{ fontSize: 14, fontWeight: 600 }}>
          {q || cat !== "Все" ? `${results.length.toLocaleString("ru-RU")} блюд` : `Топ блюд Москвы · ${index.length.toLocaleString("ru-RU")} блюд`} в {restCount} ресторан{restCount % 10 === 1 && restCount % 100 !== 11 ? "е" : restCount % 10 >= 2 && restCount % 10 <= 4 && (restCount % 100 < 10 || restCount % 100 >= 20) ? "ах" : "ах"}
        </span>
        <select className="chip" style={{ fontWeight: 600, paddingRight: 30 }} value={sort} onChange={(e) => setSort(e.target.value)}>
          <option value="best">Сначала лучшие</option>
          <option value="price">Сначала дешевле</option>
          <option value="cheap">Сначала дороже</option>
        </select>
      </div>

      {results.length === 0 ? (
        <EmptyState emoji="🔎" title="Ничего не нашлось" text="Попробуй другое блюдо — например «паста», «тартар», «хачапури» или выбери категорию." />
      ) : (
        <>
          <div className="dgrid">
            {results.slice(0, limit).map((it, i) => <RealDishSearchCard key={it.r.id + "-" + i} item={it} onOpen={setPicked} />)}
          </div>
          {results.length > limit && (
            <div style={{ textAlign: "center", marginTop: 22 }}>
              <button className="btn btn-soft" onClick={() => setLimit((l) => l + 48)}>Показать ещё {Math.min(48, results.length - limit)} <Icon name="arrowDown" size={16} /></button>
            </div>
          )}
        </>
      )}

      {picked && <RealDishDetail dish={picked.d} rest={picked.r} onClose={() => setPicked(null)} onOpenRest={onOpenRest} />}
    </div>
  );
}

function RestaurantsScreen({ ctx }) {
  const REAL = (window.PAELLA_REAL && window.PAELLA_REAL.restaurants) || [];
  const [realTab, setRealTab] = useState("dish");
  const [q, setQ] = useState("");
  const [district, setDistrict] = useState("Все районы");
  const [detailRest, setDetailRest] = useState(null);

  const districts = ["Все районы", ...Array.from(new Set(REAL.map((r) => r.district))).sort()];
  const list = REAL.filter((r) => {
    const okQ = !q.trim() || r.name.toLowerCase().includes(q.toLowerCase()) || (r.cuisine || "").toLowerCase().includes(q.toLowerCase());
    const okD = district === "Все районы" || r.district === district;
    return okQ && okD;
  });

  // restaurant detail view
  if (detailRest) {
    return <RealRestaurantDetail rest={detailRest} onBack={() => setDetailRest(null)} />;
  }

  return (
    <div className="fadein wrap">
      <div className="pintro">
        <span className="eyebrow">{realTab === "dish" ? "Поиск блюд · не ресторанов" : "Рестораны Москвы"}</span>
        <h1>{realTab === "dish" ? <>Найди лучшее <span className="serif-i" style={{ color: "var(--olive)" }}>блюдо</span> в Москве</> : "Где живут лучшие блюда"}</h1>
        <p>{realTab === "dish"
          ? `Главная идея Paella: ищи блюдо, а не ресторан. Введи «хачапури», «тартар» или «паста карбонара» — покажем, где их подают по всей Москве, с реальными фото, ценами, оценками гостей и тем, что о блюде пишут в отзывах.`
          : `${REAL.length} реальных заведений Москвы. Меню, фото блюд и оценки гостей — из Яндекс.Еды, рейтинги и отзывы — из 2ГИС. Открой ресторан: меню с фото и ценами, хиты заведения, реальные отзывы, а по блюду — оценка гостей и обработанные отзывы.`}</p>
      </div>

      <div className="seg" style={{ display: "flex", gap: 4, padding: 4, background: "var(--bg-2)", borderRadius: 14, marginBottom: 20, maxWidth: 420 }}>
        <button className="chip" style={{ border: "none", flex: 1, fontWeight: 700, background: realTab === "dish" ? "var(--surface)" : "transparent", boxShadow: realTab === "dish" ? "var(--sh-sm)" : "none" }} onClick={() => setRealTab("dish")}><Icon name="utensils" size={15} /> Поиск блюд</button>
        <button className="chip" style={{ border: "none", flex: 1, fontWeight: 700, background: realTab === "rest" ? "var(--surface)" : "transparent", boxShadow: realTab === "rest" ? "var(--sh-sm)" : "none" }} onClick={() => setRealTab("rest")}><Icon name="grid" size={15} /> По ресторанам</button>
      </div>

      {realTab === "dish" ? (
        <DishSearchScreen REAL={REAL} onOpenRest={(r) => { setRealTab("rest"); setDetailRest(r); }} />
      ) : (
        <>
          <div className="search" style={{ margin: "6px 0 14px" }}>
            <span className="sic"><Icon name="search" size={19} /></span>
            <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Название ресторана или кухня…" />
          </div>
          <div className="chips" style={{ marginBottom: 22 }}>
            {districts.map((d) => (
              <button key={d} className={"chip" + (district === d ? " on" : "")} onClick={() => setDistrict(d)}>{d}</button>
            ))}
          </div>

          {list.length === 0 ? (
            <EmptyState emoji="🏙️" title="Ресторанов не найдено" text="Попробуй другой район или запрос." />
          ) : (
            <div className="rgrid">
              {list.map((r) => <RealRestaurantCard key={r.id} rest={r} onOpen={setDetailRest} />)}
            </div>
          )}
        </>
      )}
    </div>
  );
}

Object.assign(window, { RestaurantsScreen, RealRestaurantCard, RealRestaurantDetail, RealDishDetail, RealDishCard, RealDishRow, RealHitCard, DishSearchScreen, RealDishSearchCard, buildDishIndex, topDishes, dishScore, DishPhoto });
