"use client";

import { Fragment, useState } from "react";
import { useBestLayouts } from "@/hooks/useBestLayouts";
import { API_BASE_URL } from "@/lib/api";
import type {
  BestLayoutsRequest,
  BestLayoutsResponse,
  Confidence,
  LayoutTzMixRow,
  SupplyOnlyLayoutRow,
  TimeWindow,
  TopLayoutRow,
} from "@/types/best-layouts";

// ── Constants ─────────────────────────────────────────────────────────────────

// Inline-токены (НЕ Tailwind-классы): на site-finder-отчёте Tailwind-утилиты
// (flex/gap/bg-*) не генерируются в сборку — секции стилизованы inline (#1953).
const CONFIDENCE_STYLES: Record<
  Confidence,
  { fg: string; bg: string; label: string }
> = {
  high: {
    fg: "var(--success, #0A7A3A)",
    bg: "var(--success-soft, #DCFCE7)",
    label: "Высокое",
  },
  medium: {
    fg: "var(--warn, #9A6700)",
    bg: "var(--warn-soft, #FEF3C7)",
    label: "Среднее",
  },
  low: {
    fg: "var(--danger, #B3261E)",
    bg: "var(--danger-soft, #FEE2E2)",
    label: "Низкое",
  },
};

const TIME_WINDOW_LABELS: Record<TimeWindow, string> = {
  last_month: "Последний месяц",
  last_quarter: "Последний квартал",
  last_year: "Последний год",
};

// Период «распроданности» для провенанса — соответствует выбранному time_window
// (backend считает sold_pct_of_supply по сделкам этого окна).
const TIME_WINDOW_OVERSOLD_PERIOD: Record<TimeWindow, string> = {
  last_month: "за последний месяц",
  last_quarter: "за последний квартал",
  last_year: "за последний год",
};

const ROOM_BUCKET_LABELS: Record<string, string> = {
  studio: "Студия",
  "1": "1-комн.",
  "2": "2-комн.",
  "3": "3-комн.",
  "4+": "4+ комн.",
};

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

function DataQualityCard({ dq }: { dq: BestLayoutsResponse["data_quality"] }) {
  const conf = CONFIDENCE_STYLES[dq.confidence];
  return (
    // Inline-стили (НЕ Tailwind): flex/gap/bg-* не применяются на этой странице —
    // спаны текли inline и слипались («НизкоеПокрытие», #1953). Inline гарантирует
    // flex+gap (воздух вокруг «·») и фон бейджа.
    <div
      style={{
        display: "flex",
        flexWrap: "wrap",
        alignItems: "center",
        columnGap: 12,
        rowGap: 4,
        border: "1px solid var(--border-card, #E6E8EC)",
        borderRadius: 12,
        padding: "14px 18px",
        background: "var(--bg-card, #ffffff)",
      }}
    >
      <span
        style={{
          fontWeight: 600,
          fontSize: 13,
          color: "var(--fg-secondary, #5B6066)",
        }}
      >
        Качество данных:
      </span>
      <span
        style={{
          fontSize: 12,
          fontWeight: 600,
          padding: "2px 10px",
          borderRadius: 6,
          color: conf.fg,
          background: conf.bg,
        }}
      >
        {conf.label}
      </span>
      <span
        style={{ fontSize: 12, color: "var(--fg-tertiary, #73767E)" }}
        aria-hidden
      >
        ·
      </span>
      <span style={{ fontSize: 12, color: "var(--fg-secondary, #5B6066)" }}>
        покрытие {dq.velocity_coverage_pct.toFixed(0)}% (
        {dq.objects_with_velocity_data} из {dq.objects_total_in_radius}{" "}
        комплексов)
      </span>
    </div>
  );
}

const COMPETITOR_PREVIEW_LIMIT = 5;

function salesPeriodMonths(supply: number, velocity: number): number | null {
  if (velocity <= 0) return null;
  return Math.ceil(supply / velocity);
}

