"use client";

/**
 * Section6Forecast — "6. Прогноз" (958-B3 / §22).
 *
 * Renders the async §22 demand/supply forecast, scenarios and confidence on the
 * Site Finder analysis screen. Self-fetches via useParcelForecastQuery, which
 * POLLs GET /api/v1/parcels/{cad}/forecast every 4s until status === "ready"
 * (the forecast is enqueued by useParcelAnalyzeQuery on page mount — render-only
 * here, no trigger).
 *
 * Layout mirrors Section1-5: dark HeadlineBar + section root id="section-6".
 *   exec_summary banner (headline + verdict + KPI row)
 *   6.1 Прогноз по горизонтам    → ForecastHorizonsBlock
 *   6.2 Сценарии                 → ScenariosBlock
 *   6.3 Уверенность              → ForecastConfidenceBlock
 *   6.4 Рекомендация по продукту → ForecastProductTzBlock (что строить — §13.4)
 *   6.5 Прозрачность скоринга    → ForecastScoringBlock (почему скор — §13.6)
 *   6.6 Будущее предложение и конкуренты → ForecastFutureSupplyBlock
 *       (§16-evidence за прогнозом предложения — §9.3 давление + §9.7 конкуренты)
 *   advisory disclaimer (footer)
 */

import { AlertTriangle, LineChart } from "lucide-react";

import { HeadlineBar } from "@/components/ui/HeadlineBar";
import { KpiCard } from "@/components/site-finder/KpiCard";
import { useParcelForecastQuery } from "@/lib/site-finder-api";
import type { ForecastReport } from "@/types/forecast";

import { ForecastChart } from "./ForecastChart";
import { ForecastHorizonsBlock } from "./ForecastHorizonsBlock";
import { ScenariosBlock } from "./ScenariosBlock";
import { ForecastConfidenceBlock } from "./ForecastConfidenceBlock";
import { ForecastProductTzBlock } from "./ForecastProductTzBlock";
import { ForecastAssortmentBlock } from "./ForecastAssortmentBlock";
import { ForecastScoringBlock } from "./ForecastScoringBlock";
import { ForecastFutureSupplyBlock } from "./ForecastFutureSupplyBlock";
import { PermitsNearbyBlock } from "./PermitsNearbyBlock";
import { ForecastMetaLine } from "./ForecastMetaLine";
import { ForecastExportButtons } from "./ForecastExportButtons";
import { StageDetails } from "./StageDetails";
import {
  CONFIDENCE_RU,
  deficitPegState,
  deficitVariant,
  fmtNum,
  hasSupplySignal,
} from "./forecast-helpers";

interface Props {
  cad: string;
  /**
   * Horizon chosen in the analysis-screen HorizonSelector (6 / 12 / 18 / 24). When
   * set it takes priority over the report-derived horizon so the user's choice
   * instantly drives which horizon is treated as the target (client-side), even
   * before the recomputed forecast finishes (see AnalysisPageContent comment).
   */
  selectedHorizon?: number;
  /**
   * Сырой `permits_nearby` из /analyze (#2367) — разрешения РС/ввод в
   * эксплуатацию в радиусе 500 м. Приходит из analyze-payload (НЕ из forecast-
   * эндпоинта, который self-fetch'ит эта секция), поэтому прокидывается пропом из
   * AnalysisPageContent. `unknown` — backend держит его generic-объектом; форму
   * narrow'ит PermitsNearbyBlock. Рендерится ПЕРВЫМ в секции §6, ДО forecast-
   * контента и НЕЗАВИСИМО от статуса §22-прогноза (данные /analyze уже загружены,
   * не должны ждать медленный несвязанный прогноз 30-180с).
   */
  permitsNearby?: unknown;
}

const ADVISORY_DISCLAIMER =
  "Оценка advisory — не основание для инвест-решения.";

const SCROLL_MARGIN = 72;

