// satellites.js - Satellite visualization module with real SGP4 positions and animations

import * as THREE from 'three';
import { twoline2satrec, sgp4, propagate, degreesToRadians, radiansToDegrees, eciToGeodetic } from 'satellite.js';
import { CONFIG, SATELLITE_CONFIG } from './constants.js';

let satellitePoints = null;
let satelliteTrails = null;
let satelliteData = [];
let showSatellites = false;
let showTrails = true;
let animationTime = 0;
let selectedSatellite = null;
let satellitePositions = [];
let hoverRingSprite = null;
let lockedRingSprite = null;
let lockedDotSprite = null;
export let breathingPhase = 0;

export function updateBreathingPhase() {
  breathingPhase += SATELLITE_CONFIG.breathingSpeed;
}

const SATELLITE_API = SATELLITE_CONFIG.apiPath + '?limit=' + SATELLITE_CONFIG.maxCount;
const MAX_SATELLITES = SATELLITE_CONFIG.maxCount;
const TRAIL_LENGTH = SATELLITE_CONFIG.trailLength;
const DOT_TEXTURE_SIZE = 32;

function createDotTexture() {
  const canvas = document.createElement('canvas');
  canvas.width = DOT_TEXTURE_SIZE;
  canvas.height = DOT_TEXTURE_SIZE;
  const ctx = canvas.getContext('2d');
  const center = DOT_TEXTURE_SIZE / 2;
  const radius = center - 2;
  
  const gradient = ctx.createRadialGradient(center, center, 0, center, center, radius);
  gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
  gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.8)');
  gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
  
  ctx.fillStyle = gradient;
  ctx.beginPath();
  ctx.arc(center, center, radius, 0, Math.PI * 2);
  ctx.fill();
  
  const texture = new THREE.CanvasTexture(canvas);
  texture.needsUpdate = true;
  return texture;
}

function createRingTexture(innerRadius, outerRadius, color = '#ffffff') {
  const size = DOT_TEXTURE_SIZE * 2;
  const canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext('2d');
  const center = size / 2;
  
  ctx.strokeStyle = color;
  ctx.lineWidth = 3;
  ctx.beginPath();
  ctx.arc(center, center, (innerRadius + outerRadius) / 2, 0, Math.PI * 2);
  ctx.stroke();
  
  const texture = new THREE.CanvasTexture(canvas);
  texture.needsUpdate = true;
  return texture;
}

export function createSatellites(scene, earthObj) {
  initSatelliteScene(scene, earthObj);
  
  const positions = new Float32Array(MAX_SATELLITES * 3);
  const colors = new Float32Array(MAX_SATELLITES * 3);
  
  const dotTexture = createDotTexture();
  
  const pointsGeometry = new THREE.BufferGeometry();
  pointsGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  pointsGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
  
  const pointsMaterial = new THREE.PointsMaterial({
    size: SATELLITE_CONFIG.dotSize,
    map: dotTexture,
    vertexColors: true,
    transparent: true,
    opacity: 0.9,
    sizeAttenuation: false,
    alphaTest: 0.1
  });
  
  satellitePoints = new THREE.Points(pointsGeometry, pointsMaterial);
  satellitePoints.visible = false;
  satellitePoints.userData = { type: 'satellitePoints' };
  
  const originalScale = { x: 1, y: 1, z: 1 };
  satellitePoints.onBeforeRender = (renderer, scene, camera, geometry, material) => {
    if (earthObj && earthObj.scale.x !== 1) {
      satellitePoints.scale.set(
        originalScale.x / earthObj.scale.x,
        originalScale.y / earthObj.scale.y,
        originalScale.z / earthObj.scale.z
      );
    } else {
      satellitePoints.scale.set(originalScale.x, originalScale.y, originalScale.z);
    }
  };
  
  earthObj.add(satellitePoints);
  
  const trailPositions = new Float32Array(MAX_SATELLITES * TRAIL_LENGTH * 3);
  const trailColors = new Float32Array(MAX_SATELLITES * TRAIL_LENGTH * 3);
  
  const trailGeometry = new THREE.BufferGeometry();
  trailGeometry.setAttribute('position', new THREE.BufferAttribute(trailPositions, 3));
  trailGeometry.setAttribute('color', new THREE.BufferAttribute(trailColors, 3));
  
  const trailMaterial = new THREE.LineBasicMaterial({
    vertexColors: true,
    transparent: true,
    opacity: 0.3,
    blending: THREE.AdditiveBlending
  });
  
  satelliteTrails = new THREE.LineSegments(trailGeometry, trailMaterial);
  satelliteTrails.visible = false;
  satelliteTrails.userData = { type: 'satelliteTrails' };
  earthObj.add(satelliteTrails);
  
  satellitePositions = [];
  for (let i = 0; i < MAX_SATELLITES; i++) {
    satellitePositions.push({
      current: new THREE.Vector3(),
      trail: [],
      trailIndex: 0,
      trailCount: 0
    });
  }
  
  return satellitePoints;
}

