"use client";

/**
 * ConceptVariantsResult — variant tabs + per-variant placement map, ТЭП KPIs,
 * financial model, and export controls. Driven by the ConceptOutput returned
 * from POST /concepts. The map is lazy-mounted (Leaflet uses `window`).
 */

import dynamic from "next/dynamic";
import { useState } from "react";
import { Box, Map as MapIcon } from "lucide-react";

import { KpiCard } from "@/components/analytics/KpiCard";
import { Section } from "@/components/analytics/Section";
import { Badge } from "@/components/ui/Badge";
import {
  priceSourceCaption,
  STRATEGY_HINTS,
  STRATEGY_LABELS,
  type ConceptVariant,
  type FinancialModel,
} from "@/lib/concept-api";
import type { MultiPolygon, Polygon } from "geojson";

import { ConceptExportButtons } from "./ConceptExportButtons";

const ConceptResultMap = dynamic(
  () =>
    import("./ConceptResultMap").then((m) => ({ default: m.ConceptResultMap })),
  {
    ssr: false,
    loading: () => (
      <div
        style={{
          height: 360,
          background: "var(--bg-card-alt)",
          border: "1px solid var(--border-card)",
          borderRadius: 12,
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          color: "var(--fg-tertiary)",
          fontSize: 13,
        }}
      >
        Загрузка карты…
      </div>
    ),
  },
);

// 3D massing viewer — Three.js, client-only (ssr:false), lazy so it never bloats
// the report bundle and only the active placement tab mounts its engine.
const Massing3DViewer = dynamic(
  () =>
    import("./Massing3DViewer").then((m) => ({ default: m.Massing3DViewer })),
  {
    ssr: false,
    loading: () => (
      <div
        style={{
          height: 420,
          background: "var(--bg-card-alt)",
          border: "1px solid var(--border-card)",
          borderRadius: 12,
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          color: "var(--fg-tertiary)",
          fontSize: 13,
        }}
      >
        Загрузка 3D-сцены…
      </div>
    ),
  },
);

/** Desktop defaults to 3D; phones (<768px) default to the lighter 2D plan. */
function defaultPlacementView(): "2d" | "3d" {
  if (typeof window === "undefined") return "3d";
  return window.innerWidth < 768 ? "2d" : "3d";
}

// ── Formatters (ru microcopy) ─────────────────────────────────────────────────

const nf = new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 0 });

/** Compact ₽ for headline figures: "2.4 млрд ₽", "145 млн ₽". */
function formatMoneyCompact(rub: number): string {
  const abs = Math.abs(rub);
  if (abs >= 1e9) return `${(rub / 1e9).toFixed(1)} млрд ₽`;
  if (abs >= 1e6) return `${(rub / 1e6).toFixed(0)} млн ₽`;
  return `${nf.format(Math.round(rub))} ₽`;
}

function formatInt(n: number): string {
  return nf.format(Math.round(n));
}

/**
 * Число корпусов на участке = count of placed building footprints (each Feature
 * in buildings_geojson is one корпус). Guards a missing FeatureCollection.
 */
function buildingCount(variant: ConceptVariant): number {
  return variant.buildings_geojson?.features?.length ?? 0;
}

/**
 * A program variant is built from a `building_program` (not a 1b strategy): the
 * backend tags every placed feature `properties.strategy === "program"` while
 * reporting the variant under the fixed "balanced" strategy slot. Detect it from
 * the first feature so we can use the honest «Ваша программа» label instead of
 * STRATEGY_LABELS["balanced"] («Баланс»), which would mislabel it.
 */
function isProgramVariant(variant: ConceptVariant): boolean {
  const first = variant.buildings_geojson?.features?.[0];
  return first?.properties?.strategy === "program";
}

/** Display label: «Ваша программа» for program variants, else strategy label. */
function variantLabel(variant: ConceptVariant): string {
  return isProgramVariant(variant)
    ? "Ваша программа"
    : STRATEGY_LABELS[variant.strategy];
}

/**
 * Variant subtitle hint: program variants get a program-appropriate sentence
 * (the 1b STRATEGY_HINTS speak of «приоритет площади/инсоляции/баланс», which
 * misdescribes a user-defined program reported under the "balanced" slot).
 */
function variantHint(variant: ConceptVariant): string {
  return isProgramVariant(variant)
    ? "Раскладка заданной программы по геометрии участка."
    : STRATEGY_HINTS[variant.strategy];
}

