import { getRadarPosition } from './utilities.jsx';

// ─── Constants ────────────────────────────────────────────────────────────────

export const QUEUE_SIZE = 6;

// Default values (will be overridden by settings)
export const DEFAULT_MIN_ZOOM   = 1.0;
export const DEFAULT_MAX_ZOOM   = 6.0;
export const DEFAULT_PADDING    = 0.01;  // reduced from 0.15 — much tighter fit around players

// Minimum spread to prevent extreme zoom when players are very close together.
// At spread=0 (single player / all at same point), zoom would be MAX_ZOOM without this floor.
const MIN_SPREAD_FOR_ZOOM = 0.04;  // ~4% of map width — roughly 1 bombsite diameter

// ─── computeBoundingBox ───────────────────────────────────────────────────────
/**
 * Compute the bounding box (in radar-percentage [0,1] space) that contains
 * every alive player.
 *
 * @param {Array}       alivePlayers  Players with m_position already filtered to alive.
 * @param {object|null} mapData       Map metadata required by getRadarPosition.
 * @returns {{ x: { min: number, max: number }, y: { min: number, max: number } } | null}
 *   Returns null when mapData is null/undefined or alivePlayers is empty.
 */
export function computeBoundingBox(alivePlayers, mapData) {
  if (!mapData || !alivePlayers || alivePlayers.length === 0) {
    return null;
  }

  const bounds = {
    x: { min: Infinity,  max: -Infinity },
    y: { min: Infinity,  max: -Infinity },
  };

  for (const player of alivePlayers) {
    const pos = getRadarPosition(mapData, player.m_position ?? player);
    bounds.x.min = Math.min(bounds.x.min, pos.x);
    bounds.x.max = Math.max(bounds.x.max, pos.x);
    bounds.y.min = Math.min(bounds.y.min, pos.y);
    bounds.y.max = Math.max(bounds.y.max, pos.y);
  }

  return bounds;
}

// ─── computeTargetScale ───────────────────────────────────────────────────────
/**
 * Compute the target zoom scale from a bounding box.
 *
 * Formula (adapted from boltobserv to [0,1] space):
 *   targetScale = 1 + (1 - spread) - PADDING
 *
 * @param {{ x: { min: number, max: number }, y: { min: number, max: number } }} bounds
 * @param {object} settings - radar settings for min/max zoom and padding
 * @returns {{ scale: number, reset: boolean }}
 *   reset=true signals that pan should also be reset to {x:0, y:0}.
 */
export function computeTargetScale(bounds, settings) {
  const boundWidth  = bounds.x.max - bounds.x.min;
  const boundHeight = bounds.y.max - bounds.y.min;

  // Clamp spread to a minimum so a single player (spread=0) doesn't zoom to MAX_ZOOM.
  // MIN_SPREAD_FOR_ZOOM acts as a virtual "view radius" around the player group.
  const spread = Math.max(Math.max(boundWidth, boundHeight), MIN_SPREAD_FOR_ZOOM);

  // Get settings with fallbacks
  const minZoom = settings?.autoZoomMinZoom ?? DEFAULT_MIN_ZOOM;
  const maxZoom = settings?.autoZoomMaxZoom ?? DEFAULT_MAX_ZOOM;
  const padding = settings?.autoZoomPadding ?? DEFAULT_PADDING;

  // Revised formula: scale inversely with spread, with a multiplier for tighter zoom.
  // When spread=MIN_SPREAD_FOR_ZOOM (≈0.04): raw ≈ 1/0.04 * 0.8 ≈ 20 → clamped to maxZoom
  // When spread=0.15 (few players close): raw ≈ 1/0.15 * 0.8 ≈ 5.3 → zoom 5.3x
  // When spread=0.4 (half map): raw ≈ 1/0.4 * 0.8 ≈ 2.0 → zoom 2.0x
  // When spread=0.7+ (most of map): raw ≈ 1/0.7 * 0.8 ≈ 1.14 → zoom 1.14x
  // Only resets when spread is very high (>0.88)
  const raw = (1 / spread) * 0.8 - padding;

  if (raw < minZoom) {
    return { scale: minZoom, reset: true };
  }

  const scale = Math.min(Math.max(raw, minZoom), maxZoom);
  return { scale, reset: false };
}

// ─── computeTargetPan ─────────────────────────────────────────────────────────
/**
 * Compute the target pan (in pixels) to centre the bounding box in the
 * radar container.
 *
 * @param {{ x: { min: number, max: number }, y: { min: number, max: number } }} bounds
 * @param {{ w: number, h: number }}                                              imgStyle
 * @param {number}                                                                scale
 * @returns {{ x: number, y: number }}
 */
export function computeTargetPan(bounds, imgStyle, scale) {
  if (!imgStyle || imgStyle.w === 0) {
    return { x: 0, y: 0 };
  }

  const centerX = (bounds.x.min + bounds.x.max) / 2;
  const centerY = (bounds.y.min + bounds.y.max) / 2;

  const panX = (0.5 - centerX) * imgStyle.w * scale;
  const panY = (0.5 - centerY) * imgStyle.h * scale;

  return { x: panX, y: panY };
}

// ─── updateQueues ─────────────────────────────────────────────────────────────
/**
 * Prepend new values to each smoothing queue and trim to QUEUE_SIZE.
 * Mutates the queues object in place.
 *
 * @param {{ scale: number[], x: number[], y: number[] }} queues
 * @param {number} newScale
 * @param {number} newPanX
 * @param {number} newPanY
 */
export function updateQueues(queues, newScale, newPanX, newPanY) {
  // Guard: only push finite values; fall back to the last known good value.
  const safeScale = Number.isFinite(newScale) ? newScale : (queues.scale[0] ?? 1);
  const safeX     = Number.isFinite(newPanX)  ? newPanX  : (queues.x[0]     ?? 0);
  const safeY     = Number.isFinite(newPanY)  ? newPanY  : (queues.y[0]     ?? 0);

  queues.scale = [safeScale, ...queues.scale].slice(0, QUEUE_SIZE);
  queues.x     = [safeX,     ...queues.x    ].slice(0, QUEUE_SIZE);
  queues.y     = [safeY,     ...queues.y    ].slice(0, QUEUE_SIZE);
}

// ─── computeAverages ──────────────────────────────────────────────────────────
/**
 * Compute the arithmetic mean of each smoothing queue.
 *
 * @param {{ scale: number[], x: number[], y: number[] }} queues
 * @returns {{ scale: number, x: number, y: number }}
 */
export function computeAverages(queues) {
  const avg = (arr) => {
    if (!arr || arr.length === 0) return 1;
    const sum = arr.reduce((a, b) => a + b, 0);
    const result = sum / arr.length;
    return Number.isFinite(result) ? result : (arr[0] ?? 1);
  };

  return {
    scale: avg(queues.scale),
    x:     avg(queues.x),
    y:     avg(queues.y),
  };
}