function computeSatellitePosition(satellite, time) {
  try {
    const props = satellite.properties;
    if (!props || !props.norad_cat_id) {
      return null;
    }
    
    const noradId = props.norad_cat_id;
    const inclination = props.inclination || 53;
    const raan = props.raan || 0;
    const eccentricity = props.eccentricity || 0.0001;
    const argOfPerigee = props.arg_of_perigee || 0;
    const meanAnomaly = props.mean_anomaly || 0;
    const meanMotion = props.mean_motion || 15;
    const epoch = props.epoch || '';
    
    // Simplified epoch calculation
    let epochDate = epoch && epoch.length >= 10 ? new Date(epoch) : time;
    const epochYear = epochDate.getUTCFullYear() % 100;
    const startOfYear = new Date(Date.UTC(epochDate.getUTCFullYear(), 0, 1));
    const dayOfYear = Math.floor((epochDate - startOfYear) / 86400000) + 1;
    const msOfDay = epochDate.getUTCHours() * 3600000 + epochDate.getUTCMinutes() * 60000 + epochDate.getUTCSeconds() * 1000 + epochDate.getUTCMilliseconds();
    const dayFraction = msOfDay / 86400000;
    const epochStr = String(epochYear).padStart(2, '0') + String(dayOfYear).padStart(3, '0') + '.' + dayFraction.toFixed(8).substring(2);
    
    // Format eccentricity as "0.0001652" (7 chars after decimal)
    const eccStr = '0' + eccentricity.toFixed(7);
    const tleLine1 = `1 ${noradId.toString().padStart(5)}U 00001A   ${epochStr}  .00000000  00000-0  00000-0  0  9999`;
    const tleLine2 = `2 ${noradId.toString().padStart(5)} ${raan.toFixed(4).padStart(8)} ${inclination.toFixed(4).padStart(8)} ${eccStr.substring(1)} ${argOfPerigee.toFixed(4).padStart(8)} ${meanAnomaly.toFixed(4).padStart(8)} ${meanMotion.toFixed(8).padStart(11)}  0  9999`;
    
    const satrec = twoline2satrec(tleLine1, tleLine2);
    if (!satrec || satrec.error) {
      return null;
    }
    
    const positionAndVelocity = propagate(satrec, time);
    if (!positionAndVelocity || !positionAndVelocity.position) {
      return null;
    }
    
    const x = positionAndVelocity.position.x;
    const y = positionAndVelocity.position.y;
    const z = positionAndVelocity.position.z;
    
    if (!x || !y || !z) {
      return null;
    }
    
    const r = Math.sqrt(x * x + y * y + z * z);
    const earthRadius = 6371;
    const displayRadius = CONFIG.earthRadius * (earthRadius / 6371) * 1.05;
    
    const scale = displayRadius / r;
    
    return new THREE.Vector3(x * scale, y * scale, z * scale);
  } catch (e) {
    return null;
  }
}