/** Russian plural for «корпус» (1 корпус / 2–4 корпуса / 5+ корпусов). */
function pluralizeCorpus(n: number): string {
  const mod100 = n % 100;
  const mod10 = n % 10;
  if (mod100 >= 11 && mod100 <= 14) return "корпусов";
  if (mod10 === 1) return "корпус";
  if (mod10 >= 2 && mod10 <= 4) return "корпуса";
  return "корпусов";
}

/** «3 корпуса» / «1 корпус» / «12 корпусов» — count + agreed plural. */
function formatCorpuses(n: number): string {
  return `${formatInt(n)} ${pluralizeCorpus(n)}`;
}

function formatPct(fraction: number): string {
  return `${(fraction * 100).toFixed(1)}%`;
}

/**
 * Знаковый процент — для метрик, которые честно могут быть отрицательными
 * (IRR на собственные средства при негативном леверидже). Отрицательное число
 * печатается с Unicode-минусом «−» (U+2212), не ASCII «-» (ui-microcopy).
 */
function formatSignedPct(fraction: number): string {
  const body = `${(Math.abs(fraction) * 100).toFixed(1)}%`;
  return fraction < 0 ? `−${body}` : body;
}

/** Срок окупаемости: «28.5 мес» или «не окупается» (null). */
function formatPayback(months: number | null): string {
  if (months === null) return "не окупается";
  return `${months.toFixed(1)} мес`;
}

// ── Placement view tab (2D / 3D) ──────────────────────────────────────────────

function PlacementTab({
  active,
  icon,
  label,
  onClick,
}: {
  active: boolean;
  icon: React.ReactNode;
  label: string;
  onClick: () => void;
}) {
  return (
    <button
      type="button"
      role="tab"
      aria-selected={active}
      onClick={onClick}
      style={{
        display: "inline-flex",
        alignItems: "center",
        gap: 8,
        padding: "6px 12px",
        background: active ? "var(--accent)" : "transparent",
        border: "none",
        borderRadius: 6,
        color: active ? "#fff" : "var(--fg-secondary)",
        fontSize: 13,
        fontWeight: active ? 600 : 500,
        cursor: "pointer",
        whiteSpace: "nowrap",
      }}
    >
      {icon}
      {label}
    </button>
  );
}

// ── Variant panel ─────────────────────────────────────────────────────────────

interface PanelProps {
  /** Parcel geometry — Polygon OR MultiPolygon (whole geometry, all parts). */
  parcel: Polygon | MultiPolygon;
  variant: ConceptVariant;
  /** Visual floor height (m) for the 3D viewer; default 3.0 (backend constant). */
  floorHeightM?: number;
  /**
   * Кадастровый номер участка — для подгрузки соседних зданий (макет квартала)
   * в 2D-план / 3D-сцену (#2180). Отсутствует на standalone /concept без
   * кадастра → соседи не грузятся.
   */
  cadNum?: string | null;
}

