/**
 * Site Finder API hooks — TanStack Query wrappers.
 *
 * Mock fallback strategy (from mock-toggle.ts):
 *  MOCK_PARCELS_BBOX   — uses parcels-bbox.json fixture (B1 not yet in prod)
 *  MOCK_RECENT_PARCELS — uses localStorage only (B2 stub not yet in prod)
 *  MOCK_ANALYZE        — uses parcel-analyze.json fixture (B5)
 *  MOCK_POI_SCORE      — uses poi-score.json fixture (B6)
 */

import { useQuery } from "@tanstack/react-query";
import { apiFetch } from "@/lib/api";
import {
  MOCK_PARCELS_BBOX,
  MOCK_RECENT_PARCELS,
  MOCK_ANALYZE,
  MOCK_POI_SCORE,
} from "@/lib/mock-toggle";
import fixtureParcels from "@/lib/mocks/parcels-bbox.json";
import fixtureAnalyze from "@/lib/mocks/parcel-analyze.json";
import fixturePoiScore from "@/lib/mocks/poi-score.json";

// ── Types ─────────────────────────────────────────────────────────────────────

export type ParcelStatus = "free" | "in_progress" | "favorite";

export type ParcelVri =
  | "multistory"
  | "mixed"
  | "individual"
  | "office"
  | "other";

export interface ParcelBboxItem {
  cad_num: string;
  address: string;
  area_ha: number;
  status: ParcelStatus;
  district: string;
  vri: ParcelVri;
  lat: number;
  lon: number;
}

export interface ParcelBboxFilters {
  min_area?: number;
  max_area?: number;
  status?: ParcelStatus;
  district?: string;
  vri?: ParcelVri;
}

export interface BboxCoords {
  minLat: number;
  minLon: number;
  maxLat: number;
  maxLon: number;
}

export interface RecentParcel {
  cad_num: string;
  address: string;
  area_ha: number;
  district: string;
  visited_at: string; // ISO string
}

// ── Constants ─────────────────────────────────────────────────────────────────

const LOCAL_STORAGE_KEY = "gd_recent_parcels";
const MAX_RECENT = 10;

// ── localStorage helpers ──────────────────────────────────────────────────────

export function getLocalRecentParcels(): RecentParcel[] {
  if (typeof window === "undefined") return [];
  try {
    const raw = localStorage.getItem(LOCAL_STORAGE_KEY);
    if (!raw) return [];
    const parsed: unknown = JSON.parse(raw);
    if (!Array.isArray(parsed)) return [];
    return parsed as RecentParcel[];
  } catch {
    return [];
  }
}

export function addLocalRecentParcel(parcel: RecentParcel): void {
  if (typeof window === "undefined") return;
  try {
    const existing = getLocalRecentParcels();
    const filtered = existing.filter((p) => p.cad_num !== parcel.cad_num);
    const updated = [parcel, ...filtered].slice(0, MAX_RECENT);
    localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(updated));
  } catch {
    // ignore localStorage errors
  }
}

// ── Hook: useParcelsBboxQuery ─────────────────────────────────────────────────

function applyFilters(
  parcels: ParcelBboxItem[],
  filters: ParcelBboxFilters,
): ParcelBboxItem[] {
  return parcels.filter((p) => {
    if (filters.min_area != null && p.area_ha < filters.min_area) return false;
    if (filters.max_area != null && p.area_ha > filters.max_area) return false;
    if (filters.status != null && p.status !== filters.status) return false;
    if (
      filters.district != null &&
      filters.district !== "" &&
      p.district !== filters.district
    )
      return false;
    if (filters.vri != null && filters.vri !== "other" && p.vri !== filters.vri)
      return false;
    return true;
  });
}

function filterByBbox(
  parcels: ParcelBboxItem[],
  bbox: BboxCoords,
): ParcelBboxItem[] {
  return parcels.filter(
    (p) =>
      p.lat >= bbox.minLat &&
      p.lat <= bbox.maxLat &&
      p.lon >= bbox.minLon &&
      p.lon <= bbox.maxLon,
  );
}

