"use client";

// /trade-in/v2 — "МЕРА Оценка" HUD dashboard, wired to the real trade-in API.
// This page OWNS the data: it runs the estimate mutation + restore-by-id query,
// spawns the dashboard sub-hooks (street deals, house analytics) and feeds the
// pixel-perfect HUD components via the pure mappers (mapReport / mapObject /
// mapResultPanel / mapSummary). The artboard frame / brackets / 474-1fr-250 grid
// are unchanged — only the result/summary areas swap between honest empty / grey
// skeleton / inline-error / real-data states. Sub-hooks resolve independently:
// a pending or errored street-deals / analytics call degrades its section to a
// fallback inside the mapper instead of blanking the page.

import { useEffect, useMemo, useState } from "react";
import type { CSSProperties, ReactNode } from "react";
import { useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";

import { tokens } from "@/components/trade-in/v2/tokens";
import TopNav from "@/components/trade-in/v2/TopNav";
import HeroBar from "@/components/trade-in/v2/HeroBar";
import ParamsPanel from "@/components/trade-in/v2/ParamsPanel";
import ResultPanel from "@/components/trade-in/v2/ResultPanel";
import { ObjectSummary } from "@/components/trade-in/v2/ObjectSummary";
import { Footer } from "@/components/trade-in/v2/Footer";
import SectionOverlay from "@/components/trade-in/v2/SectionOverlay";
import { LocationDrawer } from "@/components/trade-in/v2/LocationDrawer";
import type { Analytics, ObjectInfo, Report } from "@/components/trade-in/v2/types";
import type {
  CacheData,
  HistoryData,
  SourcesData,
} from "@/components/trade-in/v2/mappers";
import {
  mapAnalytics,
  mapCache,
  mapHistory,
  mapMarkers,
  mapObject,
  mapReport,
  mapResultPanel,
  mapSources,
  mapSummary,
} from "@/components/trade-in/v2/mappers";

import type {
  AggregatedEstimate,
  TradeInEstimateInput,
} from "@/types/trade-in";
import { HTTPError } from "@/lib/api";
import {
  useEstimate,
  useEstimateHistory,
  useEstimateHouseAnalytics,
  useEstimateMutation,
  useEstimatePlacementHistory,
  useEstimateSellTimeSensitivity,
  useSalesVsListings,
  useStreetDeals,
} from "@/lib/trade-in-api";
import { useQuota } from "@/lib/useQuota";
import { useMe } from "@/lib/useMe";
import { logout } from "@/lib/logout";

// OUTER HUD FRAME + 4 corner brackets (design lines 31-37). Decorative,
// non-interactive overlay drawn over the artboard gradient. The frame has
// chamfered octagonal corners (clip-path polygon) + 4px radius; the brackets
// sit just outside it at the artboard corners.
const FRAME_INSET = 14;
const BRACKET_INSET = 6;
const BRACKET_ARM = 22;
const BRACKET_W = 1.5;

const FRAME_CLIP =
  "polygon(18px 0,calc(100% - 18px) 0,100% 18px,100% calc(100% - 18px),calc(100% - 18px) 100%,18px 100%,0 calc(100% - 18px),0 18px)";

const brackets: { key: string; style: CSSProperties }[] = [
  {
    key: "tl",
    style: {
      top: BRACKET_INSET,
      left: BRACKET_INSET,
      borderTop: `${BRACKET_W}px solid ${tokens.bracket2}`,
      borderLeft: `${BRACKET_W}px solid ${tokens.bracket2}`,
    },
  },
  {
    key: "tr",
    style: {
      top: BRACKET_INSET,
      right: BRACKET_INSET,
      borderTop: `${BRACKET_W}px solid ${tokens.bracket2}`,
      borderRight: `${BRACKET_W}px solid ${tokens.bracket2}`,
    },
  },
  {
    key: "bl",
    style: {
      bottom: BRACKET_INSET,
      left: BRACKET_INSET,
      borderBottom: `${BRACKET_W}px solid ${tokens.bracket2}`,
      borderLeft: `${BRACKET_W}px solid ${tokens.bracket2}`,
    },
  },
  {
    key: "br",
    style: {
      bottom: BRACKET_INSET,
      right: BRACKET_INSET,
      borderBottom: `${BRACKET_W}px solid ${tokens.bracket2}`,
      borderRight: `${BRACKET_W}px solid ${tokens.bracket2}`,
    },
  },
];

// Honest neutral fallbacks for the meta blocks (HeroBar / Footer) before there
// is an estimate. Dashes — never the design fixtures (which would read as a fake
// real report).
const EMPTY_REPORT: Report = { id: "—", date: "—", validUntil: "—" };
const EMPTY_OBJECT: ObjectInfo = {
  address: "—",
  city: "",
  area: "—",
  rooms: "—",
  floor: "—",
  totalFloors: "—",
  year: "—",
  houseType: "—",
  repair: "—",
  balcony: false,
  locationCoef: "—",
  streetView: "",
  compass: "",
};

// Skeleton pulse (opacity only — NO shimmer sweep, per .claude/rules/ui-*) +
// retry-button hover.
const HELPER_STYLES = `
@keyframes v2-skel-pulse{0%{opacity:.45}50%{opacity:.78}100%{opacity:.45}}
.v2-skel{animation:v2-skel-pulse 1.5s ease-in-out infinite}
.v2-retry-btn{transition:all .15s;cursor:pointer}
.v2-retry-btn:hover{border-color:${tokens.accent};color:${tokens.accent};background:rgba(46,139,255,.07)}
`;

const retryBtnStyle: CSSProperties = {
  padding: "8px 16px",
  background: "transparent",
  border: `1px solid ${tokens.line}`,
  borderRadius: 6,
  fontFamily: tokens.font.sans,
  fontSize: 11,
  fontWeight: 600,
  letterSpacing: 1.5,
  color: tokens.muted,
};

// ── Result/summary area state panels (kept inside the page — these are layout
//    states, not reusable design components, so they do not belong in v2/). ───

function SkeletonBlock({
  height,
  flex,
}: {
  height?: number;
  flex?: number;
}) {
  return (
    <div
      className="v2-skel"
      style={{
        height,
        flex,
        minHeight: 0,
        background: tokens.surface.w50,
        border: `1px solid ${tokens.line2}`,
        borderRadius: 8,
      }}
    />
  );
}

function ResultSkeleton() {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: 14,
        minWidth: 0,
        height: "100%",
      }}
    >
      <SkeletonBlock height={18} />
      <div
        style={{
          display: "grid",
          gridTemplateColumns: "1fr 1fr 1fr",
          gap: 14,
          flex: "0 0 auto",
        }}
      >
        <SkeletonBlock height={150} />
        <SkeletonBlock height={150} />
        <SkeletonBlock height={150} />
      </div>
      <SkeletonBlock height={150} />
      <SkeletonBlock flex={1} />
    </div>
  );
}

