import { html, type TemplateResult } from '@design.estate/dees-element';
import maplibregl from 'maplibre-gl';
import { renderIcon } from './geo-map.icons.js';
import type {
  ITrafficProvider,
  ITrafficFlowData,
  ITrafficFlowSegment,
  ITrafficAwareRoute,
} from './geo-map.traffic.providers.js';
import type { TNavigationMode } from './geo-map.navigation.js';

// ─── Traffic Controller Types ────────────────────────────────────────────────

export interface ITrafficControllerCallbacks {
  /** Called when traffic state changes, to trigger re-render */
  onRequestUpdate: () => void;
  /** Returns the MapLibre map instance */
  getMap: () => maplibregl.Map | null;
}

export interface ITrafficState {
  /** Whether traffic layer is enabled */
  isEnabled: boolean;
  /** Whether data is being loaded */
  isLoading: boolean;
  /** Last update timestamp */
  lastUpdate: Date | null;
  /** Error message if fetch failed */
  error: string | null;
  /** Number of traffic segments displayed */
  segmentCount: number;
}

// ─── Traffic Controller ──────────────────────────────────────────────────────

/**
 * Controller for traffic visualization and traffic-aware routing
 *
 * Manages:
 * - Traffic flow layer on the map
 * - Auto-refresh of traffic data
 * - Traffic-aware routing through providers
 */
export class TrafficController {
  // ─── State ─────────────────────────────────────────────────────────────────

  /** Traffic display state */
  public trafficState: ITrafficState = {
    isEnabled: false,
    isLoading: false,
    lastUpdate: null,
    error: null,
    segmentCount: 0,
  };

  /** Currently active traffic provider */
  public provider: ITrafficProvider | null = null;

  // ─── Configuration ─────────────────────────────────────────────────────────

  /** Auto-refresh interval in milliseconds (default: 60 seconds) */
  public updateInterval: number = 60000;

  /** Whether to automatically refresh traffic data */
  public autoRefresh: boolean = true;

  /** Minimum zoom level to show traffic (higher zoom = more detail but more API calls) */
  public minZoomForTraffic: number = 10;

  // ─── Private ───────────────────────────────────────────────────────────────

  private callbacks: ITrafficControllerCallbacks;
  private refreshTimer: ReturnType<typeof setInterval> | null = null;
  private currentTrafficData: ITrafficFlowData | null = null;

  // MapLibre source/layer IDs
  private static readonly SOURCE_ID = 'traffic-flow-source';
  private static readonly LAYER_ID = 'traffic-flow-layer';
  private static readonly LAYER_OUTLINE_ID = 'traffic-flow-outline-layer';

  constructor(callbacks: ITrafficControllerCallbacks) {
    this.callbacks = callbacks;
  }

  // ─── Provider Management ───────────────────────────────────────────────────

  /**
   * Set the traffic data provider
   */
  public setProvider(provider: ITrafficProvider): void {
    this.provider = provider;

    // If traffic is enabled, refresh data with new provider
    if (this.trafficState.isEnabled) {
      this.refresh();
    }
  }

  // ─── Enable/Disable ────────────────────────────────────────────────────────

  /**
   * Enable traffic visualization
   */
  public enable(): void {
    if (this.trafficState.isEnabled) return;

    this.trafficState = {
      ...this.trafficState,
      isEnabled: true,
      error: null,
    };

    // Start auto-refresh if configured
    if (this.autoRefresh) {
      this.startAutoRefresh();
    }

    // Initial data fetch
    this.refresh();

    this.callbacks.onRequestUpdate();
  }

  /**
   * Disable traffic visualization
   */
  public disable(): void {
    if (!this.trafficState.isEnabled) return;

    this.stopAutoRefresh();
    this.removeTrafficLayer();

    this.trafficState = {
      isEnabled: false,
      isLoading: false,
      lastUpdate: null,
      error: null,
      segmentCount: 0,
    };

    this.currentTrafficData = null;
    this.callbacks.onRequestUpdate();
  }

  /**
   * Toggle traffic visualization
   */
  public toggle(): void {
    if (this.trafficState.isEnabled) {
      this.disable();
    } else {
      this.enable();
    }
  }

