import { demoFunc } from './dees-geo-map.demo.js';
import {
  customElement,
  html,
  DeesElement,
  property,
  state,
  type TemplateResult,
  cssManager,
  css,
  domtools,
} from '@design.estate/dees-element';
import { DeesContextmenu } from '@design.estate/dees-catalog';
import { geoComponentStyles, mapContainerStyles, toolbarStyles, searchStyles, navigationStyles, trafficStyles, headerToolbarStyles, guidanceStyles, maplibreMarkerStyles } from '../../00componentstyles.js';

// MapLibre imports
import maplibregl from 'maplibre-gl';

// Terra Draw imports
import {
  TerraDraw,
  TerraDrawPolygonMode,
  TerraDrawRectangleMode,
  TerraDrawPointMode,
  TerraDrawLineStringMode,
  TerraDrawCircleMode,
  TerraDrawFreehandMode,
  TerraDrawSelectMode,
  TerraDrawRenderMode,
} from 'terra-draw';
import { TerraDrawMapLibreGLAdapter } from 'terra-draw-maplibre-gl-adapter';

// Modular imports
import { renderIcon } from './geo-map.icons.js';
import { SearchController, type INominatimResult, type IAddressSelectedEvent } from './geo-map.search.js';
import { NavigationController, type TNavigationMode, type INavigationState, type IRouteCalculatedEvent, type IOSRMRoute } from './geo-map.navigation.js';
import { TrafficController } from './geo-map.traffic.js';
import { HereTrafficProvider, type ITrafficProvider } from './geo-map.traffic.providers.js';
import { NavigationGuideController, type IGPSPosition, type IGuidanceEvent, type INavigationGuideState, type INavigationCameraConfig } from './geo-map.navigation-guide.js';
import { MockGPSSimulator, type TSimulationSpeed, type IMockGPSConfig } from './geo-map.mock-gps.js';
import type { IVoiceConfig } from './geo-map.voice.js';

// Re-export types for external consumers
export type { INominatimResult, IAddressSelectedEvent } from './geo-map.search.js';
export type {
  TNavigationMode,
  INavigationState,
  IOSRMRoute,
  IOSRMLeg,
  IOSRMStep,
  IRouteCalculatedEvent,
} from './geo-map.navigation.js';
export type { ITrafficProvider, ITrafficFlowData, ITrafficAwareRoute } from './geo-map.traffic.providers.js';
export type { IGPSPosition, IGuidanceEvent, INavigationGuideState, TGuidanceEventType, INavigationCameraConfig } from './geo-map.navigation-guide.js';
export type { IVoiceConfig } from './geo-map.voice.js';
export type { TSimulationSpeed, IMockGPSConfig } from './geo-map.mock-gps.js';

export type TDrawTool = 'polygon' | 'rectangle' | 'point' | 'linestring' | 'circle' | 'freehand' | 'select' | 'static';

export interface IDrawChangeEvent {
  ids: string[];
  type: string;
  features: GeoJSON.Feature[];
}

export interface IDrawFinishEvent {
  id: string;
  context: { action: string; mode: string };
  features: GeoJSON.Feature[];
}

export interface IDrawSelectEvent {
  id: string;
}

declare global {
  interface HTMLElementTagNameMap {
    'dees-geo-map': DeesGeoMap;
  }
}

@customElement('dees-geo-map')
export class DeesGeoMap extends DeesElement {
  public static demo = demoFunc;

  // ─── Properties ─────────────────────────────────────────────────────────────

  @property({ type: Array })
  accessor center: [number, number] = [0, 0]; // [lng, lat]

  @property({ type: Number })
  accessor zoom: number = 2;

  @property({ type: String })
  accessor mapStyle: string = 'osm';

  @property({ type: String })
  accessor activeTool: TDrawTool = 'static';

  @property({ type: Object })
  accessor geoJson: GeoJSON.FeatureCollection = {
    type: 'FeatureCollection',
    features: [],
  };

  @property({ type: Boolean })
  accessor showToolbar: boolean = true;

  @property({ type: Boolean })
  accessor dragToDraw: boolean = true; // Default to drag behavior for circle/rectangle

  @property({ type: String })
  accessor projection: 'mercator' | 'globe' = 'globe';

  @property({ type: Boolean })
  accessor showSearch: boolean = false;

  @property({ type: String })
  accessor searchPlaceholder: string = 'Search address...';

  @property({ type: Boolean })
  accessor showNavigation: boolean = false;

  @property({ type: String })
  accessor navigationMode: TNavigationMode = 'driving';

  // Traffic properties
  @property({ type: Boolean })
  accessor showTraffic: boolean = false;

  @property({ type: Object })
  accessor trafficProvider: ITrafficProvider | null = null;

  @property({ type: String })
  accessor trafficApiKey: string = '';

