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

import * as THREE from "three";
import { twoline2satrec, propagate } 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 selectedSatellite = null;
let satellitePositions = [];
let hoverRingSprite = null;
let lockedRingSprite = null;
let lockedDotSprite = null;
let predictedOrbitLine = null;
let earthObjRef = null;
let sceneRef = null;
let cameraRef = null;
let lockedSatelliteIndex = null;
let hoveredSatelliteIndex = null;
let positionUpdateAccumulator = 0;
let satelliteCapacity = 0;

const TRAIL_LENGTH = SATELLITE_CONFIG.trailLength;
const DOT_TEXTURE_SIZE = 32;
const POSITION_UPDATE_INTERVAL_MS = 250;

const scratchWorldSatellitePosition = new THREE.Vector3();
const scratchToCamera = new THREE.Vector3();
const scratchToSatellite = new THREE.Vector3();

export let breathingPhase = 0;

export function updateBreathingPhase(deltaTime = 16) {
  breathingPhase += SATELLITE_CONFIG.breathingSpeed * (deltaTime / 16);
}

function disposeMaterial(material) {
  if (!material) return;
  if (Array.isArray(material)) {
    material.forEach(disposeMaterial);
    return;
  }
  if (material.map) {
    material.map.dispose();
  }
  material.dispose();
}

function disposeObject3D(object, parent = earthObjRef) {
  if (!object) return;
  if (parent) {
    parent.remove(object);
  } else if (object.parent) {
    object.parent.remove(object);
  }
  if (object.geometry) {
    object.geometry.dispose();
  }
  if (object.material) {
    disposeMaterial(object.material);
  }
}

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 dotTexture = createDotTexture();

  const pointsGeometry = new THREE.BufferGeometry();

  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 = () => {
    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 trailGeometry = new THREE.BufferGeometry();

  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);
  ensureSatelliteCapacity(0);

  positionUpdateAccumulator = POSITION_UPDATE_INTERVAL_MS;
  return satellitePoints;
}

function getRequestedSatelliteLimit() {
  return SATELLITE_CONFIG.maxCount < 0 ? null : SATELLITE_CONFIG.maxCount;
}

function createSatellitePositionState() {
  return {
    current: new THREE.Vector3(),
    trail: [],
    trailIndex: 0,
    trailCount: 0,
  };
}

function ensureSatelliteCapacity(count) {
  if (!satellitePoints || !satelliteTrails) return;

  const nextCapacity = Math.max(count, 0);
  if (nextCapacity === satelliteCapacity) return;

  const positions = new Float32Array(nextCapacity * 3);
  const colors = new Float32Array(nextCapacity * 3);
  satellitePoints.geometry.setAttribute(
    "position",
    new THREE.BufferAttribute(positions, 3),
  );
  satellitePoints.geometry.setAttribute(
    "color",
    new THREE.BufferAttribute(colors, 3),
  );
  satellitePoints.geometry.setDrawRange(0, 0);

  const trailPositions = new Float32Array(nextCapacity * TRAIL_LENGTH * 3);
  const trailColors = new Float32Array(nextCapacity * TRAIL_LENGTH * 3);
  satelliteTrails.geometry.setAttribute(
    "position",
    new THREE.BufferAttribute(trailPositions, 3),
  );
  satelliteTrails.geometry.setAttribute(
    "color",
    new THREE.BufferAttribute(trailColors, 3),
  );

  satellitePositions = Array.from(
    { length: nextCapacity },
    createSatellitePositionState,
  );
  satelliteCapacity = nextCapacity;
}

function computeSatellitePosition(satellite, time) {
  try {
    const props = satellite.properties;
    if (!props || !props.norad_cat_id) {
      return null;
    }

    const satrec = buildSatrecFromProperties(props, time);
    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 (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
      return null;
    }

    const r = Math.sqrt(x * x + y * y + z * z);
    const displayRadius = CONFIG.earthRadius * 1.05;
    const scale = displayRadius / r;

    return new THREE.Vector3(x * scale, y * scale, z * scale);
  } catch (error) {
    return null;
  }
}

