/* ===== Site Analysis — the HERO reveal =====
   Each step is keyed to a real GIS fetch. Steps light up as their fetch
   resolves; failures log [novale] <layer> unavailable and the corresponding
   Mapbox layer is simply not rendered (no fake fallback). Lot stats are
   computed from the resolved data. */

const ANALYSIS_STEPS = [
  { id: 'satellite',  label: 'Satellite imagery',   source: 'Mapbox',                 icon: 'satellite',       layer: null },
  { id: 'boundary',   label: 'Property boundary',   source: 'County assessor',        icon: 'square-dashed',   layer: 'boundary' },
  { id: 'structures', label: 'Structures',          source: 'OpenStreetMap',          icon: 'home',            layer: 'structures' },
  { id: 'contours',   label: 'Topography',          source: 'County contours',        icon: 'mountain',        layer: 'contours' },
  { id: 'trees',      label: 'Tree canopy',         source: 'USFS NLCD',              icon: 'trees',           layer: 'trees' },
  { id: 'wetland',    label: 'Wetlands & streams',  source: 'County sensitive areas', icon: 'droplets',        layer: 'wetland' },
  { id: 'critical',   label: 'Critical areas',      source: 'County sensitive areas', icon: 'alert-triangle',  layer: 'critical' },
  { id: 'flood',      label: 'Floodplain',          source: 'FEMA + county',          icon: 'waves',           layer: 'flood' },
  { id: 'soil',       label: 'Soil type',           source: 'USDA NRCS',              icon: 'layers',          layer: 'soil' },
  { id: 'zones',      label: 'Zoning & setbacks',   source: 'County planning',        icon: 'ruler',           layer: 'zones' },
  { id: 'climate',    label: 'Climate zone',        source: 'USDA hardiness',         icon: 'thermometer-sun', layer: null },
];

const MIN_STEP_MS = 220;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

function fmtNumber(n, opts) {
  if (n == null || !Number.isFinite(n)) return null;
  return new Intl.NumberFormat('en-US', opts).format(n);
}