export function useParcelsBboxQuery(
  bbox: BboxCoords | null,
  filters: ParcelBboxFilters = {},
) {
  return useQuery({
    queryKey: [
      "parcels-bbox",
      bbox?.minLat,
      bbox?.minLon,
      bbox?.maxLat,
      bbox?.maxLon,
      filters.min_area,
      filters.max_area,
      filters.status,
      filters.district,
      filters.vri,
    ],
    queryFn: async (): Promise<ParcelBboxItem[]> => {
      if (MOCK_PARCELS_BBOX) {
        // Fixture: filter by bbox + filters client-side
        const typed = fixtureParcels as ParcelBboxItem[];
        const inBbox = bbox ? filterByBbox(typed, bbox) : typed;
        return applyFilters(inBbox, filters);
      }

      if (!bbox) return [];

      const params = new URLSearchParams({
        min_lat: String(bbox.minLat),
        min_lon: String(bbox.minLon),
        max_lat: String(bbox.maxLat),
        max_lon: String(bbox.maxLon),
      });
      if (filters.min_area != null)
        params.set("min_area", String(filters.min_area));
      if (filters.max_area != null)
        params.set("max_area", String(filters.max_area));
      if (filters.status) params.set("status", filters.status);
      if (filters.district) params.set("district", filters.district);
      if (filters.vri) params.set("vri", filters.vri);

      // Backend returns { parcels, count, limit, bbox_area_km2 } with field
      // names centroid_lat/centroid_lon/area_m2 and nullable status. Adapt to
      // the flat ParcelBboxItem[] shape the map components consume. district /
      // vri / address are not in B1 yet — defaulted until enrichment lands.
      const raw = await apiFetch<{
        parcels: Array<{
          cad_num: string;
          centroid_lat: number;
          centroid_lon: number;
          area_m2: number | null;
          land_category: string | null;
          status: ParcelStatus | null;
        }>;
      }>(`/api/v1/parcels/by-bbox?${params.toString()}`);

      const adapted: ParcelBboxItem[] = raw.parcels.map((p) => ({
        cad_num: p.cad_num,
        lat: p.centroid_lat,
        lon: p.centroid_lon,
        area_ha: p.area_m2 != null ? p.area_m2 / 10000 : 0,
        status: p.status ?? "free",
        district: "—",
        vri: "other",
        address: "",
      }));
      // Apply filters client-side until backend supports them (B1 follow-up)
      return applyFilters(adapted, filters);
    },
    enabled: MOCK_PARCELS_BBOX || bbox != null,
    staleTime: 30_000,
  });
}

// ── Hook: useRecentParcels ────────────────────────────────────────────────────

interface RecentParcelsResponse {
  parcels: RecentParcel[];
}

export function useRecentParcels() {
  return useQuery({
    queryKey: ["recent-parcels"],
    queryFn: async (): Promise<RecentParcel[]> => {
      const localItems = getLocalRecentParcels();

      if (MOCK_RECENT_PARCELS) {
        return localItems;
      }

      try {
        const serverData = await apiFetch<RecentParcelsResponse>(
          "/api/v1/users/me/recent-parcels",
        );
        // Merge: server list + local items not already in server list
        const serverCads = new Set(serverData.parcels.map((p) => p.cad_num));
        const localOnly = localItems.filter((p) => !serverCads.has(p.cad_num));
        return [...serverData.parcels, ...localOnly].slice(0, MAX_RECENT);
      } catch {
        // B2 stub may not be deployed yet — fall back to localStorage
        return localItems;
      }
    },
    staleTime: 60_000,
  });
}

// ── Types: B5 extended analyze response ──────────────────────────────────────

export interface ParcelEgrn {
  cad_num: string;
  address: string;
  area_m2: number;
  vri: string;
  category: string;
  registration_date: string | null;
  owner_type: string;
  encumbrance: string;
  status: string;
  last_updated: string | null;
}

// ── Types: utilities (engineering nearby) ────────────────────────────────────