function generateFallbackPosition(satellite, index, total) {
  const radius = CONFIG.earthRadius + 5;
  
  const noradId = satellite.properties?.norad_cat_id || index;
  const inclination = satellite.properties?.inclination || 53;
  const raan = satellite.properties?.raan || 0;
  const meanAnomaly = satellite.properties?.mean_anomaly || 0;
  
  const hash = String(noradId).split('').reduce((a, b) => a + b.charCodeAt(0), 0);
  const randomOffset = (hash % 1000) / 1000;
  
  const normalizedIndex = index / total;
  const theta = normalizedIndex * Math.PI * 2 * 10 + (raan * Math.PI / 180);
  const phi = (inclination * Math.PI / 180) + (meanAnomaly * Math.PI / 180 * 0.1);
  
  const adjustedPhi = Math.abs(phi % Math.PI);
  const adjustedTheta = theta + randomOffset * Math.PI * 2;
  
  const x = radius * Math.sin(adjustedPhi) * Math.cos(adjustedTheta);
  const y = radius * Math.cos(adjustedPhi);
  const z = radius * Math.sin(adjustedPhi) * Math.sin(adjustedTheta);
  
  return new THREE.Vector3(x, y, z);
}

export async function loadSatellites() {
  try {
    const response = await fetch(SATELLITE_API);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    
    const data = await response.json();
    satelliteData = data.features || [];
    
    console.log(`Loaded ${satelliteData.length} satellites`);
    return satelliteData.length;
  } catch (error) {
    console.error('Failed to load satellites:', error);
    return [];
  }
}

export function updateSatellitePositions(deltaTime = 0) {
  if (!satellitePoints || satelliteData.length === 0) return;
  
  animationTime += deltaTime * 0.001;
  
  const positions = satellitePoints.geometry.attributes.position.array;
  const colors = satellitePoints.geometry.attributes.color.array;
  
  const trailPositions = satelliteTrails.geometry.attributes.position.array;
  const trailColors = satelliteTrails.geometry.attributes.color.array;
  
  const baseTime = new Date();
  const count = Math.min(satelliteData.length, MAX_SATELLITES);
  
  for (let i = 0; i < count; i++) {
    const satellite = satelliteData[i];
    const props = satellite.properties;
    
    const timeOffset = (i / count) * 2 * Math.PI * 0.1;
    const adjustedTime = new Date(baseTime.getTime() + timeOffset * 1000 * 60 * 10);
    
    let pos = computeSatellitePosition(satellite, adjustedTime);
    
    if (!pos) {
      pos = generateFallbackPosition(satellite, i, count);
    }
    
    satellitePositions[i].current.copy(pos);
    
    const satPos = satellitePositions[i];
    if (i !== window.lockedSatelliteIndex) {
      satPos.trail[satPos.trailIndex] = pos.clone();
      satPos.trailIndex = (satPos.trailIndex + 1) % TRAIL_LENGTH;
      if (satPos.trailCount < TRAIL_LENGTH) satPos.trailCount++;
    }
    
    positions[i * 3] = pos.x;
    positions[i * 3 + 1] = pos.y;
    positions[i * 3 + 2] = pos.z;
    
    const inclination = props?.inclination || 53;
    const name = props?.name || '';
    const isStarlink = name.includes('STARLINK');
    const isGeo = inclination > 20 && inclination < 30;
    const isIridium = name.includes('IRIDIUM');
    
    let r, g, b;
    if (isStarlink) {
      r = 0.0; g = 0.9; b = 1.0;
    } else if (isGeo) {
      r = 1.0; g = 0.8; b = 0.0;
    } else if (isIridium) {
      r = 1.0; g = 0.5; b = 0.0;
    } else if (inclination > 50 && inclination < 70) {
      r = 0.0; g = 1.0; b = 0.3;
    } else {
      r = 1.0; g = 1.0; b = 1.0;
    }
    
    colors[i * 3] = r;
    colors[i * 3 + 1] = g;
    colors[i * 3 + 2] = b;
    
    const sp = satellitePositions[i];
    const trail = sp.trail;
    const tc = sp.trailCount;
    const ti = sp.trailIndex;
    
    for (let j = 0; j < TRAIL_LENGTH; j++) {
      const trailIdx = (i * TRAIL_LENGTH + j) * 3;
      
      if (j < tc) {
        const idx = (ti - tc + j + TRAIL_LENGTH) % TRAIL_LENGTH;
        const t = trail[idx];
        if (t) {
          trailPositions[trailIdx] = t.x;
          trailPositions[trailIdx + 1] = t.y;
          trailPositions[trailIdx + 2] = t.z;
          const alpha = (j + 1) / tc;
          trailColors[trailIdx] = r * alpha;
          trailColors[trailIdx + 1] = g * alpha;
          trailColors[trailIdx + 2] = b * alpha;
        } else {
          trailPositions[trailIdx] = pos.x;
          trailPositions[trailIdx + 1] = pos.y;
          trailPositions[trailIdx + 2] = pos.z;
          trailColors[trailIdx] = 0;
          trailColors[trailIdx + 1] = 0;
          trailColors[trailIdx + 2] = 0;
        }
      } else {
        trailPositions[trailIdx] = pos.x;
        trailPositions[trailIdx + 1] = pos.y;
        trailPositions[trailIdx + 2] = pos.z;
        trailColors[trailIdx] = 0;
        trailColors[trailIdx + 1] = 0;
        trailColors[trailIdx + 2] = 0;
      }
    }
  }
  
  for (let i = count; i < MAX_SATELLITES; i++) {
    positions[i * 3] = 0;
    positions[i * 3 + 1] = 0;
    positions[i * 3 + 2] = 0;
    
    for (let j = 0; j < TRAIL_LENGTH; j++) {
      const trailIdx = (i * TRAIL_LENGTH + j) * 3;
      trailPositions[trailIdx] = 0;
      trailPositions[trailIdx + 1] = 0;
      trailPositions[trailIdx + 2] = 0;
    }
  }
  
  satellitePoints.geometry.attributes.position.needsUpdate = true;
  satellitePoints.geometry.attributes.color.needsUpdate = true;
  satellitePoints.geometry.setDrawRange(0, count);
  
  satelliteTrails.geometry.attributes.position.needsUpdate = true;
  satelliteTrails.geometry.attributes.color.needsUpdate = true;
}

