/**
 * Location Service Module
 *
 * Provides geographic location context and utilities.
 */
import { EventEmitter } from 'events';
export const DEFAULT_LOCATION_CONFIG = {
    enabled: true,
    preferredSource: 'ip',
    cacheEnabled: true,
    cacheTTLMs: 5 * 60 * 1000, // 5 minutes
    autoUpdateIntervalMs: 0,
    reverseGeocode: true,
};
// ============================================================================
// Utility Functions
// ============================================================================
/**
 * Calculate distance between two points (Haversine formula)
 * @returns Distance in meters
 */
export function calculateDistance(point1, point2) {
    const R = 6371000; // Earth's radius in meters
    const lat1 = toRadians(point1.latitude);
    const lat2 = toRadians(point2.latitude);
    const deltaLat = toRadians(point2.latitude - point1.latitude);
    const deltaLon = toRadians(point2.longitude - point1.longitude);
    const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
        Math.cos(lat1) * Math.cos(lat2) *
            Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return R * c;
}
/**
 * Calculate bearing between two points
 * @returns Bearing in degrees (0-360)
 */
export function calculateBearing(from, to) {
    const lat1 = toRadians(from.latitude);
    const lat2 = toRadians(to.latitude);
    const deltaLon = toRadians(to.longitude - from.longitude);
    const y = Math.sin(deltaLon) * Math.cos(lat2);
    const x = Math.cos(lat1) * Math.sin(lat2) -
        Math.sin(lat1) * Math.cos(lat2) * Math.cos(deltaLon);
    let bearing = toDegrees(Math.atan2(y, x));
    return (bearing + 360) % 360;
}
/**
 * Get cardinal direction from bearing
 */
export function bearingToCardinal(bearing) {
    const directions = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
        'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'];
    const index = Math.round(bearing / 22.5) % 16;
    return directions[index];
}
/**
 * Check if a point is within a radius of another point
 */
export function isWithinRadius(point, center, radiusMeters) {
    return calculateDistance(point, center) <= radiusMeters;
}
/**
 * Convert degrees to radians
 */
function toRadians(degrees) {
    return degrees * (Math.PI / 180);
}
/**
 * Convert radians to degrees
 */
function toDegrees(radians) {
    return radians * (180 / Math.PI);
}
/**
 * Format coordinates as string
 */