// KpiCard color enum (site-finder variant) mapped from Badge variant words.
function kpiColorForDeficit(deficitIndex: number): "green" | "red" | "neutral" {
  const v = deficitVariant(deficitIndex);
  if (v === "success") return "green";
  if (v === "danger") return "red";
  return "neutral";
}

// ── Section6Forecast ─────────────────────────────────────────────────────────

export function Section6Forecast({
  cad,
  selectedHorizon,
  permitsNearby,
}: Props) {
  const { data, isLoading, error } = useParcelForecastQuery(cad);

  const isPending =
    isLoading || (data != null && data.status !== "ready") || data == null;

  // permits_nearby (#2367) приходит из /analyze (быстрый, уже загружен к этому
  // рендеру), а НЕ из forecast-эндпоинта, который эта секция self-fetch'ит и
  // который на холодном участке считается 30-180с. Рендерим блок разрешений
  // ПЕРВЫМ и БЕЗУСЛОВНО — он не должен ждать §22-прогноз (юзер видит данные,
  // которые уже есть). Дальше — обычная pending/error/ready-логика прогноза.
  const permits = <PermitsNearbyBlock permitsNearby={permitsNearby} />;

  // ── Pending: forecast still computing (poll in flight) ─────────────────────
  if (isPending && !error) {
    return (
      <section id="section-6" style={{ scrollMarginTop: SCROLL_MARGIN }}>
        {permits}
        <HeadlineBar
          title="6. Прогноз"
          subtitle="Прогноз спроса, предложения и сценарии (§22)."
        />
        <div
          style={{
            marginTop: 12,
            display: "flex",
            flexDirection: "column",
            gap: 12,
          }}
        >
          <div
            style={{
              borderRadius: 12,
              background: "var(--bg-card-alt)",
              border: "1px solid var(--border-soft)",
              padding: "24px 20px",
              textAlign: "center",
              color: "var(--fg-tertiary)",
              fontSize: 13,
            }}
          >
            Прогноз рассчитывается…
          </div>
          <div
            style={{
              display: "grid",
              gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
              gap: 12,
            }}
          >
            {[0, 1, 2].map((i) => (
              <div
                key={i}
                style={{
                  borderRadius: 12,
                  background: "var(--bg-card-alt)",
                  border: "1px solid var(--border-soft)",
                  height: 88,
                }}
                aria-hidden
              />
            ))}
          </div>
        </div>
      </section>
    );
  }

  // ── Error ──────────────────────────────────────────────────────────────────
  if (error || !data || data.status !== "ready" || !data.report) {
    return (
      <section id="section-6" style={{ scrollMarginTop: SCROLL_MARGIN }}>
        {permits}
        <HeadlineBar title="6. Прогноз" />
        <div
          style={{
            marginTop: 12,
            borderRadius: 12,
            padding: "32px 24px",
            background: "var(--bg-card-alt)",
            border: "1px dashed var(--border-strong)",
            textAlign: "center",
            color: "var(--fg-tertiary)",
            fontSize: 13,
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
            gap: 8,
          }}
        >
          <LineChart size={20} aria-hidden />
          Данные прогноза недоступны
        </div>
      </section>
    );
  }

  return (
    <ForecastReady
      cad={cad}
      report={data.report}
      runCreatedAt={data.created_at}
      selectedHorizon={selectedHorizon}
      permits={permits}
    />
  );
}

// ── ForecastReady (report present) ───────────────────────────────────────────