function buildSatrecFromProperties(props, fallbackTime) {
  if (props.tle_line1 && props.tle_line2) {
    // Prefer source-provided TLE lines so the client does not need to rebuild them.
    const satrec = twoline2satrec(props.tle_line1, props.tle_line2);
    if (!satrec.error) {
      return satrec;
    }
  }

  const tleLines = buildTleLinesFromElements(props, fallbackTime);
  if (!tleLines) {
    return null;
  }

  return twoline2satrec(tleLines.line1, tleLines.line2);
}

function computeTleChecksum(line) {
  let sum = 0;

  for (const char of line.slice(0, 68)) {
    if (char >= "0" && char <= "9") {
      sum += Number(char);
    } else if (char === "-") {
      sum += 1;
    }
  }

  return String(sum % 10);
}

function buildTleLinesFromElements(props, fallbackTime) {
  if (!props?.norad_cat_id) {
    return null;
  }

  const requiredValues = [
    props.inclination,
    props.raan,
    props.eccentricity,
    props.arg_of_perigee,
    props.mean_anomaly,
    props.mean_motion,
  ];
  if (requiredValues.some((value) => value === null || value === undefined)) {
    return null;
  }

  const epochDate =
    props.epoch && String(props.epoch).length >= 10
      ? new Date(props.epoch)
      : fallbackTime;
  if (Number.isNaN(epochDate.getTime())) {
    return null;
  }

  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).slice(1);

  const eccentricityDigits = Math.round(Number(props.eccentricity) * 1e7)
    .toString()
    .padStart(7, "0");

  // Keep a local fallback for historical rows that do not have stored TLE lines yet.
  const line1Core = `1 ${String(props.norad_cat_id).padStart(5, "0")}U 00001A   ${epochStr}  .00000000  00000-0  00000-0 0  999`;
  const line2Core = `2 ${String(props.norad_cat_id).padStart(5, "0")} ${Number(
    props.inclination,
  )
    .toFixed(4)
    .padStart(
      8,
    )} ${Number(props.raan).toFixed(4).padStart(8)} ${eccentricityDigits} ${Number(
    props.arg_of_perigee,
  )
    .toFixed(4)
    .padStart(8)} ${Number(props.mean_anomaly).toFixed(4).padStart(8)} ${Number(
    props.mean_motion,
  )
    .toFixed(8)
    .padStart(11)}00000`;

  return {
    line1: line1Core + computeTleChecksum(line1Core),
    line2: line2Core + computeTleChecksum(line2Core),
  };
}

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() {
  const limit = getRequestedSatelliteLimit();
  const url = new URL(SATELLITE_CONFIG.apiPath, window.location.origin);
  if (limit !== null) {
    url.searchParams.set("limit", String(limit));
  }

  const response = await fetch(url.toString());
  if (!response.ok) {
    throw new Error(`卫星接口返回 HTTP ${response.status}`);
  }

  const data = await response.json();
  satelliteData = data.features || [];
  ensureSatelliteCapacity(satelliteData.length);
  positionUpdateAccumulator = POSITION_UPDATE_INTERVAL_MS;
  return satelliteData.length;
}