function SalesPeriodCell({ months }: { months: number | null }) {
  if (months === null) return <span className="text-gray-400">—</span>;
  if (months < 12)
    return (
      <span className="text-emerald-600 font-semibold tabular-nums">
        {months} мес
      </span>
    );
  if (months <= 24)
    return <span className="text-slate-600 tabular-nums">{months} мес</span>;
  return <span className="text-amber-600 tabular-nums">{months} мес</span>;
}

function CompetitorDrillIn({
  ids,
  colSpan,
}: {
  ids: number[];
  colSpan: number;
}) {
  const [showAll, setShowAll] = useState(false);
  const visible = showAll ? ids : ids.slice(0, COMPETITOR_PREVIEW_LIMIT);
  const hidden = ids.length - COMPETITOR_PREVIEW_LIMIT;

  return (
    <tr className="bg-blue-50">
      <td colSpan={colSpan} style={{ padding: "8px 16px" }}>
        <div
          style={{
            display: "flex",
            alignItems: "center",
            flexWrap: "wrap",
            gap: 6,
            fontSize: 11,
          }}
        >
          <span
            className="text-gray-500 font-semibold"
            style={{ marginRight: 4 }}
          >
            ЖК-источники:
          </span>
          {visible.map((id) => (
            <a
              key={id}
              href={`https://наш.дом.рф/services/catalog-newbuildings/object/${id}`}
              target="_blank"
              rel="noopener noreferrer"
              className="bg-blue-100 text-blue-700 rounded px-2 py-0.5 font-mono hover:bg-blue-200 transition-colors"
            >
              {id}
            </a>
          ))}
          {!showAll && hidden > 0 && (
            <button
              onClick={() => setShowAll(true)}
              className="text-blue-600 underline text-[11px] cursor-pointer bg-transparent border-none p-0"
            >
              и ещё {hidden}
            </button>
          )}
          {showAll && ids.length > COMPETITOR_PREVIEW_LIMIT && (
            <button
              onClick={() => setShowAll(false)}
              className="text-gray-400 underline text-[11px] cursor-pointer bg-transparent border-none p-0"
            >
              свернуть
            </button>
          )}
        </div>
      </td>
    </tr>
  );
}

