import type { AppContext, AppModule } from '@/app/app-context';
import type { SearchResult } from '@/components/SearchModal';
import type { NewsItem, MapLayers, MilitaryBase, MilitaryFlight } from '@/types';
import type { MapView, TimeRange } from '@/components/MapContainer';
import type { Command } from '@/config/commands';
import { SearchModal } from '@/components/SearchModal';
import type { CIIPanel } from '@/components/CIIPanel';
import { SITE_VARIANT, STORAGE_KEYS, ALL_PANELS, getEffectivePanelConfig, isPanelEntitled } from '@/config';
import { getAllowedLayerKeys, isLayerExecutable } from '@/config/map-layer-definitions';
import type { MapRenderer } from '@/config/map-layer-definitions';
import type { MapVariant } from '@/config/map-layer-definitions';
import { LAYER_PRESETS, LAYER_KEY_MAP } from '@/config/commands';
import { calculateCII, TIER1_COUNTRIES } from '@/services/country-instability';
import { getCachedCountryScores } from '@/services/cached-risk-scores';
import { CURATED_COUNTRIES } from '@/config/countries';
import { getCountryBbox } from '@/services/country-geometry';
import { INTEL_HOTSPOTS, CONFLICT_ZONES } from '@/config/geo';
import { getCachedMilitaryBases, preloadMilitaryBases } from '@/services/military-base-config';
import { UNDERSEA_CABLES, NUCLEAR_FACILITIES } from '@/config/geo-map';
import { PIPELINES } from '@/config/pipelines';
import { AI_DATA_CENTERS } from '@/config/ai-datacenters';
import { GAMMA_IRRADIATORS } from '@/config/irradiators';
import { TECH_COMPANIES } from '@/config/tech-companies';
import { AI_RESEARCH_LABS } from '@/config/ai-research-labs';
import { STARTUP_ECOSYSTEMS } from '@/config/startup-ecosystems';
import { TECH_HQS, ACCELERATORS } from '@/config/tech-geo';
import { STOCK_EXCHANGES, FINANCIAL_CENTERS, CENTRAL_BANKS, COMMODITY_HUBS } from '@/config/finance-geo';
import { trackSearchResultSelected, trackCountrySelected } from '@/services/analytics';
import { t } from '@/services/i18n';
import { saveToStorage, setTheme } from '@/utils';
import { CountryIntelManager } from '@/app/country-intel';
import type { PositionSample } from '@/services/aviation';
import { fetchAircraftPositions } from '@/services/aviation';
import { isProUser } from '@/services/widget-store';
import { getAuthState } from '@/services/auth-state';

export interface SearchManagerCallbacks {
  openCountryBriefByCode: (code: string, country: string) => void;
  /** Enables a currently-disabled panel (CMD+K "Add"). Returns false if blocked (unknown / free-tier cap). */
  enablePanel: (panelId: string) => boolean;
}

export class SearchManager implements AppModule {
  private ctx: AppContext;
  private callbacks: SearchManagerCallbacks;
  private highlightTimers = new WeakMap<Element, ReturnType<typeof setTimeout>>();

  constructor(ctx: AppContext, callbacks: SearchManagerCallbacks) {
    this.ctx = ctx;
    this.callbacks = callbacks;
  }

  init(): void {
    this.setupSearchModal();
  }

  destroy(): void {}

