"use client";

// "ПОЯСНЕНИЕ К РАСЧЁТУ" right-side drawer for the /trade-in/v2 "МЕРА Оценка"
// design port. Opened from HeroBar (the "?" near "ЛОКАЦИЯ"). It used to render
// a FABRICATED location coefficient (0.87), a fake "base × coef = result"
// formula and invented POI factor lists with a false "Источник: OpenStreetMap"
// footer. That location-coef metric was replaced outright (see
// backend/app/services/location_index.py for the full audit): ±5% range,
// uncorrelated with real prices, never actually fed the estimate. This drawer
// now renders GET /trade-in/location-index (mapLocation, ./mappers.ts): a real
// % deviation of the local median ₽/м² from the citywide median — framed as a
// comparison metric, explicitly NOT a price adjustment — plus the real
// nearest-POI list. The three degraded states (loading / out_of_coverage /
// insufficient_data) each get their own honest explanation instead of one
// blank "недоступно". Keeps the same drawer shell / slide animation / close
// button. Open/close is driven entirely by props; while open it is a real
// modal dialog (role=dialog/aria-modal, focus-trap, Esc) with semantics
// mirrored from SectionOverlay.

import { useEffect, useRef } from "react";
import { tokens } from "./tokens";
import { pluralRu } from "./mappers";
import type { LocationData } from "./mappers";

// Default presentation data (unwired usage / not fetched yet): the honest
// loading state, never a fabricated coefficient.
const LOCATION_FIXTURE: LocationData = {
  status: "loading",
  indexLabel: "—",
  localMedianLabel: "—",
  cityMedianLabel: "—",
  sampleSize: 0,
  radiusLabel: "—",
  poiAvailable: false,
  factors: [],
};

// data.indexLabel is already the signed, rounded fmtPct string produced by
// mapLocation ("+12%" / "−8%" / "0%" / "—") — reusing it here (rather than a
// second raw-number field) keeps the sign/rounding logic in one place
// (./mappers.ts). Turns it into a plain-language comparison sentence instead
// of a bare percent, so it reads as "vs. the city", never as a price change.
function locationDirectionSentence(indexLabel: string): string {
  if (indexLabel.startsWith("−")) {
    return `Район дешевле города на ${indexLabel.slice(1)}`;
  }
  if (indexLabel.startsWith("+")) {
    return `Район дороже города на ${indexLabel.slice(1)}`;
  }
  if (indexLabel === "0%") return "Район на уровне медианы по городу";
  return "—";
}

// «Что рядом» — qualitative POI list, independent of the numeric index
// (poi_status degrades separately from status, see mapLocation/./mappers.ts).
function PoiSection({ data }: { data: LocationData }) {
  if (!data.poiAvailable) {
    return (
      <div style={{ marginTop: 12, fontSize: 11.5, color: tokens.muted3 }}>
        Данные о ближайшей инфраструктуре сейчас недоступны.
      </div>
    );
  }
  return (
    <>
      <div
        style={{
          marginTop: 12,
          marginBottom: 6,
          fontSize: 10,
          letterSpacing: "1px",
          color: tokens.muted2,
        }}
      >
        ЧТО РЯДОМ
      </div>
      {data.factors.length > 0 ? (
        <ul
          style={{
            listStyle: "none",
            margin: 0,
            padding: 0,
            display: "flex",
            flexDirection: "column",
            gap: 6,
          }}
        >
          {data.factors.map((f, i) => (
            <li
              key={i}
              style={{
                display: "flex",
                alignItems: "baseline",
                justifyContent: "space-between",
                gap: 10,
                fontSize: 11.5,
              }}
            >
              <span style={{ color: tokens.ink2 }}>
                {f.label}
                {f.category !== f.label && (
                  <span style={{ color: tokens.muted3 }}> · {f.category}</span>
                )}
              </span>
              <span
                style={{
                  flex: "0 0 auto",
                  color: tokens.muted,
                  fontFamily: tokens.font.mono,
                }}
              >
                {f.distance}
              </span>
            </li>
          ))}
        </ul>
      ) : (
        <div style={{ fontSize: 11.5, color: tokens.muted3 }}>
          Объектов инфраструктуры в радиусе поиска не найдено.
        </div>
      )}
    </>
  );
}

interface LocationDrawerProps {
  open: boolean;
  onClose: () => void;
  data?: LocationData;
}