function SummarySkeleton() {
  return (
    <div
      className="v2-skel"
      style={{
        height: "100%",
        background: tokens.surface.w50,
        border: `1px solid ${tokens.line2}`,
        borderRadius: 8,
      }}
    />
  );
}

function PlaceholderPanel({
  title,
  body,
  children,
}: {
  title: string;
  body: string;
  children?: ReactNode;
}) {
  return (
    <div
      style={{
        height: "100%",
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        justifyContent: "center",
        textAlign: "center",
        gap: 12,
        background: tokens.surface.w50,
        backdropFilter: "blur(6px)",
        border: `1px solid ${tokens.line2}`,
        borderRadius: 8,
        padding: "40px 28px",
        fontFamily: tokens.font.sans,
      }}
    >
      <div
        style={{
          fontSize: 13,
          fontWeight: 600,
          letterSpacing: 2,
          color: tokens.ink,
        }}
      >
        {title}
      </div>
      <div
        style={{
          fontSize: 12,
          color: tokens.muted,
          maxWidth: 360,
          lineHeight: 1.5,
        }}
      >
        {body}
      </div>
      {children}
    </div>
  );
}

function EmptyResultPanel() {
  return (
    <PlaceholderPanel
      title="РЕЗУЛЬТАТ ОЦЕНКИ"
      body="Заполните параметры квартиры слева и нажмите «ОЦЕНИТЬ КВАРТИРУ» — здесь появится рыночная оценка, диапазоны цен и источники данных."
    />
  );
}

function InsufficientPanel() {
  return (
    <PlaceholderPanel
      title="НЕДОСТАТОЧНО ДАННЫХ"
      body="По этому адресу не нашлось достаточного числа аналогов и сделок для надёжной оценки. Уточните адрес или параметры квартиры."
    />
  );
}

