export function haversineKm(lat1, lon1, lat2, lon2) {
  const R = 6371;
  const dLat = ((lat2 - lat1) * Math.PI) / 180;
  const dLon = ((lon2 - lon1) * Math.PI) / 180;
  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos((lat1 * Math.PI) / 180) *
      Math.cos((lat2 * Math.PI) / 180) *
      Math.sin(dLon / 2) ** 2;
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}

// --- Position Cache (localStorage) ---
const POSITION_CACHE_KEY = 'farmafinder_last_position';
const POSITION_CACHE_MAX_AGE = 5 * 60 * 1000; // 5 minutes

function getCachedPosition() {
  try {
    const raw = localStorage.getItem(POSITION_CACHE_KEY);
    if (!raw) return null;
    const cached = JSON.parse(raw);
    if (Date.now() - cached.timestamp > POSITION_CACHE_MAX_AGE) return null;
    return { lat: cached.lat, lon: cached.lon };
  } catch {
    return null;
  }
}

function setCachedPosition(lat, lon) {
  try {
    localStorage.setItem(POSITION_CACHE_KEY, JSON.stringify({ lat, lon, timestamp: Date.now() }));
  } catch {}
}

function clearCachedPosition() {
  try {
    localStorage.removeItem(POSITION_CACHE_KEY);
  } catch {}
}

// --- Position Manager ---
let pendingPositionRequest = null;

/**
 * Get user position with caching strategy:
 * 1. Return cached position if fresh (< 5 min)
 * 2. Request browser geolocation in background
 * 3. If browser fails, fall back to cached (up to 15 min)
 *
 * This makes the first request nearly instant for repeat users.
 */
export function getUserPosition({ timeout = 15000, maximumAge = 300000, retries = 1 } = {}) {
  // Cancel any pending request before starting a new one
  if (pendingPositionRequest) {
    pendingPositionRequest.cancel();
    pendingPositionRequest = null;
  }

  // 1. Fast path: return fresh cached position immediately
  const cached = getCachedPosition();
  if (cached) {
    // Also refresh in background (don't await)
    refreshPositionInBackground(timeout, maximumAge);
    return Promise.resolve(cached);
  }

  // 2. Slow path: request from browser
  return requestBrowserPosition({ timeout, maximumAge, retries }).then(pos => {
    setCachedPosition(pos.lat, pos.lon);
    return pos;
  });
}

/**
 * Refresh position silently in background — updates cache for next call.
 */
function refreshPositionInBackground(timeout, maximumAge) {
  if (!navigator.geolocation) return;

  navigator.geolocation.getCurrentPosition(
    pos => {
      const newPos = { lat: pos.coords.latitude, lon: pos.coords.longitude };
      setCachedPosition(newPos.lat, newPos.lon);
    },
    () => {}, // silently fail — we already have a cached position
    { timeout, maximumAge, enableHighAccuracy: false }
  );
}

/**
 * Core browser geolocation request with retry logic.
 */
function requestBrowserPosition({ timeout = 15000, maximumAge = 300000, retries = 1 } = {}) {
  return new Promise((resolve, reject) => {
    if (!navigator.geolocation) {
      reject(new Error('Geolocalización no compatible con este navegador'));
      return;
    }
    if (typeof window !== 'undefined' && window.isSecureContext === false) {
      reject(new Error('La geolocalización requiere HTTPS (o localhost)'));
      return;
    }

    let attempts = 0;
    let watchId = null;
    let settled = false;

    function cleanup() {
      if (watchId !== null) {
        navigator.geolocation.clearWatch(watchId);
        watchId = null;
      }
      if (pendingPositionRequest?.id === requestId) {
        pendingPositionRequest = null;
      }
    }

    function succeed(pos) {
      if (settled) return;
      settled = true;
      cleanup();
      const newPos = { lat: pos.coords.latitude, lon: pos.coords.longitude };
      setCachedPosition(newPos.lat, newPos.lon);
      resolve(newPos);
    }

    function fail(err) {
      if (settled) return;
      attempts++;
      if (attempts <= retries && err?.code === 3) {
        const delay = Math.min(1000 * Math.pow(2, attempts - 1), 5000);
        console.warn(`[geo] timeout, retrying in ${delay}ms (attempt ${attempts}/${retries})`);
        setTimeout(() => {
          if (!settled) startWatching();
        }, delay);
      } else {
        settled = true;
        cleanup();
        console.error('[geo] getCurrentPosition failed — code:', err?.code, 'message:', err?.message);
        reject(err);
      }
    }

    function startWatching() {
      if (settled) return;
      watchId = navigator.geolocation.watchPosition(succeed, fail, {
        timeout,
        maximumAge,
        enableHighAccuracy: false,
      });
      setTimeout(() => {
        if (!settled && watchId !== null) {
          fail({ code: 3, message: 'Timeout' });
        }
      }, timeout + 2000);
    }

    const requestId = Date.now();
    pendingPositionRequest = { id: requestId, cancel: () => { settled = true; cleanup(); } };

    startWatching();
  });
}

/**
 * Clear cached position (e.g. on logout or when user explicitly wants fresh location).
 */
export function clearPositionCache() {
  clearCachedPosition();
}

/**
 * Check if we have any cached position (for UI hints).
 */
export function hasCachedPosition() {
  return getCachedPosition() !== null;
}

export function formatDistance(km) {
  if (km < 1) return `${Math.round(km * 1000)} m`;
  if (km < 10) return `${km.toFixed(1)} km`;
  return `${Math.round(km)} km`;
}