  // Guidance properties
  @property({ type: Boolean })
  accessor enableGuidance: boolean = false;

  @property({ type: Object })
  accessor voiceConfig: Partial<IVoiceConfig> = {};

  // ─── State ──────────────────────────────────────────────────────────────────

  @state()
  private accessor map: maplibregl.Map | null = null;

  @state()
  private accessor draw: TerraDraw | null = null;

  @state()
  private accessor isMapReady: boolean = false;

  @state()
  private accessor isNavigationOpen: boolean = true;

  @state()
  private accessor isDrawPanelOpen: boolean = true;

  // Controllers
  private searchController: SearchController | null = null;
  private navigationController: NavigationController | null = null;
  private trafficController: TrafficController | null = null;
  private guidanceController: NavigationGuideController | null = null;
  private mockGPSSimulator: MockGPSSimulator | null = null;

  // ─── Styles ─────────────────────────────────────────────────────────────────

  public static styles = [
    cssManager.defaultStyles,
    geoComponentStyles,
    mapContainerStyles,
    toolbarStyles,
    searchStyles,
    navigationStyles,
    trafficStyles,
    headerToolbarStyles,
    guidanceStyles,
    maplibreMarkerStyles,
    css`
      :host {
        display: block;
        width: 100%;
        height: 400px;
      }

      .maplibregl-map {
        width: 100%;
        height: 100%;
        font-family: inherit;
      }

      .maplibregl-ctrl-attrib {
        font-size: 11px;
        background: ${cssManager.bdTheme('rgba(255, 255, 255, 0.8)', 'rgba(0, 0, 0, 0.5)')};
        color: ${cssManager.bdTheme('rgba(0, 0, 0, 0.7)', 'rgba(255, 255, 255, 0.8)')};
        padding: 2px 6px;
        border-radius: 4px;
      }

      .maplibregl-ctrl-attrib a {
        color: ${cssManager.bdTheme('rgba(0, 0, 0, 0.7)', 'rgba(255, 255, 255, 0.8)')};
      }

      .feature-count {
        padding: 6px 12px;
        background: ${cssManager.bdTheme('rgba(255, 255, 255, 0.95)', 'rgba(30, 30, 30, 0.9)')};
        border: 1px solid ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')};
        border-radius: 6px;
        backdrop-filter: blur(8px);
        -webkit-backdrop-filter: blur(8px);
        font-size: 12px;
        color: ${cssManager.bdTheme('rgba(0, 0, 0, 0.6)', 'rgba(255, 255, 255, 0.7)')};
      }
    `,
  ];

  // Map theme subscription for cleanup
  private mapThemeSubscription: { unsubscribe: () => void } | null = null;

  // ─── Lifecycle ──────────────────────────────────────────────────────────────

  public async firstUpdated() {
    this.initializeControllers();
    await this.initializeMap();

    // Subscribe to theme changes to update map style
    this.subscribeToThemeChanges();
  }

  public async disconnectedCallback() {
    await super.disconnectedCallback();
    if (this.mapThemeSubscription) {
      this.mapThemeSubscription.unsubscribe();
      this.mapThemeSubscription = null;
    }
    this.cleanup();
  }

  /**
   * Subscribe to theme changes via domtools
   */
  private async subscribeToThemeChanges(): Promise<void> {
    const domtoolsInstance = await domtools.DomTools.setupDomTools();
    this.mapThemeSubscription = domtoolsInstance.themeManager.themeObservable.subscribe(
      (_goBright: boolean) => {
        this.updateMapStyleForTheme();
      }
    );
  }

  public updated(changedProperties: Map<string, unknown>) {
    super.updated(changedProperties);
    if (changedProperties.has('dragToDraw') && this.draw && this.map) {
      // Reinitialize terra-draw with new settings
      const currentFeatures = this.draw.getSnapshot();
      this.draw.stop();
      this.draw = null;
      this.initializeTerraDraw();
      // Restore features
      if (currentFeatures.length > 0) {
        for (const feature of currentFeatures) {
          this.draw?.addFeatures([feature as GeoJSON.Feature<GeoJSON.Geometry, { mode: string }>]);
        }
      }
    }
    if (changedProperties.has('projection') && this.map && this.isMapReady) {
      this.map.setProjection({ type: this.projection });
    }
    if (changedProperties.has('navigationMode') && this.navigationController) {
      this.navigationController.navigationMode = this.navigationMode;
    }
    // Traffic property changes
    if (changedProperties.has('showTraffic') && this.trafficController) {
      if (this.showTraffic) {
        this.trafficController.enable();
      } else {
        this.trafficController.disable();
      }
    }
    if (changedProperties.has('trafficProvider') && this.trafficController && this.trafficProvider) {
      this.trafficController.setProvider(this.trafficProvider);
    }
    if (changedProperties.has('trafficApiKey') && this.trafficController && this.trafficApiKey) {
      // Auto-configure HERE provider if API key is provided
      const hereProvider = new HereTrafficProvider();
      hereProvider.configure({ apiKey: this.trafficApiKey });
      this.trafficController.setProvider(hereProvider);
    }
  }