  private setupSearchModal(): void {
    const searchOptions = SITE_VARIANT === 'tech'
      ? { placeholder: t('modals.search.placeholderTech') }
      : SITE_VARIANT === 'happy'
        ? { placeholder: 'Search or type a command...' }
        : SITE_VARIANT === 'finance'
          ? { placeholder: t('modals.search.placeholderFinance') }
          : { placeholder: t('modals.search.placeholder') };
    this.ctx.searchModal = new SearchModal(this.ctx.container, searchOptions);

    if (SITE_VARIANT === 'happy') {
      // Happy variant: no geopolitical/military/infrastructure sources
    } else if (SITE_VARIANT === 'tech') {
      this.ctx.searchModal.registerSource('techcompany', TECH_COMPANIES.map(c => ({
        id: c.id,
        title: c.name,
        subtitle: `${c.sector} ${c.city} ${c.keyProducts?.join(' ') || ''}`.trim(),
        data: c,
      })));

      this.ctx.searchModal.registerSource('ailab', AI_RESEARCH_LABS.map(l => ({
        id: l.id,
        title: l.name,
        subtitle: `${l.type} ${l.city} ${l.focusAreas?.join(' ') || ''}`.trim(),
        data: l,
      })));

      this.ctx.searchModal.registerSource('startup', STARTUP_ECOSYSTEMS.map(s => ({
        id: s.id,
        title: s.name,
        subtitle: `${s.ecosystemTier} ${s.topSectors?.join(' ') || ''} ${s.notableStartups?.join(' ') || ''}`.trim(),
        data: s,
      })));

      this.ctx.searchModal.registerSource('datacenter', AI_DATA_CENTERS.map(d => ({
        id: d.id,
        title: d.name,
        subtitle: `${d.owner} ${d.chipType || ''}`.trim(),
        data: d,
      })));

      this.ctx.searchModal.registerSource('cable', UNDERSEA_CABLES.map(c => ({
        id: c.id,
        title: c.name,
        subtitle: c.major ? 'Major internet backbone' : 'Undersea cable',
        data: c,
      })));

      this.ctx.searchModal.registerSource('techhq', TECH_HQS.map(h => ({
        id: h.id,
        title: h.company,
        subtitle: `${h.type === 'faang' ? 'Big Tech' : h.type === 'unicorn' ? 'Unicorn' : 'Public'} • ${h.city}, ${h.country}`,
        data: h,
      })));

      this.ctx.searchModal.registerSource('accelerator', ACCELERATORS.map(a => ({
        id: a.id,
        title: a.name,
        subtitle: `${a.type} • ${a.city}, ${a.country}${a.notable ? ` • ${a.notable.slice(0, 2).join(', ')}` : ''}`,
        data: a,
      })));
    } else {
      this.ctx.searchModal.registerSource('hotspot', INTEL_HOTSPOTS.map(h => ({
        id: h.id,
        title: h.name,
        subtitle: `${h.subtext || ''} ${h.keywords?.join(' ') || ''} ${h.description || ''}`.trim(),
        data: h,
      })));

      this.ctx.searchModal.registerSource('conflict', CONFLICT_ZONES.map(c => ({
        id: c.id,
        title: c.name,
        subtitle: `${c.parties?.join(' ') || ''} ${c.keywords?.join(' ') || ''} ${c.description || ''}`.trim(),
        data: c,
      })));

      this.registerBaseSearchSource();

      this.ctx.searchModal.registerSource('pipeline', PIPELINES.map(p => ({
        id: p.id,
        title: p.name,
        subtitle: `${p.type} ${p.operator || ''} ${p.countries?.join(' ') || ''}`.trim(),
        data: p,
      })));

      this.ctx.searchModal.registerSource('cable', UNDERSEA_CABLES.map(c => ({
        id: c.id,
        title: c.name,
        subtitle: c.major ? 'Major cable' : '',
        data: c,
      })));

      this.ctx.searchModal.registerSource('datacenter', AI_DATA_CENTERS.map(d => ({
        id: d.id,
        title: d.name,
        subtitle: `${d.owner} ${d.chipType || ''}`.trim(),
        data: d,
      })));

      this.ctx.searchModal.registerSource('nuclear', NUCLEAR_FACILITIES.map(n => ({
        id: n.id,
        title: n.name,
        subtitle: `${n.type} ${n.operator || ''}`.trim(),
        data: n,
      })));

      this.ctx.searchModal.registerSource('irradiator', GAMMA_IRRADIATORS.map(g => ({
        id: g.id,
        title: `${g.city}, ${g.country}`,
        subtitle: g.organization || '',
        data: g,
      })));
    }

    if (SITE_VARIANT === 'finance') {
      this.ctx.searchModal.registerSource('exchange', STOCK_EXCHANGES.map(e => ({
        id: e.id,
        title: `${e.shortName} - ${e.name}`,
        subtitle: `${e.tier} • ${e.city}, ${e.country}${e.marketCap ? ` • $${e.marketCap}T` : ''}`,
        data: e,
      })));

      this.ctx.searchModal.registerSource('financialcenter', FINANCIAL_CENTERS.map(f => ({
        id: f.id,
        title: f.name,
        subtitle: `${f.type} financial center${f.gfciRank ? ` • GFCI #${f.gfciRank}` : ''}${f.specialties ? ` • ${f.specialties.slice(0, 3).join(', ')}` : ''}`,
        data: f,
      })));

      this.ctx.searchModal.registerSource('centralbank', CENTRAL_BANKS.map(b => ({
        id: b.id,
        title: `${b.shortName} - ${b.name}`,
        subtitle: `${b.type}${b.currency ? ` • ${b.currency}` : ''} • ${b.city}, ${b.country}`,
        data: b,
      })));

      this.ctx.searchModal.registerSource('commodityhub', COMMODITY_HUBS.map(h => ({
        id: h.id,
        title: h.name,
        subtitle: `${h.type} • ${h.city}, ${h.country}${h.commodities ? ` • ${h.commodities.slice(0, 3).join(', ')}` : ''}`,
        data: h,
      })));
    }

    this.ctx.searchModal.registerSource('country', this.buildCountrySearchItems());

    this.syncPanelSearchIndex();
    // Filter CMD+K layer commands by (a) variant-allowed, (b) renderer
    // compatibility, (c) DeckGL state for deckGLOnly layers. Without this,
    // layer commands surface in CMD+K on variants where they'd silently
    // fail the variantAllowed guard in handleCommand (e.g.
    // `layer:storageFacilities` on tech/finance/commodity/happy), or on
    // globe / SVG-mobile where they'd hit the renderer guard. Better to
    // hide the toggle than expose a button that does nothing.
    this.ctx.searchModal.setLayerExecutableFn((layerKey) => {
      const key = (LAYER_KEY_MAP[layerKey] || layerKey) as keyof MapLayers;
      if (!(key in this.ctx.mapLayers)) return false;
      const variantAllowed = getAllowedLayerKeys((SITE_VARIANT || 'full') as MapVariant);
      if (!variantAllowed.has(key)) return false;
      const renderer: MapRenderer = this.ctx.map?.isGlobeMode?.() ? 'globe' : 'flat';
      const isDeckGL = this.ctx.map?.isDeckGLActive?.() ?? false;
      return isLayerExecutable(key, renderer, isDeckGL);
    });
    this.ctx.searchModal.setOnSelect((result) => this.handleSearchResult(result));
    this.ctx.searchModal.setOnCommand((cmd) => this.handleCommand(cmd));
    // Always wire flight search; check pro status reactively inside the callback
    // so mid-session sign-ins get the feature without a page reload.
    this.ctx.searchModal.setOnFlightSearch((callsign) => {
      if (!isProUser() && getAuthState().user?.role !== 'pro') return;
      fetchAircraftPositions({ callsign }).then((positions) => {
        if (!this.ctx.searchModal) return;
        // Deduplicate by callsign: keep the most recently observed entry per callsign.
        const seen = new Map<string, PositionSample>();
        for (const p of positions) {
          const key = (p.callsign || p.icao24).trim().toUpperCase();
          const existing = seen.get(key);
          if (!existing || p.observedAt > existing.observedAt) {
            seen.set(key, p);
          }
        }
        const items = [...seen.values()].map(p => {
          const fl = Number.isFinite(p.altitudeFt) ? Math.round(p.altitudeFt / 100) : null;
          const kts = Number.isFinite(p.groundSpeedKts) ? Math.round(p.groundSpeedKts) : null;
          return {
            id: p.icao24,
            title: (p.callsign || p.icao24).trim().toUpperCase(),
            subtitle: p.onGround
              ? t('modals.search.flightOnGround')
              : fl !== null && kts !== null
                ? t('modals.search.flightAirborne', { fl: String(fl), kts: String(kts) })
                : fl !== null ? `FL${fl}` : t('modals.search.flightOnGround'),
            data: { kind: 'adsb' as const, lat: p.lat, lon: p.lon, layer: 'flights' as const },
          };
        });
        this.ctx.searchModal.registerSource('flight', items);
        this.ctx.searchModal.refreshSearch();
      }).catch(() => {/* silent — show no results */});
    });

  }

