<?php

declare(strict_types=1);

namespace App\Framework\Core\ValueObjects;

use InvalidArgumentException;

/**
 * Geographic coordinates value object (latitude/longitude)
 */
final readonly class Coordinates
{
    public function __construct(
        public float $latitude,
        public float $longitude
    ) {
        $this->validate();
    }

    /**
     * Create from latitude and longitude
     */
    public static function fromLatLng(float $latitude, float $longitude): self
    {
        return new self($latitude, $longitude);
    }

    /**
     * Create from decimal degrees string
     */
    public static function fromString(string $coordinates): self
    {
        $parts = array_map('trim', explode(',', $coordinates));

        if (count($parts) !== 2) {
            throw new InvalidArgumentException('Coordinates must be in format "latitude,longitude"');
        }

        $latitude = (float) $parts[0];
        $longitude = (float) $parts[1];

        return new self($latitude, $longitude);
    }

    /**
     * Create coordinates for Berlin, Germany
     */
    public static function berlin(): self
    {
        return new self(52.5200, 13.4050);
    }

    /**
     * Create coordinates for New York, USA
     */
    public static function newYork(): self
    {
        return new self(40.7128, -74.0060);
    }

    /**
     * Create coordinates for London, UK
     */
    public static function london(): self
    {
        return new self(51.5074, -0.1278);
    }

    /**
     * Calculate distance to another coordinate in kilometers
     */
    public function distanceTo(self $other): float
    {
        $earthRadius = 6371; // Earth's radius in kilometers

        $lat1Rad = deg2rad($this->latitude);
        $lat2Rad = deg2rad($other->latitude);
        $deltaLatRad = deg2rad($other->latitude - $this->latitude);
        $deltaLngRad = deg2rad($other->longitude - $this->longitude);

        $a = sin($deltaLatRad / 2) * sin($deltaLatRad / 2) +
             cos($lat1Rad) * cos($lat2Rad) *
             sin($deltaLngRad / 2) * sin($deltaLngRad / 2);

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

        return $earthRadius * $c;
    }

    /**
     * Check if coordinates are within a certain radius of another point
     */
    public function isWithinRadius(self $center, float $radiusKm): bool
    {
        return $this->distanceTo($center) <= $radiusKm;
    }

    /**
     * Get hemisphere information
     */
    public function getHemisphere(): array
    {
        return [
            'north_south' => $this->latitude >= 0 ? 'Northern' : 'Southern',
            'east_west' => $this->longitude >= 0 ? 'Eastern' : 'Western',
        ];
    }

    /**
     * Check if coordinates are in the northern hemisphere
     */
    public function isNorthern(): bool
    {
        return $this->latitude >= 0;
    }

    /**
     * Check if coordinates are in the southern hemisphere
     */
    public function isSouthern(): bool
    {
        return $this->latitude < 0;
    }

    /**
     * Check if coordinates are in the eastern hemisphere
     */
    public function isEastern(): bool
    {
        return $this->longitude >= 0;
    }

    /**
     * Check if coordinates are in the western hemisphere
     */
    public function isWestern(): bool
    {
        return $this->longitude < 0;
    }

    /**
     * Get formatted coordinates as string
     */
    public function toString(): string
    {
        return sprintf('%.6f,%.6f', $this->latitude, $this->longitude);
    }

    /**
     * Get formatted coordinates with cardinal directions
     */
    public function toCardinalString(): string
    {
        $latDirection = $this->latitude >= 0 ? 'N' : 'S';
        $lngDirection = $this->longitude >= 0 ? 'E' : 'W';

        return sprintf(
            '%.6f°%s, %.6f°%s',
            abs($this->latitude),
            $latDirection,
            abs($this->longitude),
            $lngDirection
        );
    }

    /**
     * Convert to array
     */
    public function toArray(): array
    {
        return [
            'latitude' => $this->latitude,
            'longitude' => $this->longitude,
            'hemisphere' => $this->getHemisphere(),
            'formatted' => $this->toString(),
        ];
    }

    /**
     * Validate coordinates
     */
    private function validate(): void
    {
        if ($this->latitude < -90.0 || $this->latitude > 90.0) {
            throw new InvalidArgumentException(
                "Latitude must be between -90 and 90 degrees, got: {$this->latitude}"
            );
        }

        if ($this->longitude < -180.0 || $this->longitude > 180.0) {
            throw new InvalidArgumentException(
                "Longitude must be between -180 and 180 degrees, got: {$this->longitude}"
            );
        }
    }

    public function __toString(): string
    {
        return $this->toString();
    }
}