export function toggleSatellites(visible) {
  showSatellites = visible;
  if (satellitePoints) {
    satellitePoints.visible = visible;
  }
  if (satelliteTrails) {
    satelliteTrails.visible = visible && showTrails;
  }
}

export function toggleTrails(visible) {
  showTrails = visible;
  if (satelliteTrails) {
    satelliteTrails.visible = visible && showSatellites;
  }
}

export function getShowSatellites() {
  return showSatellites;
}

export function getSatelliteCount() {
  return satelliteData.length;
}

export function getSatelliteAt(index) {
  if (index >= 0 && index < satelliteData.length) {
    return satelliteData[index];
  }
  return null;
}

export function getSatelliteData() {
  return satelliteData;
}

export function selectSatellite(index) {
  selectedSatellite = index;
  return getSatelliteAt(index);
}

export function getSatellitePoints() {
  return satellitePoints;
}

export function getSatellitePositions() {
  return satellitePositions;
}

let earthObjRef = null;
let sceneRef = null;

export function showHoverRing(position, isLocked = false) {
  if (!sceneRef || !earthObjRef) return;
  
  const ringTexture = createRingTexture(8, 12, isLocked ? '#ffcc00' : '#ffffff');
  const spriteMaterial = new THREE.SpriteMaterial({
    map: ringTexture,
    transparent: true,
    opacity: 0.8,
    depthTest: false,
    sizeAttenuation: false
  });
  
  const ringSize = SATELLITE_CONFIG.ringSize;
  const sprite = new THREE.Sprite(spriteMaterial);
  sprite.position.copy(position);
  
  const camera = window.camera;
  const cameraDistance = camera ? camera.position.distanceTo(position) : 400;
  const scale = ringSize;
  sprite.scale.set(scale, scale, 1);
  console.log(`[Ring create] ringSize: ${ringSize}, camDist: ${cameraDistance}, scale: ${scale}`);
  
  earthObjRef.add(sprite);
  
  if (isLocked) {
    if (lockedRingSprite) {
      earthObjRef.remove(lockedRingSprite);
    }
    lockedRingSprite = sprite;
    
    if (lockedDotSprite) {
      earthObjRef.remove(lockedDotSprite);
    }
    const dotCanvas = createBrighterDotCanvas();
    const dotTexture = new THREE.CanvasTexture(dotCanvas);
    dotTexture.needsUpdate = true;
    const dotMaterial = new THREE.SpriteMaterial({
      map: dotTexture,
      transparent: true,
      opacity: 1.0,
      depthTest: false
    });
    lockedDotSprite = new THREE.Sprite(dotMaterial);
    lockedDotSprite.position.copy(position);
    lockedDotSprite.scale.set(4 * cameraDistance / 200, 4 * cameraDistance / 200, 1);
    
    earthObjRef.add(lockedDotSprite);
  } else {
    if (hoverRingSprite) {
      earthObjRef.remove(hoverRingSprite);
    }
    hoverRingSprite = sprite;
  }
  
  return sprite;
}