function VariantPanel({
  parcel,
  variant,
  floorHeightM = 3.0,
  cadNum,
}: PanelProps) {
  const { teap, financial } = variant;

  // Placement view: «План (2D)» (Leaflet) ↔ «Объём (3D)» (Three.js). Desktop
  // defaults to 3D; phones default to 2D and mount 3D only on tap. Only the
  // active tab is rendered, so the inactive engine frees its GPU/Leaflet
  // resources via its teardown effect on switch.
  const [view, setView] = useState<"2d" | "3d">(defaultPlacementView);
  const netPositive =
    financial.net_profit_rub > 0
      ? true
      : financial.net_profit_rub < 0
        ? false
        : null;

  // Stage 3b (#1965) — partial-fit honest note: program mode (requested_count
  // != null) where fewer sections fit than were requested. Neutral, not an
  // error: the user asked for M, the parcel takes N < M. Greedy / full-fit → null.
  const partialFit =
    variant.requested_count != null &&
    variant.placed_count != null &&
    variant.placed_count < variant.requested_count
      ? { placed: variant.placed_count, requested: variant.requested_count }
      : null;

  return (
    <div>
      {/* Headline-bar — one-sentence JTBD verdict for the variant. */}
      <div
        style={{
          background: "var(--bg-headline)",
          color: "var(--fg-on-dark)",
          borderRadius: 12,
          padding: "14px 18px",
        }}
      >
        <div style={{ fontSize: 15, fontWeight: 600 }}>
          {variantLabel(variant)}: {formatCorpuses(buildingCount(variant))} ·{" "}
          {formatInt(teap.total_floor_area_sqm)} м² надземной площади ·{" "}
          {formatInt(teap.apartments_count)} квартир · чистая прибыль{" "}
          {formatMoneyCompact(financial.net_profit_rub)} · ROI{" "}
          {formatPct(financial.roi)}
        </div>
        <div
          style={{
            marginTop: 8,
            paddingTop: 8,
            borderTop: "1px solid rgba(255,255,255,0.12)",
            fontSize: 12,
            color: "var(--fg-on-dark-muted)",
          }}
        >
          {variantHint(variant)} NPV {formatMoneyCompact(financial.npv_rub)} ·
          IRR {formatPct(financial.irr)}
          {financial.irr_is_proxy ? " (оценочный)" : ""} · окупаемость{" "}
          {formatPayback(financial.payback_months)} · плотность (FAR){" "}
          {teap.density.toLocaleString("ru-RU", {
            minimumFractionDigits: 2,
            maximumFractionDigits: 2,
          })}
          .
        </div>
      </div>

      {/* Stage 3b — partial-fit note: program asked for more sections than fit. */}
      {partialFit && (
        <div
          role="note"
          style={{
            marginTop: 12,
            padding: "12px 16px",
            background: "var(--bg-card-alt)",
            border: "1px solid var(--border-card)",
            borderRadius: 12,
            fontSize: 13,
            lineHeight: "20px",
            color: "var(--fg-secondary)",
          }}
        >
          Размещено {formatInt(partialFit.placed)} из{" "}
          {formatCorpuses(partialFit.requested)} — участок вмещает меньше, чем в
          заданной программе. ТЭП и финмодель рассчитаны по фактически
          размещённым корпусам.
        </div>
      )}

      {/* Placement — 2D plan / 3D massing tab switch (same FeatureCollection). */}
      <Section
        title="Размещение застройки"
        subtitle={`Полигон участка и ${formatCorpuses(
          buildingCount(variant),
        )} — 2D-план или объёмная модель по программе.`}
        right={
          <div
            role="tablist"
            aria-label="Вид размещения"
            style={{
              display: "inline-flex",
              gap: 4,
              padding: 4,
              background: "var(--bg-card-alt)",
              border: "1px solid var(--border-card)",
              borderRadius: 8,
            }}
          >
            <PlacementTab
              active={view === "2d"}
              icon={<MapIcon size={16} strokeWidth={1.5} aria-hidden="true" />}
              label="План (2D)"
              onClick={() => setView("2d")}
            />
            <PlacementTab
              active={view === "3d"}
              icon={<Box size={16} strokeWidth={1.5} aria-hidden="true" />}
              label="Объём (3D)"
              onClick={() => setView("3d")}
            />
          </div>
        }
      >
        {view === "2d" ? (
          <ConceptResultMap
            parcel={parcel}
            buildings={variant.buildings_geojson}
            variantKey={variant.strategy}
            cadNum={cadNum}
          />
        ) : (
          <Massing3DViewer
            parcel={parcel}
            variant={variant}
            floorHeightM={floorHeightM}
            cadNum={cadNum}
          />
        )}
      </Section>

      {/* ТЭП — KPI grid */}
      <Section
        title="ТЭП — технико-экономические показатели"
        subtitle="Расчёт движка генерации по выбранной стратегии размещения."
      >
        <div
          style={{
            display: "grid",
            gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
            gap: 12,
          }}
        >
          <KpiCard
            label="Площадь застройки"
            value={formatInt(teap.built_area_sqm)}
            unit="м²"
          />
          <KpiCard
            label="Общая площадь"
            value={formatInt(teap.total_floor_area_sqm)}
            unit="м²"
          />
          <KpiCard
            label="Жилая площадь"
            value={formatInt(teap.residential_area_sqm)}
            unit="м²"
          />
          <KpiCard
            label="Квартир"
            value={formatInt(teap.apartments_count)}
            unit="шт"
          />
          <KpiCard
            label="Плотность (FAR)"
            value={teap.density.toLocaleString("ru-RU", {
              minimumFractionDigits: 2,
              maximumFractionDigits: 2,
            })}
          />
          <KpiCard
            label="Машино-мест"
            value={formatInt(teap.parking_spaces)}
            unit="шт"
          />
        </div>
      </Section>

      {/* Финмодель */}
      <Section
        title="Финансовая модель"
        subtitle="Девелоперский расчёт: выручка, каскад затрат, НДС и налог на прибыль → чистая прибыль и ROI; поверх — помесячный DCF (NPV, IRR, окупаемость)."
        right={<ConceptExportButtons variant={variant} />}
      >
        <div
          style={{
            display: "grid",
            gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
            gap: 12,
          }}
        >
          <KpiCard
            label="Выручка (GDV)"
            value={formatMoneyCompact(financial.revenue_rub)}
          />
          <KpiCard
            label="Итого затраты"
            value={formatMoneyCompact(financial.cost_rub)}
          />
          <KpiCard
            label="Чистая прибыль"
            value={formatMoneyCompact(financial.net_profit_rub)}
            delta={{
              value:
                netPositive === true
                  ? "Положительная (после НДС и налога на прибыль)"
                  : netPositive === false
                    ? "Отрицательная (после НДС и налога на прибыль)"
                    : "Нулевая",
              positive: netPositive,
            }}
          />
          <KpiCard
            label="ROI на затраты"
            value={formatPct(financial.roi)}
            delta={{
              value: `Чистая маржа на выручку ${formatPct(financial.margin_pct)}`,
              positive: null,
            }}
          />
          <KpiCard
            label="NPV (DCF)"
            value={formatMoneyCompact(financial.npv_rub)}
            delta={{
              value: `Дисконт ${formatPct(financial.discount_rate_used)} годовых`,
              positive:
                financial.npv_rub > 0
                  ? true
                  : financial.npv_rub < 0
                    ? false
                    : null,
            }}
          />
          <KpiCard
            label="IRR (DCF, годовой)"
            value={formatPct(financial.irr)}
            delta={{
              value: financial.irr_is_proxy
                ? "Оценочный (вырожденный поток, не DCF)"
                : `Окупаемость ${formatPayback(financial.payback_months)}`,
              positive: financial.irr_is_proxy
                ? null
                : financial.irr > financial.discount_rate_used
                  ? true
                  : false,
            }}
          />
          <KpiCard
            label="Цена продажи жилья"
            value={`${formatInt(financial.price_per_sqm_used)} ₽/м²`}
            delta={{
              value: priceSourceCaption(financial),
              positive: null,
            }}
          />
        </div>

        {/* Каскад затрат + БДР — под аккордеоном (плотность > выкладки) */}
        <details style={{ marginTop: 12 }}>
          <summary
            style={{
              cursor: "pointer",
              fontSize: 13,
              fontWeight: 500,
              color: "var(--fg-secondary)",
            }}
          >
            Подробнее — каскад затрат и расчёт прибыли
          </summary>
          <div style={{ marginTop: 12 }}>
            <FinancialCascadeTable financial={financial} />
            <p
              style={{
                marginTop: 12,
                fontSize: 12,
                color: "var(--fg-tertiary)",
              }}
            >
              NPV / IRR / окупаемость рассчитаны помесячным DCF.{" "}
              {financial.schedule_is_default
                ? `График фаз и темп продаж — типовые допущения (ПИР 6 мес → СМР по типу застройки → распродажа 30 мес, дисконт ${formatPct(
                    financial.discount_rate_used,
                  )} годовых); метрики рассчитаны DCF по этому графику, точность зависит от графика конкретного проекта. `
                : ""}
              {financial.irr_is_proxy
                ? "IRR помечен как оценочный: денежный поток вырожденный (нет смены знака), показан аннуализированный ROI вместо DCF-IRR. "
                : ""}
              НДС: жильё и услуги застройщика по ДДУ освобождены (ст. 149 НК
              РФ); НДС начисляется только на паркинг (нежилые машиноместа).
              Входной НДС по строительству уже учтён в себестоимости. Налог на
              прибыль — 25% (с 2025 года). Цена продажи жилья —{" "}
              {financial.price_is_calibrated
                ? `калибрована по рынку (${priceSourceCaption(financial)})`
                : "норматив класса (нет рыночных данных по участку)"}
              ; себестоимость СМР и цена паркинга — нормативные ориентиры.
              Коммерческие и офисные площади не учитываются.
              {financial.financing_enabled && financial.financing_is_simplified
                ? " Финансирование упрощено: весь кассовый разрыв покрыт " +
                  "кредитом по ставке-нормативу, проценты капитализируются, " +
                  "эскроу не моделируется точно. Проценты — реальная стоимость " +
                  "(чистая прибыль после финансирования ниже прибыли без него)."
                : ""}
              {financial.financing_enabled
                ? " IRR на собственные средства — доходность на собственные " +
                  "средства инвестора при кредите LTC 70 %. Может быть выше " +
                  "проектной IRR при положительном леверидже или ниже неё " +
                  "(вплоть до отрицательной), если проект не покрывает стоимость " +
                  "долга." +
                  (financial.levered_irr_is_proxy
                    ? " Помечена как оценочная: посчитана по вырожденному " +
                      "equity-потоку (proxy), а не DCF."
                    : "")
                : ""}
            </p>
          </div>
        </details>
      </Section>
    </div>
  );
}