  // ─── Data Fetching ─────────────────────────────────────────────────────────

  /**
   * Refresh traffic data from the provider
   */
  public async refresh(): Promise<void> {
    if (!this.trafficState.isEnabled) return;

    const map = this.callbacks.getMap();
    if (!map) return;

    // Check zoom level
    const currentZoom = map.getZoom();
    if (currentZoom < this.minZoomForTraffic) {
      // Remove traffic layer if zoom is too low
      this.removeTrafficLayer();
      this.trafficState = {
        ...this.trafficState,
        error: `Zoom in to see traffic (current: ${Math.round(currentZoom)}, required: ${this.minZoomForTraffic})`,
        segmentCount: 0,
      };
      this.callbacks.onRequestUpdate();
      return;
    }

    if (!this.provider) {
      this.trafficState = {
        ...this.trafficState,
        error: 'No traffic provider configured',
      };
      this.callbacks.onRequestUpdate();
      return;
    }

    if (!this.provider.isConfigured) {
      this.trafficState = {
        ...this.trafficState,
        error: `${this.provider.name} is not configured (missing API key?)`,
      };
      this.callbacks.onRequestUpdate();
      return;
    }

    // Get current map bounds
    const mapBounds = map.getBounds();
    const bounds: [number, number, number, number] = [
      mapBounds.getWest(),
      mapBounds.getSouth(),
      mapBounds.getEast(),
      mapBounds.getNorth(),
    ];

    this.trafficState = {
      ...this.trafficState,
      isLoading: true,
      error: null,
    };
    this.callbacks.onRequestUpdate();

    try {
      const data = await this.provider.fetchTrafficFlow(bounds);

      if (data) {
        this.currentTrafficData = data;
        this.renderTrafficLayer(data);

        this.trafficState = {
          ...this.trafficState,
          isLoading: false,
          lastUpdate: data.timestamp,
          segmentCount: data.segments.length,
        };
      } else {
        this.trafficState = {
          ...this.trafficState,
          isLoading: false,
          error: 'No traffic data available for this area',
        };
      }
    } catch (error) {
      this.trafficState = {
        ...this.trafficState,
        isLoading: false,
        error: error instanceof Error ? error.message : 'Failed to fetch traffic data',
      };
    }

    this.callbacks.onRequestUpdate();
  }

  // ─── Traffic-Aware Routing ─────────────────────────────────────────────────

  /**
   * Fetch a route with traffic-aware duration
   * Returns null if the provider doesn't support traffic-aware routing
   */
  public async fetchRouteWithTraffic(
    start: [number, number],
    end: [number, number],
    mode: TNavigationMode
  ): Promise<ITrafficAwareRoute | null> {
    if (!this.provider || !this.provider.fetchRouteWithTraffic) {
      return null;
    }

    if (!this.provider.isConfigured) {
      console.warn('[TrafficController] Provider not configured for traffic routing');
      return null;
    }

    try {
      return await this.provider.fetchRouteWithTraffic(start, end, mode);
    } catch (error) {
      console.error('[TrafficController] Traffic routing error:', error);
      return null;
    }
  }

  /**
   * Check if the current provider supports traffic-aware routing
   */
  public supportsTrafficRouting(): boolean {
    return !!(this.provider?.fetchRouteWithTraffic && this.provider.isConfigured);
  }

  // ─── Layer Management ──────────────────────────────────────────────────────

