"use client";

/**
 * Section7Concept — «7. Концепция» (epic #1953, unify-concept rewrite).
 *
 * ONE engine. The section folds the generative concept tooling into the LIGHT
 * analysis report after «6. Прогноз». A SINGLE source of numbers: POST /concepts
 * with a `building_program` set drives the placement map + ТЭП + финмодель
 * (ConceptVariantsResult). The old two-engine split (live 3D massing + a
 * separate params form / greedy run) is gone — it produced incoherent numbers
 * and a torn layout.
 *
 * «Число корпусов» maps directly to a single-type program item
 * `[{ section_type, floors, count }]`: the backend's `place_program` lays out
 * EXACTLY `count` footprints, so scatter is structurally impossible while the
 * «уплотнить» toggle is OFF. Toggle ON → building_program is omitted → greedy
 * max-FAR (the explicit, opt-in dense buildout).
 *
 * Inputs are seeded from the analysis the report already has (no re-entry):
 *   • parcel polygon  ← extractPolygon(analysis.geom_geojson)
 *   • housing_class   ← financial_estimate.housing_class_inferred
 *   • development_type← financial_estimate.development_type_inferred
 *   • land_cost_rub   ← egrn.cadastral_value_rub
 * Falls back to comfort / mid_rise when those are null.
 *
 * The section is OPEN by default (Anton feedback 2026-07-02, issue #2179): the
 * concept is the report's key deliverable. It auto-runs once on mount with the
 * default program (count = 3) so the user lands on a result, not an empty form.
 * The <details> can still be collapsed via its summary.
 *
 * СЗЗ / financial_estimate === null is NOT a dead end: /concepts only needs a
 * polygon + form inputs (independent of the financial_estimate regulatory gate),
 * so we show an honest banner and STILL allow a run.
 */

import dynamic from "next/dynamic";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";

import { HeadlineBar } from "@/components/ui/HeadlineBar";
import { Section } from "@/components/analytics/Section";
import {
  ConceptProgramForm,
  type ConceptProgramFormState,
} from "@/components/concept/ConceptProgramForm";
import {
  extractParcelGeometry,
  extractPolygon,
  polygonAreaSqm,
  useCreateConcept,
  useHouseTypes,
  type BuildingProgramItem,
  type DevelopmentType,
  type HousingClass,
  type HouseTypeCatalogItem,
} from "@/lib/concept-api";
import type {
  ParcelAnalysis,
  PendingConceptProgram,
} from "@/types/site-finder";

// ConceptVariantsResult hosts a Leaflet placement map — lazy-mount, no SSR, so
// it never bloats the main report bundle (loaded only when results exist).
const ConceptVariantsResult = dynamic(
  () =>
    import("@/components/concept/ConceptVariantsResult").then(
      (m) => m.ConceptVariantsResult,
    ),
  {
    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>
    ),
  },
);

const SCROLL_MARGIN = 72;

// Fallback floors when neither the catalog nor a selection is available yet.
const FLOORS_FALLBACK = 9;
const CORPUSES_DEFAULT = 3;

// ── Narrowing helpers (inferred values are typed `string` on the analysis) ────

const HOUSING_CLASSES: readonly HousingClass[] = [
  "econom",
  "comfort",
  "business",
];
const DEVELOPMENT_TYPES: readonly DevelopmentType[] = [
  "spot",
  "mid_rise",
  "high_rise",
];

function asHousingClass(value: string | undefined): HousingClass | null {
  return value != null && (HOUSING_CLASSES as readonly string[]).includes(value)
    ? (value as HousingClass)
    : null;
}

function asDevelopmentType(value: string | undefined): DevelopmentType | null {
  return value != null &&
    (DEVELOPMENT_TYPES as readonly string[]).includes(value)
    ? (value as DevelopmentType)
    : null;
}

/** First comfort-class catalog type (else first entry, else null). */
function pickDefaultType(
  catalog: HouseTypeCatalogItem[],
): HouseTypeCatalogItem | null {
  if (catalog.length === 0) return null;
  return catalog.find((ht) => ht.housing_class === "comfort") ?? catalog[0];
}

/**
 * Каталожный тип секции ДЛЯ ЗАДАННОГО класса (apply-alternative, план #2181).
 * Как pickDefaultType, но фильтр по housing_class варианта, а не по comfort:
 * первый тип нужного класса → иначе pickDefaultType (comfort/первый). Так §7
 * подставляет пятно/этажность из типа, подходящего к пришедшему классу.
 */