function ForecastReady({
  cad,
  report,
  runCreatedAt,
  selectedHorizon,
  permits,
}: {
  cad: string;
  report: ForecastReport;
  /** Envelope `created_at` — the run's persist time, the real «когда рассчитан». */
  runCreatedAt?: string | null;
  selectedHorizon?: number;
  /**
   * #2367 — готовый <PermitsNearbyBlock> (собран в Section6Forecast из
   * analyze-payload). Рендерится ПЕРВЫМ в секции, до forecast-контента, чтобы
   * не зависеть от статуса §22-прогноза (данные /analyze уже есть).
   */
  permits: React.ReactNode;
}) {
  const exec = report.exec_summary;
  const fm = report.future_market;

  // Target horizon: the user's HorizonSelector choice wins (instant client-side
  // highlight), else future_supply.horizon_months, else median of meta horizons,
  // else 12.
  const targetHorizon =
    selectedHorizon ??
    fm.future_supply?.horizon_months ??
    medianHorizon(report.meta.horizons) ??
    12;

  const kn = exec.key_numbers;

  const hasHorizons = fm.forecasts_by_horizon.length > 0;
  const hasScenarios =
    Object.keys(report.scenarios.by_scenario).length > 0 ||
    fm.scenarios_summary != null;
  const hasConfidence =
    report.confidence.level != null ||
    Object.keys(report.confidence.factors).length > 0;

  // 6.4 — рекомендация продукта (§13.4). product_tz is optional on the partial
  // ForecastReport type; treat absent/thin section as "nothing to show".
  const pt = report.product_tz;
  const hasProductTz =
    pt != null &&
    (pt.obj_class != null ||
      pt.mix.length > 0 ||
      pt.usp.length > 0 ||
      pt.reasons.length > 0 ||
      !!pt.summary ||
      (pt.commercial != null &&
        (pt.commercial.available != null || !!pt.commercial.caveat)));

  // 6.4a — прогноз по всем форматам (#1745). Показываем, когда квартирография
  // несёт хотя бы одну форматную ячейку (с bucket) рекомендованного класса —
  // иначе таблица пуста (объект класс не задан → деградируем к любым ячейкам).
  const hasAssortment =
    pt != null &&
    pt.mix.some(
      (m) =>
        m.bucket != null &&
        (pt.obj_class == null || m.obj_class === pt.obj_class),
    );

  // 6.5 — прозрачность скоринга (§13.6). scoring is optional on the partial
  // ForecastReport type; populated when product_scores / special_indices exist.
  const sc = report.scoring;
  const hasScoring =
    sc != null &&
    ((sc.product_scores != null &&
      Object.keys(sc.product_scores.scores).length > 0) ||
      (sc.special_indices != null &&
        Object.keys(sc.special_indices.indices).length > 0));

  // 6.6 — будущее предложение и конкуренты (§13.3 evidence). future_supply is
  // shown only when it carries a genuinely-computed pressure signal (see
  // hasSupplySignal — gates on computed metrics, NOT open/hidden stock which are
  // 0-not-null when supply_layers is unloaded); future_competitors when the list
  // is non-empty. The per-horizon 6.1 table stays the aggregate; this is the detail.
  const hasFutureSupplyDetail =
    hasSupplySignal(fm.future_supply) || fm.future_competitors.length > 0;

  const hasAny =
    hasHorizons ||
    hasScenarios ||
    hasConfidence ||
    hasProductTz ||
    hasAssortment ||
    hasScoring ||
    hasFutureSupplyDetail;

  // B1 (#1958/#1959) — pegged-signal honesty-guard. deficit_index упирается в край
  // шкалы (|value| ≥ 0.99) на ВСЕХ горизонтах того же знака → «−1.00 ×4» не
  // различает горизонты. Считаем ОДИН раз тут (источник — общий ряд горизонтов) и
  // прокидываем в 6.1 (таблица) и 6.2 (сценарии), чтобы плашки не разъезжались.
  const peg = deficitPegState(fm.forecasts_by_horizon);

  // B2 — gate-caveat участка (см. блокеры: «Нельзя строить МКД»). Бэкенд проставляет
  // product_tz.gate_caveat, когда жильё под запретом → весь §6-прогноз справочный.
  // Поднимаем его на уровень ВСЕЙ секции (был бы закопан в 6.4) — заметная плашка.
  const gateCaveat = pt?.gate_caveat?.trim() || null;

  // Headline subtitle = future_market summary (one sentence «откуда / что значит»).
  const subtitle = fm.summary ?? undefined;

  return (
    <section id="section-6" style={{ scrollMarginTop: SCROLL_MARGIN }}>
      {/* Разрешения на строительство поблизости (#2367) — ПЕРВЫМ в секции, до
          прогноза: приходит из /analyze (уже загружен), не ждёт §22-прогноз. */}
      {permits}
      <HeadlineBar title={exec.headline ?? "6. Прогноз"} subtitle={subtitle} />

      <div
        style={{
          marginTop: 12,
          display: "flex",
          flexDirection: "column",
          gap: 24,
        }}
      >
        {/* B2 — участок не под жильё: прогноз спроса/предложения справочный.
            Заметная плашка В ВЕРХУ всего §6 (выше KPI), а не закопанная в 6.4. */}
        {gateCaveat && <NonResidentialCaveat caveat={gateCaveat} />}

        {/* Export / share forecast report (EPIC #959) */}
        <ForecastExportButtons cad={cad} />

        {/* exec_summary verdict + KPI row */}
        {exec.verdict && (
          <p
            style={{
              margin: 0,
              fontSize: 14,
              lineHeight: 1.5,
              color: "var(--fg-primary)",
            }}
          >
            {exec.verdict}
          </p>
        )}

        <div
          style={{
            display: "grid",
            gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
            gap: 12,
          }}
        >
          <KpiCard
            label="Индекс дефицита"
            value={
              kn.deficit_index != null
                ? `${kn.deficit_index > 0 ? "+" : ""}${fmtNum(kn.deficit_index, 2)}`
                : "—"
            }
            color={
              kn.deficit_index != null
                ? kpiColorForDeficit(kn.deficit_index)
                : "neutral"
            }
            caption="шкала от −1 до +1"
          />
          <KpiCard
            label="Мес. предложения (MOI)"
            value={
              kn.months_of_inventory != null
                ? fmtNum(kn.months_of_inventory, 1)
                : "—"
            }
            color="neutral"
            caption="за сколько месяцев район распродаст текущее предложение"
          />
          <KpiCard
            label="Итоговый скор"
            value={kn.overall_score != null ? fmtNum(kn.overall_score, 2) : "—"}
            color="blue"
          />
          <KpiCard
            label="Уверенность"
            value={
              (exec.overall_confidence ?? kn.confidence) != null
                ? CONFIDENCE_RU[
                    (exec.overall_confidence ?? kn.confidence) as
                      "high" | "medium" | "low"
                  ]
                : "—"
            }
            color={confidenceKpiColor(exec.overall_confidence ?? kn.confidence)}
          />
        </div>

        {/* Легенда индекса дефицита (#1963) — что значит −1 / 0 / +1 + трактовка. */}
        <DeficitLegend />

        {!hasAny && (
          <div
            style={{
              borderRadius: 12,
              padding: "24px 20px",
              background: "var(--bg-card-alt)",
              border: "1px dashed var(--border-strong)",
              textAlign: "center",
              color: "var(--fg-tertiary)",
              fontSize: 13,
            }}
          >
            Данные прогноза недоступны
          </div>
        )}

        <StageDetails>
          {/* Траектория индекса дефицита (chart-then-table, выше 6.1). Причину
            схлопывания сценариев показываем в 6.2 под заголовком «Почему один
            сценарий, а не три» (#1963) — здесь дубль убран. */}
          <ForecastChart report={report} selectedHorizon={selectedHorizon} />

          {/* 6.1 Прогноз по горизонтам */}
          {hasHorizons && (
            <SubBlock id="section-6-1" title="6.1 Прогноз по горизонтам">
              <ForecastHorizonsBlock
                forecasts={fm.forecasts_by_horizon}
                targetHorizon={targetHorizon}
                peg={peg}
              />
            </SubBlock>
          )}

          {/* 6.2 Сценарии */}
          {hasScenarios && (
            <SubBlock id="section-6-2" title="6.2 Сценарии">
              <ScenariosBlock
                scenarios={report.scenarios}
                scenariosSummary={fm.scenarios_summary}
                targetHorizon={targetHorizon}
                peg={peg}
              />
            </SubBlock>
          )}

          {/* 6.3 Уверенность */}
          {hasConfidence && (
            <SubBlock id="section-6-3" title="6.3 Уверенность">
              <ForecastConfidenceBlock confidence={report.confidence} />
            </SubBlock>
          )}

          {/* 6.4 Рекомендация по продукту — что строить на участке (§13.4) */}
          {hasProductTz && pt && (
            <SubBlock id="section-6-4" title="6.4 Рекомендация по продукту">
              <ForecastProductTzBlock productTz={pt} />
            </SubBlock>
          )}

          {/* 6.4a Прогноз по всем форматам — таблица ассортимента (#1745, фаза 1).
            Под рекомендацией: весь срез дефицита по форматам рекомендованного
            класса. Показываем только когда есть форматные ячейки квартирографии. */}
          {hasAssortment && pt && (
            <SubBlock id="section-6-4a" title="6.4 Прогноз по всем форматам">
              <ForecastAssortmentBlock objClass={pt.obj_class} mix={pt.mix} />
            </SubBlock>
          )}

          {/* 6.5 Прозрачность скоринга — почему итоговый скор такой (§13.6) */}
          {hasScoring && sc && (
            <SubBlock id="section-6-5" title="6.5 Прозрачность скоринга">
              <ForecastScoringBlock scoring={sc} />
            </SubBlock>
          )}

          {/* 6.6 Будущее предложение и конкуренты — §16-evidence за прогнозом (§13.3) */}
          {hasFutureSupplyDetail && (
            <SubBlock
              id="section-6-6"
              title="6.6 Будущее предложение и конкуренты"
            >
              <ForecastFutureSupplyBlock
                futureSupply={fm.future_supply}
                futureCompetitors={fm.future_competitors}
              />
            </SubBlock>
          )}

          {/* Freshness / traceability: когда рассчитан + горизонты + версия схемы */}
          <div
            style={{
              display: "flex",
              flexDirection: "column",
              gap: 6,
              paddingTop: 12,
              borderTop: "1px solid var(--border-soft)",
            }}
          >
            <ForecastMetaLine meta={report.meta} runCreatedAt={runCreatedAt} />

            {/* Advisory disclaimer (всегда последним) */}
            <p
              style={{
                margin: 0,
                fontSize: 12,
                lineHeight: 1.5,
                color: "var(--fg-tertiary)",
              }}
            >
              {ADVISORY_DISCLAIMER}
            </p>
          </div>
        </StageDetails>
      </div>
    </section>
  );
}