  /**
   * Render traffic data as a MapLibre layer
   */
  private renderTrafficLayer(data: ITrafficFlowData): void {
    const map = this.callbacks.getMap();
    if (!map || data.segments.length === 0) return;

    // Convert segments to GeoJSON FeatureCollection
    const geojson = this.createTrafficGeoJson(data.segments);

    // Remove existing layer/source if present
    this.removeTrafficLayer();

    // Add source
    map.addSource(TrafficController.SOURCE_ID, {
      type: 'geojson',
      data: geojson,
    });

    // Add outline layer (for better visibility)
    map.addLayer({
      id: TrafficController.LAYER_OUTLINE_ID,
      type: 'line',
      source: TrafficController.SOURCE_ID,
      layout: {
        'line-join': 'round',
        'line-cap': 'round',
      },
      paint: {
        'line-color': '#000',
        'line-width': 6,
        'line-opacity': 0.3,
      },
    });

    // Add main traffic layer with congestion colors
    map.addLayer({
      id: TrafficController.LAYER_ID,
      type: 'line',
      source: TrafficController.SOURCE_ID,
      layout: {
        'line-join': 'round',
        'line-cap': 'round',
      },
      paint: {
        // Color based on congestion level (0-1)
        'line-color': [
          'interpolate',
          ['linear'],
          ['get', 'congestion'],
          0, '#00c853',      // Green - free flow
          0.3, '#ffeb3b',    // Yellow - light congestion
          0.6, '#ff9800',    // Orange - moderate congestion
          0.8, '#f44336',    // Red - heavy congestion
          1, '#b71c1c',      // Dark red - severe/stopped
        ],
        'line-width': [
          'interpolate',
          ['linear'],
          ['zoom'],
          10, 2,
          14, 4,
          18, 6,
        ],
        'line-opacity': 0.85,
      },
    });
  }

  /**
   * Update the traffic layer with new data (without removing/recreating)
   */
  public updateTrafficLayer(data: ITrafficFlowData): void {
    const map = this.callbacks.getMap();
    if (!map) return;

    const source = map.getSource(TrafficController.SOURCE_ID) as maplibregl.GeoJSONSource;
    if (source) {
      const geojson = this.createTrafficGeoJson(data.segments);
      source.setData(geojson);
      this.currentTrafficData = data;
      this.trafficState = {
        ...this.trafficState,
        lastUpdate: data.timestamp,
        segmentCount: data.segments.length,
      };
    } else {
      // Source doesn't exist, create full layer
      this.renderTrafficLayer(data);
    }
  }

  /**
   * Remove traffic layer from the map
   */
  public removeTrafficLayer(): void {
    const map = this.callbacks.getMap();
    if (!map) return;

    if (map.getLayer(TrafficController.LAYER_ID)) {
      map.removeLayer(TrafficController.LAYER_ID);
    }
    if (map.getLayer(TrafficController.LAYER_OUTLINE_ID)) {
      map.removeLayer(TrafficController.LAYER_OUTLINE_ID);
    }
    if (map.getSource(TrafficController.SOURCE_ID)) {
      map.removeSource(TrafficController.SOURCE_ID);
    }
  }

  /**
   * Convert traffic segments to GeoJSON FeatureCollection
   */
  private createTrafficGeoJson(segments: ITrafficFlowSegment[]): GeoJSON.FeatureCollection {
    return {
      type: 'FeatureCollection',
      features: segments.map((segment, index) => ({
        type: 'Feature' as const,
        id: index,
        properties: {
          congestion: segment.congestion,
          speed: segment.speed,
          freeFlowSpeed: segment.freeFlowSpeed,
          confidence: segment.confidence,
        },
        geometry: segment.geometry,
      })),
    };
  }

  // ─── Auto-Refresh ──────────────────────────────────────────────────────────

  /**
   * Start automatic refresh timer
   */
  private startAutoRefresh(): void {
    this.stopAutoRefresh();

    this.refreshTimer = setInterval(() => {
      if (this.trafficState.isEnabled) {
        this.refresh();
      }
    }, this.updateInterval);
  }

  /**
   * Stop automatic refresh timer
   */
  private stopAutoRefresh(): void {
    if (this.refreshTimer) {
      clearInterval(this.refreshTimer);
      this.refreshTimer = null;
    }
  }

  // ─── Map Event Handling ────────────────────────────────────────────────────

  /**
   * Handle map move/zoom end - refresh traffic for new bounds
   */
  public handleMapMoveEnd(): void {
    if (this.trafficState.isEnabled && !this.trafficState.isLoading) {
      // Debounce to avoid too many API calls
      this.refresh();
    }
  }

  // ─── Cleanup ───────────────────────────────────────────────────────────────