export function LocationDrawer({
  open,
  onClose,
  data = LOCATION_FIXTURE,
}: LocationDrawerProps) {
  // Modal-dialog plumbing — mirrors SectionOverlay. `dialogRef` is the dialog
  // root, `lastFocused` keeps the trigger so focus can be restored on close, and
  // `onCloseRef` holds the latest onClose so the effect (keyed only on `open`)
  // never re-runs / re-traps focus when page.tsx passes a fresh arrow each render.
  const dialogRef = useRef<HTMLDivElement>(null);
  const lastFocused = useRef<Element | null>(null);
  const onCloseRef = useRef(onClose);
  onCloseRef.current = onClose;

  // Focus management + Escape/Tab handling while open. Unlike SectionOverlay
  // (mounted only when open) this drawer stays mounted for the slide animation,
  // so the effect is keyed on `open`: trap focus on open, restore it on close.
  useEffect(() => {
    if (!open) return;
    const dialog = dialogRef.current;
    // Remember the trigger, then move focus into the dialog.
    lastFocused.current = document.activeElement;
    dialog?.focus();

    function getFocusable(): HTMLElement[] {
      if (!dialog) return [];
      const nodes = dialog.querySelectorAll<HTMLElement>(
        'button, [href], input, select, [tabindex]:not([tabindex="-1"])',
      );
      return Array.from(nodes).filter((el) => !el.hasAttribute("disabled"));
    }

    function onKeyDown(e: KeyboardEvent) {
      if (e.key === "Escape") {
        // Stop the event so a stacked overlay below can't also consume it and so
        // the browser runs no default action; then close this (topmost) drawer.
        e.preventDefault();
        e.stopPropagation();
        onCloseRef.current();
        return;
      }
      if (e.key !== "Tab" || !dialog) return;

      const focusable = getFocusable();
      if (focusable.length === 0) {
        // Nothing tabbable inside — keep focus pinned on the dialog itself.
        e.preventDefault();
        dialog.focus();
        return;
      }

      const first = focusable[0];
      const last = focusable[focusable.length - 1];
      const activeEl = document.activeElement;
      const inside = dialog.contains(activeEl);

      if (e.shiftKey) {
        if (activeEl === first || activeEl === dialog || !inside) {
          e.preventDefault();
          last.focus();
        }
      } else if (activeEl === last || !inside) {
        e.preventDefault();
        first.focus();
      }
    }

    document.addEventListener("keydown", onKeyDown);
    return () => {
      document.removeEventListener("keydown", onKeyDown);
      // Return focus to whatever opened the drawer.
      (lastFocused.current as HTMLElement | null)?.focus?.();
    };
    // `onClose` is read via onCloseRef; refs are stable → only `open` is reactive.
  }, [open]);

  return (
    <>
      <style>{`
        .ld-close {
          width: 32px;
          height: 32px;
          flex: 0 0 auto;
          padding: 0;
          border: 1px solid ${tokens.line};
          border-radius: 6px;
          display: flex;
          align-items: center;
          justify-content: center;
          cursor: pointer;
          color: ${tokens.muted};
          font-family: inherit;
          font-size: 16px;
          line-height: 1;
          background: ${tokens.surface.w60};
          transition: all .15s;
        }
        .ld-close:hover {
          border-color: ${tokens.accent};
          color: ${tokens.accent};
        }
      `}</style>

      {/* scrim */}
      <div
        onClick={onClose}
        style={{
          position: "absolute",
          inset: 0,
          background: "rgba(22,42,70,.32)",
          backdropFilter: "blur(2px)",
          opacity: open ? 1 : 0,
          pointerEvents: open ? "auto" : "none",
          transition: "opacity .3s",
          zIndex: 40,
        }}
      />

      {/* drawer — modal dialog (role / focus-trap / inert mirror SectionOverlay) */}
      <div
        ref={dialogRef}
        role="dialog"
        aria-modal="true"
        aria-labelledby="ld-drawer-title"
        tabIndex={-1}
        // Off-canvas when closed → keep it out of the a11y tree (axe "region")
        // AND inert, so the now-focusable close button is not a focusable node
        // inside an aria-hidden subtree (axe "aria-hidden-focus").
        aria-hidden={!open}
        inert={!open ? true : undefined}
        style={{
          position: "absolute",
          top: 0,
          right: 0,
          bottom: 0,
          width: 452,
          transform: open ? "translateX(0)" : "translateX(108%)",
          transition: "transform .38s cubic-bezier(.22,.61,.36,1)",
          zIndex: 41,
          background:
            "linear-gradient(180deg,rgba(245,249,253,.97),rgba(235,242,250,.97))",
          backdropFilter: "blur(10px)",
          borderLeft: `1px solid ${tokens.line3}`,
          boxShadow: "-18px 0 50px rgba(40,80,130,.16)",
          display: "flex",
          flexDirection: "column",
          padding: "30px 32px",
          overflowY: "auto",
          fontFamily: tokens.font.sans,
        }}
      >
        {/* left accent rail */}
        <div
          style={{
            position: "absolute",
            left: 0,
            top: 18,
            bottom: 18,
            width: 1,
            background: `linear-gradient(180deg,transparent,${tokens.accent},transparent)`,
            opacity: 0.5,
          }}
        />
        {/* corner brackets */}
        <div
          style={{
            position: "absolute",
            left: 14,
            top: 14,
            width: 16,
            height: 16,
            borderLeft: `1.5px solid ${tokens.accent}`,
            borderTop: `1.5px solid ${tokens.accent}`,
          }}
        />
        <div
          style={{
            position: "absolute",
            right: 14,
            bottom: 14,
            width: 16,
            height: 16,
            borderRight: `1.5px solid ${tokens.accent}`,
            borderBottom: `1.5px solid ${tokens.accent}`,
          }}
        />

        {/* header */}
        <div
          style={{
            display: "flex",
            alignItems: "flex-start",
            justifyContent: "space-between",
          }}
        >
          <div>
            <div
              style={{
                fontSize: 9,
                letterSpacing: "3px",
                color: tokens.body2,
              }}
            >
              МЕТОДИКА
            </div>
            <div
              id="ld-drawer-title"
              style={{
                fontSize: 22,
                fontWeight: 300,
                letterSpacing: "1.5px",
                color: tokens.ink2,
                marginTop: 8,
              }}
            >
              ПОЯСНЕНИЕ К РАСЧЁТУ
            </div>
          </div>
          <button
            type="button"
            className="ld-close"
            onClick={onClose}
            aria-label="Закрыть"
          >
            ✕
          </button>
        </div>

        {/* divider under header */}
        <div
          style={{
            marginTop: 22,
            borderBottom: `1px solid ${tokens.lineSoft}`,
          }}
        />

        {/* methodology section */}
        <div
          style={{
            fontSize: 9,
            letterSpacing: "2px",
            color: tokens.body2,
            marginTop: 22,
            marginBottom: 12,
          }}
        >
          КАК СЧИТАЕМ
        </div>
        <div
          style={{
            fontSize: 12.5,
            lineHeight: 1.7,
            color: tokens.body2,
          }}
        >
          Агрегируем объявления (Циан, Я.Недвижимость, Авито, Домклик) и сделки
          Росреестра по сопоставимым квартирам. Медиана{" "}
          <b style={{ color: tokens.ink2 }}>₽/м²</b> → 3 оценки:{" "}
          <b style={{ color: tokens.ink2 }}>
            рекомендованная цена в объявлении
          </b>
          , <b style={{ color: tokens.ink2 }}>ожидаемая цена сделки</b> (с
          учётом торга по ДКП),{" "}
          <b style={{ color: tokens.ink2 }}>ДКП·Росреестр</b> (фактические
          сделки). CV — коэффициент вариации выборки (разброс цен).
        </div>

        {/* location section */}
        <div
          style={{
            fontSize: 9,
            letterSpacing: "2px",
            color: tokens.body2,
            marginTop: 26,
            marginBottom: 12,
          }}
        >
          ЛОКАЦИЯ
        </div>
        <div
          style={{
            fontSize: 12.5,
            lineHeight: 1.7,
            color: tokens.body2,
            background: tokens.infoSoftBg,
            border: `1px solid ${tokens.infoSoftBorder}`,
            borderRadius: 7,
            padding: "12px 14px",
          }}
        >
          {data.status === "ok" && (
            <>
              <div
                style={{ fontSize: 13, fontWeight: 500, color: tokens.ink2 }}
              >
                {locationDirectionSentence(data.indexLabel)}
              </div>
              <div
                style={{
                  display: "flex",
                  alignItems: "baseline",
                  justifyContent: "space-between",
                  marginTop: 10,
                }}
              >
                <span>Медиана ₽/м² рядом (радиус {data.radiusLabel})</span>
                <span
                  style={{ fontFamily: tokens.font.mono, color: tokens.ink2 }}
                >
                  {data.localMedianLabel}
                </span>
              </div>
              <div
                style={{
                  display: "flex",
                  alignItems: "baseline",
                  justifyContent: "space-between",
                  marginTop: 4,
                }}
              >
                <span>Медиана ₽/м² по Екатеринбургу</span>
                <span
                  style={{ fontFamily: tokens.font.mono, color: tokens.ink2 }}
                >
                  {data.cityMedianLabel}
                </span>
              </div>
              {/* Honest, not illustrative: this metric никогда не идёт в цену
                  (аналоги уже берутся из этого же района — повторный учёт
                  локации был бы задвоением, см. app/services/location_index.py). */}
              <div
                style={{
                  marginTop: 8,
                  fontSize: 10.5,
                  lineHeight: 1.5,
                  color: tokens.muted3,
                }}
              >
                Посчитано по {data.sampleSize}{" "}
                {pluralRu(data.sampleSize, [
                  "объявлению",
                  "объявлениям",
                  "объявлениям",
                ])}{" "}
                в радиусе {data.radiusLabel}. Справочно — на итоговую оценку не
                влияет: аналоги для расчёта уже берутся из этого района.
              </div>
              <PoiSection data={data} />
            </>
          )}
          {data.status === "out_of_coverage" && (
            <>
              Локационный индекс считаем только по{" "}
              <b style={{ color: tokens.ink2 }}>Екатеринбургу</b> — этот адрес
              вне зоны покрытия, сравнение с городом недоступно.
            </>
          )}
          {data.status === "insufficient_data" && (
            <>
              Рядом нашлось только{" "}
              <b style={{ color: tokens.ink2 }}>{data.sampleSize}</b>{" "}
              сопоставимых объявлений (радиус {data.radiusLabel}) — этого
              недостаточно для надёжного сравнения с городом.
              <PoiSection data={data} />
            </>
          )}
          {data.status === "loading" && "Считаем локационный индекс…"}
        </div>
      </div>
    </>
  );
}
