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

const DEMO_VIEW = { longitude: -0.12, latitude: 51.51, zoom: 5.5, pitch: 0 };
const DEMO_FEATURE_NAMES = [
  'Estimated current price',
  'Good+ primary schools within 5km',
  'Number of restaurants within 2km',
];
const noop = () => {};

// Filter fractions per stage: featureName -> [minFrac, maxFrac]
// 0 = feature.min, 1 = feature.max
interface StageDef {
  filters: Record<string, [number, number]>;
  travel?: { mode: string; slug: string; min: number; max: number };
}

const STAGES: StageDef[] = [
  // 0: No filters — the problem
  { filters: {} },
  // 1: Price filter
  {
    filters: { 'Estimated current price': [0, 0.4] },
  },
  // 2: Price + schools
  {
    filters: {
      'Estimated current price': [0, 0.4],
      'Good+ primary schools within 5km': [0.3, 1],
    },
  },
  // 3: Price + schools + restaurants
  {
    filters: {
      'Estimated current price': [0, 0.4],
      'Good+ primary schools within 5km': [0.3, 1],
      'Number of restaurants within 2km': [0.15, 1],
    },
  },
  // 4: Price + schools + restaurants + commute to Manchester
  {
    filters: {
      'Estimated current price': [0, 0.4],
      'Good+ primary schools within 5km': [0.3, 1],
      'Number of restaurants within 2km': [0.15, 1],
    },
    travel: { mode: 'transit', slug: 'manchester', min: 0, max: 45 },
  },
  // 5: Summary — same filters
  {
    filters: {
      'Estimated current price': [0, 0.4],
      'Good+ primary schools within 5km': [0.3, 1],
      'Number of restaurants within 2km': [0.15, 1],
    },
    travel: { mode: 'transit', slug: 'manchester', min: 0, max: 45 },
  },
];

const STEPS: { heading: string | null; body: React.ReactNode }[] = [
  {
    heading: null,
    body: (
      <>
        <p className="text-base md:text-lg leading-snug md:leading-relaxed mb-2 md:mb-4">
          Let&apos;s look at an example:
        </p>
        <p className="text-base md:text-lg leading-snug md:leading-relaxed">
          You&apos;re about to spend{' '}
          <strong className="text-navy-950 dark:text-warm-100">up to &pound;500k</strong> on a home.
        </p>
      </>
    ),
  },
  {
    heading: null,
    body: (
      <>
        <div className="flex items-center gap-2 md:gap-3 mb-2 md:mb-3">
          <div className="shrink-0 w-7 h-7 md:w-8 md:h-8 rounded-full bg-teal-600 text-white flex items-center justify-center font-bold text-xs md:text-sm">
            1
          </div>
          <h3 className="text-lg md:text-xl font-bold text-navy-950 dark:text-warm-100">
            Set your must-haves
          </h3>
        </div>
        <p className="text-base md:text-lg leading-snug md:leading-relaxed">
          Say you want a home{' '}
          <strong className="text-navy-950 dark:text-warm-100">under &pound;500k</strong>&hellip;
        </p>
      </>
    ),
  },
  {
    heading: null,
    body: (
      <p className="text-base md:text-lg leading-snug md:leading-relaxed">
        &hellip;with{' '}
        <strong className="text-navy-950 dark:text-warm-100">good primary schools</strong>{' '}
        nearby&hellip;
      </p>
    ),
  },
  {
    heading: null,
    body: (
      <p className="text-base md:text-lg leading-snug md:leading-relaxed">
        &hellip;and{' '}
        <strong className="text-navy-950 dark:text-warm-100">
          restaurants within walking distance
        </strong>
        &hellip;
      </p>
    ),
  },
  {
    heading: null,
    body: (
      <p className="text-base md:text-lg leading-snug md:leading-relaxed">
        &hellip;all within{' '}
        <strong className="text-navy-950 dark:text-warm-100">45 minutes of Manchester</strong> by
        public transport.
      </p>
    ),
  },
  {
    heading: null,
    body: (
      <>
        <p className="text-base md:text-lg leading-snug md:leading-relaxed mb-2 md:mb-4 font-semibold text-navy-950 dark:text-warm-100">
          No area chosen. No listings browsed. Yet you already know exactly where your needs are
          met.
        </p>
        <p className="text-base md:text-lg leading-snug md:leading-relaxed">
          That&apos;s just 4 filters. We&apos;ve built{' '}
          <strong className="text-navy-950 dark:text-warm-100">56</strong>, covering commute times,
          crime, broadband, noise, schools, amenities, and more.
        </p>
      </>
    ),
  },
];

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