  // ─── Controller Initialization ──────────────────────────────────────────────

  private initializeControllers(): void {
    // Initialize search controller
    this.searchController = new SearchController(
      { placeholder: this.searchPlaceholder },
      {
        onResultSelected: (result, coordinates, zoom) => {
          this.flyTo(coordinates, zoom);
          this.dispatchEvent(new CustomEvent<IAddressSelectedEvent>('address-selected', {
            detail: {
              address: result.display_name,
              coordinates,
              boundingBox: [
                parseFloat(result.boundingbox[0]),
                parseFloat(result.boundingbox[1]),
                parseFloat(result.boundingbox[2]),
                parseFloat(result.boundingbox[3]),
              ],
              placeId: String(result.place_id),
              type: result.type,
            },
            bubbles: true,
            composed: true,
          }));
        },
        onRequestUpdate: () => this.requestUpdate(),
      }
    );

    // Initialize navigation controller
    this.navigationController = new NavigationController({
      onRouteCalculated: (event) => {
        this.dispatchEvent(new CustomEvent<IRouteCalculatedEvent>('route-calculated', {
          detail: event,
          bubbles: true,
          composed: true,
        }));
      },
      onRequestUpdate: () => this.requestUpdate(),
      getMap: () => this.map,
      // Connect traffic controller for traffic-aware routing
      getTrafficRoute: async (start, end, mode) => {
        if (this.showTraffic && this.trafficController?.supportsTrafficRouting()) {
          return this.trafficController.fetchRouteWithTraffic(start, end, mode);
        }
        return null;
      },
      // Traffic toggle callbacks
      onTrafficToggle: (enabled) => {
        if (enabled) {
          this.trafficController?.enable();
        } else {
          this.trafficController?.disable();
        }
        this.showTraffic = enabled;
      },
      getTrafficEnabled: () => this.showTraffic,
      // Pass guidance state for turn-by-turn synchronization
      getGuidanceState: () => this.guidanceController?.state ?? null,
    });
    this.navigationController.navigationMode = this.navigationMode;

    // Initialize traffic controller
    this.trafficController = new TrafficController({
      onRequestUpdate: () => this.requestUpdate(),
      getMap: () => this.map,
    });

    // Configure traffic provider if API key or provider is set
    if (this.trafficProvider) {
      this.trafficController.setProvider(this.trafficProvider);
    } else if (this.trafficApiKey) {
      const hereProvider = new HereTrafficProvider();
      hereProvider.configure({ apiKey: this.trafficApiKey });
      this.trafficController.setProvider(hereProvider);
    }

    // Initialize guidance controller
    this.guidanceController = new NavigationGuideController(
      {
        onGuidanceEvent: (event) => {
          this.dispatchEvent(new CustomEvent<IGuidanceEvent>('guidance-event', {
            detail: event,
            bubbles: true,
            composed: true,
          }));

          // Auto-scroll turn-by-turn list when step changes
          if (event.type === 'step-change' && this.navigationController) {
            this.navigationController.scrollToCurrentStep(event.stepIndex);
          }
        },
        onRequestUpdate: () => this.requestUpdate(),
        getMap: () => this.map,
      },
      this.voiceConfig
    );
  }

  // ─── Map Initialization ─────────────────────────────────────────────────────

  private async initializeMap() {
    const container = this.shadowRoot?.querySelector('.map-wrapper') as HTMLElement;
    if (!container) return;

    // Ensure MapLibre CSS is loaded in the document
    this.ensureMaplibreCssLoaded();

    const style = this.getMapStyle();

    this.map = new maplibregl.Map({
      container,
      style,
      center: this.center,
      zoom: this.zoom,
      attributionControl: {},
    });

    this.map.on('load', () => {
      this.isMapReady = true;

      // Set projection (globe or mercator)
      this.map!.setProjection({ type: this.projection });

      this.initializeTerraDraw();

      // Enable traffic if configured
      if (this.showTraffic && this.trafficController) {
        this.trafficController.enable();
      }

      this.dispatchEvent(new CustomEvent('map-ready', { detail: { map: this.map } }));
    });

    // Forward map events
    this.map.on('moveend', () => {
      this.dispatchEvent(new CustomEvent('map-move', {
        detail: {
          center: this.map?.getCenter().toArray(),
          zoom: this.map?.getZoom(),
        },
      }));

      // Refresh traffic data when map moves
      if (this.trafficController) {
        this.trafficController.handleMapMoveEnd();
      }
    });

    // Handle clicks for navigation point selection
    this.map.on('click', (e: maplibregl.MapMouseEvent) => {
      if (this.showNavigation && this.navigationController?.navClickMode) {
        this.navigationController.handleMapClickForNavigation(e);
      }
    });
  }