// ── NonResidentialCaveat — участок не под жильё (B2) ──────────────────────────
//
// Заметная warn-плашка В ВЕРХУ §6: бэкенд проставил product_tz.gate_caveat, когда
// gate=«Нельзя строить МКД». Весь прогноз спроса/предложения тогда — справочный
// (считается по сегменту рынка, но строить жильё на участке нельзя). Паттерн
// --warn-soft / role="note" как у СЗЗ-баннера / VelocityBlock-caveat; иконка
// Lucide (НЕ emoji), токены с fallback. `caveat` — готовый RU-текст от бэкенда;
// под ним — наша микрокопия-следствие «прогноз носит справочный характер».

function NonResidentialCaveat({ caveat }: { caveat: string }) {
  return (
    <div
      role="note"
      style={{
        display: "flex",
        gap: 10,
        alignItems: "flex-start",
        padding: "12px 16px",
        borderRadius: 12,
        background: "var(--warn-soft, #FEF3C7)",
        border: "1px solid var(--warn, #9A6700)",
      }}
    >
      <AlertTriangle
        size={18}
        aria-hidden
        style={{ flexShrink: 0, marginTop: 1, color: "var(--warn, #9A6700)" }}
      />
      <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
        <span
          style={{
            fontSize: 13,
            fontWeight: 600,
            lineHeight: 1.5,
            color: "var(--warn, #9A6700)",
          }}
        >
          Участок не предназначен под жильё (см. блокеры) — прогноз спроса и
          предложения носит справочный характер.
        </span>
        <span
          style={{
            fontSize: 12,
            lineHeight: 1.5,
            color: "var(--fg-secondary)",
          }}
        >
          {caveat}
        </span>
      </div>
    </div>
  );
}

