"use client";

import { Suspense, useEffect, useRef, useState } from "react";
import dynamic from "next/dynamic";
import Link from "next/link";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import type { FeatureCollection } from "geojson";

import { CadInput } from "@/components/site-finder/CadInput";
import { FetchingState } from "@/components/site-finder/FetchingState";
import { KpiCard } from "@/components/site-finder/KpiCard";
import { OverviewTab } from "@/components/site-finder/OverviewTab";
import { EnvironmentTab } from "@/components/site-finder/EnvironmentTab";
import { LandTab } from "@/components/site-finder/LandTab";
import { MarketTab } from "@/components/site-finder/MarketTab";
import { WeightProfilePanel } from "@/components/site-finder/WeightProfilePanel";
import { useSiteAnalysis } from "@/hooks/useSiteAnalysis";
import { useDebouncedValue } from "@/hooks/useDebouncedValue";
import { useConnectionPoints } from "@/hooks/useConnectionPoints";
import { useCustomPois } from "@/hooks/useCustomPois";
import {
  POI_DEFAULT_WEIGHTS,
  type PoiCategoryKey,
} from "@/lib/api/weightProfiles";

// SiteMap imports Leaflet which requires browser APIs — load without SSR
const SiteMap = dynamic(
  () => import("@/components/site-finder/SiteMap").then((m) => m.SiteMap),
  {
    ssr: false,
    loading: () => (
      <div
        style={{
          height: 420,
          border: "1px solid #e5e7eb",
          borderRadius: 12,
          background: "#f9fafb",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          color: "#9ca3af",
          fontSize: 14,
        }}
      >
        Загрузка карты…
      </div>
    ),
  },
);

type TabId = "overview" | "env" | "land" | "market";

const TABS: Array<{ id: TabId; label: string }> = [
  { id: "overview", label: "Обзор" },
  { id: "env", label: "Окружение" },
  { id: "land", label: "Земля" },
  { id: "market", label: "Рынок" },
];

const TAB_IDS = TABS.map((t) => t.id);

function isTabId(v: string | null | undefined): v is TabId {
  return !!v && (TAB_IDS as readonly string[]).includes(v);
}

// ── KPI helpers ───────────────────────────────────────────────────────────────

type KpiColor = "neutral" | "amber" | "green" | "red" | "blue";

// Пороги синхронизированы с backend market_trend.label (parcels.py:2250-2257):
// >8 «Сильный рост» (green), >0 «Умеренный рост» (blue),
// >-5 «Стагнация» (amber), иначе «Падение» (red). #1415.
function trendColor(pct: number): KpiColor {
  if (pct > 8) return "green";
  if (pct > 0) return "blue";
  if (pct > -5) return "amber";
  return "red";
}

// Пороги синхронизированы с backend noise.level (parcels.py:1809-1814):
// <50 «тихо» (green), <65 «умеренный» (amber), иначе «шумно» (red). #1416.
function noiseColor(db: number): KpiColor {
  if (db < 50) return "green";
  if (db < 65) return "amber";
  return "red";
}

function scoreKpiColor(score: number, max: number): KpiColor {
  const ratio = score / max;
  if (ratio >= 0.65) return "green";
  if (ratio >= 0.35) return "amber";
  return "red";
}

// ── Page ──────────────────────────────────────────────────────────────────────

