"use client";

/**
 * MassingEconomics — LIVE financial KPI strip for «7. Концепция» (#1965 Stage 2b,
 * epic #1953).
 *
 * Driven by the interactive 3D MassingScene: every time the user drags the
 * этажность / секций sliders, Section7Concept maps the scene's `computeModel`
 * result + the analysis context into a `MassingProgram` and hands it here via
 * `program`. We POST it to `/api/v1/concepts/recompute` (debounced ~250 ms) and
 * render the recomputed ТЭП + финмодель (NPV / IRR / выручка / себестоимость /
 * прибыль / ROI).
 *
 * Robustness:
 *   • debounce — slider drags fire many programs; only the settled one is sent.
 *   • latest-wins — an in-flight request is superseded by a newer one via a
 *     monotonic request id; a stale response is dropped, never overwriting a
 *     fresher result (mutateAsync + id guard, no UI flicker from out-of-order).
 *   • last-good — on a failed recompute we keep the last successful values and
 *     show a subtle inline note rather than blanking the panel.
 *   • skeleton — a plain grey fade KPI grid while the FIRST recompute is in
 *     flight (no shimmer, per ui-conventions).
 *
 * Light-theme only (Section7 is light): the 3D viewport stays dark-canvas, but
 * this strip uses the light KPI tokens via the shared KpiCard.
 */

import { useEffect, useRef, useState } from "react";
import { AlertTriangle } from "lucide-react";

import { KpiCard } from "@/components/analytics/KpiCard";
import { Section } from "@/components/analytics/Section";
import {
  priceSourceCaption,
  useRecomputeMassing,
  type FinancialModel,
  type MassingProgram,
  type MassingRecomputeOutput,
  type Teap,
} from "@/lib/concept-api";

const DEBOUNCE_MS = 250;

// ── Formatters (ru microcopy, shared shape with ConceptVariantsResult) ─────────

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));
}

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

function formatFar(far: number): string {
  return far.toLocaleString("ru-RU", {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });
}

// ── KPI grid ───────────────────────────────────────────────────────────────────

interface KpiGridProps {
  teap: Teap;
  financial: FinancialModel;
  /** Регламентная КСИТ-цель (max_far) — to flag the КСИТ over-cap. */
  farTarget: number;
  /** True → факт-КСИТ превышает регламентный потолок (model.over). */
  ksitOver: boolean;
  /** Dim the strip while a fresher recompute is in flight (last-good values). */
  stale: boolean;
}

