import type { Site, CoveragePoint, CoverageResult, CoverageSettings, GridPoint } from '@/types/index.ts';

export class RFCalculator {
  /**
   * Calculate coverage for multiple sites using Web Workers.
   */
  async calculateCoverage(
    sites: Site[],
    mapBounds: { north: number; south: number; east: number; west: number },
    settings: CoverageSettings
  ): Promise<CoverageResult> {
    const startTime = performance.now();

    // Generate grid of points
    const gridPoints = this.generateGrid(mapBounds, settings.resolution);

    // Determine number of workers
    const numWorkers = Math.min(4, navigator.hardwareConcurrency || 4);
    const chunks = this.chunkArray(gridPoints, numWorkers);

    // Serialize site data for workers
    const sitesData = sites.map((s) => ({
      id: s.id,
      lat: s.lat,
      lon: s.lon,
      height: s.height,
      power: s.power,
      gain: s.gain,
      frequency: s.frequency,
      antennaType: s.antennaType,
      azimuth: s.azimuth,
      beamwidth: s.beamwidth,
    }));

    // Calculate in parallel using Web Workers
    const workers: Worker[] = [];
    const promises: Promise<CoveragePoint[]>[] = [];

    for (let i = 0; i < chunks.length; i++) {
      const worker = new Worker('/workers/rf-worker.js');
      workers.push(worker);
      promises.push(
        this.calculateChunk(worker, sitesData, chunks[i], settings.rsrpThreshold)
      );
    }

    const results = await Promise.all(promises);

    // Cleanup workers
    workers.forEach((w) => w.terminate());

    // Merge results
    const allPoints = results.flat();

    const calculationTime = performance.now() - startTime;

    return {
      points: allPoints,
      calculationTime,
      totalPoints: allPoints.length,
      settings,
    };
  }

  private generateGrid(
    bounds: { north: number; south: number; east: number; west: number },
    resolution: number
  ): GridPoint[] {
    const points: GridPoint[] = [];

    // Convert resolution to degrees
    const latStep = resolution / 111000; // ~111km per degree latitude
    const centerLat = (bounds.north + bounds.south) / 2;
    const lonStep =
      resolution / (111000 * Math.cos((centerLat * Math.PI) / 180));

    let lat = bounds.south;
    while (lat <= bounds.north) {
      let lon = bounds.west;
      while (lon <= bounds.east) {
        points.push({ lat, lon });
        lon += lonStep;
      }
      lat += latStep;
    }

    return points;
  }

  private chunkArray<T>(array: T[], numChunks: number): T[][] {
    const chunks: T[][] = [];
    const chunkSize = Math.ceil(array.length / numChunks);

    for (let i = 0; i < array.length; i += chunkSize) {
      chunks.push(array.slice(i, i + chunkSize));
    }

    return chunks;
  }

  private calculateChunk(
    worker: Worker,
    sites: unknown[],
    points: GridPoint[],
    rsrpThreshold: number
  ): Promise<CoveragePoint[]> {
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject(new Error('Worker timeout'));
      }, 30000);

      worker.postMessage({
        type: 'calculate',
        sites,
        points,
        rsrpThreshold,
      });

      worker.onmessage = (e: MessageEvent) => {
        clearTimeout(timeout);
        if (e.data.type === 'complete') {
          resolve(e.data.results);
        } else if (e.data.type === 'error') {
          reject(new Error(e.data.message));
        }
      };

      worker.onerror = (err) => {
        clearTimeout(timeout);
        reject(err);
      };
    });
  }
}