export function updateSatellitePositions(deltaTime = 0, force = false) {
  if (!satellitePoints || satelliteData.length === 0) return;

  const shouldUpdateTrails =
    showSatellites || showTrails || lockedSatelliteIndex !== null;
  positionUpdateAccumulator += deltaTime;

  if (!force && positionUpdateAccumulator < POSITION_UPDATE_INTERVAL_MS) {
    return;
  }

  const elapsedMs = Math.max(
    positionUpdateAccumulator,
    POSITION_UPDATE_INTERVAL_MS,
  );
  positionUpdateAccumulator = 0;

  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(Date.now() + elapsedMs);
  const count = Math.min(satelliteData.length, satelliteCapacity);

  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);

    if (shouldUpdateTrails && i !== lockedSatelliteIndex) {
      const satPos = satellitePositions[i];
      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;
    let g;
    let 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 satPosition = satellitePositions[i];
    for (let j = 0; j < TRAIL_LENGTH; j++) {
      const trailIdx = (i * TRAIL_LENGTH + j) * 3;

      if (j < satPosition.trailCount) {
        const idx =
          (satPosition.trailIndex - satPosition.trailCount + j + TRAIL_LENGTH) %
          TRAIL_LENGTH;
        const trailPoint = satPosition.trail[idx];
        if (trailPoint) {
          trailPositions[trailIdx] = trailPoint.x;
          trailPositions[trailIdx + 1] = trailPoint.y;
          trailPositions[trailIdx + 2] = trailPoint.z;
          const alpha = (j + 1) / satPosition.trailCount;
          trailColors[trailIdx] = r * alpha;
          trailColors[trailIdx + 1] = g * alpha;
          trailColors[trailIdx + 2] = b * alpha;
          continue;
        }
      }

      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 < satelliteCapacity; 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;

  // Keep the hover ring synced with the propagated satellite position even
  // when the pointer stays still and no new hover event is emitted.
  if (
    hoveredSatelliteIndex !== null &&
    hoveredSatelliteIndex >= 0 &&
    hoveredSatelliteIndex < count &&
    hoveredSatelliteIndex !== lockedSatelliteIndex
  ) {
    updateHoverRingPosition(satellitePositions[hoveredSatelliteIndex].current);
  }
}

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;
}

export function setSatelliteCamera(camera) {
  cameraRef = camera;
}

export function setLockedSatelliteIndex(index) {
  lockedSatelliteIndex = index;
}

export function setHoveredSatelliteIndex(index) {
  hoveredSatelliteIndex = index;
}

export function isSatelliteFrontFacing(index, camera = cameraRef) {
  if (!earthObjRef || !camera) return true;
  if (!satellitePositions || !satellitePositions[index]) return true;

  const satPos = satellitePositions[index].current;
  if (!satPos) return true;

  scratchWorldSatellitePosition
    .copy(satPos)
    .applyMatrix4(earthObjRef.matrixWorld);
  scratchToCamera.subVectors(camera.position, earthObjRef.position).normalize();
  scratchToSatellite
    .subVectors(scratchWorldSatellitePosition, earthObjRef.position)
    .normalize();

  return scratchToCamera.dot(scratchToSatellite) > 0;
}

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;
}

function createRingSprite(position, isLocked = false) {
  if (!earthObjRef) return null;

  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 sprite = new THREE.Sprite(spriteMaterial);
  sprite.position.copy(position);
  sprite.scale.set(SATELLITE_CONFIG.ringSize, SATELLITE_CONFIG.ringSize, 1);
  earthObjRef.add(sprite);
  return sprite;
}

export function showHoverRing(position, isLocked = false) {
  if (!earthObjRef || !position) return null;

  if (isLocked) {
    hideLockedRing();
    lockedRingSprite = createRingSprite(position, true);

    const dotCanvas = createBrighterDotCanvas();
    const dotTexture = new THREE.CanvasTexture(dotCanvas);
    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, 4, 1);
    earthObjRef.add(lockedDotSprite);
    return lockedRingSprite;
  }

  hideHoverRings();
  hoverRingSprite = createRingSprite(position, false);
  return hoverRingSprite;
}

export function hideHoverRings() {
  if (hoverRingSprite) {
    disposeObject3D(hoverRingSprite);
    hoverRingSprite = null;
  }
}

export function hideLockedRing() {
  if (lockedRingSprite) {
    disposeObject3D(lockedRingSprite);
    lockedRingSprite = null;
  }
  if (lockedDotSprite) {
    disposeObject3D(lockedDotSprite);
    lockedDotSprite = null;
  }
}