  private getMapStyle(): maplibregl.StyleSpecification | string {
    if (this.mapStyle === 'osm') {
      // Check current theme and use appropriate tiles
      const isDarkTheme = !cssManager.goBright;

      if (isDarkTheme) {
        // CartoDB Dark Matter GL vector style - high quality dark theme
        return 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json';
      } else {
        // CartoDB Voyager GL vector style - high quality light theme
        return 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json';
      }
    }
    return this.mapStyle;
  }

  /**
   * Update the map style when theme changes
   */
  private updateMapStyleForTheme(): void {
    if (!this.map || !this.isMapReady || this.mapStyle !== 'osm') return;

    const newStyle = this.getMapStyle();
    this.map.setStyle(newStyle as maplibregl.StyleSpecification);
  }

  // ─── Terra Draw Initialization ──────────────────────────────────────────────

  private initializeTerraDraw() {
    if (!this.map || this.draw) return;

    const adapter = new TerraDrawMapLibreGLAdapter({
      map: this.map,
    });

    this.draw = new TerraDraw({
      adapter,
      modes: [
        new TerraDrawPointMode(),
        new TerraDrawLineStringMode(),
        new TerraDrawPolygonMode(),
        new TerraDrawRectangleMode({
          drawInteraction: this.dragToDraw ? 'click-drag' : 'click-move',
        }),
        new TerraDrawCircleMode({
          drawInteraction: this.dragToDraw ? 'click-drag' : 'click-move',
        }),
        new TerraDrawFreehandMode(),
        new TerraDrawSelectMode({
          flags: {
            polygon: {
              feature: {
                draggable: true,
                coordinates: {
                  midpoints: true,
                  draggable: true,
                  deletable: true,
                },
              },
            },
            rectangle: {
              feature: {
                draggable: true,
                coordinates: {
                  draggable: true,
                  deletable: true,
                },
              },
            },
            point: {
              feature: {
                draggable: true,
              },
            },
            linestring: {
              feature: {
                draggable: true,
                coordinates: {
                  midpoints: true,
                  draggable: true,
                  deletable: true,
                },
              },
            },
            circle: {
              feature: {
                draggable: true,
              },
            },
            freehand: {
              feature: {
                draggable: true,
              },
            },
          },
        }),
        // Static mode for pan/zoom only (no drawing)
        new TerraDrawRenderMode({
          modeName: 'static',
        }),
      ],
    });

    this.draw.start();

    // Register event handlers
    this.draw.on('change', (ids: (string | number)[], type: string) => {
      const features = this.draw?.getSnapshot() || [];
      this.dispatchEvent(new CustomEvent('draw-change', {
        detail: { ids, type, features } as IDrawChangeEvent,
      }));
      this.requestUpdate();
    });

    this.draw.on('finish', (id: string | number, context: { action: string; mode: string }) => {
      const features = this.draw?.getSnapshot() || [];
      this.dispatchEvent(new CustomEvent('draw-finish', {
        detail: { id: String(id), context, features } as IDrawFinishEvent,
      }));
    });

    // Load initial geoJson if provided
    if (this.geoJson.features.length > 0) {
      this.loadGeoJson(this.geoJson);
    }

    // Set initial mode (always set a mode, including 'static')
    this.draw.setMode(this.activeTool);

    // Set initial drag state based on active tool
    // Drawing modes need drag disabled; static/select need it enabled
    const isDrawingMode = !['static', 'select'].includes(this.activeTool);
    if (isDrawingMode) {
      this.map.dragPan.disable();
      this.map.dragRotate.disable();
    }
  }

  // ─── Public Methods ─────────────────────────────────────────────────────────

  /**
   * Get the current snapshot of all drawn features
   */
  public getFeatures(): GeoJSON.Feature[] {
    return this.draw?.getSnapshot() || [];
  }

  /**
   * Get features as a GeoJSON FeatureCollection
   */
  public getGeoJson(): GeoJSON.FeatureCollection {
    return {
      type: 'FeatureCollection',
      features: this.getFeatures(),
    };
  }

  /**
   * Load GeoJSON features into the map
   */
  public loadGeoJson(geojson: GeoJSON.FeatureCollection) {
    if (!this.draw) return;

    // Clear existing features first
    this.clearAllFeatures();

    // Add features from the GeoJSON
    for (const feature of geojson.features) {
      if (feature.geometry && feature.properties) {
        this.draw.addFeatures([feature as GeoJSON.Feature<GeoJSON.Geometry, { mode: string }>]);
      }
    }
  }