  /**
   * Clean up resources
   */
  public cleanup(): void {
    this.stopAutoRefresh();
    this.removeTrafficLayer();
    this.currentTrafficData = null;
  }

  // ─── Utilities ─────────────────────────────────────────────────────────────

  /**
   * Format last update time for display
   */
  public formatLastUpdate(): string {
    if (!this.trafficState.lastUpdate) return 'Never';

    const now = new Date();
    const diff = now.getTime() - this.trafficState.lastUpdate.getTime();
    const seconds = Math.floor(diff / 1000);

    if (seconds < 60) return 'Just now';
    if (seconds < 120) return '1 minute ago';
    if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes ago`;
    return this.trafficState.lastUpdate.toLocaleTimeString();
  }

  /**
   * Get congestion color for a given level
   */
  public getCongestionColor(congestion: number): string {
    if (congestion < 0.3) return '#00c853'; // Green
    if (congestion < 0.6) return '#ffeb3b'; // Yellow
    if (congestion < 0.8) return '#ff9800'; // Orange
    if (congestion < 1) return '#f44336';   // Red
    return '#b71c1c'; // Dark red
  }

  /**
   * Get congestion label for a given level
   */
  public getCongestionLabel(level: 'low' | 'moderate' | 'heavy' | 'severe'): string {
    const labels = {
      low: 'Light traffic',
      moderate: 'Moderate traffic',
      heavy: 'Heavy traffic',
      severe: 'Severe congestion',
    };
    return labels[level];
  }

  // ─── Rendering ─────────────────────────────────────────────────────────────

  /**
   * Render the traffic toggle button and status
   */
  public render(): TemplateResult {
    const { isEnabled, isLoading, error, segmentCount } = this.trafficState;
    const hasProvider = this.provider !== null && this.provider.isConfigured;

    return html`
      <div class="traffic-control">
        <button
          class="traffic-toggle-btn ${isEnabled ? 'active' : ''}"
          ?disabled=${!hasProvider}
          @click=${() => this.toggle()}
          title="${!hasProvider ? 'Configure a traffic provider first' : isEnabled ? 'Hide traffic' : 'Show traffic'}"
        >
          ${renderIcon('traffic')}
          ${isLoading ? html`
            <span class="traffic-loading-indicator">${renderIcon('spinner')}</span>
          ` : ''}
        </button>

        ${isEnabled && !error ? html`
          <div class="traffic-status">
            <span class="traffic-status-dot"></span>
            <span class="traffic-status-text">
              ${segmentCount > 0 ? `${segmentCount} segments` : 'Loading...'}
            </span>
          </div>
        ` : ''}

        ${error ? html`
          <div class="traffic-error" title="${error}">
            ${renderIcon('error')}
          </div>
        ` : ''}
      </div>
    `;
  }

  /**
   * Render traffic legend overlay
   * @param extraClass - Optional CSS class to add to the legend for positioning
   */
  public renderLegend(extraClass?: string): TemplateResult {
    if (!this.trafficState.isEnabled) return html``;

    return html`
      <div class="traffic-legend ${extraClass || ''}">
        <div class="traffic-legend-title">Traffic</div>
        <div class="traffic-legend-items">
          <div class="traffic-legend-item">
            <span class="traffic-legend-color" style="background: #00c853"></span>
            <span>Free flow</span>
          </div>
          <div class="traffic-legend-item">
            <span class="traffic-legend-color" style="background: #ffeb3b"></span>
            <span>Light</span>
          </div>
          <div class="traffic-legend-item">
            <span class="traffic-legend-color" style="background: #ff9800"></span>
            <span>Moderate</span>
          </div>
          <div class="traffic-legend-item">
            <span class="traffic-legend-color" style="background: #f44336"></span>
            <span>Heavy</span>
          </div>
          <div class="traffic-legend-item">
            <span class="traffic-legend-color" style="background: #b71c1c"></span>
            <span>Severe</span>
          </div>
        </div>
        <div class="traffic-legend-updated">
          Updated: ${this.formatLastUpdate()}
        </div>
      </div>
    `;
  }
}