  private registerBaseSearchSource(): void {
    const register = (bases: MilitaryBase[]) => {
      this.ctx.searchModal?.registerSource('base', bases.map(b => ({
        id: b.id,
        title: b.name,
        subtitle: `${b.type} ${b.description || ''}`.trim(),
        data: b,
      })));
    };

    const cached = getCachedMilitaryBases();
    if (cached.length > 0) register(cached);
    void preloadMilitaryBases().then(register).catch(() => {});
  }

  private handleSearchResult(result: SearchResult): void {
    trackSearchResultSelected(result.type);
    switch (result.type) {
      case 'news': {
        const item = result.data as NewsItem;
        // Find which panel contains this item (may not always be 'politics')
        let targetPanelId = 'politics';
        let targetPanel = this.ctx.newsPanels['politics'] ?? null;
        for (const [panelId, panel] of Object.entries(this.ctx.newsPanels)) {
          if (panel.hasNewsItem(item.link)) {
            targetPanelId = panelId;
            targetPanel = panel;
            break;
          }
        }
        this.scrollToPanel(targetPanelId);
        if (targetPanel) {
          setTimeout(() => targetPanel!.scrollToNewsItem(item.link), 300);
        }
        break;
      }
      case 'hotspot': {
        const hotspot = result.data as typeof INTEL_HOTSPOTS[0];
        this.ctx.map?.setView('global');
        setTimeout(() => { this.ctx.map?.triggerHotspotClick(hotspot.id); }, 300);
        break;
      }
      case 'conflict': {
        const conflict = result.data as typeof CONFLICT_ZONES[0];
        this.ctx.map?.setView('global');
        setTimeout(() => { this.ctx.map?.triggerConflictClick(conflict.id); }, 300);
        break;
      }
      case 'market': {
        this.scrollToPanel('markets');
        break;
      }
      case 'prediction': {
        this.scrollToPanel('polymarket');
        break;
      }
      case 'base': {
        const base = result.data as MilitaryBase;
        this.ctx.map?.setView('global');
        setTimeout(() => { this.ctx.map?.triggerBaseClick(base.id); }, 300);
        break;
      }
      case 'pipeline': {
        const pipeline = result.data as typeof PIPELINES[0];
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('pipelines');
        this.ctx.mapLayers.pipelines = true;
        setTimeout(() => { this.ctx.map?.triggerPipelineClick(pipeline.id); }, 300);
        break;
      }
      case 'cable': {
        const cable = result.data as typeof UNDERSEA_CABLES[0];
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('cables');
        this.ctx.mapLayers.cables = true;
        setTimeout(() => { this.ctx.map?.triggerCableClick(cable.id); }, 300);
        break;
      }
      case 'datacenter': {
        const dc = result.data as typeof AI_DATA_CENTERS[0];
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('datacenters');
        this.ctx.mapLayers.datacenters = true;
        setTimeout(() => { this.ctx.map?.triggerDatacenterClick(dc.id); }, 300);
        break;
      }
      case 'nuclear': {
        const nuc = result.data as typeof NUCLEAR_FACILITIES[0];
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('nuclear');
        this.ctx.mapLayers.nuclear = true;
        setTimeout(() => { this.ctx.map?.triggerNuclearClick(nuc.id); }, 300);
        break;
      }
      case 'irradiator': {
        const irr = result.data as typeof GAMMA_IRRADIATORS[0];
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('irradiators');
        this.ctx.mapLayers.irradiators = true;
        setTimeout(() => { this.ctx.map?.triggerIrradiatorClick(irr.id); }, 300);
        break;
      }
      case 'earthquake':
      case 'outage':
        this.ctx.map?.setView('global');
        break;
      case 'techcompany': {
        const company = result.data as typeof TECH_COMPANIES[0];
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('techHQs');
        this.ctx.mapLayers.techHQs = true;
        setTimeout(() => { this.ctx.map?.setCenter(company.lat, company.lon, 4); }, 300);
        break;
      }
      case 'ailab': {
        const lab = result.data as typeof AI_RESEARCH_LABS[0];
        this.ctx.map?.setView('global');
        setTimeout(() => { this.ctx.map?.setCenter(lab.lat, lab.lon, 4); }, 300);
        break;
      }
      case 'startup': {
        const ecosystem = result.data as typeof STARTUP_ECOSYSTEMS[0];
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('startupHubs');
        this.ctx.mapLayers.startupHubs = true;
        setTimeout(() => { this.ctx.map?.setCenter(ecosystem.lat, ecosystem.lon, 4); }, 300);
        break;
      }
      case 'techevent':
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('techEvents');
        this.ctx.mapLayers.techEvents = true;
        break;
      case 'techhq': {
        const hq = result.data as typeof TECH_HQS[0];
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('techHQs');
        this.ctx.mapLayers.techHQs = true;
        setTimeout(() => { this.ctx.map?.setCenter(hq.lat, hq.lon, 4); }, 300);
        break;
      }
      case 'accelerator': {
        const acc = result.data as typeof ACCELERATORS[0];
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('accelerators');
        this.ctx.mapLayers.accelerators = true;
        setTimeout(() => { this.ctx.map?.setCenter(acc.lat, acc.lon, 4); }, 300);
        break;
      }
      case 'exchange': {
        const exchange = result.data as typeof STOCK_EXCHANGES[0];
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('stockExchanges');
        this.ctx.mapLayers.stockExchanges = true;
        setTimeout(() => { this.ctx.map?.setCenter(exchange.lat, exchange.lon, 4); }, 300);
        break;
      }
      case 'financialcenter': {
        const fc = result.data as typeof FINANCIAL_CENTERS[0];
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('financialCenters');
        this.ctx.mapLayers.financialCenters = true;
        setTimeout(() => { this.ctx.map?.setCenter(fc.lat, fc.lon, 4); }, 300);
        break;
      }
      case 'centralbank': {
        const bank = result.data as typeof CENTRAL_BANKS[0];
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('centralBanks');
        this.ctx.mapLayers.centralBanks = true;
        setTimeout(() => { this.ctx.map?.setCenter(bank.lat, bank.lon, 4); }, 300);
        break;
      }
      case 'commodityhub': {
        const hub = result.data as typeof COMMODITY_HUBS[0];
        this.ctx.map?.setView('global');
        this.ctx.map?.enableLayer('commodityHubs');
        this.ctx.mapLayers.commodityHubs = true;
        setTimeout(() => { this.ctx.map?.setCenter(hub.lat, hub.lon, 4); }, 300);
        break;
      }
      case 'country': {
        const { code, name } = result.data as { code: string; name: string };
        trackCountrySelected(code, name, 'search');
        this.callbacks.openCountryBriefByCode(code, name);
        break;
      }
      case 'flight': {
        const { lat, lon, layer } = result.data as { kind: string; lat: number; lon: number; layer: keyof MapLayers };
        this.ctx.map?.enableLayer(layer);
        this.ctx.mapLayers[layer] = true;
        setTimeout(() => { this.ctx.map?.setCenter(lat, lon, 9); }, 300);
        break;
      }
    }
  }