  /**
   * Clear all drawn features
   */
  public clearAllFeatures() {
    if (!this.draw) return;
    const features = this.draw.getSnapshot();
    const ids = features.map((f) => f.id).filter((id): id is string | number => id !== undefined);
    if (ids.length > 0) {
      this.draw.removeFeatures(ids);
    }
  }

  /**
   * Set the active drawing tool
   */
  public setTool(tool: TDrawTool) {
    this.activeTool = tool;
    if (this.draw && this.map) {
      this.draw.setMode(tool);

      // Manually control map dragging based on mode
      // Drawing modes need drag disabled; static/select need it enabled
      const isDrawingMode = !['static', 'select'].includes(tool);
      if (isDrawingMode) {
        this.map.dragPan.disable();
        this.map.dragRotate.disable();
      } else {
        this.map.dragPan.enable();
        this.map.dragRotate.enable();
      }
    }
  }

  /**
   * Get the underlying MapLibre map instance
   */
  public getMap(): maplibregl.Map | null {
    return this.map;
  }

  /**
   * Get the Terra Draw instance
   */
  public getTerraDraw(): TerraDraw | null {
    return this.draw;
  }

  /**
   * Fly to a specific location
   */
  public flyTo(center: [number, number], zoom?: number) {
    this.map?.flyTo({
      center,
      zoom: zoom ?? this.map.getZoom(),
      duration: 1500,
    });
  }

  /**
   * Set the map projection
   */
  public setProjection(projection: 'mercator' | 'globe') {
    this.projection = projection;
  }

  /**
   * Fit the map to show all drawn features
   */
  public fitToFeatures(padding = 50) {
    const features = this.getFeatures();
    if (features.length === 0 || !this.map) return;

    const bounds = new maplibregl.LngLatBounds();

    for (const feature of features) {
      const geometry = feature.geometry;
      if (!geometry) continue;

      if (geometry.type === 'Point') {
        bounds.extend(geometry.coordinates as [number, number]);
      } else if (geometry.type === 'LineString' || geometry.type === 'MultiPoint') {
        for (const coord of geometry.coordinates) {
          bounds.extend(coord as [number, number]);
        }
      } else if (geometry.type === 'Polygon' || geometry.type === 'MultiLineString') {
        for (const ring of geometry.coordinates) {
          for (const coord of ring) {
            bounds.extend(coord as [number, number]);
          }
        }
      }
    }

    if (!bounds.isEmpty()) {
      this.map.fitBounds(bounds, { padding });
    }
  }

  // ─── Navigation Public Methods (delegated to controller) ────────────────────

  /**
   * Calculate and display route
   */
  public async calculateRoute(): Promise<void> {
    await this.navigationController?.calculateRoute();
  }

  /**
   * Set navigation start point
   */
  public setNavigationStart(coords: [number, number], address?: string): void {
    this.navigationController?.setNavigationStart(coords, address);
  }

  /**
   * Set navigation end point
   */
  public setNavigationEnd(coords: [number, number], address?: string): void {
    this.navigationController?.setNavigationEnd(coords, address);
  }

  /**
   * Clear all navigation state
   */
  public clearNavigation(): void {
    this.navigationController?.clearNavigation();
  }

  /**
   * Get current navigation state
   */
  public getNavigationState(): INavigationState | null {
    return this.navigationController?.navigationState ?? null;
  }

  // ─── Traffic Public Methods ────────────────────────────────────────────────

  /**
   * Enable traffic visualization
   */
  public enableTraffic(): void {
    this.showTraffic = true;
    this.trafficController?.enable();
  }

  /**
   * Disable traffic visualization
   */
  public disableTraffic(): void {
    this.showTraffic = false;
    this.trafficController?.disable();
  }

  /**
   * Toggle traffic visualization
   */
  public toggleTraffic(): void {
    this.showTraffic = !this.showTraffic;
    this.trafficController?.toggle();
  }

  /**
   * Refresh traffic data
   */
  public async refreshTraffic(): Promise<void> {
    await this.trafficController?.refresh();
  }

  /**
   * Set traffic provider
   */
  public setTrafficProvider(provider: ITrafficProvider): void {
    this.trafficProvider = provider;
    this.trafficController?.setProvider(provider);
  }

  /**
   * Check if traffic-aware routing is available
   */
  public supportsTrafficRouting(): boolean {
    return this.trafficController?.supportsTrafficRouting() ?? false;
  }

  /**
   * Get traffic controller for advanced usage
   */
  public getTrafficController(): TrafficController | null {
    return this.trafficController;
  }

  // ─── Guidance Public Methods ────────────────────────────────────────────────