// ── Cost cascade / P&L detail table ────────────────────────────────────────────

function CascadeRow({
  label,
  value,
  emphasis = false,
}: {
  label: string;
  value: string;
  emphasis?: boolean;
}) {
  return (
    <tr>
      <td
        style={{
          padding: "6px 8px",
          borderBottom: "1px solid var(--border-soft)",
          color: emphasis ? "var(--fg-primary)" : "var(--fg-secondary)",
          fontWeight: emphasis ? 600 : 400,
        }}
      >
        {label}
      </td>
      <td
        style={{
          padding: "6px 8px",
          borderBottom: "1px solid var(--border-soft)",
          textAlign: "right",
          fontWeight: emphasis ? 600 : 400,
          fontVariantNumeric: "tabular-nums",
        }}
      >
        {value}
      </td>
    </tr>
  );
}

function FinancialCascadeTable({ financial }: { financial: FinancialModel }) {
  return (
    <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
      <tbody>
        <CascadeRow
          label="Выручка — жильё"
          value={formatMoneyCompact(financial.revenue_residential_rub)}
        />
        <CascadeRow
          label="Выручка — паркинг"
          value={formatMoneyCompact(financial.revenue_parking_rub)}
        />
        <CascadeRow
          label="Выручка — нежилое (1-й этаж)"
          value={formatMoneyCompact(financial.revenue_office_rub)}
        />
        <CascadeRow
          label="Выручка (GDV)"
          value={formatMoneyCompact(financial.revenue_rub)}
          emphasis
        />
        <CascadeRow
          label="СМР"
          value={formatMoneyCompact(financial.construction_rub)}
        />
        <CascadeRow
          label="ПИР (проектирование)"
          value={formatMoneyCompact(financial.pir_rub)}
        />
        <CascadeRow
          label="Сети (ТУ)"
          value={formatMoneyCompact(financial.networks_rub)}
        />
        <CascadeRow
          label="Услуги заказчика"
          value={formatMoneyCompact(financial.developer_services_rub)}
        />
        <CascadeRow
          label="Резерв"
          value={formatMoneyCompact(financial.contingency_rub)}
        />
        <CascadeRow
          label="Маркетинг и риэлтор"
          value={formatMoneyCompact(financial.marketing_rub)}
        />
        <CascadeRow
          label="Земля"
          value={formatMoneyCompact(financial.land_rub)}
        />
        <CascadeRow
          label="Итого затраты"
          value={formatMoneyCompact(financial.cost_rub)}
          emphasis
        />
        <CascadeRow
          label="Валовая маржа"
          value={formatMoneyCompact(financial.gross_margin_rub)}
        />
        <CascadeRow
          label="НДС (паркинг)"
          value={formatMoneyCompact(financial.vat_rub)}
        />
        <CascadeRow
          label="Прибыль до налога"
          value={formatMoneyCompact(financial.profit_before_tax_rub)}
        />
        <CascadeRow
          label="Налог на прибыль (25%)"
          value={formatMoneyCompact(financial.profit_tax_rub)}
        />
        <CascadeRow
          label="Чистая прибыль"
          value={formatMoneyCompact(financial.net_profit_rub)}
          emphasis
        />
        <CascadeRow
          label={`NPV (DCF, дисконт ${formatPct(financial.discount_rate_used)})`}
          value={formatMoneyCompact(financial.npv_rub)}
        />
        <CascadeRow
          label={`IRR (годовой)${financial.irr_is_proxy ? ", оценочный" : ""}`}
          value={formatPct(financial.irr)}
        />
        <CascadeRow
          label="Окупаемость (PBP)"
          value={formatPayback(financial.payback_months)}
        />
        {/* #1881 PR-5 — финансирование (кредит). Показываем только когда движок
            смоделировал финансирование (financing_enabled). */}
        {financial.financing_enabled ? (
          <>
            <CascadeRow
              label="Пиковый долг"
              value={formatMoneyCompact(financial.peak_debt_rub)}
            />
            <CascadeRow
              label={`Проценты по кредиту (ставка ${formatPct(
                financial.annual_rate_used,
              )} годовых)`}
              value={formatMoneyCompact(financial.total_interest_rub)}
            />
            <CascadeRow
              label="Чистая прибыль после финансирования"
              value={formatMoneyCompact(
                financial.net_profit_after_financing_rub,
              )}
              emphasis
            />
            {/* IRR на собственные средства (equity) при LTC 70 %. Может быть выше
                проектной IRR (положительный леверидж) или ниже / отрицательной
                (проект не покрывает стоимость долга) — знак показываем честно. */}
            <CascadeRow
              label={`IRR на собственные средства (с учётом кредита, LTC 70%)${
                financial.levered_irr_is_proxy ? ", оценочный" : ""
              }`}
              value={formatSignedPct(financial.levered_irr)}
            />
          </>
        ) : null}
      </tbody>
    </table>
  );
}