// ── DeficitLegend — что значит шкала −1…+1 + связь с MOI (#1963) ─────────────
//
// Плоская строка-легенда под KPI: финдиректор видит «−0.42» / «116.6 мес» без
// объяснения. Расшифровываем три точки шкалы простым языком + actionable-трактовку
// и привязываем MOI к индексу. Только токены, без хардкод-цвета.

function DeficitLegend() {
  const items: { sign: string; color: string; text: string }[] = [
    {
      sign: "−1",
      color: "var(--danger)",
      text: "затоварка: предложения больше спроса — выходить осторожно, держать темп продаж",
    },
    {
      sign: "0",
      color: "var(--fg-secondary)",
      text: "баланс: спрос и предложение сопоставимы",
    },
    {
      sign: "+1",
      color: "var(--success)",
      text: "острый дефицит: спрос недозакрыт — окно для выхода, возможна премия к цене",
    },
  ];
  return (
    <div
      style={{
        borderRadius: 12,
        border: "1px solid var(--border-soft)",
        background: "var(--bg-card-alt)",
        padding: "12px 16px",
        display: "flex",
        flexDirection: "column",
        gap: 8,
      }}
    >
      <span
        style={{
          fontSize: 11,
          fontWeight: 500,
          letterSpacing: "0.04em",
          textTransform: "uppercase",
          color: "var(--fg-secondary)",
        }}
      >
        Как читать индекс дефицита (шкала −1…+1)
      </span>
      <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
        {items.map((it) => (
          <div
            key={it.sign}
            style={{ display: "flex", alignItems: "baseline", gap: 8 }}
          >
            <span
              style={{
                fontSize: 13,
                fontWeight: 600,
                color: it.color,
                minWidth: 24,
                fontVariantNumeric: "tabular-nums",
              }}
            >
              {it.sign}
            </span>
            <span
              style={{
                fontSize: 12,
                lineHeight: 1.5,
                color: "var(--fg-secondary)",
              }}
            >
              {it.text}
            </span>
          </div>
        ))}
      </div>
      <span
        style={{ fontSize: 12, lineHeight: 1.5, color: "var(--fg-tertiary)" }}
      >
        «Мес. предложения» (MOI) — за сколько месяцев район распродаст текущее
        предложение при нынешнем темпе продаж: чем больше месяцев, тем сильнее
        затоварка и ниже индекс.
      </span>
    </div>
  );
}

// ── SubBlock wrapper (6.1 / 6.2 / 6.3) ───────────────────────────────────────

function SubBlock({
  id,
  title,
  children,
}: {
  id: string;
  title: string;
  children: React.ReactNode;
}) {
  return (
    <div id={id} style={{ scrollMarginTop: SCROLL_MARGIN }}>
      <h3
        style={{
          margin: "0 0 12px",
          fontSize: 14,
          fontWeight: 600,
          color: "var(--fg-primary)",
        }}
      >
        {title}
      </h3>
      <div
        style={{
          borderRadius: 12,
          border: "1px solid var(--border-card)",
          background: "var(--bg-card)",
          padding: 20,
        }}
      >
        {children}
      </div>
    </div>
  );
}

// ── Helpers ──────────────────────────────────────────────────────────────────

function medianHorizon(horizons: number[]): number | null {
  if (horizons.length === 0) return null;
  const sorted = [...horizons].sort((a, b) => a - b);
  return sorted[Math.floor(sorted.length / 2)];
}

function confidenceKpiColor(
  level: "high" | "medium" | "low" | null,
): "green" | "amber" | "red" | "neutral" {
  if (level === "high") return "green";
  if (level === "medium") return "amber";
  if (level === "low") return "red";
  return "neutral";
}
