// Helper functions for map.ts to reduce code duplication

import type * as L from 'leaflet';
import type { BaseEventPayload, PushEventFunction } from './types/events';
import type { LiveSocket } from './types/map';

export interface MapState {
  lat: number;
  lng: number;
  zoom: number;
}

export interface BoundsData {
  north: number;
  south: number;
  east: number;
  west: number;
}

/**
 * Escape HTML special characters to prevent XSS when building DOM strings.
 */
export function escapeHtml(str: string): string {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

/**
 * Parse timestamp to milliseconds
 */
export function parseTimestamp(timestamp: string | number | Date | undefined): number {
  if (!timestamp) return Date.now();
  
  if (typeof timestamp === "number") {
    return timestamp;
  } else if (typeof timestamp === "string") {
    const parsed = new Date(timestamp).getTime();
    return Number.isNaN(parsed) ? Date.now() : parsed;
  } else if (timestamp instanceof Date) {
    const parsed = timestamp.getTime();
    return Number.isNaN(parsed) ? Date.now() : parsed;
  }
  return Date.now();
}

/**
 * Get trail ID from marker data
 */
export function getTrailId(data: { callsign_group?: string; callsign?: string; id: string | number }): string {
  // Prioritize callsign_group and callsign over extracting from ID
  if (data.callsign_group) {
    return data.callsign_group;
  }
  
  if (data.callsign) {
    return data.callsign;
  }
  
  // For historical markers like "hist_CALLSIGN_123", extract the base callsign
  const id = String(data.id);
  if (id.startsWith("hist_")) {
    // Remove hist_ prefix and any trailing numeric index
    const withoutPrefix = id.replace(/^hist_/, "");
    // Remove trailing _digits pattern
    return withoutPrefix.replace(/_\d+$/, "");
  }
  
  // For regular IDs, return as string
  return String(data.id);
}

/**
 * Save map state to localStorage and send to server
 */
// PushEventFunction is now imported from types/events

export function saveMapState(map: L.Map, pushEvent: PushEventFunction) {
  if (!map || !pushEvent) {
    console.warn("saveMapState called with invalid map or pushEvent");
    return;
  }
  
  try {
    const center = map.getCenter();
    const zoom = map.getZoom();
    
    // Truncate lat/lng to 5 decimal places for URL
    const truncatedLat = Math.round(center.lat * 100000) / 100000;
    const truncatedLng = Math.round(center.lng * 100000) / 100000;
    
    // Send combined map state update to server for URL and bounds updating
    const payload = {
      center: { lat: truncatedLat, lng: truncatedLng },
      zoom: zoom,
      bounds: {
        north: map.getBounds().getNorth(),
        south: map.getBounds().getSouth(),
        east: map.getBounds().getEast(),
        west: map.getBounds().getWest(),
      }
    };
    
    // Use safePushEvent to handle disconnected state
    safePushEvent(pushEvent, "update_map_state", payload);
  } catch (error) {
    console.error("Error in saveMapState:", error);
  }
}

/**
 * Safely push event to LiveView
 */
export function safePushEvent<T extends BaseEventPayload = BaseEventPayload>(pushEvent: PushEventFunction | undefined, event: string, payload: T): boolean {
  if (!pushEvent || typeof pushEvent !== 'function') {
    return false;
  }
  
  try {
    pushEvent(event, payload);
    return true;
  } catch (e) {
    return false;
  }
}

/**
 * Check if LiveView socket is available
 */
export function isLiveViewConnected(): boolean {
  const liveSocket = getLiveSocket();
  if (!liveSocket) {
    return false;
  }

  if (typeof liveSocket.isConnected === "function") {
    return liveSocket.isConnected();
  }

  return liveSocket.connected !== false;
}

/**
 * Get LiveView socket
 */
export function getLiveSocket(): LiveSocket | null {
  return typeof window !== 'undefined' ? window.liveSocket || null : null;
}

/**
 * Validate that a lat/lng pair is a real, in-range, finite coordinate.
 * Used everywhere we receive coordinates from the server or the DOM, so
 * NaN / Infinity / out-of-range values never reach Leaflet.
 */
export function isValidCoordinate(lat: number, lng: number): boolean {
  return (
    typeof lat === "number" &&
    typeof lng === "number" &&
    !isNaN(lat) &&
    !isNaN(lng) &&
    isFinite(lat) &&
    isFinite(lng) &&
    lat >= -90 &&
    lat <= 90 &&
    lng >= -180 &&
    lng <= 180
  );
}

/**
 * Unwrap a sequence of [lat, lng] points so a polyline that crosses the
 * antimeridian renders as a short hop instead of a line looping around the
 * world. Each point's lng is shifted by a cumulative multiple of 360 so the
 * delta between consecutive points is always the short-way difference.
 */
export function unwrapLongitudes(
  positions: [number, number][],
): [number, number][] {
  if (positions.length === 0) return [];
  const unwrapped: [number, number][] = [[positions[0][0], positions[0][1]]];
  let offset = 0;
  for (let i = 1; i < positions.length; i++) {
    const [lat, lng] = positions[i];
    const prevRawLng = positions[i - 1][1];
    const delta = lng - prevRawLng;
    if (delta > 180) offset -= 360;
    else if (delta < -180) offset += 360;
    unwrapped.push([lat, lng + offset]);
  }
  return unwrapped;
}
