"use client";

import { useState, useEffect } from "react";
import dynamic from "next/dynamic";
import type {
  City,
  CityStats,
  CategoryId,
  TravelMode,
  ProfileId,
} from "@transportationer/shared";
import { PROFILES } from "@transportationer/shared";
import { ControlPanel } from "@/components/control-panel";
import { StatsBar } from "@/components/stats-bar";
import { CitySelector } from "@/components/city-selector";
import {
  LocationScorePanel,
  type LocationScoreData,
  type OverlayMode,
} from "@/components/location-score-panel";
import { MapLegend } from "@/components/map-legend";

const MapView = dynamic(
  () => import("@/components/map-view").then((m) => m.MapView),
  { ssr: false, loading: () => <div className="flex-1 bg-gray-200 animate-pulse" /> },
);

/** Compute 3 evenly-spaced contour values up to the threshold (deduped, min 1). */
function isochroneContours(threshold: number): number[] {
  const raw = [
    Math.max(1, Math.round(threshold / 3)),
    Math.max(2, Math.round((threshold * 2) / 3)),
    threshold,
  ];
  return [...new Set(raw)];
}

export default function HomePage() {
  const [cities, setCities] = useState<City[]>([]);
  const [selectedCity, setSelectedCity] = useState<string | null>(null);
  const [profile, setProfile] = useState<ProfileId>("universal");
  const [mode, setMode] = useState<TravelMode>("walking");
  const [threshold, setThreshold] = useState(15);
  const [weights, setWeights] = useState({ ...PROFILES["universal"].categoryWeights });
  const [activeCategory, setActiveCategory] = useState<CategoryId | "composite">("composite");
  const [stats, setStats] = useState<CityStats | null>(null);

  // Pin / location rating
  const [pinLocation, setPinLocation] = useState<{ lat: number; lng: number } | null>(null);
  const [pinData, setPinData] = useState<LocationScoreData | null>(null);
  const [pinAddress, setPinAddress] = useState<string | undefined>(undefined);
  const [pinEstateValue, setPinEstateValue] = useState<number | null>(null);
  const [pinEstatePercentile, setPinEstatePercentile] = useState<number | null>(null);
  const [pinEstateScorePercentile, setPinEstateScorePercentile] = useState<number | null>(null);

  // Overlay mode: isochrone (new default) or relative heatmap
  const [overlayMode, setOverlayMode] = useState<OverlayMode>("isochrone");
  const [isochroneData, setIsochroneData] = useState<object | null>(null);
  const [isochroneLoading, setIsochroneLoading] = useState(false);

  // Base overlay: which layer to show when no pin is active
  const [baseOverlay, setBaseOverlay] = useState<"accessibility" | "estate-value" | "hidden-gem">("accessibility");

  // Derived city data — used in effects below so must be declared before them
  const selectedCityData = cities.find((c) => c.slug === selectedCity);
  const cityBbox = selectedCityData?.bbox;
  const estateValueAvailable =
    cityBbox != null &&
    cityBbox[0] < 11.779 &&
    cityBbox[2] > 6.526 &&
    cityBbox[1] < 54.033 &&
    cityBbox[3] > 51.197;

  // Reset base overlay when city changes (availability depends on city)
  useEffect(() => {
    setBaseOverlay("accessibility");
  }, [selectedCity]);

  // Load city list
  useEffect(() => {
    fetch("/api/cities")
      .then((r) => r.json())
      .then((data: City[]) => {
        setCities(data);
        const firstReady = data.find((c) => c.status === "ready");
        if (firstReady) setSelectedCity(firstReady.slug);
      })
      .catch(console.error);
  }, []);

  // Load stats when city/mode/threshold change
  useEffect(() => {
    if (!selectedCity) return;
    const params = new URLSearchParams({ city: selectedCity, mode, threshold: String(threshold), profile });
    fetch(`/api/stats?${params}`)
      .then((r) => r.json())
      .then(setStats)
      .catch(console.error);
  }, [selectedCity, mode, threshold, profile]);

  // Fetch location score + reverse geocode when pin changes
  useEffect(() => {
    if (!pinLocation || !selectedCity) {
      setPinData(null);
      setPinAddress(undefined);
      return;
    }

    let cancelled = false;

    const params = new URLSearchParams({
      lat: String(pinLocation.lat),
      lng: String(pinLocation.lng),
      city: selectedCity,
      mode,
      threshold: String(threshold),
      profile,
    });

    Promise.all([
      fetch(`/api/location-score?${params}`).then((r) => r.json()),
      fetch(
        `https://nominatim.openstreetmap.org/reverse?lat=${pinLocation.lat}&lon=${pinLocation.lng}&format=json`,
        { headers: { "Accept-Language": "en" } },
      )
        .then((r) => r.json())
        .then((d) => d.display_name as string)
        .catch(() => undefined),
    ])
      .then(([scoreData, address]) => {
        if (cancelled) return;
        if (scoreData?.error) {
          // No grid data for this location — clear the pin so the skeleton doesn't persist.
          setPinLocation(null);
          return;
        }
        setPinData(scoreData as LocationScoreData);
        setPinAddress(address);
      })
      .catch(() => { if (!cancelled) setPinLocation(null); });

    return () => { cancelled = true; };
  }, [pinLocation, selectedCity, mode, threshold, profile]);

  // Fetch estate value + percentile ratings for the clicked location
  useEffect(() => {
    if (!pinLocation || !estateValueAvailable || !selectedCity) {
      setPinEstateValue(null);
      setPinEstatePercentile(null);
      setPinEstateScorePercentile(null);
      return;
    }
    const { lat, lng } = pinLocation;
    let cancelled = false;
    const params = new URLSearchParams({
      lat: String(lat),
      lng: String(lng),
      city: selectedCity,
      mode,
      threshold: String(threshold),
      profile,
    });
    fetch(`/api/estate-value?${params}`)
      .then((r) => r.json())
      .then((geojson) => {
        if (cancelled) return;
        const props = geojson?.features?.[0]?.properties;
        setPinEstateValue(typeof props?.value === "number" ? props.value : null);
        setPinEstatePercentile(typeof props?.percentileRank === "number" ? props.percentileRank : null);
        setPinEstateScorePercentile(typeof props?.scorePercentileRank === "number" ? props.scorePercentileRank : null);
      })
      .catch(() => {
        if (!cancelled) {
          setPinEstateValue(null);
          setPinEstatePercentile(null);
          setPinEstateScorePercentile(null);
        }
      });
    return () => {
      cancelled = true;
    };
  }, [pinLocation, estateValueAvailable, selectedCity, mode, threshold, profile]);

  // Pre-fetch the isochrone whenever a pin is placed (in accessibility mode).
  // overlayMode is intentionally NOT a dep — fetching is decoupled from display.
  // This means:
  //   - Switching to "relative" doesn't discard the data.
  //   - Switching back to "isochrone" shows the isochrone instantly (no re-fetch).
  // Isochrone data is cleared when switching to a non-accessibility overlay
  // (estate-value / hidden-gem), since those overlays replace the isochrone slot.
  useEffect(() => {
    if (!pinLocation || baseOverlay !== "accessibility") {
      setIsochroneData(null);
      return;
    }

    let cancelled = false;
    setIsochroneLoading(true);
    setIsochroneData(null);

    fetch("/api/isochrones", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        lng: pinLocation.lng,
        lat: pinLocation.lat,
        travelMode: mode,
        contourMinutes: isochroneContours(threshold),
      }),
    })
      .then((r) => r.json())
      .then((data) => {
        if (cancelled) return;
        // Valhalla may return 200 OK with error_code (not error) for unroutable locations.
        // Only accept valid FeatureCollections.
        if (!data.error && !data.error_code && Array.isArray(data.features)) {
          setIsochroneData(data);
        }
      })
      .catch(() => {})
      .finally(() => { if (!cancelled) setIsochroneLoading(false); });

    return () => {
      cancelled = true;
      setIsochroneLoading(false);
    };
  }, [pinLocation, mode, threshold, baseOverlay]); // eslint-disable-line react-hooks/exhaustive-deps

  function handleProfileChange(newProfile: ProfileId) {
    setProfile(newProfile);
    setWeights({ ...PROFILES[newProfile].categoryWeights });
    // Clear pin when profile changes so scores are re-fetched with new profile
    setPinLocation(null);
    setPinData(null);
    setPinAddress(undefined);
    setPinEstateValue(null);
    setPinEstatePercentile(null);
    setPinEstateScorePercentile(null);
    setIsochroneData(null);
  }

  function handleLocationClick(lat: number, lng: number, estateValue: number | null) {
    setPinLocation({ lat, lng });
    setPinData(null);
    setPinAddress(undefined);
    setPinEstateValue(estateValue);
    setPinEstatePercentile(null);
    setPinEstateScorePercentile(null);
    setIsochroneData(null);
  }

  function handlePinClose() {
    setPinLocation(null);
    setPinData(null);
    setPinAddress(undefined);
    setPinEstateValue(null);
    setPinEstatePercentile(null);
    setPinEstateScorePercentile(null);
    setIsochroneData(null);
  }


  return (
    <div className="flex flex-col h-screen overflow-hidden">
      <header className="bg-white border-b border-gray-200 px-4 py-2.5 flex items-center gap-4 shrink-0 z-10">
        <h1 className="font-bold text-gray-900 hidden sm:block">Transportationer</h1>
        <div className="w-px h-5 bg-gray-200 hidden sm:block" />
        <CitySelector cities={cities} selected={selectedCity} onSelect={setSelectedCity} />
        <div className="ml-auto">
          <a href="/admin" className="text-xs text-gray-400 hover:text-gray-600">Admin</a>
        </div>
      </header>

      <div className="flex flex-1 overflow-hidden">
        <ControlPanel
          profile={profile}
          mode={mode}
          threshold={threshold}
          weights={weights}
          activeCategory={activeCategory}
          baseOverlay={baseOverlay}
          estateValueAvailable={estateValueAvailable}
          onProfileChange={handleProfileChange}
          onModeChange={setMode}
          onThresholdChange={setThreshold}
          onWeightChange={(cat, w) => setWeights((prev) => ({ ...prev, [cat]: w }))}
          onCategoryChange={setActiveCategory}
          onBaseOverlayChange={setBaseOverlay}
        />

        <div className="flex-1 relative">
          {!selectedCity ? (
            <div className="flex items-center justify-center h-full text-gray-400">
              <div className="text-center">
                <p className="text-xl mb-2">Select a city to begin</p>
                <p className="text-sm">
                  Or{" "}
                  <a href="/admin/cities/new" className="text-brand-600 underline">
                    add a new city
                  </a>{" "}
                  in the admin area.
                </p>
              </div>
            </div>
          ) : (
            <MapView
              citySlug={selectedCity}
              cityBbox={selectedCityData?.bbox ?? [-180, -90, 180, 90]}
              profile={profile}
              mode={mode}
              threshold={threshold}
              activeCategory={activeCategory}
              weights={weights}
              pinLocation={pinLocation}
              pinCategoryScores={
                overlayMode === "relative" ? (pinData?.categoryScores ?? null) : null
              }
              isochrones={overlayMode === "isochrone" ? isochroneData : null}
              baseOverlay={baseOverlay}
              onLocationClick={handleLocationClick}
            />
          )}

          <MapLegend
            overlayMode={overlayMode}
            baseOverlay={baseOverlay}
            threshold={threshold}
            hasPinData={!!pinData}
          />

          {pinLocation && !pinData && (
            <div className="absolute bottom-8 right-4 z-20 bg-white rounded-xl shadow-lg border border-gray-100 w-72 p-4">
              <button
                onClick={handlePinClose}
                className="absolute top-3 right-3 text-gray-300 hover:text-gray-500 text-xl leading-none"
                aria-label="Close"
              >×</button>
              <div className="animate-pulse">
                <div className="flex items-start gap-3 mb-3">
                  <div className="w-14 h-14 bg-gray-200 rounded-lg shrink-0" />
                  <div className="flex-1 pt-1 space-y-2">
                    <div className="h-3 bg-gray-200 rounded w-3/4" />
                    <div className="h-2.5 bg-gray-200 rounded w-1/2" />
                  </div>
                </div>
                <div className="h-7 bg-gray-100 rounded-lg mb-3.5" />
                <div className="space-y-3">
                  {[0, 1, 2, 3, 4].map((i) => (
                    <div key={i}>
                      <div className="flex justify-between mb-1">
                        <div className="h-2.5 bg-gray-200 rounded w-24" />
                        <div className="h-2.5 bg-gray-200 rounded w-14" />
                      </div>
                      <div className="h-1.5 bg-gray-100 rounded-full" />
                    </div>
                  ))}
                </div>
              </div>
            </div>
          )}

          {pinData && (
            <LocationScorePanel
              data={pinData}
              weights={weights}
              address={pinAddress}
              estateValue={pinEstateValue}
              estatePercentile={pinEstatePercentile}
              estateScorePercentile={pinEstateScorePercentile}
              overlayMode={overlayMode}
              isochroneLoading={isochroneLoading}
              onOverlayModeChange={setOverlayMode}
              onClose={handlePinClose}
            />
          )}
        </div>
      </div>

      <StatsBar stats={stats} activeCategory={activeCategory} />
    </div>
  );
}