export function updateLockedRingPosition(position) {
  if (!position) return;
  if (lockedRingSprite) {
    lockedRingSprite.position.copy(position);
    const breathScale =
      1 + Math.sin(breathingPhase) * SATELLITE_CONFIG.breathingScaleAmplitude;
    lockedRingSprite.scale.set(
      SATELLITE_CONFIG.ringSize * breathScale,
      SATELLITE_CONFIG.ringSize * breathScale,
      1,
    );
    lockedRingSprite.material.opacity =
      SATELLITE_CONFIG.breathingOpacityMin +
      Math.sin(breathingPhase) *
        (SATELLITE_CONFIG.breathingOpacityMax -
          SATELLITE_CONFIG.breathingOpacityMin);
  }

  if (lockedDotSprite) {
    lockedDotSprite.position.copy(position);
    const dotBreathScale =
      1 +
      Math.sin(breathingPhase) * SATELLITE_CONFIG.dotBreathingScaleAmplitude;
    lockedDotSprite.scale.set(4 * dotBreathScale, 4 * dotBreathScale, 1);
    lockedDotSprite.material.opacity =
      SATELLITE_CONFIG.dotOpacityMin +
      Math.sin(breathingPhase) *
        (SATELLITE_CONFIG.dotOpacityMax - SATELLITE_CONFIG.dotOpacityMin);
  }
}

export function updateHoverRingPosition(position) {
  if (hoverRingSprite && position) {
    hoverRingSprite.position.copy(position);
    hoverRingSprite.scale.set(
      SATELLITE_CONFIG.ringSize,
      SATELLITE_CONFIG.ringSize,
      1,
    );
  }
}

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

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

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

function calculatePredictedOrbit(
  satellite,
  periodSeconds,
  sampleInterval = 10,
) {
  const points = [];
  const samples = Math.ceil(periodSeconds / sampleInterval);
  const now = new Date();

  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 (points.length < samples * 0.5) {
    points.length = 0;
    const radius = CONFIG.earthRadius + 5;
    const inclination = satellite.properties?.inclination || 53;
    const raan = satellite.properties?.raan || 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();
  if (!earthObjRef) return;

  const meanMotion = satellite.properties?.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) {
    disposeObject3D(predictedOrbitLine);
    predictedOrbitLine = null;
  }
}

export function clearSatelliteData() {
  satelliteData = [];
  selectedSatellite = null;
  lockedSatelliteIndex = null;
  hoveredSatelliteIndex = null;
  positionUpdateAccumulator = 0;

  satellitePositions.forEach((position) => {
    position.current.set(0, 0, 0);
    position.trail = [];
    position.trailIndex = 0;
    position.trailCount = 0;
  });

  if (satellitePoints) {
    const positionAttr = satellitePoints.geometry.attributes.position;
    const colorAttr = satellitePoints.geometry.attributes.color;
    if (positionAttr?.array) {
      positionAttr.array.fill(0);
      positionAttr.needsUpdate = true;
    }
    if (colorAttr?.array) {
      colorAttr.array.fill(0);
      colorAttr.needsUpdate = true;
    }
    satellitePoints.geometry.setDrawRange(0, 0);
  }

  if (satelliteTrails) {
    const trailPositionAttr = satelliteTrails.geometry.attributes.position;
    const trailColorAttr = satelliteTrails.geometry.attributes.color;
    if (trailPositionAttr?.array) {
      trailPositionAttr.array.fill(0);
      trailPositionAttr.needsUpdate = true;
    }
    if (trailColorAttr?.array) {
      trailColorAttr.array.fill(0);
      trailColorAttr.needsUpdate = true;
    }
  }

  hideHoverRings();
  hideLockedRing();
  hidePredictedOrbit();
}

export function resetSatelliteState() {
  clearSatelliteData();

  if (satellitePoints) {
    disposeObject3D(satellitePoints);
    satellitePoints = null;
  }

  if (satelliteTrails) {
    disposeObject3D(satelliteTrails);
    satelliteTrails = null;
  }

  satellitePositions = [];
  satelliteCapacity = 0;
  showSatellites = false;
  showTrails = true;
}