  /**
   * Set current GPS position for navigation guidance
   * @param coords - [lng, lat] coordinates
   * @param heading - Optional heading in degrees (0 = North)
   * @param speed - Optional speed in meters/second
   */
  public setPosition(coords: [number, number], heading?: number, speed?: number): void {
    this.guidanceController?.setPosition(coords[0], coords[1], heading, speed);
  }

  /**
   * Start voice-guided navigation for the current route
   */
  public startGuidance(): void {
    const route = this.navigationController?.navigationState?.route;
    if (route && this.guidanceController) {
      this.guidanceController.startGuidance(route);
    } else {
      console.warn('[dees-geo-map] Cannot start guidance: no route calculated');
    }
  }

  /**
   * Stop voice-guided navigation
   */
  public stopGuidance(): void {
    this.guidanceController?.stopGuidance();
    this.mockGPSSimulator?.stop();
  }

  /**
   * Enable or disable voice guidance
   */
  public setVoiceEnabled(enabled: boolean): void {
    this.guidanceController?.setVoiceEnabled(enabled);
  }

  /**
   * Check if voice guidance is enabled
   */
  public isVoiceEnabled(): boolean {
    return this.guidanceController?.isVoiceEnabled() ?? false;
  }

  /**
   * Get current guidance state
   */
  public getGuidanceState(): INavigationGuideState | null {
    return this.guidanceController?.state ?? null;
  }

  /**
   * Check if actively navigating
   */
  public isNavigating(): boolean {
    return this.guidanceController?.isNavigating() ?? false;
  }

  /**
   * Create a mock GPS simulator for testing/demo
   * The simulator emits position updates along the current route
   */
  public createMockGPSSimulator(config?: Partial<IMockGPSConfig>): MockGPSSimulator {
    // Clean up existing simulator
    if (this.mockGPSSimulator) {
      this.mockGPSSimulator.cleanup();
    }

    this.mockGPSSimulator = new MockGPSSimulator(
      {
        onPositionUpdate: (position: IGPSPosition) => {
          if (this.guidanceController) {
            this.guidanceController.updatePosition(position);
          }
        },
        onSimulationStart: () => {
          console.log('[MockGPSSimulator] Simulation started');
        },
        onSimulationPause: () => {
          console.log('[MockGPSSimulator] Simulation paused');
        },
        onSimulationStop: () => {
          console.log('[MockGPSSimulator] Simulation stopped');
        },
        onSimulationComplete: () => {
          console.log('[MockGPSSimulator] Simulation complete');
        },
      },
      config
    );

    // Set the route if available
    const route = this.navigationController?.navigationState?.route;
    if (route) {
      this.mockGPSSimulator.setRoute(route);
    }

    return this.mockGPSSimulator;
  }

  /**
   * Get the mock GPS simulator (if created)
   */
  public getMockGPSSimulator(): MockGPSSimulator | null {
    return this.mockGPSSimulator;
  }

  /**
   * Get the guidance controller for advanced usage
   */
  public getGuidanceController(): NavigationGuideController | null {
    return this.guidanceController;
  }

  // ─── Navigation Camera Control Methods ─────────────────────────────────────

  /**
   * Enable or disable camera following the GPS position during navigation
   * @param enabled - Whether the camera should follow the position
   */
  public setNavigationFollowPosition(enabled: boolean): void {
    this.guidanceController?.setFollowPosition(enabled);
  }

  /**
   * Check if camera is following position during navigation
   */
  public isNavigationFollowingPosition(): boolean {
    return this.guidanceController?.isFollowingPosition() ?? true;
  }

  /**
   * Enable or disable camera rotating with heading during navigation
   * @param enabled - Whether the camera should rotate with heading
   */
  public setNavigationFollowBearing(enabled: boolean): void {
    this.guidanceController?.setFollowBearing(enabled);
  }

  /**
   * Check if camera is following bearing during navigation
   */
  public isNavigationFollowingBearing(): boolean {
    return this.guidanceController?.isFollowingBearing() ?? true;
  }

  /**
   * Set the navigation camera pitch (3D tilt angle)
   * @param pitch - Angle in degrees (0 = flat, 60 = tilted for 3D view)
   */
  public setNavigationPitch(pitch: number): void {
    this.guidanceController?.setPitch(pitch);
  }

  /**
   * Get current navigation pitch setting
   */
  public getNavigationPitch(): number {
    return this.guidanceController?.getPitch() ?? 60;
  }

  /**
   * Set the navigation zoom level
   * @param zoom - Zoom level (typically 15-19 for street-level navigation)
   */
  public setNavigationZoom(zoom: number): void {
    this.guidanceController?.setZoom(zoom);
  }

  /**
   * Get current navigation zoom setting
   */
  public getNavigationZoom(): number {
    return this.guidanceController?.getZoom() ?? 17;
  }

