import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import MapComponent from '../map/Map';
import { Slider } from '../ui/Slider';
import { apiUrl, assertOk, authHeaders, logNonAbortError } from '../../lib/api';
import { formatValue } from '../../lib/format';
import { FEATURE_GRADIENT, DENSITY_GRADIENT, DENSITY_GRADIENT_DARK } from '../../lib/consts';
import { gradientToCss } from '../../lib/utils';
import { TickerValue } from '../ui/TickerValue';
import { SpinnerIcon } from '../ui/icons/SpinnerIcon';
import type { FeatureMeta, HexagonData } from '../../types';

const DEMO_VIEW = { longitude: -1.9, latitude: 52.2, zoom: 5.5, pitch: 0 };
const DEMO_FEATURE_NAMES = ['Estimated current price', 'Good+ primary schools within 5km', 'Number of restaurants within 2km'];
const DEMO_BOUNDS = '49,-9.5,57,5';
const DEMO_RESOLUTION = 5;

const noop = () => {};
const featureGradientStyle = gradientToCss(FEATURE_GRADIENT);

interface HomeDemoProps {
  features: FeatureMeta[];
  theme: 'light' | 'dark';
}

export default function HomeDemo({ features, theme }: HomeDemoProps) {
  const [hexData, setHexData] = useState<HexagonData[]>([]);
  const [loading, setLoading] = useState(true);
  const [fetching, setFetching] = useState(false);
  const [sliderValues, setSliderValues] = useState<Record<string, [number, number]>>({});
  const [activeFeature, setActiveFeature] = useState<string | null>(null);
  const [dragValue, setDragValue] = useState<[number, number] | null>(null);
  const [dragHexData, setDragHexData] = useState<HexagonData[] | null>(null);
  const fetchTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
  const abortRef = useRef<AbortController>();
  const dragAbortRef = useRef<AbortController>();
  const activeFeatureRef = useRef<string | null>(null);
  activeFeatureRef.current = activeFeature;

  const demoFeatures = useMemo(
    () =>
      DEMO_FEATURE_NAMES.map((name) => features.find((f) => f.name === name)).filter(
        Boolean
      ) as FeatureMeta[],
    [features]
  );

  // Initialize slider values when features arrive
  useEffect(() => {
    if (demoFeatures.length === 0) return;
    const initial: Record<string, [number, number]> = {};
    for (const f of demoFeatures) {
      if (f.min != null && f.max != null) {
        initial[f.name] = [f.min, f.max];
      }
    }
    setSliderValues(initial);
  }, [demoFeatures]);

  // Feature coloring only during drag; density (property count) otherwise
  const viewFeatureName = activeFeature;
  const viewMeta = viewFeatureName ? features.find((f) => f.name === viewFeatureName) : null;
  const colorRange: [number, number] | null =
    viewMeta?.min != null && viewMeta?.max != null ? [viewMeta.min, viewMeta.max] : null;
  const filterRange: [number, number] | null = activeFeature && dragValue ? dragValue : null;
  const displayData = dragHexData ?? hexData;

  // Fetch hexagons (debounced) — skipped while dragging
  const fetchHexagons = useCallback(() => {
    if (activeFeatureRef.current) return;
    if (features.length === 0 || Object.keys(sliderValues).length === 0) return;
    const params = new URLSearchParams({
      resolution: String(DEMO_RESOLUTION),
      bounds: DEMO_BOUNDS,
    });
    const filterParts: string[] = [];
    for (const [name, [min, max]] of Object.entries(sliderValues)) {
      const meta = features.find((f) => f.name === name);
      if (meta?.min != null && meta?.max != null) {
        if (min !== meta.min || max !== meta.max) {
          filterParts.push(`${name}:${min}:${max}`);
        }
      }
    }
    if (filterParts.length > 0) {
      params.set('filters', filterParts.join(','));
    }
    abortRef.current?.abort();
    abortRef.current = new AbortController();
    setFetching(true);
    fetch(apiUrl('hexagons', params), authHeaders({ signal: abortRef.current.signal }))
      .then((res) => {
        assertOk(res, 'hexagons');
        return res.json();
      })
      .then((data: { features: HexagonData[] }) => {
        setHexData(data.features);
        setLoading(false);
        setFetching(false);
      })
      .catch((err) => {
        logNonAbortError('Failed to fetch demo hexagons', err);
        setFetching(false);
      });
  }, [features, sliderValues]);

  useEffect(() => {
    clearTimeout(fetchTimeoutRef.current);
    fetchTimeoutRef.current = setTimeout(fetchHexagons, 200);
    return () => clearTimeout(fetchTimeoutRef.current);
  }, [fetchHexagons]);

  useEffect(() => {
    return () => {
      abortRef.current?.abort();
      dragAbortRef.current?.abort();
      clearTimeout(fetchTimeoutRef.current);
    };
  }, []);

  // Drag start: fetch preview data with other filters only, fields=dragged feature
  const handleDragStart = useCallback(
    (name: string) => {
      setActiveFeature(name);
      const currentVal = sliderValues[name];
      const meta = features.find((f) => f.name === name);
      setDragValue(currentVal || (meta?.min != null ? [meta.min, meta.max!] : null));

      const params = new URLSearchParams({
        resolution: String(DEMO_RESOLUTION),
        bounds: DEMO_BOUNDS,
      });
      const otherFilterParts: string[] = [];
      for (const [n, [min, max]] of Object.entries(sliderValues)) {
        if (n === name) continue;
        const m = features.find((f) => f.name === n);
        if (m?.min != null && m?.max != null && (min !== m.min || max !== m.max)) {
          otherFilterParts.push(`${n}:${min}:${max}`);
        }
      }
      if (otherFilterParts.length > 0) {
        params.set('filters', otherFilterParts.join(','));
      }
      params.set('fields', name);

      dragAbortRef.current?.abort();
      dragAbortRef.current = new AbortController();
      fetch(apiUrl('hexagons', params), authHeaders({ signal: dragAbortRef.current.signal }))
        .then((res) => {
          assertOk(res, 'hexagons');
          return res.json();
        })
        .then((data: { features: HexagonData[] }) => setDragHexData(data.features))
        .catch((err) => logNonAbortError('Failed to fetch demo drag data', err));
    },
    [features, sliderValues]
  );

  const handleSliderChange = useCallback(
    (name: string, value: [number, number]) => {
      setSliderValues((prev) => ({ ...prev, [name]: value }));
      if (activeFeatureRef.current === name) {
        setDragValue(value);
      }
    },
    []
  );

  const handleDragEnd = useCallback(() => {
    setActiveFeature(null);
    setDragValue(null);
    setDragHexData(null);
  }, []);

  return (
    <div className="flex flex-col md:flex-row gap-6">
      {/* Map */}
      <div className="relative rounded-xl overflow-hidden shadow-sm aspect-[4/3] md:w-3/5">
        <div className="absolute inset-0 z-50 cursor-default" />
        <div className="absolute inset-0">
          <MapComponent
            data={displayData}
            postcodeData={[]}
            usePostcodeView={false}
            pois={[]}
            onViewChange={noop}
            viewFeature={viewFeatureName}
            colorRange={colorRange}
            filterRange={filterRange}
            viewSource={activeFeature ? 'drag' : null}
            onCancelPin={noop}
            features={features}
            selectedHexagonId={null}
            hoveredHexagonId={null}
            onHexagonClick={noop}
            onHexagonHover={noop}
            initialViewState={DEMO_VIEW}
            theme={theme}
            screenshotMode={true}
            hideLegend={true}
          />
        </div>
        {loading && (
          <div className="absolute inset-0 z-50 flex items-center justify-center bg-warm-50/80 dark:bg-navy-950/80 backdrop-blur-sm">
            <div className="flex flex-col items-center gap-4">
              <SpinnerIcon className="w-12 h-12 text-teal-600 dark:text-teal-400 animate-spin" />
              <p className="text-warm-600 dark:text-warm-300 text-sm font-medium">
                Connecting to server...
              </p>
            </div>
          </div>
        )}
        {!loading && fetching && (
          <div className="absolute top-3 left-3 z-50 bg-white dark:bg-navy-800 dark:text-warm-200 px-2 py-1 rounded shadow text-xs">
            Loading...
          </div>
        )}
        {/* Colour spectrum legend */}
        <div className="absolute bottom-3 left-3 right-3 z-50 pointer-events-none">
          <div className="bg-white/90 dark:bg-warm-800/90 rounded-lg px-3 py-2 backdrop-blur-sm text-xs">
            <div className="font-semibold text-navy-950 dark:text-warm-100 mb-1 truncate">
              {activeFeature ? viewMeta?.name || activeFeature : 'Property density'}
            </div>
            <div
              className="h-2.5 rounded-full"
              style={{
                background: activeFeature
                  ? featureGradientStyle
                  : gradientToCss(theme === 'dark' ? DENSITY_GRADIENT_DARK : DENSITY_GRADIENT),
              }}
            />
            {colorRange && (
              <div className="flex justify-between mt-0.5 text-warm-500 dark:text-warm-400">
                <TickerValue text={formatValue(colorRange[0], viewMeta ?? undefined)} />
                <TickerValue text={formatValue(colorRange[1], viewMeta ?? undefined)} />
              </div>
            )}
          </div>
        </div>
      </div>

      {/* Sliders */}
      <div className="md:w-2/5 flex flex-col justify-center space-y-6">
        {demoFeatures.map((feature) => {
          const value = sliderValues[feature.name];
          if (!value || feature.min == null || feature.max == null) return null;
          const isActive = activeFeature === feature.name;
          return (
            <div
              key={feature.name}
              className={`rounded-lg p-3 ${isActive ? 'ring-2 ring-teal-400 bg-teal-50 dark:bg-teal-900/30' : ''}`}
            >
              <div className="flex justify-between mb-2">
                <span className="text-sm font-semibold text-navy-950 dark:text-warm-100">
                  {feature.name}
                </span>
                <span className="text-sm text-warm-500 dark:text-warm-400">
                  {formatValue(value[0], feature)} &ndash; {formatValue(value[1], feature)}
                </span>
              </div>
              <Slider
                min={feature.min}
                max={feature.max}
                step={feature.step || 1}
                value={[value[0], value[1]]}
                onValueChange={([min, max]) => handleSliderChange(feature.name, [min, max])}
                onPointerDown={() => handleDragStart(feature.name)}
                onPointerUp={() => handleDragEnd()}
              />
            </div>
          );
        })}
      </div>
    </div>
  );
}