function pickTypeForClass(
  catalog: HouseTypeCatalogItem[],
  housingClass: HousingClass,
): HouseTypeCatalogItem | null {
  return (
    catalog.find((ht) => ht.housing_class === housingClass) ??
    pickDefaultType(catalog)
  );
}

// ── Props ─────────────────────────────────────────────────────────────────────

interface Props {
  /** The /analyze response the report already holds — no re-fetch / re-entry. */
  analysis: ParcelAnalysis;
  /**
   * «Пересчитать §7 с этой программой» (план #2181, хвост). Не null → §7
   * раскрывается, форма выставляется под класс/этажность/тип варианта (тип
   * секции и пятно берём из каталога по классу), запускается пересчёт. После
   * запуска §7 зовёт onProgramApplied() — родитель обнуляет pending, чтобы
   * повторный клик по тому же варианту снова сработал.
   */
  pendingProgram?: PendingConceptProgram | null;
  /** Вызывается после старта пересчёта по pendingProgram — родитель чистит pending. */
  onProgramApplied?: () => void;
  /** §7 сообщает родителю своё concept.isPending (дизейбл CTA в блоке альтернатив). */
  onPendingChange?: (pending: boolean) => void;
}

// ── Section7Concept ───────────────────────────────────────────────────────────

export function Section7Concept({
  analysis,
  pendingProgram,
  onProgramApplied,
  onPendingChange,
}: Props) {
  // Parcel polygon for the /concepts REQUEST — a single Polygon (MultiPolygon
  // collapses to its LARGEST-area part per extractPolygon, so the generated corpus
  // sits on the dominant part, not a sliver).
  const polygon = useMemo(
    () => extractPolygon(analysis.geom_geojson),
    [analysis.geom_geojson],
  );

  // WHOLE parcel geometry for the 2D/3D placement scenes — Polygon OR
  // MultiPolygon preserving every part. The scenes derive their projection origin,
  // OSM tile bounds and outline over ALL parts so neighbour context (anchored by
  // the backend to the entire parcel geometry) lands geo-aligned rather than
  // offset from part 0. Null only when the geometry itself is missing (same as
  // `polygon`), so the parcel-present branch below always has both.
  const parcelGeometry = useMemo(
    () => extractParcelGeometry(analysis.geom_geojson),
    [analysis.geom_geojson],
  );

  // Seed defaults from the analysis: inferred class/type + cadastral value.
  // financial_estimate is null under the regulatory gate (СЗЗ / ЗОУИТ / нет
  // зоны) — fall back to comfort / mid_rise so a manual run is still possible.
  const fin = analysis.financial_estimate;
  const seeded = useMemo(
    () => ({
      housing_class: asHousingClass(fin?.housing_class_inferred) ?? "comfort",
      development_type:
        asDevelopmentType(fin?.development_type_inferred) ?? "mid_rise",
      land_cost_rub: analysis.egrn?.cadastral_value_rub ?? null,
    }),
    [
      fin?.housing_class_inferred,
      fin?.development_type_inferred,
      analysis.egrn?.cadastral_value_rub,
    ],
  );

  const concept = useCreateConcept();
  // Lift the catalog to the parent so auto-run-on-first-expand can build the
  // default program from the real catalog (single source of truth, no hardcode).
  const houseTypes = useHouseTypes();
  const catalog = useMemo(() => houseTypes.data ?? [], [houseTypes.data]);

  const financeUnavailable = fin == null;

  const parcelAreaSqm = polygon ? polygonAreaSqm(polygon) : 0;

  // Build a concept.mutate payload from a fully-resolved form state. «Число
  // корпусов» → a single-type program item's `count` (toggle OFF). Toggle ON →
  // building_program omitted → greedy max-FAR (explicit dense opt-in).
  const handleGenerate = useCallback(
    (state: ConceptProgramFormState) => {
      if (!polygon) return;
      const building_program: BuildingProgramItem[] | undefined = state.densify
        ? undefined
        : [
            {
              section_type: state.section_type,
              floors: state.floors,
              count: state.corpuses,
              footprint_w_m: state.footprint_w_m,
              footprint_d_m: state.footprint_d_m,
            },
          ];
      concept.mutate({
        parcel_geojson: polygon,
        housing_class: state.housing_class,
        // Backward-compat scalar; the program item carries the real floors.
        target_floors: state.floors,
        development_type: state.development_type,
        land_cost_rub: state.land_cost_rub,
        building_program,
      });
    },
    [polygon, concept],
  );

  // ── Auto-run-on-first-expand ──────────────────────────────────────────────
  // When the <details> is opened the FIRST time AND a polygon + catalog exist
  // AND there's no result yet AND nothing is in flight → fire one run with the
  // default program (count = 3, comfort-class section type). A ref guards a
  // single execution; if the catalog isn't ready at open the effect re-fires
  // when it becomes ready (still only once, still only after first open). The
  // <details onToggle> flips `expanded`, which (re)triggers the effect.
  const autoRanRef = useRef(false);
  // §7 «Концепция» is OPEN by default (Anton feedback 2026-07-02, issue #2179):
  // the concept is the report's key deliverable, so the auto-run fires on mount
  // instead of waiting for a first expand. The user can still collapse the
  // <details> via the summary; onToggle keeps `expanded` in sync when reopened.
  const [expanded, setExpanded] = useState(true);

  // Одноразовый авторетрай (fix 2026-07-03): на узких/мелких участках дефолтная
  // программа (3 корпуса comfort 21×18) честно не влезает — движок отвечает 422
  // «программа застройки не вместила ни одной секции». Показывать это ошибкой
  // при ЗАГРУЗКЕ отчёта нельзя (прод-кейс 66:41:0702033:20) — пробуем один раз
  // densify=true: greedy-раскладка сама вмещает, что физически влезает.
  // Не влезло и так → честная ошибка остаётся. Ручные запуски не ретраятся.
  const autoRetriedRef = useRef(false);

  useEffect(() => {
    if (autoRanRef.current || !expanded) return;
    if (!polygon || catalog.length === 0) return;
    if (concept.isPending || concept.isSuccess || concept.isError) return;
    const def = pickDefaultType(catalog);
    if (!def) return;
    autoRanRef.current = true;
    handleGenerate({
      corpuses: CORPUSES_DEFAULT,
      floors: def.default_floors > 0 ? def.default_floors : FLOORS_FALLBACK,
      floor_height_m: 3.0,
      section_type: def.section_type,
      footprint_w_m: def.footprint_w_m,
      footprint_d_m: def.footprint_d_m,
      housing_class: seeded.housing_class,
      development_type: seeded.development_type,
      land_cost_rub: seeded.land_cost_rub,
      densify: false,
    });
  }, [expanded, polygon, catalog, concept, seeded, handleGenerate]);

  useEffect(() => {
    if (!autoRanRef.current || autoRetriedRef.current) return;
    if (!concept.isError || !polygon || catalog.length === 0) return;
    if (!/не вместила/i.test(concept.error.message)) return;
    const def = pickDefaultType(catalog);
    if (!def) return;
    autoRetriedRef.current = true;
    handleGenerate({
      corpuses: CORPUSES_DEFAULT,
      floors: def.default_floors > 0 ? def.default_floors : FLOORS_FALLBACK,
      floor_height_m: 3.0,
      section_type: def.section_type,
      footprint_w_m: def.footprint_w_m,
      footprint_d_m: def.footprint_d_m,
      housing_class: seeded.housing_class,
      development_type: seeded.development_type,
      land_cost_rub: seeded.land_cost_rub,
      densify: true, // greedy max-FAR: вместить, что физически влезает
    });
  }, [concept, polygon, catalog, seeded, handleGenerate]);

  // ── Apply-alternative («Пересчитать §7 с этой программой», план #2181) ────
  // Клик по варианту в блоке «Как участок сходится» ставит pendingProgram в
  // родителе. Здесь: раскрыть §7, подобрать тип секции + пятно из каталога по
  // КЛАССУ варианта (pickTypeForClass), выставить этажность/класс/тип застройки
  // варианта, densify=false → запустить пересчёт этой программой. Ручной запуск
  // всегда разрешён (autoRanRef/autoRetriedRef НЕ трогаем; concept.mutate сам
  // ресетит предыдущий результат). После старта зовём onProgramApplied() —
  // родитель обнуляет pending, чтобы повторный клик по тому же варианту сработал.
  useEffect(() => {
    if (!pendingProgram || !polygon || catalog.length === 0) return;
    const hc = asHousingClass(pendingProgram.housing_class) ?? "comfort";
    const dt = asDevelopmentType(pendingProgram.development_type) ?? "mid_rise";
    const type = pickTypeForClass(catalog, hc);
    if (!type) return;
    const floors =
      pendingProgram.floors > 0 ? pendingProgram.floors : FLOORS_FALLBACK;
    setExpanded(true);
    handleGenerate({
      corpuses: CORPUSES_DEFAULT,
      floors,
      floor_height_m: 3.0,
      section_type: type.section_type,
      footprint_w_m: type.footprint_w_m,
      footprint_d_m: type.footprint_d_m,
      housing_class: hc,
      development_type: dt,
      land_cost_rub: seeded.land_cost_rub,
      densify: false,
    });
    onProgramApplied?.();
  }, [
    pendingProgram,
    polygon,
    catalog,
    seeded.land_cost_rub,
    handleGenerate,
    onProgramApplied,
  ]);

  // Сообщаем родителю concept.isPending — блок альтернатив дизейблит CTA, пока §7
  // считает (одна раскладка за раз). Тонкий канал вверх, без подъёма мутации.
  useEffect(() => {
    onPendingChange?.(concept.isPending);
  }, [concept.isPending, onPendingChange]);

  const handleToggle = useCallback(
    (e: React.SyntheticEvent<HTMLDetailsElement>) => {
      if (e.currentTarget.open) setExpanded(true);
    },
    [],
  );

  return (
    <section id="section-7" style={{ scrollMarginTop: SCROLL_MARGIN }}>
      <HeadlineBar
        title="7. Концепция"
        subtitle="Единая программа застройки: задайте число корпусов, этажность и класс — движок разложит ровно эту программу и посчитает ТЭП и финмодель по геометрии участка."
      />

      {/* Open by default (Anton feedback 2026-07-02, issue #2179): концепция —
          ключевой deliverable отчёта, поэтому §7 раскрыт и авторасчёт стартует
          сразу на маунте. Пользователь может свернуть блок через summary. */}
      <details open style={{ marginTop: 12 }} onToggle={handleToggle}>
        <summary
          style={{
            cursor: "pointer",
            fontSize: 14,
            fontWeight: 600,
            color: "var(--fg-primary)",
            padding: "8px 0",
          }}
        >
          Параметры и расчёт концепции
        </summary>

        <div style={{ display: "grid", gap: 24 }}>
          {!polygon ? (
            <Section title="Концепция застройки">
              <p
                style={{
                  margin: 0,
                  fontSize: 13,
                  color: "var(--fg-secondary)",
                }}
              >
                Геометрия участка недоступна — концепцию построить нельзя.
                Геометрия загружается из НСПД; повторите анализ позже либо
                воспользуйтесь страницей «Концепция застройки», где границы
                можно задать вручную.
              </p>
            </Section>
          ) : (
            <>
              {/* СЗЗ / нет финмодели по регламенту — честный баннер, но НЕ
                  тупик: /concepts работает по полигону + параметрам формы
                  независимо от регуляторного гейта financial_estimate. */}
              {financeUnavailable && (
                <div
                  role="note"
                  style={{
                    padding: "12px 16px",
                    background: "var(--warn-soft)",
                    border: "1px solid var(--warn)",
                    borderRadius: 12,
                    fontSize: 13,
                    lineHeight: "20px",
                    color: "var(--fg-primary)",
                  }}
                >
                  Финмодель по регламенту недоступна — на участке ЗОУИТ / СЗЗ
                  либо не определена территориальная зона. Концепцию можно
                  рассчитать вручную: задайте число корпусов, класс, тип
                  застройки и этажность ниже — движок построит застройку по
                  геометрии участка и заданным параметрам.
                </div>
              )}

              <ConceptProgramForm
                parcelAreaSqm={parcelAreaSqm}
                seeded={seeded}
                hasPolygon={polygon != null}
                isPending={concept.isPending}
                onSubmit={handleGenerate}
              />

              {/* Result region */}
              {concept.isError && (
                <Section title="Варианты застройки">
                  <p
                    style={{ margin: 0, fontSize: 13, color: "var(--danger)" }}
                    role="alert"
                  >
                    Не удалось рассчитать концепцию: {concept.error.message}
                  </p>
                </Section>
              )}

              {concept.isPending && (
                <div
                  style={{
                    minHeight: 700,
                    background: "var(--bg-card-alt)",
                    border: "1px solid var(--border-card)",
                    borderRadius: 12,
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                    color: "var(--fg-secondary)",
                    fontSize: 14,
                  }}
                >
                  Движок раскладывает корпуса и считает ТЭП и финмодель…
                </div>
              )}

              {concept.isSuccess && (
                <ConceptVariantsResult
                  parcel={parcelGeometry ?? polygon}
                  variants={concept.data.variants}
                  cadNum={analysis.cad_num}
                />
              )}
            </>
          )}
        </div>
      </details>
    </section>
  );
}