export default function ScrollStory({ features, theme }: ScrollStoryProps) {
  const [stage, setStage] = useState(0);
  const [hexData, setHexData] = useState<HexagonData[]>([]);
  const [loading, setLoading] = useState(true);
  const abortRef = useRef<AbortController>();

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

  // Compute actual filter values from stage fractions + feature metadata
  const stageFilters = useMemo(() => {
    const stageDef = STAGES[stage];
    const result: Record<string, [number, number]> = {};
    for (const [name, [minFrac, maxFrac]] of Object.entries(stageDef.filters)) {
      const meta = demoFeatures.find((f) => f.name === name);
      if (meta?.min != null && meta?.max != null) {
        const range = meta.max - meta.min;
        result[name] = [meta.min + range * minFrac, meta.min + range * maxFrac];
      }
    }
    return result;
  }, [stage, demoFeatures]);

  const demoView = useMemo(() => DEMO_VIEW, []);

  // Derive H3 resolution from current zoom (discrete — only changes at thresholds)
  const resolution = zoomToResolution(demoView.zoom);

  // Compute bounds string from current view, rounded to 0.5° for stability
  const demoBounds = useMemo(() => {
    const { longitude, latitude, zoom } = demoView;
    const scale = Math.pow(2, zoom);
    const degreesPerPixelLng = 360 / (512 * scale);
    const halfW = (1200 / 2) * degreesPerPixelLng * 1.3;
    const latRad = (latitude * Math.PI) / 180;
    const mercY = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2;
    const worldSize = 512 * scale;
    const halfH = (800 / 2) * 1.3;
    const topY = mercY * worldSize - halfH;
    const botY = mercY * worldSize + halfH;
    const toLat = (py: number) => {
      const my = Math.max(0.001, Math.min(0.999, py / worldSize));
      return (Math.atan(Math.sinh(Math.PI * (1 - 2 * my))) * 180) / Math.PI;
    };
    const snap = (v: number) => Math.round(v * 2) / 2;
    const south = snap(Math.max(-85, toLat(botY)));
    const west = snap(Math.max(-180, longitude - halfW));
    const north = snap(Math.min(85, toLat(topY)));
    const east = snap(Math.min(180, longitude + halfW));
    return `${south},${west},${north},${east}`;
  }, [demoView]);

  // Fetch hex data when stage filters change
  useEffect(() => {
    if (features.length === 0) return;

    // Clear stale data and show loading spinner immediately
    setLoading(true);
    setHexData([]);

    const params = new URLSearchParams({
      resolution: String(resolution),
      bounds: demoBounds,
    });
    const filterParts: string[] = [];
    for (const [name, [min, max]] of Object.entries(stageFilters)) {
      filterParts.push(`${name}:${min}:${max}`);
    }
    if (filterParts.length > 0) params.set('filters', filterParts.join(','));

    const stageDef = STAGES[stage];
    if (stageDef.travel) {
      const { mode, slug, min, max } = stageDef.travel;
      params.set('travel', `${mode}:${slug}:${min}:${max}`);
    }

    const controller = new AbortController();
    abortRef.current?.abort();
    abortRef.current = controller;
    fetch(apiUrl('hexagons', params), authHeaders({ signal: controller.signal }))
      .then((res) => {
        assertOk(res, 'hexagons');
        return res.json();
      })
      .then((data: { features: HexagonData[] }) => {
        setHexData(data.features);
        setLoading(false);
      })
      .catch((err) => {
        if (!isAbortError(err)) {
          logNonAbortError('Failed to fetch story hexagons', err);
          setLoading(false);
        }
      });

    return () => controller.abort();
  }, [features, stageFilters, stage, resolution, demoBounds]);

  const isLastStage = stage === STEPS.length - 1;

  return (
    <section className="relative h-[calc(100dvh-3rem)]">
      {/* Map background */}
      <div className="absolute inset-0 z-0">
        <MapComponent
          data={stage === 0 ? [] : hexData}
          postcodeData={[]}
          usePostcodeView={false}
          pois={[]}
          onViewChange={noop}
          viewFeature={null}
          colorRange={null}
          filterRange={null}
          viewSource={null}
          onCancelPin={noop}
          features={features}
          selectedHexagonId={null}
          hoveredHexagonId={null}
          onHexagonClick={noop}
          onHexagonHover={noop}
          initialViewState={demoView}
          theme={theme}
          screenshotMode={true}
          hideLegend={true}
        />
      </div>

      {/* Interaction blocker */}
      <div className="absolute inset-0 z-30" />

      {/* Loading */}
      {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">
          <SpinnerIcon className="w-10 h-10 text-teal-600 dark:text-teal-400 animate-spin" />
        </div>
      )}

      {/* Filter indicators — top left */}
      <div className="absolute top-3 left-3 z-40 pointer-events-none w-[200px] md:w-[340px]">
        <div className="bg-white/85 dark:bg-warm-800/85 rounded-xl p-3 md:p-6 backdrop-blur-sm shadow-lg space-y-2.5 md:space-y-5 w-full">
          <div className="text-[10px] md:text-xs font-semibold uppercase tracking-wider text-warm-500 dark:text-warm-400">
            Filters
          </div>
          {demoFeatures.map((feature) => {
            const filterVal = stageFilters[feature.name];
            const isActive = !!filterVal;
            const min = feature.min ?? 0;
            const max = feature.max ?? 1;
            const range = max - min || 1;
            const leftPct = filterVal ? ((filterVal[0] - min) / range) * 100 : 0;
            const widthPct = filterVal ? ((filterVal[1] - filterVal[0]) / range) * 100 : 100;
            return (
              <div
                key={feature.name}
                className={`transition-opacity duration-700 ${isActive ? 'opacity-100' : 'opacity-30'}`}
              >
                <div className="flex justify-between items-baseline text-xs md:text-sm mb-1 md:mb-1.5 gap-1.5 md:gap-2">
                  <span
                    className={`font-medium truncate ${isActive ? 'text-navy-950 dark:text-warm-100' : 'text-warm-400 dark:text-warm-500'}`}
                  >
                    {feature.name}
                  </span>
                  {isActive && filterVal && (
                    <span className="text-teal-600 dark:text-teal-400 font-semibold whitespace-nowrap">
                      {formatValue(filterVal[0], feature)}&ndash;
                      {formatValue(filterVal[1], feature)}
                    </span>
                  )}
                </div>
                <div className="relative h-1.5 md:h-2.5 bg-warm-200 dark:bg-warm-700 rounded-full overflow-hidden">
                  <div
                    className="absolute h-full bg-teal-500 dark:bg-teal-400 rounded-full transition-all duration-700 ease-out"
                    style={{ left: `${leftPct}%`, width: `${widthPct}%` }}
                  />
                </div>
              </div>
            );
          })}
          {/* Travel time indicator */}
          <div
            className={`transition-opacity duration-700 ${STAGES[stage].travel ? 'opacity-100' : 'opacity-30'}`}
          >
            <div className="flex justify-between items-baseline text-xs md:text-sm mb-1 md:mb-1.5 gap-1.5 md:gap-2">
              <span
                className={`font-medium truncate ${STAGES[stage].travel ? 'text-navy-950 dark:text-warm-100' : 'text-warm-400 dark:text-warm-500'}`}
              >
                Commute to Manchester
              </span>
              {STAGES[stage].travel && (
                <span className="text-teal-600 dark:text-teal-400 font-semibold whitespace-nowrap">
                  0&ndash;45 min
                </span>
              )}
            </div>
            <div className="relative h-1.5 md:h-2.5 bg-warm-200 dark:bg-warm-700 rounded-full overflow-hidden">
              <div
                className="absolute h-full bg-teal-500 dark:bg-teal-400 rounded-full transition-all duration-700 ease-out"
                style={{ left: '0%', width: STAGES[stage].travel ? '45%' : '100%' }}
              />
            </div>
          </div>
        </div>
      </div>

      {/* Density legend — top right */}
      <div className="absolute top-3 right-3 z-40 pointer-events-none w-[180px] md:w-[220px]">
        <div className="bg-white/85 dark:bg-warm-800/85 rounded-xl p-3 md:p-4 backdrop-blur-sm shadow-lg">
          <div className="text-[10px] md:text-xs font-semibold uppercase tracking-wider text-warm-500 dark:text-warm-400 mb-2">
            Colour
          </div>
          <div className="text-xs md:text-sm font-medium text-navy-950 dark:text-warm-100 mb-1.5">
            Number of properties
          </div>
          <div
            className="h-1.5 md:h-2.5 rounded-full overflow-hidden"
            style={{
              background: gradientToCss(
                theme === 'dark' ? DENSITY_GRADIENT_DARK : DENSITY_GRADIENT
              ),
            }}
          />
          <div className="flex justify-between mt-1 text-[10px] md:text-xs text-warm-500 dark:text-warm-400">
            <span>Fewer</span>
            <span>More</span>
          </div>
        </div>
      </div>

      {/* Card stack overlay — bottom on mobile, right-center on desktop */}
      <div className="absolute left-0 right-0 bottom-4 md:top-0 md:bottom-0 md:left-auto z-40 flex items-end md:items-center pointer-events-none mx-4 md:mr-[4%] md:ml-auto md:max-w-md md:w-full">
        <div className="grid grid-cols-1 grid-rows-1 items-end w-full pointer-events-auto">
          {STEPS.map((step, i) => (
            <div
              key={i}
              className={`col-start-1 row-start-1 transition-all duration-500 ease-out ${
                i === stage
                  ? 'opacity-100 translate-y-0'
                  : i < stage
                    ? 'opacity-0 -translate-y-4 pointer-events-none'
                    : 'opacity-0 translate-y-4 pointer-events-none'
              }`}
            >
              <div className="bg-white/90 dark:bg-warm-800/90 rounded-xl p-4 md:p-6 backdrop-blur-sm shadow-lg border border-warm-200/40 dark:border-warm-700/40">
                {step.heading && (
                  <h3 className="text-lg md:text-xl font-bold text-navy-950 dark:text-warm-100 mb-2 md:mb-3 leading-snug">
                    {step.heading}
                  </h3>
                )}
                <div className="text-warm-700 dark:text-warm-300">{step.body}</div>

                {/* Navigation */}
                <div className="flex items-center justify-between mt-3 md:mt-5">
                  {/* Step dots */}
                  <div className="flex gap-1.5">
                    {STEPS.map((_, dotIdx) => (
                      <button
                        key={dotIdx}
                        onClick={() => setStage(dotIdx)}
                        className={`w-2 h-2 rounded-full transition-all duration-300 ${
                          dotIdx === stage
                            ? 'bg-teal-600 dark:bg-teal-400 w-4'
                            : dotIdx < stage
                              ? 'bg-teal-600/40 dark:bg-teal-400/40'
                              : 'bg-warm-300 dark:bg-warm-600'
                        }`}
                        aria-label={`Go to step ${dotIdx + 1}`}
                      />
                    ))}
                  </div>

                  {/* Prev / Next / CTA buttons */}
                  <div className="flex items-center gap-2">
                    {stage > 0 && (
                      <button
                        onClick={() => setStage(stage - 1)}
                        className="inline-flex items-center gap-1 px-4 py-2 rounded-lg border border-warm-200 dark:border-warm-700 text-warm-600 dark:text-warm-300 font-semibold text-sm hover:bg-warm-50 dark:hover:bg-warm-800 transition-colors"
                      >
                        &larr; Back
                      </button>
                    )}
                    {isLastStage ? (
                      <a
                        href="/dashboard"
                        className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-teal-600 text-white font-semibold text-sm hover:bg-teal-700 transition-colors"
                      >
                        Start exploring &rarr;
                      </a>
                    ) : (
                      <button
                        onClick={() => setStage(stage + 1)}
                        className="inline-flex items-center gap-1 px-4 py-2 rounded-lg bg-teal-600 text-white font-semibold text-sm hover:bg-teal-700 transition-colors"
                      >
                        Next &rarr;
                      </button>
                    )}
                  </div>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}