function KpiGrid({
  teap,
  financial,
  farTarget,
  ksitOver,
  stale,
}: KpiGridProps) {
  const netPositive =
    financial.net_profit_rub > 0
      ? true
      : financial.net_profit_rub < 0
        ? false
        : null;

  return (
    <div
      style={{
        opacity: stale ? 0.55 : 1,
        transition: "opacity 150ms linear",
      }}
    >
      {/* ТЭП */}
      <div
        style={{
          display: "grid",
          gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
          gap: 12,
        }}
      >
        <KpiCard
          label="Общая площадь (GFA)"
          value={formatInt(teap.total_floor_area_sqm)}
          unit="м²"
          hint="Поэтажная площадь всех корпусов = пятно застройки × этажность."
        />
        <KpiCard
          label="Продаваемая площадь"
          value={formatInt(teap.residential_area_sqm)}
          unit="м²"
          hint="Жилая к продаже = (GFA − нежилой 1-й этаж) × коэффициент эффективности класса."
        />
        <KpiCard
          label="Квартир"
          value={formatInt(teap.apartments_count)}
          unit="шт"
          hint="Продаваемая площадь ÷ средняя площадь квартиры класса."
        />
        <KpiCard
          label="КСИТ — факт / цель"
          value={`${formatFar(teap.density)} / ${formatFar(farTarget)}`}
          delta={{
            value: ksitOver
              ? "Факт превышает регламентный потолок"
              : "В пределах регламента",
            positive: ksitOver ? false : true,
          }}
          hint="КСИТ (коэффициент строительного использования) = надземная GFA ÷ площадь участка. Цель — предельный max_far по регламенту НСПД."
        />
      </div>

      {/* Финмодель */}
      <div
        style={{
          display: "grid",
          gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
          gap: 12,
          marginTop: 12,
        }}
      >
        <KpiCard
          label="Выручка (GDV)"
          value={formatMoneyCompact(financial.revenue_rub)}
          hint="Продаваемая площадь × цена продажи м² (+ машиноместа и нежилой 1-й этаж по ценам класса)."
        />
        <KpiCard
          label="Себестоимость"
          value={formatMoneyCompact(financial.cost_rub)}
          hint="Строительство (GFA × удельная по классу) + ПИР, сети, услуги застройщика, непредвиденные, маркетинг + стоимость земли."
        />
        <KpiCard
          label="Чистая прибыль"
          value={formatMoneyCompact(financial.net_profit_rub)}
          delta={{
            value:
              netPositive === true
                ? "Положительная (после НДС и налога на прибыль)"
                : netPositive === false
                  ? "Отрицательная (после НДС и налога на прибыль)"
                  : "Нулевая",
            positive: netPositive,
          }}
          hint="Выручка − себестоимость − НДС на нежилое − налог на прибыль 25%. Жильё по ДДУ от НДС освобождено."
        />
        <KpiCard
          label="ROI на затраты"
          value={formatPct(financial.roi)}
          delta={{
            value: `Чистая маржа на выручку ${formatPct(financial.margin_pct)}`,
            positive: null,
          }}
          hint="Чистая прибыль ÷ себестоимость. Маржа считается от выручки."
        />
        <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,
          }}
          hint={`Сумма дисконтированных помесячных денежных потоков по графику стройки и продаж (ставка дисконта ${formatPct(
            financial.discount_rate_used,
          )} годовых).`}
        />
        <KpiCard
          label="IRR (DCF, годовой)"
          value={formatPct(financial.irr)}
          delta={{
            value: financial.irr_is_proxy
              ? "Оценочный (вырожденный поток, не DCF)"
              : "Дисконтированный денежный поток",
            positive: financial.irr_is_proxy
              ? null
              : financial.irr > financial.discount_rate_used
                ? true
                : false,
          }}
          hint="Годовая внутренняя ставка доходности тех же денежных потоков (ставка, при которой NPV = 0)."
        />
        <KpiCard
          label="Цена продажи жилья"
          value={`${formatInt(financial.price_per_sqm_used)} ₽/м²`}
          delta={{ value: priceSourceCaption(financial), positive: null }}
          hint="Цена, заложенная в выручку: медиана объявлений Objective по району (источник указан выше)."
        />
      </div>
    </div>
  );
}

// ── Skeleton (grey fade, no shimmer — ui-conventions) ──────────────────────────

function SkeletonGrid() {
  const cells = Array.from({ length: 7 });
  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
        gap: 12,
      }}
      aria-hidden="true"
    >
      {cells.map((_, i) => (
        <div
          key={i}
          style={{
            height: 92,
            background: "var(--bg-card-alt)",
            border: "1px solid var(--border-card)",
            borderRadius: 12,
          }}
        />
      ))}
    </div>
  );
}

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

interface Props {
  /**
   * The current massing program (Σ footprint × floors + context), mapped by
   * Section7Concept from the 3D scene's computeModel result. `null` until the
   * scene has fired its first onModelChange (or when geometry is missing).
   */
  program: MassingProgram | null;
  /** Регламентная КСИТ-цель (max_far) — for the факт/цель comparison + over-cap. */
  farTarget: number;
  /** True when факт-КСИТ exceeds the cap (model.over, computed scene-side). */
  ksitOver: boolean;
  /**
   * True when the parcel is regulatorily constrained for МКД (gate-blocked /
   * non-residential / ЗОУИТ-СЗЗ) — drives the honest «условный расчёт» caveat
   * below the strip alongside the negative-economics case.
   */
  gateConstrained?: boolean;
}

