/**
 * Concept (Generative Design) API — TanStack Query wrappers.
 *
 * Contract mirrors `backend/app/schemas/concept.py` exactly (Stage 1a frozen
 * interface). Types are defined inline rather than via `npm run codegen`
 * because codegen targets a live OpenAPI on localhost:8000 (`src/lib/
 * api-types.ts`) which is unreachable in this env — same approach as the
 * parcel-chat contract in `site-finder-api.ts`.
 *
 * Endpoint: POST /api/v1/concepts → ConceptOutput.
 * Export endpoints (DXF/PDF) are not in the Stage-1a contract yet; the export
 * buttons fall back to client-side GeoJSON/CSV generation and probe the
 * documented `/concepts/{...}/export` path opportunistically (see
 * ConceptExportButtons).
 */

import { useMutation } from "@tanstack/react-query";
import type { Feature, FeatureCollection, Polygon } from "geojson";

import { apiFetch, apiFetchWithStatus } from "@/lib/api";

// ── Input contract (ConceptInput) ───────────────────────────────────────────

export type HousingClass = "econom" | "comfort" | "business";
export type DevelopmentType = "spot" | "mid_rise" | "high_rise";
export type ConceptStrategy = "max_area" | "max_insolation" | "balanced";

export interface ConceptInput {
  /** GeoJSON Polygon of the parcel (WGS84 / EPSG:4326). */
  parcel_geojson: Polygon;
  housing_class: HousingClass;
  /** Целевая этажность 1–30. */
  target_floors: number;
  development_type: DevelopmentType;
  /** Стоимость участка (₽) — опционально, для финмодели. */
  land_cost_rub?: number | null;
}

// ── Output contract (ConceptOutput) ─────────────────────────────────────────

/** Технико-экономические показатели (ТЭП). */
export interface Teap {
  built_area_sqm: number;
  total_floor_area_sqm: number;
  residential_area_sqm: number;
  apartments_count: number;
  density: number;
  parking_spaces: number;
}

export interface FinancialModel {
  revenue_rub: number;
  cost_rub: number;
  gross_margin_rub: number;
  irr: number;
}

export interface ConceptVariant {
  strategy: ConceptStrategy;
  /** FeatureCollection полигонов зданий (WGS84). */
  buildings_geojson: FeatureCollection;
  teap: Teap;
  financial: FinancialModel;
}

export interface ConceptOutput {
  variants: ConceptVariant[];
}

// ── Strategy display metadata ────────────────────────────────────────────────

export const STRATEGY_LABELS: Record<ConceptStrategy, string> = {
  max_area: "Макс. площадь",
  max_insolation: "Макс. инсоляция",
  balanced: "Баланс",
};

export const STRATEGY_HINTS: Record<ConceptStrategy, string> = {
  max_area: "Максимальная плотность застройки в пределах нормативов.",
  max_insolation: "Приоритет инсоляции и разрывов между корпусами.",
  balanced: "Компромисс между ТЭП и качеством среды.",
};

export const HOUSING_CLASS_LABELS: Record<HousingClass, string> = {
  econom: "Эконом",
  comfort: "Комфорт",
  business: "Бизнес",
};

export const DEVELOPMENT_TYPE_LABELS: Record<DevelopmentType, string> = {
  spot: "Точечная",
  mid_rise: "Среднеэтажная",
  high_rise: "Высотная",
};

// ── Hook: useCreateConcept ────────────────────────────────────────────────────

/**
 * POST /api/v1/concepts — generate building variants for a parcel polygon.
 * Action (not a query) → drive from `useMutation` per the data-layer rule.
 * Uses the shared `apiFetch` (base URL + session header + Content-Type) rather
 * than re-adding fetch config.
 */
export function useCreateConcept() {
  return useMutation<ConceptOutput, Error, ConceptInput>({
    mutationFn: (payload) =>
      apiFetch<ConceptOutput>("/api/v1/concepts", {
        method: "POST",
        body: JSON.stringify(payload),
      }),
  });
}

// ── Hook: useCadastreGeom ─────────────────────────────────────────────────────

/**
 * Resolves a cadastre number to its parcel polygon by POSTing the Site Finder
 * analyze endpoint (`/api/v1/parcels/{cad}/analyze`) and reading `geom_geojson`.
 * Re-uses the existing endpoint rather than adding a new backend route.
 *
 * The analyze endpoint returns 202 while НСПД geometry is still being fetched
 * (see useParcelAnalyzeQuery). For the concept input we keep the UX simple: a
 * 202 surfaces a "try again" error instead of a 2-minute poll loop — the user
 * can re-submit once geometry is cached, or draw the polygon manually.
 */
export function useCadastreGeom() {
  return useMutation<Polygon, Error, string>({
    mutationFn: async (cad) => {
      const url = `/api/v1/parcels/${encodeURIComponent(cad)}/analyze?horizon=12`;
      const { status, body } = await apiFetchWithStatus<{
        geom_geojson?: unknown;
      }>(url, { method: "POST" });

      if (status === 202) {
        throw new Error(
          "Геометрия участка ещё загружается из НСПД. Повторите через несколько секунд.",
        );
      }
      const polygon = extractPolygon(body?.geom_geojson);
      if (!polygon) {
        throw new Error(
          "Не удалось получить геометрию участка по этому номеру.",
        );
      }
      return polygon;
    },
  });
}

// ── Polygon helpers ───────────────────────────────────────────────────────────

/**
 * Narrows an unknown GeoJSON value (e.g. the analyze endpoint's `geom_geojson`)
 * into a Polygon. Accepts a bare Polygon, a Feature wrapping a Polygon, or the
 * first polygon ring of a MultiPolygon. Returns null on any other shape.
 */
export function extractPolygon(geom: unknown): Polygon | null {
  if (geom == null || typeof geom !== "object") return null;
  const g = geom as {
    type?: unknown;
    coordinates?: unknown;
    geometry?: unknown;
  };

  if (g.type === "Feature") {
    return extractPolygon((g as Feature).geometry);
  }
  if (g.type === "Polygon" && Array.isArray(g.coordinates)) {
    return {
      type: "Polygon",
      coordinates: g.coordinates as Polygon["coordinates"],
    };
  }
  if (g.type === "MultiPolygon" && Array.isArray(g.coordinates)) {
    const first = (g.coordinates as Polygon["coordinates"][])[0];
    if (first) return { type: "Polygon", coordinates: first };
  }
  return null;
}

/** Approximate parcel area in m² from a WGS84 polygon (spherical-excess shoelace). */
export function polygonAreaSqm(polygon: Polygon): number {
  const ring = polygon.coordinates[0];
  if (!ring || ring.length < 4) return 0;
  const R = 6_378_137; // WGS84 equatorial radius (m)
  const toRad = (deg: number) => (deg * Math.PI) / 180;
  let area = 0;
  for (let i = 0; i < ring.length - 1; i++) {
    const [lon1, lat1] = ring[i];
    const [lon2, lat2] = ring[i + 1];
    area +=
      toRad(lon2 - lon1) * (2 + Math.sin(toRad(lat1)) + Math.sin(toRad(lat2)));
  }
  return Math.abs((area * R * R) / 2);
}