export function formatCoordinates(coords, format = 'decimal') {
    if (format === 'decimal') {
        return `${coords.latitude.toFixed(6)}, ${coords.longitude.toFixed(6)}`;
    }
    // DMS format
    const latDir = coords.latitude >= 0 ? 'N' : 'S';
    const lonDir = coords.longitude >= 0 ? 'E' : 'W';
    const formatDMS = (decimal) => {
        const abs = Math.abs(decimal);
        const deg = Math.floor(abs);
        const minFloat = (abs - deg) * 60;
        const min = Math.floor(minFloat);
        const sec = ((minFloat - min) * 60).toFixed(1);
        return `${deg}°${min}'${sec}"`;
    };
    return `${formatDMS(coords.latitude)}${latDir}, ${formatDMS(coords.longitude)}${lonDir}`;
}
// ============================================================================
// Location Service
// ============================================================================
export class LocationService extends EventEmitter {
    config;
    cachedLocation = null;
    cacheTimestamp = 0;
    autoUpdateInterval = null;
    currentSource = 'ip';
    // Mock location for testing
    mockLocation = null;
    constructor(config = {}) {
        super();
        this.config = { ...DEFAULT_LOCATION_CONFIG, ...config };
        this.currentSource = this.config.preferredSource;
    }
    // ============================================================================
    // Configuration
    // ============================================================================
    /**
     * Get current configuration
     */
    getConfig() {
        return { ...this.config };
    }
    /**
     * Update configuration
     */
    updateConfig(config) {
        this.config = { ...this.config, ...config };
        // Restart auto-update if interval changed
        if (config.autoUpdateIntervalMs !== undefined) {
            this.stopAutoUpdate();
            if (config.autoUpdateIntervalMs > 0) {
                this.startAutoUpdate();
            }
        }
    }
    // ============================================================================
    // Location Retrieval
    // ============================================================================
    /**
     * Get current location
     */
    async getCurrentLocation(options) {
        // Check mock first
        if (this.mockLocation) {
            return this.mockLocation;
        }
        // Check cache
        if (!options?.forceRefresh &&
            this.config.cacheEnabled &&
            this.cachedLocation &&
            Date.now() - this.cacheTimestamp < this.config.cacheTTLMs) {
            return this.cachedLocation;
        }
        const source = options?.source || this.currentSource;
        let location;
        try {
            switch (source) {
                case 'ip':
                    location = await this.getLocationByIP();
                    break;
                case 'manual':
                    if (this.config.defaultLocation) {
                        location = this.createLocation(this.config.defaultLocation, 'manual');
                    }
                    else {
                        throw new Error('No default location configured');
                    }
                    break;
                case 'cached':
                    if (this.cachedLocation) {
                        location = this.cachedLocation;
                    }
                    else {
                        throw new Error('No cached location available');
                    }
                    break;
                default:
                    // For GPS/network, fall back to IP in mock implementation
                    location = await this.getLocationByIP();
            }
            // Reverse geocode if enabled
            if (this.config.reverseGeocode && !location.address) {
                location.address = await this.reverseGeocode(location);
            }
            // Add timezone
            if (!location.timezone) {
                location.timezone = this.getTimezone(location);
            }
            // Update cache
            if (this.config.cacheEnabled) {
                this.cachedLocation = location;
                this.cacheTimestamp = Date.now();
            }
            this.emit('location-update', location);
            return location;
        }
        catch (error) {
            const err = error instanceof Error ? error : new Error(String(error));
            this.emit('location-error', err);
            throw err;
        }
    }
    /**
     * Get location by IP (mock implementation)
     */
    async getLocationByIP() {
        // Mock IP geolocation response
        // In real implementation, this would call an IP geolocation API
        await new Promise(resolve => setTimeout(resolve, 50));
        return this.createLocation({
            latitude: 48.8566,
            longitude: 2.3522,
            accuracy: 1000, // 1km accuracy for IP-based
        }, 'ip', 'Paris, France');
    }
    /**
     * Create a GeoLocation object
     */
    createLocation(coords, source, name) {
        return {
            ...coords,
            timestamp: new Date(),
            source,
            name,
        };
    }
    /**
     * Reverse geocode coordinates to address
     */
    async reverseGeocode(location) {
        // Mock reverse geocoding
        // In real implementation, this would call a geocoding API
        await new Promise(resolve => setTimeout(resolve, 50));
        // Return mock address based on known locations
        if (Math.abs(location.latitude - 48.8566) < 0.1 &&
            Math.abs(location.longitude - 2.3522) < 0.1) {
            return {
                city: 'Paris',
                state: 'Île-de-France',
                country: 'France',
                countryCode: 'FR',
                postalCode: '75001',
                formatted: 'Paris, France',
            };
        }
        return {
            formatted: `${location.latitude.toFixed(4)}, ${location.longitude.toFixed(4)}`,
        };
    }
    /**
     * Get timezone for location
     */
    getTimezone(location) {
        // Simple timezone estimation based on longitude
        // Real implementation would use a timezone database
        const offsetHours = Math.round(location.longitude / 15);
        return {
            id: 'Europe/Paris', // Mock
            abbreviation: 'CET',
            offsetMinutes: offsetHours * 60,
            isDST: false,
        };
    }
    // ============================================================================
    // Auto-Update
    // ============================================================================
    /**
     * Start auto-updating location
     */
    startAutoUpdate() {
        if (this.autoUpdateInterval)
            return;
        if (this.config.autoUpdateIntervalMs > 0) {
            this.autoUpdateInterval = setInterval(() => {
                this.getCurrentLocation({ forceRefresh: true }).catch(error => {
                    this.emit('location-error', error instanceof Error ? error : new Error(String(error)));
                });
            }, this.config.autoUpdateIntervalMs);
        }
    }
    /**
     * Stop auto-updating location
     */
    stopAutoUpdate() {
        if (this.autoUpdateInterval) {
            clearInterval(this.autoUpdateInterval);
            this.autoUpdateInterval = null;
        }
    }
    // ============================================================================
    // Source Management
    // ============================================================================
    /**
     * Get current location source
     */
    getSource() {
        return this.currentSource;
    }
    /**
     * Set location source
     */
    setSource(source) {
        this.currentSource = source;
        this.emit('source-change', source);
    }
    // ============================================================================
    // Mock Support (for testing)
    // ============================================================================
    /**
     * Set mock location (for testing)
     */
    setMockLocation(location) {
        this.mockLocation = location;
    }
    /**
     * Create mock location from coordinates
     */
    createMockLocation(latitude, longitude, options) {
        return {
            latitude,
            longitude,
            timestamp: new Date(),
            source: 'mock',
            ...options,
        };
    }
    // ============================================================================
    // Cache Management
    // ============================================================================
    /**
     * Clear location cache
     */
    clearCache() {
        this.cachedLocation = null;
        this.cacheTimestamp = 0;
    }
    /**
     * Get cached location
     */
    getCachedLocation() {
        return this.cachedLocation;
    }
    // ============================================================================
    // Distance & Direction
    // ============================================================================
    /**
     * Get distance to a point from current location
     */
    async getDistanceTo(point) {
        const current = await this.getCurrentLocation();
        return calculateDistance(current, point);
    }
    /**
     * Get bearing to a point from current location
     */
    async getBearingTo(point) {
        const current = await this.getCurrentLocation();
        return calculateBearing(current, point);
    }
    /**
     * Check if within radius of a point
     */
    async isWithinRadius(center, radiusMeters) {
        const current = await this.getCurrentLocation();
        return isWithinRadius(current, center, radiusMeters);
    }
    // ============================================================================
    // Cleanup
    // ============================================================================
    /**
     * Shutdown the service
     */
    shutdown() {
        this.stopAutoUpdate();
        this.clearCache();
        this.mockLocation = null;
    }
    /**
     * Get service stats
     */
    getStats() {
        return {
            enabled: this.config.enabled,
            source: this.currentSource,
            hasCachedLocation: this.cachedLocation !== null,
            cacheAge: this.cachedLocation ? Date.now() - this.cacheTimestamp : 0,
            isAutoUpdating: this.autoUpdateInterval !== null,
        };
    }
}
// ============================================================================
// Singleton
// ============================================================================
let locationServiceInstance = null;
export function getLocationService(config) {
    if (!locationServiceInstance) {
        locationServiceInstance = new LocationService(config);
    }
    return locationServiceInstance;
}
export function resetLocationService() {
    if (locationServiceInstance) {
        locationServiceInstance.shutdown();
        locationServiceInstance = null;
    }
}
//# sourceMappingURL=index.js.map