  private handleCommand(cmd: Command): void {
    const colonIdx = cmd.id.indexOf(':');
    if (colonIdx === -1) return;
    const category = cmd.id.slice(0, colonIdx);
    const action = cmd.id.slice(colonIdx + 1);

    switch (category) {
      case 'nav':
        this.ctx.map?.setView(action as MapView);
        {
          const sel = document.getElementById('regionSelect') as HTMLSelectElement;
          if (sel) sel.value = action;
        }
        break;

      case 'layers': {
        const allowed = getAllowedLayerKeys((SITE_VARIANT || 'full') as MapVariant);
        // Preset paths (`layers:all`, `layers:infra`, …) also need the
        // renderer + DeckGL gate that per-layer toggles go through. Without
        // it, a user in globe mode or on the SVG fallback can run
        // `layers:infra` and silently flip `deckGLOnly` layers on — those
        // layers set to `true` in state but produce no rendered output,
        // and since the picker hides them under the current renderer the
        // user has no way to toggle them back off without switching
        // modes. Codex P2 on PR #3366.
        const renderer: MapRenderer = this.ctx.map?.isGlobeMode?.() ? 'globe' : 'flat';
        const isDeckGL = this.ctx.map?.isDeckGLActive?.() ?? false;
        const executable = (k: keyof MapLayers): boolean =>
          allowed.has(k) && isLayerExecutable(k, renderer, isDeckGL);
        if (action === 'all') {
          for (const key of Object.keys(this.ctx.mapLayers)) {
            this.ctx.mapLayers[key as keyof MapLayers] = executable(key as keyof MapLayers);
          }
        } else if (action === 'none') {
          for (const key of Object.keys(this.ctx.mapLayers))
            this.ctx.mapLayers[key as keyof MapLayers] = false;
        } else {
          const preset = LAYER_PRESETS[action];
          if (preset) {
            for (const key of Object.keys(this.ctx.mapLayers))
              this.ctx.mapLayers[key as keyof MapLayers] = false;
            for (const layer of preset) {
              if (executable(layer)) this.ctx.mapLayers[layer] = true;
            }
          }
        }
        saveToStorage(STORAGE_KEYS.mapLayers, this.ctx.mapLayers);
        this.ctx.map?.setLayers(this.ctx.mapLayers);
        break;
      }

      case 'layer': {
        const layerKey = (LAYER_KEY_MAP[action] || action) as keyof MapLayers;
        if (!(layerKey in this.ctx.mapLayers)) return;
        const variantAllowed = getAllowedLayerKeys((SITE_VARIANT || 'full') as MapVariant);
        if (!variantAllowed.has(layerKey)) return;
        // Renderer / DeckGL gate. Mirrors the filter applied in SearchModal
        // so direct activation paths (keyboard-accelerator, programmatic
        // dispatch, etc.) don't flip a layer on that can't render.
        const renderer: MapRenderer = this.ctx.map?.isGlobeMode?.() ? 'globe' : 'flat';
        const isDeckGL = this.ctx.map?.isDeckGLActive?.() ?? false;
        if (!isLayerExecutable(layerKey, renderer, isDeckGL)) return;
        let newValue = !this.ctx.mapLayers[layerKey];
        if (newValue && layerKey === 'resilienceScore' && !this.ctx.map?.isDeckGLActive?.()) {
          newValue = false;
        }
        this.ctx.mapLayers[layerKey] = newValue;
        saveToStorage(STORAGE_KEYS.mapLayers, this.ctx.mapLayers);
        if (newValue) {
          this.ctx.map?.enableLayer(layerKey);
        } else {
          this.ctx.map?.setLayers(this.ctx.mapLayers);
        }
        break;
      }

      case 'panel': {
        // CMD+K can now surface disabled-but-available panels (Add affordance).
        // Enable first so the element exists, then scroll once it renders.
        // An optional `@<tab>` suffix deep-links to a specific tab within the
        // panel (e.g. `consumer-prices@world` → global inflation view).
        const [panelId, subTab] = action.split('@');
        if (!panelId) break;
        const cfg = this.ctx.panelSettings[panelId];
        if (cfg && !cfg.enabled) {
          if (this.callbacks.enablePanel(panelId)) {
            this.scrollToPanelWhenReady(panelId);
            if (subTab) this.dispatchPanelTab(panelId, subTab);
            break;
          }
        }
        this.scrollToPanel(panelId);
        if (subTab) this.dispatchPanelTab(panelId, subTab);
        break;
      }

      case 'view':
        if (action === 'dark' || action === 'light') {
          setTheme(action);
        } else if (action === 'fullscreen') {
          if (document.fullscreenElement) {
            try { void document.exitFullscreen()?.catch(() => {}); } catch {}
          } else {
            const el = document.documentElement as HTMLElement & { webkitRequestFullscreen?: () => void };
            if (el.requestFullscreen) {
              try { void el.requestFullscreen()?.catch(() => {}); } catch {}
            } else if (el.webkitRequestFullscreen) {
              try { el.webkitRequestFullscreen(); } catch {}
            }
          }
        } else if (action === 'settings') {
          this.ctx.unifiedSettings?.open();
        } else if (action === 'refresh') {
          window.location.reload();
        } else if (action === 'resilience') {
          const layerKey = 'resilienceScore' as keyof MapLayers;
          const variantAllowed = getAllowedLayerKeys((SITE_VARIANT || 'full') as MapVariant);
          if (!variantAllowed.has(layerKey)) break;
          let newValue = !this.ctx.mapLayers[layerKey];
          if (newValue && !this.ctx.map?.isDeckGLActive?.()) newValue = false;
          this.ctx.mapLayers[layerKey] = newValue;
          saveToStorage(STORAGE_KEYS.mapLayers, this.ctx.mapLayers);
          if (newValue) {
            this.ctx.map?.enableLayer(layerKey);
          } else {
            this.ctx.map?.setLayers(this.ctx.mapLayers);
          }
        } else if (action === 'route-explorer') {
          void import('@/components/RouteExplorer/RouteExplorer').then((m) => {
            const explorer = m.getRouteExplorer();
            explorer.setMap(this.ctx.map);
            explorer.open();
          });
        }
        break;

      case 'time':
        this.ctx.map?.setTimeRange(action as TimeRange);
        break;

      case 'country': {
        const name = TIER1_COUNTRIES[action]
          || CURATED_COUNTRIES[action]?.name
          || new Intl.DisplayNames(['en'], { type: 'region' }).of(action)
          || action;
        trackCountrySelected(action, name, 'command');
        this.callbacks.openCountryBriefByCode(action, name);
        break;
      }

      case 'country-map': {
        const bbox = getCountryBbox(action);
        if (bbox) {
          const [minLon, minLat, maxLon, maxLat] = bbox;
          const lat = (minLat + maxLat) / 2;
          const lon = (minLon + maxLon) / 2;
          const span = Math.max(maxLat - minLat, maxLon - minLon);
          const zoom = span > 40 ? 3 : span > 15 ? 4 : span > 5 ? 5 : 6;
          this.ctx.map?.setView('global');
          setTimeout(() => { this.ctx.map?.setCenter(lat, lon, zoom); }, 300);
        }
        break;
      }
    }
  }

