// RF Coverage Calculation Web Worker
// Runs in a separate thread for parallel processing

self.onmessage = function (e) {
  const { type, sites, points, rsrpThreshold } = e.data;

  if (type === 'calculate') {
    try {
      const results = [];

      for (let i = 0; i < points.length; i++) {
        const point = points[i];
        let bestRSRP = -Infinity;
        let bestSiteId = '';

        for (let j = 0; j < sites.length; j++) {
          const site = sites[j];
          const rsrp = calculatePointRSRP(site, point);

          if (rsrp > bestRSRP) {
            bestRSRP = rsrp;
            bestSiteId = site.id;
          }
        }

        if (bestRSRP >= rsrpThreshold) {
          results.push({
            lat: point.lat,
            lon: point.lon,
            rsrp: bestRSRP,
            siteId: bestSiteId,
          });
        }
      }

      self.postMessage({ type: 'complete', results });
    } catch (error) {
      self.postMessage({ type: 'error', message: error.message });
    }
  }
};

/**
 * Calculate RSRP at a specific point (universal formula)
 *
 * RSRP = P_tx + G_tx - FSPL - PatternLoss
 *
 * Includes:
 *  - Antenna gain (site.gain)
 *  - 3GPP sector pattern with back lobe (no hard cutoff)
 *  - Radio horizon limit based on antenna height
 */
function calculatePointRSRP(site, point) {
  var distance = haversineDistance(site.lat, site.lon, point.lat, point.lon);

  // Minimum distance to prevent -Infinity
  if (distance < 0.01) distance = 0.01;

  // Radio horizon check: d_horizon = 3.57 * sqrt(h_meters)
  // Uses 4/3 Earth radius for standard atmospheric refraction
  var siteHeight = site.height || 10; // default 10m if missing
  var horizon = calculateRadioHorizon(siteHeight);
  if (distance > horizon) {
    return -Infinity;
  }

  // Free space path loss (universal)
  var fspl =
    20 * Math.log10(distance) + 20 * Math.log10(site.frequency) + 32.45;

  // Link budget: RSRP = P_tx + G_tx - FSPL
  var rsrp = site.power + site.gain - fspl;

  // Apply 3GPP sector antenna pattern (main lobe + side lobes + back lobe)
  // No hard cutoff — back lobe is attenuated by front-to-back ratio (~25 dB)
  if (site.antennaType === 'sector' && site.azimuth !== undefined) {
    var bearing = calculateBearing(site.lat, site.lon, point.lat, point.lon);
    var beamwidth = site.beamwidth || 65;
    var frontBackRatio = 25; // dB, typical for sector panel antennas

    var patternLoss = calculate3GPPPattern(
      site.azimuth,
      bearing,
      beamwidth,
      frontBackRatio
    );
    rsrp -= patternLoss; // patternLoss is positive dB
  }

  return rsrp;
}

/**
 * Radio horizon distance in km.
 * d = 3.57 * sqrt(h) where h is antenna height in meters.
 * Accounts for 4/3 Earth radius (standard atmosphere refraction).
 */
function calculateRadioHorizon(heightMeters) {
  return 3.57 * Math.sqrt(heightMeters);
}

/**
 * 3GPP TR 36.814 Horizontal Antenna Pattern.
 *
 * A(θ) = min[ 12 * (θ / θ_3dB)², A_m ]
 *
 * Returns POSITIVE dB loss value (to be subtracted from RSRP).
 *
 * - θ       = angular offset from boresight (0-180°)
 * - θ_3dB   = half-power beamwidth / 2
 * - A_m     = maximum attenuation = front-to-back ratio
 *
 * At 0°  → 0 dB loss (boresight)
 * At ±θ_3dB → 3 dB loss (half-power points)
 * At ±90° → capped at A_m (~25 dB)
 * At 180° → capped at A_m (~25 dB, back lobe)
 */
function calculate3GPPPattern(azimuth, bearing, beamwidth, frontBackRatio) {
  // Normalize angle difference to -180…+180
  var angleDiff = bearing - azimuth;
  while (angleDiff > 180) angleDiff -= 360;
  while (angleDiff < -180) angleDiff += 360;

  var theta = Math.abs(angleDiff);
  var theta3dB = beamwidth / 2;
  var Am = frontBackRatio;

  return Math.min(12 * Math.pow(theta / theta3dB, 2), Am);
}

/**
 * Haversine distance in km
 */
function haversineDistance(lat1, lon1, lat2, lon2) {
  var R = 6371;
  var dLat = ((lat2 - lat1) * Math.PI) / 180;
  var dLon = ((lon2 - lon1) * Math.PI) / 180;

  var a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos((lat1 * Math.PI) / 180) *
      Math.cos((lat2 * Math.PI) / 180) *
      Math.sin(dLon / 2) *
      Math.sin(dLon / 2);

  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c;
}

/**
 * Calculate bearing from A to B in degrees (0-360)
 */
function calculateBearing(lat1, lon1, lat2, lon2) {
  var dLon = ((lon2 - lon1) * Math.PI) / 180;
  var lat1Rad = (lat1 * Math.PI) / 180;
  var lat2Rad = (lat2 * Math.PI) / 180;

  var y = Math.sin(dLon) * Math.cos(lat2Rad);
  var x =
    Math.cos(lat1Rad) * Math.sin(lat2Rad) -
    Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(dLon);

  var bearing = (Math.atan2(y, x) * 180) / Math.PI;
  return (bearing + 360) % 360;
}