function createBrighterDotCanvas() {
  const size = DOT_TEXTURE_SIZE * 2;
  const canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext('2d');
  const center = size / 2;
  const gradient = ctx.createRadialGradient(center, center, 0, center, center, center);
  gradient.addColorStop(0, 'rgba(255, 255, 200, 1)');
  gradient.addColorStop(0.3, 'rgba(255, 220, 100, 0.9)');
  gradient.addColorStop(0.7, 'rgba(255, 180, 50, 0.5)');
  gradient.addColorStop(1, 'rgba(255, 150, 0, 0)');
  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, size, size);
  return canvas;
}

export function hideHoverRings() {
  if (!earthObjRef) return;
  
  if (hoverRingSprite) {
    earthObjRef.remove(hoverRingSprite);
    hoverRingSprite = null;
  }
}

export function hideLockedRing() {
  if (!earthObjRef) return;
  if (lockedRingSprite) {
    earthObjRef.remove(lockedRingSprite);
    lockedRingSprite = null;
  }
  if (lockedDotSprite) {
    earthObjRef.remove(lockedDotSprite);
    lockedDotSprite = null;
  }
}

export function updateLockedRingPosition(position) {
  const ringSize = SATELLITE_CONFIG.ringSize;
  const camera = window.camera;
  const cameraDistance = camera ? camera.position.distanceTo(position) : 400;
  if (lockedRingSprite && position) {
    lockedRingSprite.position.copy(position);
    const breathScale = 1 + Math.sin(breathingPhase) * SATELLITE_CONFIG.breathingScaleAmplitude;
    lockedRingSprite.scale.set(ringSize * breathScale, ringSize * breathScale, 1);
    const breathOpacity = SATELLITE_CONFIG.breathingOpacityMin + Math.sin(breathingPhase) * (SATELLITE_CONFIG.breathingOpacityMax - SATELLITE_CONFIG.breathingOpacityMin);
    lockedRingSprite.material.opacity = breathOpacity;
  }
  if (lockedDotSprite && position) {
    lockedDotSprite.position.copy(position);
    const dotBreathScale = 1 + Math.sin(breathingPhase) * SATELLITE_CONFIG.dotBreathingScaleAmplitude;
    lockedDotSprite.scale.set(4 * cameraDistance / 200 * dotBreathScale, 4 * cameraDistance / 200 * dotBreathScale, 1);
    lockedDotSprite.material.opacity = SATELLITE_CONFIG.dotOpacityMin + Math.sin(breathingPhase) * (SATELLITE_CONFIG.dotOpacityMax - SATELLITE_CONFIG.dotOpacityMin);
  }
}