  /**
   * Scrolls to a panel that may have just been enabled. Async-mounted panels
   * (e.g. deduction, regional-intelligence mount via dynamic import) aren't in
   * the DOM on the next tick, so retry over ~1s before giving up. The panel is
   * already enabled regardless — only the scroll is best-effort.
   */
  private scrollToPanelWhenReady(panelId: string, attemptsLeft = 12): void {
    if (document.querySelector(`[data-panel="${panelId}"]`)) {
      this.scrollToPanel(panelId);
      return;
    }
    if (attemptsLeft <= 0) return;
    setTimeout(() => this.scrollToPanelWhenReady(panelId, attemptsLeft - 1), 80);
  }

  /**
   * Deep-links to a tab inside a panel by dispatching the panel's open-tab
   * event once it's mounted. The element existing in the DOM implies the
   * panel's constructor (and its event listener) has run, so we retry until
   * then — mirrors scrollToPanelWhenReady for async-mounted panels.
   */
  private dispatchPanelTab(panelId: string, tab: string, attemptsLeft = 12): void {
    // Currently only Consumer Prices exposes a tab deep-link contract.
    if (panelId !== 'consumer-prices') return;
    if (document.querySelector(`[data-panel="${panelId}"]`)) {
      window.dispatchEvent(new CustomEvent('wm-consumer-prices-open-tab', { detail: { tab } }));
      return;
    }
    if (attemptsLeft <= 0) return;
    setTimeout(() => this.dispatchPanelTab(panelId, tab, attemptsLeft - 1), 80);
  }