function RestoreErrorPanel({
  notFound,
  onRetry,
  onNew,
}: {
  notFound: boolean;
  onRetry: () => void;
  onNew: () => void;
}) {
  return (
    <PlaceholderPanel
      title={notFound ? "ОТЧЁТ НЕ НАЙДЕН" : "ОШИБКА ЗАГРУЗКИ"}
      body={
        notFound
          ? "Ссылка устарела, отчёт удалён или у вас нет к нему доступа."
          : "Не удалось загрузить отчёт. Попробуйте ещё раз."
      }
    >
      <div style={{ display: "flex", gap: 10, marginTop: 4 }}>
        {!notFound && (
          <button
            type="button"
            className="v2-retry-btn"
            onClick={onRetry}
            style={retryBtnStyle}
          >
            ПОВТОРИТЬ
          </button>
        )}
        <button
          type="button"
          className="v2-retry-btn"
          onClick={onNew}
          style={retryBtnStyle}
        >
          НОВАЯ ОЦЕНКА
        </button>
      </div>
    </PlaceholderPanel>
  );
}

function SummaryPlaceholder() {
  return (
    <div
      style={{
        height: "100%",
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        justifyContent: "center",
        textAlign: "center",
        gap: 8,
        background: tokens.surface.w50,
        backdropFilter: "blur(6px)",
        border: `1px solid ${tokens.line2}`,
        borderRadius: 8,
        padding: "28px 18px",
        fontFamily: tokens.font.sans,
      }}
    >
      <div
        style={{
          fontSize: 12,
          fontWeight: 600,
          letterSpacing: 1.5,
          color: tokens.ink,
        }}
      >
        СВОДКА ОБЪЕКТА
      </div>
      <div style={{ fontSize: 11, color: tokens.muted, lineHeight: 1.5 }}>
        Появится после оценки квартиры.
      </div>
    </div>
  );
}

// Restore-by-id: read ?id= from the URL with an SSR guard. Mirrors v1 (app/
// page.tsx) deliberately — next/navigation useSearchParams would force a
// Suspense boundary and break `next build`.
function readUrlId(): string | null {
  if (typeof window === "undefined") return null;
  return new URLSearchParams(window.location.search).get("id");
}