export function MassingEconomics({
  program,
  farTarget,
  ksitOver,
  gateConstrained = false,
}: Props) {
  const recompute = useRecomputeMassing();

  // Last successful result kept locally so a failed/stale recompute never blanks
  // the panel (last-good values stay on screen).
  const [result, setResult] = useState<MassingRecomputeOutput | null>(null);
  const [errored, setErrored] = useState(false);

  // Monotonic request id → latest-wins: a response is applied only if it belongs
  // to the most recently issued request, so out-of-order arrivals are dropped.
  const reqIdRef = useRef(0);
  const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  // Stable JSON key so we only recompute when the program actually changes
  // (not on every parent re-render that hands an equal-but-new object).
  const programKey = program ? JSON.stringify(program) : null;

  useEffect(() => {
    if (!program) return;

    if (debounceRef.current) clearTimeout(debounceRef.current);
    debounceRef.current = setTimeout(() => {
      const id = ++reqIdRef.current;
      recompute
        .mutateAsync(program)
        .then((out) => {
          if (id !== reqIdRef.current) return; // stale — a newer request won.
          setResult(out);
          setErrored(false);
        })
        .catch(() => {
          if (id !== reqIdRef.current) return; // stale failure — ignore.
          setErrored(true); // keep last-good `result`.
        });
    }, DEBOUNCE_MS);

    return () => {
      if (debounceRef.current) clearTimeout(debounceRef.current);
    };
    // recompute is a stable mutation object; we key off the serialized program.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [programKey]);

  // No program yet → nothing to show (parent gates this on geometry anyway).
  if (!program) return null;

  // First recompute in flight, no last-good value yet → skeleton.
  if (!result) {
    return (
      <Section
        title="Экономика по 3D-модели"
        subtitle="Пересчёт ТЭП и финмодели по текущей массе застройки (этажность / секции) из 3D-модели слева."
      >
        <SkeletonGrid />
      </Section>
    );
  }

  // A fresher request is in flight over the last-good values.
  const stale = recompute.isPending;

  // Honest «условный расчёт» caveat: when the economics turn negative OR the
  // parcel is regulatorily constrained (gate-blocked / ЗОУИТ-СЗЗ / нежилое),
  // we say so plainly rather than presenting the figures as a viable project.
  const economicsNegative =
    result.financial.net_profit_rub < 0 || result.financial.npv_rub < 0;
  const showConditionalNote = economicsNegative || gateConstrained;

  return (
    <Section
      title="Экономика по 3D-модели"
      subtitle="Пересчитывается вживую при изменении этажности / секций в 3D-модели слева. Цена продажи — из оценки участка, без рыночного DB-запроса; ТЭП синтезируется из массинг-программы."
    >
      <KpiGrid
        teap={result.teap}
        financial={result.financial}
        farTarget={farTarget}
        ksitOver={ksitOver}
        stale={stale}
      />

      {showConditionalNote ? (
        <p
          role="note"
          style={{
            margin: "12px 0 0",
            display: "flex",
            alignItems: "flex-start",
            gap: 8,
            fontSize: 12,
            lineHeight: "16px",
            color: "var(--warn)",
          }}
        >
          <AlertTriangle
            size={16}
            strokeWidth={1.5}
            aria-hidden="true"
            style={{ flexShrink: 0, marginTop: 1 }}
          />
          <span>
            Расчёт условный: участок ограничен регламентом (см. блокеры выше)
            и/или экономика отрицательна при текущих вводных. Измените
            этажность, число секций или класс — модель пересчитается.
          </span>
        </p>
      ) : null}

      {errored ? (
        <p
          role="status"
          style={{
            margin: "12px 0 0",
            display: "flex",
            alignItems: "center",
            gap: 8,
            fontSize: 12,
            color: "var(--warn)",
          }}
        >
          <AlertTriangle size={16} strokeWidth={1.5} aria-hidden="true" />
          Не удалось пересчитать экономику по последнему изменению — показаны
          предыдущие значения. Измените параметры ещё раз для повторного
          расчёта.
        </p>
      ) : null}
    </Section>
  );
}