  private scrollToPanel(panelId: string): void {
    const panel = document.querySelector(`[data-panel="${panelId}"]`);
    if (panel) {
      panel.scrollIntoView({ behavior: 'smooth', block: 'center' });
      this.applyHighlight(panel);
    }
  }

  private applyHighlight(el: Element): void {
    const prev = this.highlightTimers.get(el);
    if (prev) clearTimeout(prev);
    el.classList.remove('search-highlight');
    void (el as HTMLElement).offsetWidth;
    el.classList.add('search-highlight');
    this.highlightTimers.set(el, setTimeout(() => {
      el.classList.remove('search-highlight');
      this.highlightTimers.delete(el);
    }, 3100));
  }

  updateFlightSource(adsb: PositionSample[], military: MilitaryFlight[]): void {
    if (!this.ctx.searchModal || !isProUser()) return;
    const items = [
      ...adsb.map(p => {
        const fl = Number.isFinite(p.altitudeFt) ? Math.round(p.altitudeFt / 100) : null;
        const kts = Number.isFinite(p.groundSpeedKts) ? Math.round(p.groundSpeedKts) : null;
        return {
          id: p.icao24,
          title: (p.callsign || p.icao24).trim().toUpperCase(),
          subtitle: p.onGround
            ? t('modals.search.flightOnGround')
            : fl !== null && kts !== null
              ? t('modals.search.flightAirborne', { fl: String(fl), kts: String(kts) })
              : fl !== null
                ? `FL${fl}`
                : t('modals.search.flightOnGround'),
          data: { kind: 'adsb' as const, lat: p.lat, lon: p.lon, layer: 'flights' as const },
        };
      }),
      ...military.map(f => {
        const fl = Number.isFinite(f.altitude) ? Math.round(f.altitude / 100) : null;
        return {
          id: f.hexCode,
          title: (f.callsign || f.hexCode).trim().toUpperCase(),
          subtitle: f.onGround
            ? t('modals.search.flightMilitaryOnGround', { type: f.aircraftType })
            : fl !== null
              ? t('modals.search.flightMilitary', { type: f.aircraftType, fl: String(fl) })
              : t('modals.search.flightMilitaryOnGround', { type: f.aircraftType }),
          data: { kind: 'military' as const, lat: f.lat, lon: f.lon, layer: 'military' as const },
        };
      }),
    ];
    this.ctx.searchModal.registerSource('flight', items);
  }