export function updateHoverRingPosition(position) {
  const ringSize = SATELLITE_CONFIG.ringSize;
  const camera = window.camera;
  const cameraDistance = camera ? camera.position.distanceTo(position) : 400;
  const scale = ringSize;
  if (hoverRingSprite && position) {
    hoverRingSprite.position.copy(position);
    hoverRingSprite.scale.set(scale, scale, 1);
    console.log(`[Hover update] ringSize: ${ringSize}, camDist: ${cameraDistance}, scale: ${scale}`);
  }
}

export function setSatelliteRingState(index, state, position) {
  switch (state) {
    case 'hover':
      hideHoverRings();
      showHoverRing(position, false);
      break;
    case 'locked':
      hideHoverRings();
      showHoverRing(position, true);
      break;
    case 'none':
      hideHoverRings();
      hideLockedRing();
      break;
  }
}

export function initSatelliteScene(scene, earth) {
  sceneRef = scene;
  earthObjRef = earth;
}

let predictedOrbitLine = null;

function calculateOrbitalPeriod(meanMotion) {
  return 86400 / meanMotion;
}

function calculatePredictedOrbit(satellite, periodSeconds, sampleInterval = 10) {
  const points = [];
  const samples = Math.ceil(periodSeconds / sampleInterval);
  const now = new Date();
  
  // Full orbit: from now to now+period (complete circle forward)
  for (let i = 0; i <= samples; i++) {
    const time = new Date(now.getTime() + i * sampleInterval * 1000);
    const pos = computeSatellitePosition(satellite, time);
    if (pos) points.push(pos);
  }
  
  // If we don't have enough points, use fallback orbit
  if (points.length < samples * 0.5) {
    points.length = 0;
    const radius = CONFIG.earthRadius + 5;
    const noradId = satellite.properties?.norad_cat_id || 0;
    const inclination = satellite.properties?.inclination || 53;
    const raan = satellite.properties?.raan || 0;
    const meanAnomaly = satellite.properties?.mean_anomaly || 0;
    
    for (let i = 0; i <= samples; i++) {
      const theta = (i / samples) * Math.PI * 2;
      const phi = (inclination * Math.PI / 180);
      const x = radius * Math.sin(phi) * Math.cos(theta + raan * Math.PI / 180);
      const y = radius * Math.cos(phi);
      const z = radius * Math.sin(phi) * Math.sin(theta + raan * Math.PI / 180);
      points.push(new THREE.Vector3(x, y, z));
    }
  }
  
  return points;
}

export function showPredictedOrbit(satellite) {
  hidePredictedOrbit();
  
  const props = satellite.properties;
  const meanMotion = props?.mean_motion || 15;
  const periodSeconds = calculateOrbitalPeriod(meanMotion);
  
  const points = calculatePredictedOrbit(satellite, periodSeconds);
  if (points.length < 2) return;
  
  const positions = new Float32Array(points.length * 3);
  const colors = new Float32Array(points.length * 3);
  
  for (let i = 0; i < points.length; i++) {
    positions[i * 3] = points[i].x;
    positions[i * 3 + 1] = points[i].y;
    positions[i * 3 + 2] = points[i].z;
    
    const t = i / (points.length - 1);
    colors[i * 3] = 1 - t * 0.4;
    colors[i * 3 + 1] = 1 - t * 0.6;
    colors[i * 3 + 2] = t;
  }
  
  const geometry = new THREE.BufferGeometry();
  geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
  
  const material = new THREE.LineBasicMaterial({
    vertexColors: true,
    transparent: true,
    opacity: 0.8,
    blending: THREE.AdditiveBlending
  });
  
  predictedOrbitLine = new THREE.Line(geometry, material);
  earthObjRef.add(predictedOrbitLine);
}

export function hidePredictedOrbit() {
  if (predictedOrbitLine) {
    earthObjRef.remove(predictedOrbitLine);
    predictedOrbitLine.geometry.dispose();
    predictedOrbitLine.material.dispose();
    predictedOrbitLine = null;
  }
}