const SiteAnalysis = ({ onContinue, site = null }) => {
  const [siteData, setSiteData] = React.useState({});
  const [stepState, setStepState] = React.useState({}); // id → 'idle' | 'active' | 'done' | 'empty'
  const [active, setActive] = React.useState(null);
  const [complete, setComplete] = React.useState(false);
  const [hardiness, setHardiness] = React.useState(null);

  React.useEffect(() => {
    if (!site || !Number.isFinite(site.lat) || !Number.isFinite(site.lng)) return;
    let cancelled = false;

    // Coords-based fallback bbox for canopy raster — set immediately so the
    // tree step can light up regardless of whether a parcel is available. If
    // the parcel resolves with a polygon, we'll override with its tighter bbox.
    {
      const dLat = 80 / 111320;
      const dLng = 80 / (111320 * Math.cos((site.lat * Math.PI) / 180));
      setSiteData((prev) => ({
        ...prev,
        treeCanopyBbox: [site.lng - dLng, site.lat - dLat, site.lng + dLng, site.lat + dLat],
      }));
    }

    console.log('[novale] analysis: site', {
      address: site.address, lat: site.lat, lng: site.lng,
      postalCode: site.postalCode, locality: site.locality, county: site.county,
    });

    // Compute initial canopy bbox right now for the canopy URL probe — we'll
    // refine to the parcel bbox once parcel resolves.
    const dLat0 = 80 / 111320;
    const dLng0 = 80 / (111320 * Math.cos((site.lat * Math.PI) / 180));
    const initBbox = [site.lng - dLng0, site.lat - dLat0, site.lng + dLng0, site.lat + dLat0];

    // Fire every fetch in parallel — the staged walker below just gates the UI
    // reveal on these resolving in step order.
    // Buildings runs first, then drives the parcel query — Mapbox geocodes
    // residential addresses to the road centerline, which KC excludes from
    // parcels, so a query at the geocoded point misses every parcel. The
    // building centroid is reliably on-lot. Other layers run fully parallel
    // since they're radius queries that don't need a parcel anchor.
    const buildingsP = window.fetchBuildings?.(site.lat, site.lng);
    const buildingP = (async () => {
      const buildings = await buildingsP;
      if (cancelled) return null;
      const b = window.pickPrimaryBuilding?.(buildings, null, site.lat, site.lng) || null;
      if (b) writeToSiteData({ building: b });
      return b;
    })();
    const parcelP = (async () => {
      const building = await buildingP;
      if (cancelled) return null;
      return window.fetchParcel?.(site.lat, site.lng, building);
    })();

    const f = {
      parcel:        parcelP,
      buildings:     buildingsP,
      zoning:        window.fetchZoning?.(site.lat, site.lng),
      contours:      window.fetchKingCountyContours?.(site.lat, site.lng),
      wetlands:      window.fetchKingCountyWetlands?.(site.lat, site.lng),
      streams:       window.fetchKingCountyStreams?.(site.lat, site.lng),
      criticalAreas: window.fetchKingCountyCriticalAreas?.(site.lat, site.lng),
      flood:         window.fetchKingCountyFlood?.(site.lat, site.lng),
      soils:         window.fetchSoils?.(site.lat, site.lng),
      hardiness:     window.fetchPlantHardinessZone?.(site.lat, site.lng),
      canopyUrl:     window.fetchTreeCanopyImageUrl?.(initBbox),
    };

    // Each step's gate — what we await before marking it done.
    const stepGate = {
      satellite:  Promise.resolve(true),
      boundary:   f.parcel,
      structures: f.buildings,
      contours:   f.contours,
      // Trees: gate on whichever finishes first — the canopy URL probe (so we
      // know whether we have a working raster) or the parcel (for tighter bbox).
      trees:      Promise.all([f.canopyUrl, f.parcel]),
      wetland:    Promise.all([f.wetlands, f.streams]),
      critical:   f.criticalAreas,
      flood:      f.flood,
      soil:       f.soils,
      zones:      f.zoning,
      climate:    f.hardiness,
    };

    // Maintain a running siteData that we set as fetches land — Basemap
    // re-renders incrementally so layers appear roughly in step order.
    const writeToSiteData = (patch) => {
      if (cancelled) return;
      setSiteData((prev) => ({ ...prev, ...patch }));
    };

    // Resolve all fetches up front (still parallel), then for each step
    // present "active → resolved" with a minimum visual dwell.
    (async () => {
      // Pre-bind data → siteData as each fetch resolves so the map starts to
      // populate even before its corresponding step ticks over.
      f.parcel?.then((parcel) => {
        writeToSiteData({ parcel });
        // Tighten canopy bbox to the actual parcel when we have it.
        if (parcel && window.bboxOfPolygon) {
          const bb = window.bboxOfPolygon(parcel);
          if (bb) writeToSiteData({ treeCanopyBbox: bb });
        }
      });
      Promise.all([f.buildings, f.parcel]).then(([buildings, parcel]) => {
        if (cancelled) return;
        const building = window.pickPrimaryBuilding?.(buildings, parcel, site.lat, site.lng) || null;
        writeToSiteData({ building });
      });
      f.contours?.then((contours) => writeToSiteData({ contours }));
      Promise.all([f.wetlands, f.streams]).then(([wetlands, streams]) =>
        writeToSiteData({ wetlands, streams }),
      );
      f.criticalAreas?.then((criticalAreas) => writeToSiteData({ criticalAreas }));
      f.flood?.then((flood) => writeToSiteData({ flood }));
      f.soils?.then((soils) => writeToSiteData({ soils }));
      Promise.all([f.parcel, f.zoning]).then(([parcel, zoning]) => {
        if (cancelled) return;
        const setbacks = window.deriveSetbacks?.(parcel, zoning) || null;
        writeToSiteData({ zoning, setbacks });
      });
      f.hardiness?.then((h) => { if (!cancelled) setHardiness(h); });
      f.canopyUrl?.then((canopyUrl) => writeToSiteData({ canopyUrl }));

      // Walk the steps one at a time — each gates on its fetch (or its share
      // of one), with a small minimum dwell so the UI doesn't flicker.
      for (const step of ANALYSIS_STEPS) {
        if (cancelled) return;
        setActive(step.id);
        setStepState((s) => ({ ...s, [step.id]: 'active' }));
        const start = Date.now();
        let result = null;
        try { result = await stepGate[step.id]; } catch (_) { /* warned by fetcher */ }
        const elapsed = Date.now() - start;
        if (elapsed < MIN_STEP_MS) await sleep(MIN_STEP_MS - elapsed);
        if (cancelled) return;

        // Treat "no data" (null or empty FC) as a soft "empty" state — it's
        // not a failure but the layer won't render. Hardiness is a flat
        // object so test it differently.
        const isEmpty = (() => {
          if (step.id === 'satellite') return false;
          if (step.id === 'climate')   return !result?.zone;
          // Trees: empty only if the canopy URL probe failed (first slot of the
          // gate Promise.all). Parcel/coords-derived bbox is always available.
          if (step.id === 'trees')     return !(result?.[0]);
          if (Array.isArray(result))   return !result.some((r) => r?.features?.length > 0 || r?.geometry);
          if (result?.type === 'FeatureCollection') return !result.features?.length;
          if (result?.type === 'Feature') return false;
          return result == null;
        })();
        setStepState((s) => ({ ...s, [step.id]: isEmpty ? 'empty' : 'done' }));
      }

      if (!cancelled) {
        setActive(null);
        setComplete(true);
        // Final summary so the dev console has one place to scan everything.
        Promise.all([
          f.parcel, f.buildings, f.zoning, f.contours,
          f.wetlands, f.streams, f.criticalAreas, f.flood, f.soils,
          f.hardiness, f.canopyUrl,
        ]).then(([parcel, buildings, zoning, contours, wetlands, streams, criticalAreas, flood, soils, hardiness, canopyUrl]) => {
          console.log('[novale] analysis complete →', {
            parcel: parcel ? `PIN ${parcel.properties?.PIN || '?'}` : null,
            buildings: buildings?.features?.length ?? null,
            zoning: zoning?.properties?.CURRZONE || null,
            contours: contours?.features?.length ?? null,
            wetlands: wetlands?.features?.length ?? null,
            streams: streams?.features?.length ?? null,
            criticalAreas: criticalAreas?.features?.length ?? null,
            flood: flood?.features?.length ?? null,
            soils: soils?.features?.length ?? null,
            hardiness: hardiness?.zone || null,
            canopyUrl: canopyUrl ? 'available' : null,
          });
        });
      }
    })();

    return () => { cancelled = true; };
  }, [site?.lat, site?.lng, site?.postalCode]);

  // Layer toggles drive Mapbox visibility — each toggle flips on as soon as
  // its step is marked done OR empty (so the user sees the timeline progress
  // even when a layer has no features).
  const layers = {
    boundary:   stepState.boundary === 'done',
    structures: stepState.structures === 'done',
    trees:      stepState.trees === 'done' && !!siteData.treeCanopyBbox && !!siteData.canopyUrl,
    soil:       stepState.soil === 'done',
    wetland:    stepState.wetland === 'done',
    zones:      stepState.zones === 'done',
    contours:   stepState.contours === 'done',
    critical:   stepState.critical === 'done',
    flood:      stepState.flood === 'done',
    labels:     true,
  };
  const currentLayer = active ? ANALYSIS_STEPS.find((s) => s.id === active)?.layer : null;

  /* ---------- Stats from real data ---------- */

  const lotInfo = window.lotSizeFromParcel?.(siteData.parcel);
  const buildingFt2 = siteData.building ? window.buildingFootprintFt2?.(siteData.building) : null;
  const zoneCode = siteData.zoning?.properties?.CURRZONE || null;
  const usdaZone = hardiness?.zone || null;
  const tempRange = hardiness?.temperature_range || null;
  const criticalCount = siteData.criticalAreas?.features?.length || 0;
  const wetlandsNearby = (siteData.wetlands?.features?.length || 0) > 0;
  const streamsNearby = (siteData.streams?.features?.length || 0) > 0;
  const inFloodplain = (siteData.flood?.features?.length || 0) > 0;

  const lotSizeStr = lotInfo
    ? lotInfo.acres >= 1
      ? `${lotInfo.acres.toFixed(2)} ac`
      : `${fmtNumber(lotInfo.ft2, { maximumFractionDigits: 0 })} ft²`
    : '—';
  const lotSizeUnit = lotInfo && lotInfo.acres >= 1 ? '' : '';

  const houseStr = buildingFt2 ? fmtNumber(buildingFt2, { maximumFractionDigits: 0 }) : '—';
  const climateLabel = !usdaZone
    ? 'Looking up…'
    : tempRange ? `Zone ${usdaZone} · ${tempRange}` : `Zone ${usdaZone}`;

  return (
    <div className="screen screen-fade-in" style={{ padding: 0 }}>
      <StepRail current="analysis" />
      <div className="analysis">
        <div className="analysis__canvas">
          <div className="analysis__canvas-inner">
            <div className="analysis__status">
              <div className="analysis__status-dot" data-done={complete} />
              {complete
                ? 'Analysis complete · live data'
                : (active ? `Pulling ${ANALYSIS_STEPS.find((s) => s.id === active)?.label.toLowerCase()}…` : 'Pulling satellite imagery…')}
            </div>
            <BaseMap
              layers={layers}
              highlight={currentLayer}
              width={900}
              height={560}
              site={site}
              siteData={siteData}
            />
          </div>

          <div className="analysis__stats">
            <div className="analysis__stat">
              <div className="analysis__stat-label">Lot size</div>
              <div className="analysis__stat-value">{lotSizeStr}<span className="analysis__stat-unit">{lotSizeUnit}</span></div>
            </div>
            <div className="analysis__stat">
              <div className="analysis__stat-label">House footprint</div>
              <div className="analysis__stat-value">{houseStr}<span className="analysis__stat-unit"> ft²</span></div>
            </div>
            <div className="analysis__stat">
              <div className="analysis__stat-label">USDA zone</div>
              <div className="analysis__stat-value">{usdaZone || '—'}</div>
            </div>
            <div className="analysis__stat">
              <div className="analysis__stat-label">Climate</div>
              <div className="analysis__stat-value" style={{ fontSize: 16 }}>{climateLabel}</div>
            </div>
            <div className="analysis__stat">
              <div className="analysis__stat-label">Zoning</div>
              <div className="analysis__stat-value" style={{ fontSize: 18 }}>{zoneCode || '—'}</div>
            </div>
            <div className="analysis__stat">
              <div className="analysis__stat-label">Site notes</div>
              <div className="analysis__stat-value" style={{ fontSize: 13, lineHeight: 1.35 }}>
                {[
                  inFloodplain && 'In floodplain',
                  criticalCount > 0 && `${criticalCount} hazard area${criticalCount === 1 ? '' : 's'}`,
                  wetlandsNearby && 'Wetlands nearby',
                  streamsNearby && 'Stream nearby',
                ].filter(Boolean).join(' · ') || 'No critical conditions found'}
              </div>
            </div>
          </div>
        </div>

        <aside className="analysis__side">
          <div className="analysis__headline">
            <div className="eyebrow"><span className="eyebrow__dot" />Site analysis</div>
            <h2 className="serif" style={{ fontSize: 32, marginTop: 10, lineHeight: 1.15, letterSpacing: '-0.018em' }}>
              Reading your property.
            </h2>
            {site?.address && (
              <p className="mono" style={{ fontSize: 12, marginTop: 8, color: 'var(--fg-muted)', letterSpacing: '0.02em' }}>
                {site.address}
              </p>
            )}
            <p className="secondary" style={{ fontSize: 14, marginTop: 10, lineHeight: 1.55 }}>
              Novale is pulling live data from {ANALYSIS_STEPS.length} sources so every recommendation is right for <strong style={{ color: 'var(--fg-primary)' }}>your</strong> lot — not a generic yard.
            </p>
          </div>

          <div className="analysis__progress">
            {ANALYSIS_STEPS.map((s) => {
              const st = stepState[s.id] || 'idle';
              const visualState =
                st === 'done' ? 'done' :
                st === 'active' ? 'active' :
                st === 'empty' ? 'empty' : 'idle';
              return (
                <div key={s.id} className="analysis__step" data-state={visualState}>
                  <div className="analysis__step-icon">
                    {visualState === 'done' ? <Icon name="check" size={14} stroke={2.5} />
                     : visualState === 'active' ? <Icon name="loader" size={14} className="spin" />
                     : visualState === 'empty' ? <Icon name="minus" size={14} stroke={2} />
                     : <Icon name={s.icon} size={14} />}
                  </div>
                  <div className="analysis__step-label">
                    {s.label}
                    {visualState === 'empty' && (
                      <span className="muted" style={{ fontSize: 11, marginLeft: 6 }}>not found</span>
                    )}
                  </div>
                  <div className="analysis__step-value">{s.source}</div>
                </div>
              );
            })}
          </div>

          {complete && (
            <div style={{ marginTop: 28, display: 'flex', flexDirection: 'column', gap: 12 }}>
              <button className="btn btn--accent btn--lg" onClick={onContinue} style={{ width: '100%' }}>
                Review conditions report
                <Icon name="arrow-right" size={16} />
              </button>
              <p className="muted" style={{ fontSize: 12, textAlign: 'center' }}>Next: check what we found</p>
            </div>
          )}
        </aside>
      </div>
    </div>
  );
};

Object.assign(window, { SiteAnalysis });
