<?php

namespace App\Helpers;

class GeoHelper
{
    /**
     * Menghitung jarak antara dua titik koordinat menggunakan formula Haversine
     *
     * @param float $lat1 Latitude titik pertama
     * @param float $lon1 Longitude titik pertama
     * @param float $lat2 Latitude titik kedua
     * @param float $lon2 Longitude titik kedua
     * @return float Jarak dalam meter
     */
    public static function calculateDistance(
        float $lat1,
        float $lon1,
        float $lat2,
        float $lon2
    ): float {
        $earthRadius = 6371000; // Radius bumi dalam meter

        $lat1Rad = deg2rad($lat1);
        $lat2Rad = deg2rad($lat2);
        $deltaLat = deg2rad($lat2 - $lat1);
        $deltaLon = deg2rad($lon2 - $lon1);

        $a = sin($deltaLat / 2) * sin($deltaLat / 2) +
            cos($lat1Rad) * cos($lat2Rad) *
            sin($deltaLon / 2) * sin($deltaLon / 2);

        $c = 2 * atan2(sqrt($a), sqrt(1 - $a));

        return $earthRadius * $c;
    }

    /**
     * Mengecek apakah koordinat berada dalam radius tertentu dari titik referensi
     *
     * @param float $lat Latitude yang dicek
     * @param float $lon Longitude yang dicek
     * @param float $refLat Latitude titik referensi
     * @param float $refLon Longitude titik referensi
     * @param int $radius Radius dalam meter
     * @return bool
     */
    public static function isWithinRadius(
        float $lat,
        float $lon,
        float $refLat,
        float $refLon,
        int $radius
    ): bool {
        $distance = self::calculateDistance($lat, $lon, $refLat, $refLon);
        return $distance <= $radius;
    }

    /**
     * Mendapatkan bounding box untuk pencarian lokasi dalam radius tertentu
     *
     * @param float $lat Latitude pusat
     * @param float $lon Longitude pusat
     * @param int $radiusKm Radius dalam kilometer
     * @return array [minLat, maxLat, minLon, maxLon]
     */
    public static function getBoundingBox(float $lat, float $lon, int $radiusKm): array
    {
        $latDelta = $radiusKm / 111.32; // 1 derajat latitude ≈ 111.32 km
        $lonDelta = $radiusKm / (111.32 * cos(deg2rad($lat)));

        return [
            'min_lat' => $lat - $latDelta,
            'max_lat' => $lat + $latDelta,
            'min_lon' => $lon - $lonDelta,
            'max_lon' => $lon + $lonDelta,
        ];
    }

    /**
     * Generate titik random dalam radius tertentu dari titik referensi
     * Berguna untuk rekomendasi lokasi presensi dinas
     *
     * @param float $lat Latitude titik referensi
     * @param float $lon Longitude titik referensi
     * @param int $radius Radius dalam meter
     * @return array [latitude, longitude]
     */
    public static function generateRandomPointInRadius(
        float $lat,
        float $lon,
        int $radius
    ): array {
        // Random distance dari 0 sampai radius
        $randomDistance = sqrt(mt_rand() / mt_getrandmax()) * $radius;

        // Random angle dari 0 sampai 360 derajat
        $randomAngle = mt_rand(0, 360) * M_PI / 180;

        // Konversi ke perubahan latitude dan longitude
        $deltaLat = $randomDistance * cos($randomAngle) / 111320;
        $deltaLon = $randomDistance * sin($randomAngle) / (111320 * cos(deg2rad($lat)));

        return [
            'latitude' => $lat + $deltaLat,
            'longitude' => $lon + $deltaLon,
        ];
    }

    /**
     * Validasi koordinat GPS
     *
     * @param float|null $lat
     * @param float|null $lon
     * @return bool
     */
    public static function isValidCoordinates(?float $lat, ?float $lon): bool
    {
        if ($lat === null || $lon === null) {
            return false;
        }

        // Latitude: -90 sampai 90
        // Longitude: -180 sampai 180
        return $lat >= -90 && $lat <= 90 && $lon >= -180 && $lon <= 180;
    }

    /**
     * Format koordinat untuk display
     *
     * @param float $lat
     * @param float $lon
     * @return string
     */
    public static function formatCoordinates(float $lat, float $lon): string
    {
        $latDir = $lat >= 0 ? 'N' : 'S';
        $lonDir = $lon >= 0 ? 'E' : 'W';

        return sprintf(
            "%.6f° %s, %.6f° %s",
            abs($lat),
            $latDir,
            abs($lon),
            $lonDir
        );
    }

    /**
     * Mengecek apakah dua lokasi berada di kota/kabupaten yang sama
     * Berdasarkan perbandingan string kota
     *
     * @param string|null $city1
     * @param string|null $city2
     * @return bool
     */
    public static function isSameCity(?string $city1, ?string $city2): bool
    {
        if (empty($city1) || empty($city2)) {
            return false;
        }

        // Normalize dan bandingkan
        $normalized1 = self::normalizeCity($city1);
        $normalized2 = self::normalizeCity($city2);

        return $normalized1 === $normalized2;
    }

    /**
     * Normalize nama kota untuk perbandingan
     *
     * @param string $city
     * @return string
     */
    private static function normalizeCity(string $city): string
    {
        $city = strtolower(trim($city));

        // Hapus prefix umum
        $prefixes = ['kota ', 'kabupaten ', 'kab. ', 'kab ', 'kec. ', 'kec '];
        foreach ($prefixes as $prefix) {
            if (str_starts_with($city, $prefix)) {
                $city = substr($city, strlen($prefix));
            }
        }

        return trim($city);
    }
}