export default function TradeInV2Page() {
  const router = useRouter();
  const queryClient = useQueryClient();

  const [nav, setNav] = useState(0);
  const [drawerOpen, setDrawerOpen] = useState(false);
  // Locally-held just-computed estimate (so we render instantly after submit
  // without a round-trip through the restore query).
  const [freshResult, setFreshResult] = useState<AggregatedEstimate | null>(
    null,
  );

  const urlId = readUrlId();

  // Post-mount flag: readUrlId() is null on SSR but a UUID on the client's first
  // render, so any structure that depends on the id (e.g. the PDF <a> vs disabled
  // <button>) would diverge between SSR and hydration on a fresh /v2?id= load.
  // Gate id-dependent structure behind `mounted` to keep first render identical.
  const [mounted, setMounted] = useState(false);
  useEffect(() => setMounted(true), []);

  const mutation = useEstimateMutation();
  // Skip the restore fetch once we already hold a fresh result.
  const restored = useEstimate(freshResult === null ? urlId : null);

  const estimate: AggregatedEstimate | null = freshResult ?? restored.data ?? null;
  const currentEstimateId = freshResult?.estimate_id ?? urlId;

  // Per-user side data, independent of the current estimate. Both fail-soft
  // (retry:false): history feeds the «Предыдущие оценки» overlay (04), quota
  // feeds the TopNav «Мои отчёты» badge. Refreshed after a successful estimate
  // via invalidateQueries in handleSubmit.
  const history = useEstimateHistory();
  const quota = useQuota();
  // Real logged-in identity for the TopNav user menu (replaces the design
  // fixture). /me returns {username, role, brand} — no display name / email, so
  // name falls back to username, org to brand ?? role, email to "" (renders
  // blank, never invented). undefined while loading → TopNav «Гость» fallback.
  const me = useMe();

  // Dashboard sub-hooks — each resolves independently; failure degrades its
  // section via the mappers (null input) rather than blanking the page.
  const streetDeals = useStreetDeals(
    estimate?.target_address ?? null,
    estimate?.area_m2 ?? null,
    estimate?.rooms ?? null,
  );
  const analytics = useEstimateHouseAnalytics(currentEstimateId);
  // Overlay-only sub-hooks (04 ПРОДАЖИ В ДОМЕ / 05 РЫНОК / 06 АНАЛИТИКА). Same
  // contract: each resolves independently; a pending/errored one degrades its
  // overlay section to an honest empty via the mapper (null input).
  const placementHistory = useEstimatePlacementHistory(currentEstimateId);
  const salesVsListings = useSalesVsListings(
    estimate?.target_address ?? null,
    estimate?.area_m2 ?? null,
    estimate?.rooms ?? null,
  );
  const sellTime = useEstimateSellTimeSensitivity(currentEstimateId);

  const streetDealsData = streetDeals.data ?? null;
  const analyticsData = analytics.data ?? null;
  const placementHistoryData = placementHistory.data ?? null;
  const salesVsListingsData = salesVsListings.data ?? null;
  const sellTimeData = sellTime.data ?? null;

  // ── Derived state flags ───────────────────────────────────────────────────
  const restoreActive = urlId !== null && freshResult === null;
  const restoreLoading = restoreActive && restored.isPending;
  const restoreError = restoreActive && restored.isError;
  const restoreNotFound =
    restoreError &&
    restored.error instanceof HTTPError &&
    restored.error.status === 404;

  const loading = mutation.isPending || restoreLoading;
  const insufficient =
    estimate != null && (estimate.insufficient_data || estimate.n_analogs === 0);
  const apiError = mutation.error?.message ?? null;

  // ── Mapped presentation data (memoised so nav/drawer toggles don't recompute
  //    geometry). ──────────────────────────────────────────────────────────
  const report = useMemo(
    () => (estimate ? mapReport(estimate) : EMPTY_REPORT),
    [estimate],
  );
  const objectInfo = useMemo(
    () => (estimate ? mapObject(estimate) : EMPTY_OBJECT),
    [estimate],
  );
  const resultPanelData = useMemo(
    () => (estimate ? mapResultPanel(estimate, streetDealsData) : null),
    [estimate, streetDealsData],
  );
  const summaryData = useMemo(
    () => (estimate ? mapSummary(estimate, analyticsData, streetDealsData) : null),
    [estimate, analyticsData, streetDealsData],
  );

  // ── Overlay (section) mapped data. Computed unconditionally: the mappers
  //    accept nulls and degrade per-section, so an overlay opened before/without
  //    an estimate shows an honest empty shell — never the design fixtures as if
  //    they were a real report. ─────────────────────────────────────────────
  const historyData = useMemo<HistoryData>(
    () =>
      mapHistory(placementHistoryData, streetDealsData, salesVsListingsData),
    [placementHistoryData, streetDealsData, salesVsListingsData],
  );
  const analyticsViewData = useMemo<Analytics>(
    () => mapAnalytics(analyticsData, sellTimeData, estimate),
    [analyticsData, sellTimeData, estimate],
  );
  const sourcesData = useMemo<SourcesData>(
    () => mapSources(estimate, streetDealsData),
    [estimate, streetDealsData],
  );
  // «Предыдущие оценки» (04) — KPIs + last-estimates table derived purely from
  // the per-user /history list (never /cache-stats, which is global/admin).
  const cacheData = useMemo<CacheData>(
    () => mapCache(history.data ?? null),
    [history.data],
  );

  // TopNav «Мои отчёты» badge: per-user estimate count. quota.used is the
  // authoritative monthly counter; fall back to the loaded history length, then
  // to the fixture default (undefined) for unwired/pre-load states.
  const reportsCount = quota.data?.used ?? history.data?.length;

  // Real user for the TopNav menu. undefined until /me resolves → TopNav shows
  // its neutral «Гость» fallback (never the old "Андрей Петров / Брусника").
  const topNavUser = useMemo(() => {
    const scope = me.data;
    if (!scope) return undefined;
    return {
      name: scope.username,
      org: scope.brand ?? scope.role,
      email: "",
      initials: scope.username.slice(0, 2).toUpperCase(),
    };
  }, [me.data]);

  // Analog price pins for the 01 map, projected from the real estimate. No
  // estimate → mapMarkers(null) → [] → ParamsPanel renders only the subject pin
  // (Finding #2: never the static fixture price markers).
  const markers = useMemo(() => mapMarkers(estimate), [estimate]);

  // Prefill for restore-by-id / re-estimate. Keyed remount applies it when the
  // estimate arrives async (useState reads initialValues only on mount).
  const initialValues = useMemo<Partial<TradeInEstimateInput> | undefined>(
    () =>
      estimate
        ? {
            address: estimate.target_address ?? undefined,
            area_m2: estimate.area_m2 ?? undefined,
            rooms: estimate.rooms ?? undefined,
            floor: estimate.floor,
            total_floors: estimate.total_floors,
            year_built: estimate.year_built ?? undefined,
            house_type: estimate.house_type ?? undefined,
            repair_state: estimate.repair_state ?? undefined,
            has_balcony: estimate.has_balcony ?? undefined,
          }
        : undefined,
    [estimate],
  );

  function handleSubmit(input: TradeInEstimateInput) {
    mutation.mutate(input, {
      onSuccess: (est) => {
        setFreshResult(est);
        // A new estimate consumed quota and added a history row — refresh both
        // so the TopNav badge + «Предыдущие оценки» overlay reflect it.
        void queryClient.invalidateQueries({
          queryKey: ["trade-in", "history"],
        });
        void queryClient.invalidateQueries({
          queryKey: ["trade-in", "quota"],
        });
        // basePath auto-prefixes → /trade-in/v2?id=...
        router.replace(`/v2?id=${est.estimate_id}`, { scroll: false });
      },
    });
  }

  function handleRetry() {
    void restored.refetch();
  }

  function handleNew() {
    setFreshResult(null);
    router.replace("/v2", { scroll: false });
  }

  let middleContent: ReactNode;
  if (loading) {
    middleContent = <ResultSkeleton />;
  } else if (restoreError) {
    middleContent = (
      <RestoreErrorPanel
        notFound={restoreNotFound}
        onRetry={handleRetry}
        onNew={handleNew}
      />
    );
  } else if (estimate && !insufficient && resultPanelData) {
    middleContent = <ResultPanel data={resultPanelData} onNavigate={setNav} />;
  } else if (estimate && insufficient) {
    middleContent = <InsufficientPanel />;
  } else {
    middleContent = <EmptyResultPanel />;
  }

  let rightContent: ReactNode;
  if (loading) {
    rightContent = <SummarySkeleton />;
  } else if (estimate && !insufficient && summaryData) {
    rightContent = (
      <ObjectSummary
        data={{ object: objectInfo, summary: summaryData }}
        onNavigate={setNav}
      />
    );
  } else {
    rightContent = <SummaryPlaceholder />;
  }

  return (
    <div
      style={{
        position: "relative",
        width: 1536,
        height: 1024,
        overflow: "hidden",
        background: tokens.gradientBg,
        fontFamily: tokens.font.sans,
        color: tokens.ink,
      }}
    >
      <style>{HELPER_STYLES}</style>

      {/* OUTER HUD FRAME */}
      <div
        style={{
          position: "absolute",
          inset: FRAME_INSET,
          border: `1px solid ${tokens.line}`,
          borderRadius: 4,
          clipPath: FRAME_CLIP,
          pointerEvents: "none",
        }}
      />
      {/* 4 corner brackets */}
      {brackets.map((b) => (
        <div
          key={b.key}
          style={{
            position: "absolute",
            width: BRACKET_ARM,
            height: BRACKET_ARM,
            pointerEvents: "none",
            ...b.style,
          }}
        />
      ))}

      {/* CONTENT WRAP */}
      <div
        style={{
          position: "absolute",
          inset: 14,
          padding: "16px 28px",
          display: "flex",
          flexDirection: "column",
        }}
      >
        <TopNav
          active={nav}
          onNavigate={setNav}
          reports={reportsCount ?? 0}
          user={topNavUser}
          onLogout={logout}
        />
        <HeroBar
          data={{ report, object: objectInfo }}
          estimateId={mounted ? currentEstimateId : null}
          onOpenInfo={() => setDrawerOpen(true)}
        />

        <div
          style={{
            display: "grid",
            gridTemplateColumns: "474px 1fr 250px",
            gap: 18,
            flex: 1,
            minHeight: 0,
          }}
        >
          <ParamsPanel
            key={estimate?.estimate_id ?? "new"}
            onSubmit={handleSubmit}
            isPending={mutation.isPending}
            error={apiError}
            initialValues={initialValues}
            markers={markers}
          />
          {middleContent}
          {rightContent}
        </div>

        <Footer data={report} />
      </div>

      {nav !== 0 && (
        <SectionOverlay
          active={nav}
          onClose={() => setNav(0)}
          onNavigate={setNav}
          history={historyData}
          analytics={analyticsViewData}
          sources={sourcesData}
          cache={cacheData}
        />
      )}
      <LocationDrawer open={drawerOpen} onClose={() => setDrawerOpen(false)} />
    </div>
  );
}