// ── Component ─────────────────────────────────────────────────────────────────

interface Props {
  /**
   * Parcel geometry for the placement views — Polygon OR MultiPolygon. The §7
   * report passes the WHOLE geometry (`extractParcelGeometry`) so the 2D/3D
   * scenes render every part and stay geo-aligned with backend-anchored
   * neighbours; the standalone /concept page passes its single drawn/resolved
   * Polygon.
   */
  parcel: Polygon | MultiPolygon;
  variants: ConceptVariant[];
  /**
   * Visual floor height (m) for the 3D massing viewer; default 3.0 = backend's
   * `_FLOOR_HEIGHT`. Optional so Phase 2 can thread the form's live knob without
   * a contract change (the ТЭП / финмодель stay computed at 3.0 m/этаж).
   */
  floorHeightM?: number;
  /**
   * Кадастровый номер участка — прокидывается в 2D-план и 3D-сцену для загрузки
   * соседних зданий (макет квартала, #2180). В отчёте берётся из analysis.cad_num;
   * на standalone /concept (границы рисуются / резолвятся вручную) не передаётся →
   * соседи не грузятся, сцена работает как раньше.
   */
  cadNum?: string | null;
}

export function ConceptVariantsResult({
  parcel,
  variants,
  floorHeightM = 3.0,
  cadNum,
}: Props) {
  const [active, setActive] = useState(0);

  if (variants.length === 0) {
    return (
      <p style={{ fontSize: 14, color: "var(--fg-secondary)" }}>
        Движок не вернул ни одного варианта застройки.
      </p>
    );
  }

  const activeVariant = variants[Math.min(active, variants.length - 1)];

  // A lone tab is noise (common for program mode → one variant). Hide the tab
  // bar entirely with a single variant; keep it as-is when there's a choice.
  const showTabs = variants.length > 1;

  return (
    <div>
      {/* Variant tabs */}
      {showTabs && (
        <div
          role="tablist"
          aria-label="Варианты застройки"
          style={{
            display: "flex",
            gap: 8,
            borderBottom: "1px solid var(--border-card)",
            marginBottom: 4,
            overflowX: "auto",
          }}
        >
          {variants.map((v, i) => {
            const isActive = i === active;
            return (
              <button
                key={v.strategy}
                type="button"
                role="tab"
                aria-selected={isActive}
                onClick={() => setActive(i)}
                style={{
                  display: "inline-flex",
                  alignItems: "center",
                  gap: 8,
                  padding: "10px 14px",
                  background: "transparent",
                  border: "none",
                  borderBottom: isActive
                    ? "2px solid var(--accent)"
                    : "2px solid transparent",
                  color: isActive ? "var(--accent)" : "var(--fg-secondary)",
                  fontSize: 14,
                  fontWeight: isActive ? 600 : 500,
                  cursor: "pointer",
                  whiteSpace: "nowrap",
                }}
              >
                {variantLabel(v)}
                <Badge variant={isActive ? "info" : "neutral"} size="sm">
                  {v.teap.apartments_count > 0
                    ? `${formatInt(v.teap.apartments_count)} кв.`
                    : "—"}
                </Badge>
              </button>
            );
          })}
        </div>
      )}

      <VariantPanel
        parcel={parcel}
        variant={activeVariant}
        floorHeightM={floorHeightM}
        cadNum={cadNum}
      />
    </div>
  );
}