  /**
   * Get the full navigation camera configuration
   */
  public getNavigationCameraConfig(): INavigationCameraConfig | null {
    return this.guidanceController?.getCameraConfig() ?? null;
  }

  /**
   * Set navigation camera configuration
   * @param config - Partial camera configuration to apply
   */
  public setNavigationCameraConfig(config: Partial<INavigationCameraConfig>): void {
    this.guidanceController?.setCameraConfig(config);
  }

  // ─── Private Methods ────────────────────────────────────────────────────────

  private ensureMaplibreCssLoaded() {
    const cssId = 'maplibre-gl-css';
    if (!document.getElementById(cssId)) {
      const link = document.createElement('link');
      link.id = cssId;
      link.rel = 'stylesheet';
      link.href = 'https://unpkg.com/maplibre-gl@5.1.1/dist/maplibre-gl.css';
      document.head.appendChild(link);
    }
  }

  private cleanup() {
    if (this.draw) {
      this.draw.stop();
      this.draw = null;
    }
    // Clean up controllers
    this.navigationController?.cleanup();
    this.trafficController?.cleanup();
    this.guidanceController?.cleanup();
    this.mockGPSSimulator?.cleanup();

    if (this.map) {
      this.map.remove();
      this.map = null;
    }
  }

  private handleToolClick(tool: TDrawTool) {
    // If clicking the same tool again, deselect it (switch to static/pan mode)
    if (this.activeTool === tool) {
      this.setTool('static');
    } else {
      this.setTool(tool);
    }
    this.requestUpdate();
  }

  private handleClearClick() {
    this.clearAllFeatures();
  }

  private handleZoomIn() {
    this.map?.zoomIn();
  }

  private handleZoomOut() {
    this.map?.zoomOut();
  }

  private handleMapContextMenu(e: MouseEvent) {
    e.preventDefault();
    const hasTrafficProvider = this.trafficController?.provider?.isConfigured ?? false;

    DeesContextmenu.openContextMenuWithOptions(e, [
      {
        name: this.dragToDraw ? '✓ Drag to Draw' : 'Drag to Draw',
        iconName: 'lucide:move',
        action: async () => {
          this.dragToDraw = !this.dragToDraw;
        },
      },
      {
        name: this.projection === 'globe' ? '✓ Globe View' : 'Globe View',
        iconName: 'lucide:globe',
        action: async () => {
          this.projection = this.projection === 'globe' ? 'mercator' : 'globe';
        },
      },
      { divider: true },
      {
        name: this.showTraffic ? '✓ Show Traffic' : 'Show Traffic',
        iconName: 'lucide:traffic-cone',
        action: async () => {
          if (hasTrafficProvider) {
            this.toggleTraffic();
          } else {
            console.warn('[dees-geo-map] No traffic provider configured. Set trafficApiKey or trafficProvider property.');
          }
        },
        disabled: !hasTrafficProvider,
      },
      { divider: true },
      {
        name: 'Clear All Features',
        iconName: 'lucide:trash2',
        action: async () => this.clearAllFeatures(),
      },
      {
        name: 'Fit to Features',
        iconName: 'lucide:maximize',
        action: async () => this.fitToFeatures(),
      },
    ]);
  }

  private toggleNavigation(): void {
    this.isNavigationOpen = !this.isNavigationOpen;
  }

  private toggleDrawPanel(): void {
    this.isDrawPanelOpen = !this.isDrawPanelOpen;
  }

  // ─── Render ─────────────────────────────────────────────────────────────────