  updateSearchIndex(): void {
    if (!this.ctx.searchModal) return;

    this.syncPanelSearchIndex();
    this.ctx.searchModal.registerSource('country', this.buildCountrySearchItems());

    const newsItems = this.ctx.allNews.slice(0, 500).map(n => ({
      id: n.link,
      title: n.title,
      subtitle: n.source,
      data: n,
    }));
    console.log(`[Search] Indexing ${newsItems.length} news items (allNews total: ${this.ctx.allNews.length})`);
    this.ctx.searchModal.registerSource('news', newsItems);

    if (this.ctx.latestPredictions.length > 0) {
      this.ctx.searchModal.registerSource('prediction', this.ctx.latestPredictions.map(p => ({
        id: p.title,
        title: p.title,
        subtitle: `${Math.round(p.yesPrice)}% probability`,
        data: p,
      })));
    }

    if (this.ctx.latestMarkets.length > 0) {
      this.ctx.searchModal.registerSource('market', this.ctx.latestMarkets.map(m => ({
        id: m.symbol,
        title: `${m.symbol} - ${m.name}`,
        subtitle: `$${m.price?.toFixed(2) || 'N/A'}`,
        data: m,
      })));
    }

    if (SITE_VARIANT === 'tech') {
      this.ctx.searchModal.registerSource('techevent', this.ctx.latestTechEvents.map((e) => ({
        id: e.id,
        title: e.title,
        subtitle: `${e.location} • ${new Date(e.startDate).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}`,
        data: e,
      })));
    }
  }