function TopLayoutsTable({
  rows,
  oversoldPeriodLabel,
}: {
  rows: TopLayoutRow[];
  oversoldPeriodLabel: string;
}) {
  const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());

  if (rows.length === 0) {
    return (
      <div className="text-gray-400 text-[13px] py-3">
        Данных недостаточно для ранжирования планировок
      </div>
    );
  }

  function toggleRow(signature: string) {
    setExpandedRows((prev) => {
      const next = new Set(prev);
      if (next.has(signature)) {
        next.delete(signature);
      } else {
        next.add(signature);
      }
      return next;
    });
  }

  const TOTAL_COLS = 9; // # Тип Площадь Скорость СрПлощадь СрЦена СрокПродажи Продано Chevron

  const headers = [
    "#",
    "Тип",
    "Площадь",
    "Скорость / мес",
    "Средн. площадь, м²",
    "Средн. цена, ₽/м²",
    "Срок продажи (мес)",
    "Продано, %",
    "",
  ];

  return (
    <div className="border border-gray-200 rounded-xl overflow-hidden bg-white">
      <div className="px-[18px] py-3 bg-gray-50 border-b border-gray-200 font-semibold text-[13px] text-gray-700">
        Топ планировок ({rows.length})
      </div>
      <div style={{ overflowX: "auto" }}>
        <table
          style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}
        >
          <thead>
            <tr className="bg-gray-50">
              {headers.map((h, idx) => (
                <th
                  key={idx}
                  className="px-3 py-2 text-left border-b border-gray-200 font-semibold text-gray-700 whitespace-nowrap"
                >
                  {h}
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {rows.map((row, i) => {
              const isExpanded = expandedRows.has(row.signature);
              const months = salesPeriodMonths(
                row.supply_units_in_radius,
                row.velocity_per_month,
              );
              const hasCompetitors = row.competitor_obj_ids.length > 0;

              return (
                <Fragment key={row.signature}>
                  <tr
                    onClick={() => {
                      if (hasCompetitors) toggleRow(row.signature);
                    }}
                    onKeyDown={(e) => {
                      if (
                        hasCompetitors &&
                        (e.key === "Enter" || e.key === " ")
                      ) {
                        e.preventDefault();
                        toggleRow(row.signature);
                      }
                    }}
                    tabIndex={hasCompetitors ? 0 : undefined}
                    role={hasCompetitors ? "button" : undefined}
                    aria-expanded={hasCompetitors ? isExpanded : undefined}
                    style={{
                      background:
                        i % 2 === 0 ? "var(--bg-card)" : "var(--bg-card-alt)",
                      borderBottom: isExpanded
                        ? "none"
                        : "1px solid var(--border-soft)",
                      cursor: hasCompetitors ? "pointer" : "default",
                    }}
                    title={
                      hasCompetitors
                        ? "Нажмите, чтобы увидеть ЖК-источники"
                        : undefined
                    }
                  >
                    <td
                      style={{
                        padding: "7px 12px",
                        fontWeight: 700,
                        color: "var(--accent)",
                        fontVariantNumeric: "tabular-nums",
                      }}
                    >
                      {row.rank}
                    </td>
                    <td
                      style={{
                        padding: "7px 12px",
                        fontWeight: 500,
                        color: "var(--fg-primary)",
                      }}
                    >
                      {ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket}
                    </td>
                    <td
                      style={{
                        padding: "7px 12px",
                        color: "var(--fg-secondary)",
                      }}
                    >
                      {row.area_bin} м²
                    </td>
                    <td
                      style={{
                        padding: "7px 12px",
                        color: "var(--fg-secondary)",
                        fontVariantNumeric: "tabular-nums",
                      }}
                    >
                      {row.velocity_per_month.toFixed(2)}
                    </td>
                    <td
                      style={{
                        padding: "7px 12px",
                        textAlign: "right",
                        color: "var(--fg-secondary)",
                        fontVariantNumeric: "tabular-nums",
                      }}
                    >
                      {row.avg_area_m2.toFixed(1)}
                    </td>
                    <td
                      style={{
                        padding: "7px 12px",
                        textAlign: "right",
                        color: "var(--fg-secondary)",
                        fontVariantNumeric: "tabular-nums",
                      }}
                    >
                      {row.avg_price_per_m2_rub != null
                        ? Math.round(row.avg_price_per_m2_rub).toLocaleString(
                            "ru-RU",
                          )
                        : "—"}
                    </td>
                    <td
                      style={{
                        padding: "7px 12px",
                        textAlign: "right",
                      }}
                    >
                      <SalesPeriodCell months={months} />
                    </td>
                    <td
                      style={{
                        padding: "7px 12px",
                        textAlign: "right",
                        color: "var(--fg-secondary)",
                        fontVariantNumeric: "tabular-nums",
                      }}
                    >
                      {row.sold_pct_of_supply != null ? (
                        <span
                          style={{
                            display: "inline-flex",
                            alignItems: "center",
                            gap: 4,
                          }}
                        >
                          {`${(row.sold_pct_of_supply ?? 0).toFixed(0)}%`}
                          {row.is_oversold && (
                            <span
                              style={{
                                background: "var(--warn-soft)",
                                color: "var(--warn)",
                                fontSize: 10,
                                fontWeight: 600,
                                padding: "1px 5px",
                                borderRadius: 4,
                                whiteSpace: "nowrap",
                              }}
                              title={`Сделки ${oversoldPeriodLabel} превышают текущий snapshot supply — значение обрезано до 100%`}
                            >
                              &gt;100%
                            </span>
                          )}
                        </span>
                      ) : (
                        "—"
                      )}
                    </td>
                    <td
                      style={{
                        padding: "7px 10px",
                        color: "var(--fg-tertiary)",
                        width: 24,
                      }}
                    >
                      {hasCompetitors && (
                        <span
                          style={{
                            display: "inline-block",
                            transform: isExpanded
                              ? "rotate(90deg)"
                              : "rotate(0deg)",
                            transition: "transform 0.15s ease",
                            fontSize: 14,
                            lineHeight: 1,
                            userSelect: "none",
                          }}
                        >
                          ›
                        </span>
                      )}
                    </td>
                  </tr>
                  {isExpanded && hasCompetitors && (
                    <CompetitorDrillIn
                      ids={row.competitor_obj_ids}
                      colSpan={TOTAL_COLS}
                    />
                  )}
                </Fragment>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// Issue #2177 шаг 3: компактный блок «По предложению (без темпа продаж)».
// Рендерится только при непустом supply_only_layouts. Честная семантика:
// комплексы без данных о сделках — показана структура текущего предложения.
function SupplyOnlyTable({ rows }: { rows: SupplyOnlyLayoutRow[] }) {
  if (rows.length === 0) return null;

  const headers = ["Комплекс", "Тип", "Площадь", "Лотов", "Медиана ₽/м²"];

  return (
    <div
      style={{
        border: "1px solid var(--border-card)",
        borderRadius: 12,
        overflow: "hidden",
        background: "var(--bg-card)",
      }}
    >
      <div
        style={{
          padding: "12px 18px",
          background: "var(--bg-card-alt)",
          borderBottom: "1px solid var(--border-card)",
          fontWeight: 600,
          fontSize: 13,
          color: "var(--fg-secondary)",
        }}
      >
        По предложению (без темпа продаж)
      </div>
      <div style={{ overflowX: "auto" }}>
        <table
          style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}
        >
          <thead>
            <tr>
              {headers.map((h, idx) => (
                <th
                  key={h}
                  style={{
                    padding: "7px 12px",
                    textAlign: idx >= 3 ? "right" : "left",
                    borderBottom: "1px solid var(--border-card)",
                    fontWeight: 600,
                    color: "var(--fg-tertiary)",
                    whiteSpace: "nowrap",
                    background: "var(--bg-card-alt)",
                  }}
                >
                  {h}
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {rows.map((row, i) => (
              <tr
                key={`${row.project_name}__${row.room_bucket}__${row.area_bin}`}
                style={{
                  background:
                    i % 2 === 0 ? "var(--bg-card)" : "var(--bg-card-alt)",
                  borderBottom: "1px solid var(--border-soft)",
                }}
              >
                <td
                  style={{
                    padding: "7px 12px",
                    fontWeight: 500,
                    color: "var(--fg-primary)",
                  }}
                >
                  {row.project_name}
                </td>
                <td
                  style={{ padding: "7px 12px", color: "var(--fg-secondary)" }}
                >
                  {ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket}
                </td>
                <td
                  style={{ padding: "7px 12px", color: "var(--fg-secondary)" }}
                >
                  {row.median_area_m2 != null
                    ? `${row.median_area_m2.toLocaleString("ru-RU")} м²`
                    : `${row.area_bin} м²`}
                </td>
                <td
                  style={{
                    padding: "7px 12px",
                    textAlign: "right",
                    color: "var(--fg-secondary)",
                    fontVariantNumeric: "tabular-nums",
                  }}
                >
                  {row.lots_count.toLocaleString("ru-RU")}
                </td>
                <td
                  style={{
                    padding: "7px 12px",
                    textAlign: "right",
                    color: "var(--fg-secondary)",
                    fontVariantNumeric: "tabular-nums",
                  }}
                >
                  {row.median_price_m2 != null
                    ? `${Math.round(row.median_price_m2).toLocaleString("ru-RU")} ₽/м²`
                    : "—"}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      <div
        style={{
          padding: "10px 18px",
          borderTop: "1px solid var(--border-soft)",
          fontSize: 11,
          color: "var(--fg-tertiary)",
          lineHeight: 1.5,
        }}
      >
        Комплексы без данных о сделках — показана структура текущего предложения
        (Объектив).
      </div>
    </div>
  );
}

function UnitMixBar({ mix }: { mix: LayoutTzMixRow[] }) {
  const COLORS = [
    "var(--viz-1)",
    "var(--viz-5)",
    "var(--viz-3)",
    "var(--viz-4)",
    "var(--viz-2)",
    "var(--viz-3)",
  ];

  return (
    <div>
      {/* Horizontal stacked bar */}
      <div
        style={{
          display: "flex",
          height: 24,
          borderRadius: 6,
          overflow: "hidden",
          border: "1px solid var(--border-card)",
          marginBottom: 10,
        }}
      >
        {mix.map((row, i) => (
          <div
            key={row.room_bucket}
            title={`${ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket}: ${row.pct}%`}
            style={{
              width: `${row.pct}%`,
              background: COLORS[i % COLORS.length],
              transition: "width 0.3s ease",
            }}
          />
        ))}
      </div>
      {/* Legend */}
      <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
        {mix.map((row, i) => (
          <div
            key={row.room_bucket}
            style={{ display: "flex", alignItems: "center", gap: 4 }}
          >
            <div
              style={{
                width: 10,
                height: 10,
                borderRadius: 2,
                background: COLORS[i % COLORS.length],
                flexShrink: 0,
              }}
            />
            <span style={{ fontSize: 11, color: "var(--fg-secondary)" }}>
              {ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket} {row.pct}
              %
            </span>
          </div>
        ))}
      </div>
    </div>
  );
}

function MixTable({ mix }: { mix: LayoutTzMixRow[] }) {
  return (
    <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
      <thead>
        <tr className="bg-gray-50">
          {["Тип", "Доля, %", "Кол-во квартир", "Ср. площадь, м²"].map((h) => (
            <th
              key={h}
              className="px-3 py-[7px] text-left border-b border-gray-200 font-semibold text-gray-700 whitespace-nowrap"
            >
              {h}
            </th>
          ))}
        </tr>
      </thead>
      <tbody>
        {mix.map((row, i) => (
          <tr
            key={row.room_bucket}
            style={{
              background: i % 2 === 0 ? "var(--bg-card)" : "var(--bg-card-alt)",
              borderBottom: "1px solid var(--border-soft)",
            }}
          >
            <td
              style={{
                padding: "7px 12px",
                fontWeight: 500,
                color: "var(--fg-primary)",
              }}
            >
              {ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket}
            </td>
            <td
              style={{
                padding: "7px 12px",
                fontVariantNumeric: "tabular-nums",
                color: "var(--fg-secondary)",
              }}
            >
              {row.pct}%
            </td>
            <td
              style={{
                padding: "7px 12px",
                fontVariantNumeric: "tabular-nums",
                color: "var(--fg-secondary)",
              }}
            >
              {row.abs_units != null
                ? row.abs_units.toLocaleString("ru-RU")
                : "—"}
            </td>
            <td
              style={{
                padding: "7px 12px",
                fontVariantNumeric: "tabular-nums",
                color: "var(--fg-secondary)",
              }}
            >
              {row.avg_target_area_m2 != null
                ? row.avg_target_area_m2.toFixed(1)
                : "—"}
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

function RecommendationCard({
  rec,
}: {
  rec: BestLayoutsResponse["recommendation_for_tz"];
}) {
  const maxPct =
    rec.mix.length > 0 ? Math.max(...rec.mix.map((r) => r.pct)) : 0;

  return (
    <div
      style={{
        border: "1px solid var(--border-card)",
        borderRadius: 10,
        background: "var(--bg-card)",
        overflow: "hidden",
      }}
    >
      <div className="px-[18px] py-3 bg-gray-50 border-b border-gray-200 font-semibold text-[13px] text-gray-700">
        Рекомендация ТЗ
      </div>
      <div
        style={{
          padding: "14px 18px",
          display: "flex",
          flexDirection: "column",
          gap: 16,
        }}
      >
        {/* SF-09: предупреждение о перекосе рынка — показываем только если cap не смог применить */}
        {rec.cap_skipped && (
          <div className="bg-amber-50 border border-amber-200 text-amber-800 rounded p-3 text-sm">
            Рекомендация имеет сильный перекос ({maxPct}% в одном формате) —
            рынок настолько асимметричен, что cap не применён. Проверьте
            competitive density и district pipeline.
          </div>
        )}

        {/* Rationale text — plain text only, no dangerouslySetInnerHTML */}
        <p
          style={{
            fontSize: 13,
            color: "var(--fg-secondary)",
            margin: 0,
            lineHeight: 1.6,
          }}
        >
          {rec.rationale_text}
        </p>

        {/* Unit-mix bar chart */}
        {rec.mix.length > 0 && (
          <div>
            <div
              style={{
                fontSize: 12,
                fontWeight: 600,
                color: "var(--fg-tertiary)",
                marginBottom: 8,
                textTransform: "uppercase",
                letterSpacing: "0.04em",
              }}
            >
              Unit-mix
            </div>
            <UnitMixBar mix={rec.mix} />
          </div>
        )}

        {/* Mix table */}
        {rec.mix.length > 0 && (
          <div
            style={{
              border: "1px solid var(--border-card)",
              borderRadius: 8,
              overflow: "hidden",
            }}
          >
            <MixTable mix={rec.mix} />
          </div>
        )}

        {/* Weighted avg price */}
        {rec.weighted_avg_price_per_m2_rub != null && (
          <div
            style={{
              display: "flex",
              alignItems: "center",
              gap: 8,
              padding: "10px 14px",
              background: "var(--accent-soft)",
              borderRadius: 8,
              fontSize: 13,
            }}
          >
            <span style={{ color: "var(--fg-tertiary)" }}>
              Средневзвешенная цена:
            </span>
            <span
              style={{
                fontWeight: 700,
                color: "var(--accent)",
                fontVariantNumeric: "tabular-nums",
              }}
            >
              {Math.round(rec.weighted_avg_price_per_m2_rub).toLocaleString(
                "ru-RU",
              )}{" "}
              ₽/м²
            </span>
          </div>
        )}

        {/* Meta */}
        <div style={{ fontSize: 11, color: "var(--fg-tertiary)" }}>
          Основано на {rec.based_on_obj_count} ЖК ·{" "}
          {rec.based_on_total_deals.toLocaleString("ru-RU")} сделках · период{" "}
          {new Date(rec.data_window_start).toLocaleDateString("ru-RU")} —{" "}
          {new Date(rec.data_window_end).toLocaleDateString("ru-RU")}
        </div>
      </div>
    </div>
  );
}

// ── Main component ─────────────────────────────────────────────────────────────

interface Props {
  cadNum: string;
  selectedCompetitorObjIds?: number[];
}

export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
  const [radiusKm, setRadiusKm] = useState(1.0);
  const [timeWindow, setTimeWindow] = useState<TimeWindow>("last_quarter");
  const [targetTotalFlats, setTargetTotalFlats] = useState<string>("300");
  const [minVelocity, setMinVelocity] = useState(0.5);
  const [isPdfLoading, setIsPdfLoading] = useState(false);
  const [pdfError, setPdfError] = useState<string | null>(null);
  // Request that produced the currently shown `data` — PDF must use this,
  // not live control state (controls may have changed without re-running «Рассчитать»).
  const [lastRequest, setLastRequest] = useState<BestLayoutsRequest | null>(
    null,
  );

  const { mutate, data, isPending, error } = useBestLayouts(cadNum);

  function buildRequest(): BestLayoutsRequest {
    const parsed = parseInt(targetTotalFlats, 10);
    return {
      radius_km: radiusKm,
      time_window: timeWindow,
      filter_competitor_obj_ids:
        selectedCompetitorObjIds && selectedCompetitorObjIds.length > 0
          ? selectedCompetitorObjIds
          : null,
      min_velocity_per_month: minVelocity,
      target_total_flats:
        !Number.isNaN(parsed) && parsed > 0
          ? Math.min(Math.max(parsed, 1), 10000)
          : null,
    };
  }

  function handleCalculate() {
    const req = buildRequest();
    setLastRequest(req);
    mutate(req);
  }

  async function handleDownloadPdf() {
    // Use the request that produced the shown `data`, not live control state.
    if (!lastRequest) return;
    setIsPdfLoading(true);
    try {
      const req = lastRequest;
      const res = await fetch(
        `${API_BASE_URL}/api/v1/parcels/${encodeURIComponent(cadNum)}/best-layouts/pdf`,
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(req),
        },
      );
      if (!res.ok) {
        throw new Error(`Ошибка генерации PDF: ${res.status}`);
      }
      const blob = await res.blob();
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = `tz-layout-${cadNum.replace(/:/g, "-")}-${new Date().toISOString().split("T")[0]}.pdf`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    } catch (e) {
      setPdfError(e instanceof Error ? e.message : "Не удалось скачать PDF");
    } finally {
      setIsPdfLoading(false);
    }
  }

  return (
    <div
      style={{
        border: "1px solid var(--border-card)",
        borderRadius: 12,
        background: "var(--bg-card)",
        overflow: "hidden",
      }}
    >
      {/* Header */}
      <div
        style={{
          padding: "14px 20px",
          background: "var(--bg-app)",
          borderBottom: "1px solid var(--border-card)",
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          flexWrap: "wrap",
          gap: 10,
        }}
      >
        <div>
          <span
            style={{
              fontWeight: 600,
              fontSize: 14,
              color: "var(--fg-primary)",
            }}
          >
            Анализ планировок
          </span>
          <span
            style={{ fontSize: 12, color: "var(--fg-tertiary)", marginLeft: 8 }}
          >
            data-driven ТЗ на проектирование
          </span>
        </div>
        {data && (
          <div className="flex flex-col items-end gap-1">
            <button
              onClick={() => {
                setPdfError(null);
                void handleDownloadPdf();
              }}
              disabled={isPdfLoading}
              style={{
                padding: "7px 16px",
                background: isPdfLoading
                  ? "var(--fg-tertiary)"
                  : "var(--accent)",
                color: "var(--fg-on-dark)",
                border: "none",
                borderRadius: 7,
                fontSize: 13,
                fontWeight: 500,
                cursor: isPdfLoading ? "not-allowed" : "pointer",
                whiteSpace: "nowrap",
              }}
            >
              {isPdfLoading ? "Генерация…" : "Скачать ТЗ (PDF)"}
            </button>
            {pdfError && (
              <span className="text-red-600 text-xs">PDF: {pdfError}</span>
            )}
          </div>
        )}
      </div>

      {/* Controls */}
      <div
        style={{
          padding: "16px 20px",
          borderBottom: "1px solid var(--border-soft)",
          display: "flex",
          flexWrap: "wrap",
          gap: 20,
          alignItems: "flex-end",
        }}
      >
        {/* Radius slider */}
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            gap: 4,
            minWidth: 160,
          }}
        >
          <label
            style={{
              fontSize: 12,
              color: "var(--fg-tertiary)",
              fontWeight: 500,
            }}
          >
            Радиус поиска: {radiusKm.toFixed(1)} км
          </label>
          <input
            type="range"
            min={0.1}
            max={1.5}
            step={0.1}
            value={radiusKm}
            onChange={(e) => setRadiusKm(parseFloat(e.target.value))}
            style={{ width: 160, accentColor: "var(--accent)" }}
          />
        </div>

        {/* Min velocity slider */}
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            gap: 4,
            minWidth: 160,
          }}
        >
          <label
            style={{
              fontSize: 12,
              color: "var(--fg-tertiary)",
              fontWeight: 500,
            }}
          >
            Мин. скорость: {minVelocity.toFixed(1)} кв/мес
          </label>
          <input
            type="range"
            min={0}
            max={5}
            step={0.1}
            value={minVelocity}
            onChange={(e) => setMinVelocity(parseFloat(e.target.value))}
            style={{ width: 160, accentColor: "var(--accent)" }}
          />
        </div>

        {/* Time window radio */}
        <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
          <span
            style={{
              fontSize: 12,
              color: "var(--fg-tertiary)",
              fontWeight: 500,
            }}
          >
            Период анализа
          </span>
          <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
            {(Object.keys(TIME_WINDOW_LABELS) as TimeWindow[]).map((tw) => (
              <label
                key={tw}
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: 4,
                  fontSize: 12,
                  cursor: "pointer",
                  color:
                    timeWindow === tw ? "var(--accent)" : "var(--fg-secondary)",
                  fontWeight: timeWindow === tw ? 600 : 400,
                }}
              >
                <input
                  type="radio"
                  name="time-window"
                  value={tw}
                  checked={timeWindow === tw}
                  onChange={() => setTimeWindow(tw)}
                  style={{ accentColor: "var(--accent)" }}
                />
                {TIME_WINDOW_LABELS[tw]}
              </label>
            ))}
          </div>
        </div>

        {/* Target flats input */}
        <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
          <label
            style={{
              fontSize: 12,
              color: "var(--fg-tertiary)",
              fontWeight: 500,
            }}
          >
            Целевой объём (квартир)
          </label>
          <input
            type="number"
            min={1}
            max={10000}
            value={targetTotalFlats}
            onChange={(e) => setTargetTotalFlats(e.target.value)}
            placeholder="300"
            style={{
              padding: "5px 10px",
              border: "1px solid var(--border-strong)",
              borderRadius: 6,
              fontSize: 13,
              width: 110,
              color: "var(--fg-primary)",
            }}
          />
        </div>

        {/* Calculate button */}
        <button
          onClick={handleCalculate}
          disabled={isPending}
          style={{
            padding: "7px 20px",
            background: isPending ? "var(--fg-tertiary)" : "var(--accent)",
            color: "var(--fg-on-dark)",
            border: "none",
            borderRadius: 7,
            fontSize: 13,
            fontWeight: 600,
            cursor: isPending ? "not-allowed" : "pointer",
            whiteSpace: "nowrap",
            alignSelf: "flex-end",
          }}
        >
          {isPending ? "Расчёт…" : "Рассчитать"}
        </button>

        {selectedCompetitorObjIds && selectedCompetitorObjIds.length > 0 && (
          <span
            style={{
              fontSize: 11,
              color: "var(--accent)",
              background: "var(--accent-soft)",
              padding: "3px 8px",
              borderRadius: 4,
              alignSelf: "flex-end",
              marginBottom: 2,
            }}
          >
            Фильтр: {selectedCompetitorObjIds.length} выбр. ЖК
          </span>
        )}
      </div>

      {/* Content area */}
      <div style={{ padding: "16px 20px" }}>
        {/* Loading skeleton */}
        {isPending && (
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            {[80, 60, 40].map((w) => (
              <div
                key={w}
                style={{
                  height: 18,
                  borderRadius: 6,
                  background: "var(--bg-card-alt)",
                  width: `${w}%`,
                  animation: "pulse 1.5s ease-in-out infinite",
                }}
              />
            ))}
          </div>
        )}

        {/* Error */}
        {error && !isPending && (
          <div
            style={{
              padding: "12px 16px",
              background: "var(--danger-soft)",
              border: "1px solid var(--danger-soft)",
              borderRadius: 8,
              color: "var(--danger)",
              fontSize: 13,
            }}
          >
            {error instanceof Error ? error.message : "Ошибка получения данных"}
          </div>
        )}

        {/* Results */}
        {data && !isPending && (
          <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
            <DataQualityCard dq={data.data_quality} />
            <TopLayoutsTable
              rows={data.top_layouts}
              oversoldPeriodLabel={
                TIME_WINDOW_OVERSOLD_PERIOD[
                  (lastRequest ?? buildRequest()).time_window
                ]
              }
            />
            {data.supply_only_layouts.length > 0 && (
              <SupplyOnlyTable rows={data.supply_only_layouts} />
            )}
            <RecommendationCard rec={data.recommendation_for_tz} />
          </div>
        )}

        {/* Idle state */}
        {!isPending && !error && !data && (
          <div
            style={{
              padding: "24px 0",
              textAlign: "center",
              color: "var(--fg-tertiary)",
              fontSize: 13,
            }}
          >
            Настройте параметры и нажмите «Рассчитать»
          </div>
        )}
      </div>
    </div>
  );
}