export type UtilitySubtype =
  | "substation"
  | "pipeline"
  | "power_line"
  | "water_intake"
  | "pumping_station"
  | string; // open for future subtypes

export interface UtilitySummaryItem {
  subtype: UtilitySubtype;
  nearest_m: number;
  name: string | null;
  count_within_2km: number;
}

export interface UtilitiesData {
  summary: UtilitySummaryItem[];
  power_line_охранная_зона_flag: boolean;
  note: string | null;
}

export interface ParcelAnalyzeResponse {
  cad_num: string;
  source: string;
  geom_geojson: unknown;
  district: {
    district_name: string;
    median_price_per_m2: number;
    dist_to_center: number;
  } | null;
  score: number;
  score_label?: string;
  score_explanation?: string;
  score_breakdown: Record<
    string,
    Array<{
      name: string | null;
      distance_m: number;
      last_edit: string | null;
      lat: number;
      lon: number;
    }>
  >;
  poi_count: number;
  /** EGRN data — may be null if B5 extended not yet deployed */
  egrn?: ParcelEgrn | null;
  /** Engineering / utilities nearby — present when NSPD data available */
  utilities?: UtilitiesData | null;
}

// ── Types: B6 poi-score response ─────────────────────────────────────────────

export interface PoiScoreItem {
  category: string;
  name: string;
  distance_m: number;
  weight: number;
  score_contribution: number;
}

export interface PoiScoreResponse {
  cad_num: string;
  poi_weighted_score: number;
  items: PoiScoreItem[];
}

// ── Hook: useParcelAnalyzeQuery (B5) ─────────────────────────────────────────

export function useParcelAnalyzeQuery(cad: string) {
  return useQuery({
    queryKey: ["parcel-analyze", cad],
    queryFn: async (): Promise<ParcelAnalyzeResponse> => {
      if (MOCK_ANALYZE) {
        // Return fixture data regardless of cad — for dev only
        return fixtureAnalyze as ParcelAnalyzeResponse;
      }
      return apiFetch<ParcelAnalyzeResponse>(
        `/api/v1/parcels/${encodeURIComponent(cad)}/analyze`,
        { method: "POST" },
      );
    },
    staleTime: 5 * 60_000, // 5 min — analyze is expensive
    retry: 1,
  });
}

// ── Hook: useParcelPoiScoreQuery (B6) ────────────────────────────────────────

/**
 * Raw shape from backend /api/v1/parcels/{cad}/poi-score.
 * Backend returns top_poi array with address field (not score_contribution).
 * We adapt to PoiScoreResponse so downstream components are stable.
 */
interface PoiScoreRaw {
  cad_num: string;
  radius_m: number;
  top_poi: Array<{
    name: string;
    category: string;
    distance_m: number;
    weight: number;
    address?: string | null;
  }>;
}

export function useParcelPoiScoreQuery(cad: string) {
  return useQuery({
    queryKey: ["parcel-poi-score", cad],
    queryFn: async (): Promise<PoiScoreResponse> => {
      if (MOCK_POI_SCORE) {
        return fixturePoiScore as PoiScoreResponse;
      }
      // Backend returns {cad_num, radius_m, top_poi: [{name,category,distance_m,weight,address}]}
      // Adapt to PoiScoreResponse {cad_num, poi_weighted_score, items} for stable consumer API.
      const raw = await apiFetch<PoiScoreRaw>(
        `/api/v1/parcels/${encodeURIComponent(cad)}/poi-score`,
      );
      const items: PoiScoreItem[] = (raw.top_poi ?? []).map((p) => ({
        category: p.category,
        name: p.name,
        distance_m: p.distance_m,
        weight: p.weight,
        // score_contribution not in backend response — derive from weight (0..1) × 100
        score_contribution: Math.round(p.weight * 100),
      }));
      const poi_weighted_score = items.reduce(
        (s, p) => s + p.score_contribution,
        0,
      );
      return { cad_num: raw.cad_num, poi_weighted_score, items };
    },
    staleTime: 5 * 60_000,
    retry: 1,
  });
}