  public render(): TemplateResult {
    const featureCount = this.draw?.getSnapshot().length || 0;
    const hasTrafficProvider = this.trafficController?.provider?.isConfigured ?? false;
    const showTrafficControls = Boolean(hasTrafficProvider || this.trafficApiKey || this.trafficProvider);

    // Calculate panel widths for CSS Grid
    const leftPanelWidth = this.showNavigation && this.isNavigationOpen ? '300px' : '0px';
    const rightPanelWidth = this.showToolbar && this.isDrawPanelOpen ? '180px' : '0px';

    return html`
      <div class="geo-component" style="--left-panel-width: ${leftPanelWidth}; --right-panel-width: ${rightPanelWidth};">
        <!-- Header Toolbar Above Map -->
        ${this.renderHeaderToolbar(showTrafficControls)}

        <!-- Left Sidebar: Navigation Panel -->
        <div class="left-sidebar ${!this.showNavigation || !this.isNavigationOpen ? 'collapsed' : ''}">
          ${this.showNavigation && this.isNavigationOpen && this.navigationController
            ? this.navigationController.render()
            : ''}
        </div>

        <!-- Map Container -->
        <div class="map-container" @contextmenu=${(e: MouseEvent) => this.handleMapContextMenu(e)}>
          <div class="map-wrapper"></div>

          <div class="map-overlay">
            <!-- Top Left: Empty (navigation moved to sidebar) -->
            <div class="overlay-top-left"></div>

            <!-- Top Right: Empty (controls in header) -->
            <div class="overlay-top-right"></div>

            <!-- Bottom Left: Traffic Legend + Feature Count -->
            <div class="overlay-bottom-left">
              ${this.showTraffic && this.trafficController ? this.trafficController.renderLegend() : ''}
              ${featureCount > 0 ? html`
                <div class="feature-count">
                  ${featureCount} feature${featureCount !== 1 ? 's' : ''}
                </div>
              ` : ''}
            </div>

            <!-- Bottom Right: Empty (zoom in header) -->
            <div class="overlay-bottom-right"></div>
          </div>

          <!-- Guidance Panel (shown during active navigation) -->
          ${this.guidanceController?.isNavigating() ? this.guidanceController.render() : ''}
        </div>

        <!-- Right Sidebar: Draw Panel -->
        <div class="right-sidebar ${!this.showToolbar || !this.isDrawPanelOpen ? 'collapsed' : ''}">
          ${this.showToolbar && this.isDrawPanelOpen ? this.renderDrawPanel() : ''}
        </div>
      </div>
    `;
  }

  private renderHeaderToolbar(showTrafficControls: boolean): TemplateResult {
    return html`
      <div class="header-toolbar">
        <!-- Left: Navigation Panel Toggle -->
        <div class="toolbar-left">
          ${this.showNavigation ? html`
            <button
              class="tool-button ${this.isNavigationOpen ? 'active' : ''}"
              title="Toggle Navigation Panel"
              @click=${() => this.toggleNavigation()}
            >
              ${renderIcon('navigation')}
            </button>
          ` : ''}
        </div>

        <!-- Center: Search Bar -->
        <div class="toolbar-center">
          ${this.showSearch && this.searchController
            ? this.searchController.render()
            : ''}
        </div>

        <!-- Right: Draw Panel Toggle + Traffic Toggle + Zoom Controls -->
        <div class="toolbar-right">
          ${this.showToolbar ? html`
            <button
              class="tool-button ${this.isDrawPanelOpen ? 'active' : ''}"
              title="Toggle Draw Tools"
              @click=${() => this.toggleDrawPanel()}
            >
              ${renderIcon('polygon')}
            </button>
          ` : ''}
          ${this.trafficController?.render()}
          <div class="toolbar-divider"></div>
          <button
            class="tool-button"
            title="Zoom in"
            @click=${this.handleZoomIn}
          >
            ${renderIcon('plus')}
          </button>
          <button
            class="tool-button"
            title="Zoom out"
            @click=${this.handleZoomOut}
          >
            ${renderIcon('minus')}
          </button>
        </div>
      </div>
    `;
  }

  private renderDrawPanel(): TemplateResult {
    const tools: { id: TDrawTool; icon: string; label: string }[] = [
      { id: 'point', icon: 'point', label: 'Point' },
      { id: 'linestring', icon: 'line', label: 'Line' },
      { id: 'polygon', icon: 'polygon', label: 'Polygon' },
      { id: 'rectangle', icon: 'rectangle', label: 'Rectangle' },
      { id: 'circle', icon: 'circle', label: 'Circle' },
      { id: 'freehand', icon: 'freehand', label: 'Freehand' },
    ];

    return html`
      <div class="draw-panel">
        <div class="draw-panel-header">
          <div class="draw-panel-header-icon">
            ${renderIcon('polygon')}
          </div>
          <div class="draw-panel-header-title">Draw Tools</div>
        </div>

        <div class="draw-tools-grid">
          ${tools.map(tool => html`
            <button
              class="draw-tool-button ${this.activeTool === tool.id ? 'active' : ''}"
              title="${tool.label}"
              @click=${() => this.handleToolClick(tool.id)}
              ?disabled=${!this.isMapReady}
            >
              ${renderIcon(tool.icon)}
              <span class="draw-tool-button-label">${tool.label}</span>
            </button>
          `)}
        </div>

        <div class="draw-panel-divider"></div>

        <div class="draw-panel-actions">
          <button
            class="draw-action-button ${this.activeTool === 'select' ? 'active' : ''}"
            title="Select & Edit"
            @click=${() => this.handleToolClick('select')}
            ?disabled=${!this.isMapReady}
          >
            ${renderIcon('select')}
            <span>Select & Edit</span>
          </button>
          <button
            class="draw-action-button danger"
            title="Clear All"
            @click=${this.handleClearClick}
            ?disabled=${!this.isMapReady}
          >
            ${renderIcon('trash')}
            <span>Clear All</span>
          </button>
        </div>
      </div>
    `;
  }

  }