  /**
   * Feeds CMD+K two panel sets: `active` (currently enabled) and `available`
   * (every entitled panel the user could cross-enable on this variant — all
   * of ALL_PANELS merge into panelSettings per App.ts). The modal surfaces
   * available-but-disabled panels with an "Add" affordance; selecting one
   * routes through enablePanel(). Without the available set, search could
   * only jump to panels already on screen — the core discoverability gap.
   */
  private syncPanelSearchIndex(): void {
    if (!this.ctx.searchModal) return;
    // isProUser() already folds in getAuthState().user?.role === 'pro'.
    const isPro = isProUser();
    this.ctx.searchModal.setActivePanels(
      Object.entries(this.ctx.panelSettings).filter(([, v]) => v.enabled).map(([k]) => k)
    );
    this.ctx.searchModal.setAvailablePanels(
      Object.keys(this.ctx.panelSettings).filter((k) => {
        // Keep unregistered/dynamic keys out of search; the resolver would
        // otherwise return a disabled synthetic fallback for unknown keys.
        const cfg = ALL_PANELS[k] ? getEffectivePanelConfig(k, SITE_VARIANT) : undefined;
        return cfg ? isPanelEntitled(k, cfg, isPro) : false;
      })
    );
  }

  private buildCountrySearchItems(): { id: string; title: string; subtitle: string; data: { code: string; name: string } }[] {
    const cachedScores = getCachedCountryScores();
    const panelScores = (this.ctx.panels.cii as CIIPanel | undefined)?.getScores() ?? [];
    const scores = cachedScores.length > 0
      ? cachedScores
      : (panelScores.length > 0 ? panelScores : calculateCII());
    const ciiByCode = new Map(scores.map((score) => [score.code, score]));
    return Object.entries(TIER1_COUNTRIES).map(([code, name]) => {
      const score = ciiByCode.get(code);
      return {
        id: code,
        title: `${CountryIntelManager.toFlagEmoji(code)} ${name}`,
        subtitle: score ? `CII: ${score.score}/100 • ${score.level}` : 'Country Brief',
        data: { code, name },
      };
    });
  }
}