function SiteFinderContent() {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();

  const { mutate, data, isPending, error, isIdle, fetchingState, cancel } =
    useSiteAnalysis();
  const [isochrones, setIsochrones] = useState<FeatureCollection | undefined>(
    undefined,
  );

  // Fetch connection points whenever a parcel is loaded
  const { data: connectionPoints } = useConnectionPoints(data?.cad_num);
  // Fetch custom POIs for current parcel
  const { data: customPois } = useCustomPois(data?.cad_num);

  // Weight profile state — lifted here so it survives tab switches.
  // userId + adminToken allow the panel to load/save named profiles.
  const [currentWeights, setCurrentWeights] = useState<
    Record<PoiCategoryKey, number>
  >(() => ({ ...POI_DEFAULT_WEIGHTS }));
  // Profile selection for the analyze call (null = system defaults).
  const [activeProfileId, setActiveProfileId] = useState<number | null>(null);

  // Pending weights change — set immediately when user clicks "Применить".
  // Debounced to 300ms before triggering re-analyze, so rapid clicks collapse
  // into a single request (#201 Phase 2).
  const [pendingWeightsChange, setPendingWeightsChange] = useState<{
    weights: Record<PoiCategoryKey, number>;
    profileId: number | null;
  } | null>(null);
  const debouncedWeightsChange = useDebouncedValue(pendingWeightsChange, 300);
  // Ref to skip the initial mount effect (we only re-analyze on actual changes).
  const weightsChangeInitializedRef = useRef(false);

  const [profileUserId, setProfileUserId] = useState<string>(() =>
    typeof window === "undefined"
      ? ""
      : (localStorage.getItem("admin_user_id") ?? ""),
  );
  const [adminToken] = useState<string>(() =>
    typeof window === "undefined"
      ? ""
      : (localStorage.getItem("admin_token") ?? ""),
  );
  // Lazy init: считаем initialTab один раз на mount (useState всё равно
  // игнорирует initializer после первого render — не тратим CPU).
  const [tab, setTabState] = useState<TabId>(() => {
    const t = searchParams.get("tab");
    return isTabId(t) ? t : "overview";
  });

  // Sync tab → URL query (?tab=env). overview = default, не пишем в URL.
  function setTab(next: TabId) {
    setTabState(next);
    const params = new URLSearchParams(searchParams.toString());
    if (next === "overview") {
      params.delete("tab");
    } else {
      params.set("tab", next);
    }
    const qs = params.toString();
    // Используем pathname вместо голого "?" — Next 15 App Router не всегда
    // чисто чистит query при `router.replace("?")` (dangling `?` остаётся).
    router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false });
  }

  // Listen to back/forward navigation — reflect URL tab in state.
  // setState с тем же значением React сам бэйлит через Object.is, поэтому
  // guard по tab не нужен (и не вводит stale-closure через eslint-disable).
  useEffect(() => {
    const urlTab = searchParams.get("tab");
    setTabState(isTabId(urlTab) ? urlTab : "overview");
  }, [searchParams]);

  // Debounced re-analyze trigger: fires 300ms after the last "Применить" click.
  // Skip initial mount (weightsChangeInitializedRef guards the first effect run).
  // dataRef lets the effect read current data without listing it as a dep
  // (avoids re-registering the timeout on every analyze completion).
  const dataRef = useRef(data);
  dataRef.current = data;
  const profileUserIdRef = useRef(profileUserId);
  profileUserIdRef.current = profileUserId;

  useEffect(() => {
    if (!weightsChangeInitializedRef.current) {
      weightsChangeInitializedRef.current = true;
      return;
    }
    if (!debouncedWeightsChange) return;
    const currentData = dataRef.current;
    if (!currentData?.cad_num) return;

    const { weights, profileId } = debouncedWeightsChange;
    const currentProfileUserId = profileUserIdRef.current;
    setIsochrones(undefined);
    mutate({
      cad: currentData.cad_num,
      options:
        profileId != null
          ? { profileId }
          : currentProfileUserId
            ? { profileUserId: currentProfileUserId, weights }
            : { weights },
    });
    // mutate is stable from useMutation — safe to omit from deps.
    // data?.cad_num — dep, чтобы при завершении ПЕРВИЧНОГО analyze (cad_num
    // переходит undefined→значение) ещё «висящий» pendingWeightsChange всё-таки
    // применился, а не терялся (#1418). При повторном analyze того же участка
    // cad_num не меняется → лишнего перезапуска/петли нет.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debouncedWeightsChange, data?.cad_num]);

  function handleAnalyze(cadNum: string) {
    setIsochrones(undefined);
    setTab("overview");
    // Priority: named profile → user default profile → inline draft weights.
    // When activeProfileId is set, backend uses that profile (ignores inline).
    // When no profile is selected, pass currentWeights as inline so draft
    // slider values are always respected even without a saved profile (#201).
    mutate({
      cad: cadNum,
      options:
        activeProfileId != null
          ? { profileId: activeProfileId }
          : profileUserId
            ? { profileUserId, weights: currentWeights }
            : { weights: currentWeights },
    });
  }

  function handleWeightsChange(
    weights: Record<PoiCategoryKey, number>,
    profileId: number | null,
  ) {
    setCurrentWeights(weights);
    setActiveProfileId(profileId);
    // Enqueue a debounced re-analyze (#201 Phase 2). The actual mutate fires
    // after 300ms of silence via debouncedWeightsChange. Не гардим по
    // data?.cad_num: если веса меняют пока первичный analyze ещё в полёте,
    // pending всё равно ставится и применяется, когда data придёт — иначе
    // изменение молча теряется и score не соответствует ползункам (#1418).
    setPendingWeightsChange({ weights, profileId });
  }

  // Derive KPI values from data
  const scoreMax = data?.score_max_reference ?? 40;
  const scoreValue = data
    ? `${data.score.toFixed(2)} / ${scoreMax.toFixed(0)}`
    : "—";
  const scoreLabel = data?.score_label
    ? `POI-скор · ${data.score_label} · оценочный`
    : "POI-скор · оценочный";
  const scoreCardColor: KpiColor = data
    ? scoreKpiColor(data.score, scoreMax)
    : "neutral";

  const districtValue = data?.district ? data.district.district_name : "—";
  // Расстояние до ЦЕНТРА ЕКБ — location.distance_to_center_km (хаверсин до EKB_CENTER,
  // уже в км). НЕ district.dist_to_center: то расстояние до полигона ближайшего района
  // (≈0 внутри района), а не до центра города (#1414).
  const distToCenterKm = data?.location?.distance_to_center_km;
  const districtLabel = data?.district
    ? (data.district.median_price_per_m2 != null
        ? `${(data.district.median_price_per_m2 / 1000).toFixed(0)} тыс ₽/м²`
        : "нет данных") +
      (distToCenterKm != null
        ? ` · ${distToCenterKm.toFixed(1)} км от центра`
        : "")
    : "Район";

  const trendPct = data?.market_trend?.delta_6m_pct;
  const trendValue =
    trendPct != null
      ? `${trendPct > 0 ? "+" : ""}${trendPct.toFixed(1)}%`
      : "—";
  const trendLabel = data?.market_trend?.label
    ? `Тренд за 6 мес · ${data.market_trend.label}`
    : "Тренд рынка";
  const trendKpiColor: KpiColor =
    trendPct != null ? trendColor(trendPct) : "neutral";

  const noiseDb = data?.noise?.estimated_db;
  const noiseValue = noiseDb != null ? `${Math.round(noiseDb)} dB` : "—";
  const noiseLabel = data?.noise ? `Шум · ${data.noise.level}` : "Шум";
  // Цвет считаем от того же округлённого значения, что показано в KPI,
  // чтобы число и окраска не расходились у границы порога (#1417).
  const noiseKpiColor: KpiColor =
    noiseDb != null ? noiseColor(Math.round(noiseDb)) : "neutral";

  return (
    <main style={{ padding: "24px 20px", maxWidth: 1400, margin: "0 auto" }}>
      {/* Breadcrumb + header */}
      <div style={{ marginBottom: 12 }}>
        <Link
          href="/"
          style={{ fontSize: 13, color: "#6b7280", textDecoration: "none" }}
        >
          ← Главная
        </Link>
      </div>

      <div
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          marginBottom: 20,
          flexWrap: "wrap",
          gap: 12,
        }}
      >
        <div>
          <h1 style={{ fontSize: 24, fontWeight: 700, margin: 0 }}>
            Site Finder
          </h1>
          <p style={{ fontSize: 13, color: "#6b7280", margin: "4px 0 0" }}>
            Введите кадастровый номер участка или квартала для анализа локации
          </p>
        </div>
        <div
          style={{
            background: "#fff",
            border: "1px solid #e5e7eb",
            borderRadius: 12,
            padding: "14px 20px",
            minWidth: 320,
          }}
        >
          <CadInput onSubmit={handleAnalyze} loading={isPending} />
        </div>
      </div>

      {/* Weight profile panel — collapsible, below header */}
      <div style={{ marginBottom: 16 }}>
        {/* Optional user-id field for profile CRUD (shown only when adminToken present) */}
        {!!adminToken && (
          <div
            style={{
              display: "flex",
              alignItems: "center",
              gap: 8,
              marginBottom: 6,
            }}
          >
            <label
              style={{ fontSize: 12, color: "#6b7280", whiteSpace: "nowrap" }}
            >
              User ID (для профилей):
            </label>
            <input
              type="text"
              value={profileUserId}
              placeholder="user-abc"
              style={{
                padding: "4px 8px",
                fontSize: 12,
                border: "1px solid #d1d5db",
                borderRadius: 6,
                width: 180,
              }}
              onChange={(e) => {
                setProfileUserId(e.target.value);
                if (typeof window !== "undefined") {
                  localStorage.setItem("admin_user_id", e.target.value);
                }
              }}
            />
          </div>
        )}
        <WeightProfilePanel
          currentWeights={currentWeights}
          onWeightsChange={handleWeightsChange}
          userId={profileUserId || undefined}
          adminToken={adminToken || undefined}
        />
        {/* Recalculation indicator — shown while re-analyze is in-flight after
            weights change (data already loaded, pendingWeightsChange set). */}
        {isPending && !!data && (
          <p
            style={{
              fontSize: 12,
              color: "#6b7280",
              margin: "6px 0 0",
            }}
          >
            Пересчёт…
          </p>
        )}
      </div>

      {/* Error state */}
      {error && (
        <div
          style={{
            padding: "12px 20px",
            background: "#fef2f2",
            border: "1px solid #fca5a5",
            borderRadius: 10,
            color: "#dc2626",
            fontSize: 13,
            marginBottom: 20,
          }}
        >
          {error instanceof Error ? error.message : "Ошибка анализа участка"}
        </div>
      )}

      {/* #93 — Fetching state: backend на 202 Accepted, идёт on-demand NSPD fetch */}
      {fetchingState && (
        <FetchingState
          cadNum={fetchingState.cadNum}
          etaSeconds={fetchingState.etaSeconds}
          onCancel={cancel}
        />
      )}

      {/* Pending skeleton — первичный analyze в полёте (но fetching state ещё не активирован) */}
      {isPending && !fetchingState && (
        <div
          style={{
            padding: 40,
            textAlign: "center",
            color: "#9ca3af",
            fontSize: 14,
          }}
        >
          Анализируем участок…
        </div>
      )}

      {/* Empty initial state */}
      {isIdle && !error && (
        <div
          style={{
            padding: 40,
            textAlign: "center",
            color: "#d1d5db",
            fontSize: 14,
            border: "1px dashed #e5e7eb",
            borderRadius: 12,
          }}
        >
          Введите кадастровый номер и нажмите «Анализировать»
        </div>
      )}

      {/* Results */}
      {data && (
        <>
          {/* Cad label */}
          <div
            style={{
              fontSize: 11,
              fontWeight: 600,
              color: "#9ca3af",
              textTransform: "uppercase",
              letterSpacing: "0.05em",
              marginBottom: 10,
            }}
          >
            {data.cad_num}
          </div>

          {/* Hero band — sticky KPI cards */}
          <div
            style={{
              position: "sticky",
              top: 0,
              zIndex: 20,
              background: "#fff",
              borderBottom: "1px solid #e5e7eb",
              padding: "12px 0 14px",
              marginBottom: 0,
              display: "grid",
              gridTemplateColumns: "repeat(4, 1fr)",
              gap: 12,
            }}
          >
            <KpiCard
              value={scoreValue}
              label={scoreLabel}
              color={scoreCardColor}
            />
            <KpiCard
              value={districtValue}
              label={districtLabel}
              color="neutral"
            />
            <KpiCard
              value={trendValue}
              label={trendLabel}
              color={trendKpiColor}
            />
            <KpiCard
              value={noiseValue}
              label={noiseLabel}
              color={noiseKpiColor}
            />
          </div>

          {/* Tab buttons */}
          <div
            style={{
              display: "flex",
              gap: 0,
              borderBottom: "1px solid #e5e7eb",
              marginBottom: 20,
              background: "#fff",
              position: "sticky",
              top: 86,
              zIndex: 19,
            }}
          >
            {TABS.map((t) => (
              <button
                key={t.id}
                onClick={() => setTab(t.id)}
                style={{
                  padding: "10px 20px",
                  background: "none",
                  border: "none",
                  cursor: "pointer",
                  fontSize: 14,
                  fontWeight: 500,
                  borderBottom:
                    tab === t.id
                      ? "2px solid #1d4ed8"
                      : "2px solid transparent",
                  color: tab === t.id ? "#1d4ed8" : "#6b7280",
                  transition: "color 0.15s, border-color 0.15s",
                }}
              >
                {t.label}
              </button>
            ))}
          </div>

          {/* 2-column grid: tab content + sticky map */}
          <div
            style={{
              display: "grid",
              gridTemplateColumns: "1fr 480px",
              gap: 24,
              alignItems: "start",
            }}
          >
            {/* Tab content */}
            <div>
              {tab === "overview" && (
                <OverviewTab data={data} onIsochronesResult={setIsochrones} />
              )}
              {tab === "env" && <EnvironmentTab data={data} />}
              {tab === "land" && <LandTab data={data} />}
              {tab === "market" && <MarketTab data={data} />}
            </div>

            {/* Sticky right sidebar — map always visible */}
            <aside
              style={{
                position: "sticky",
                top: 140,
                alignSelf: "start",
                maxHeight: "calc(100vh - 156px)",
                overflowY: "auto",
                display: "flex",
                flexDirection: "column",
                gap: 12,
              }}
            >
              <SiteMap
                data={data}
                isochrones={isochrones}
                connectionPoints={connectionPoints}
                customPois={customPois}
                parcelCad={data.cad_num}
              />
            </aside>
          </div>
        </>
      )}
    </main>
  );
}

// useSearchParams() в Next 15 App Router throw'ит при static rendering без
// <Suspense> boundary — поэтому оборачиваем page-level.
export default function SiteFinderPage() {
  return (
    <Suspense fallback={null}>
      <SiteFinderContent />
    </Suspense>
  );
}
