<?php

namespace App\Traits;

use App\Models\Location;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Support\Facades\DB;

trait HasLocation
{
    /**
     * Boot the trait.
     */
    public static function bootHasLocation(): void
    {
        static::deleting(function ($model) {
            // Delete associated location when model is deleted
            $model->location?->delete();
        });
    }

    /**
     * Get the model's location.
     */
    public function location(): MorphOne
    {
        return $this->morphOne(Location::class, 'locatable');
    }

    /**
     * Get the model's coordinates.
     *
     * @return array<string, float|null>
     */
    public function getCoordinatesAttribute(): array
    {
        if (!$this->location?->coordinates) {
            return [
                'lat' => null,
                'lng' => null,
            ];
        }

        return [
            'lat' => $this->location->coordinates->getLat(),
            'lng' => $this->location->coordinates->getLng(),
        ];
    }

    /**
     * Get the model's formatted address.
     *
     * @return string|null
     */
    public function getFormattedAddressAttribute(): ?string
    {
        return $this->location?->formatted_address;
    }

    /**
     * Get the model's map URL.
     *
     * @return string|null
     */
    public function getMapUrlAttribute(): ?string
    {
        return $this->location?->map_url;
    }

    /**
     * Update or create the model's location.
     *
     * @param array<string, mixed> $attributes
     * @return \App\Models\Location
     */
    public function updateLocation(array $attributes): Location
    {
        if ($this->location) {
            $this->location->update($attributes);
            return $this->location;
        }

        return $this->location()->create($attributes);
    }

    /**
     * Set the model's coordinates.
     *
     * @param float $latitude
     * @param float $longitude
     * @param float|null $elevation
     * @return \App\Models\Location
     */
    public function setCoordinates(float $latitude, float $longitude, ?float $elevation = null): Location
    {
        return $this->updateLocation([
            'coordinates' => DB::raw("ST_SetSRID(ST_MakePoint($longitude, $latitude), 4326)"),
            'elevation' => $elevation,
        ]);
    }

    /**
     * Set the model's address.
     *
     * @param array<string, string> $components
     * @return \App\Models\Location
     */
    public function setAddress(array $components): Location
    {
        return $this->updateLocation($components);
    }

    /**
     * Calculate distance to another model with location.
     *
     * @param mixed $model Model using HasLocation trait
     * @param string $unit 'km' or 'mi'
     * @return float|null
     */
    public function distanceTo($model, string $unit = 'km'): ?float
    {
        if (!$this->location || !$model->location) {
            return null;
        }

        return $this->location->distanceTo(
            $model->location->latitude,
            $model->location->longitude,
            $unit
        );
    }

    /**
     * Scope a query to find models near coordinates.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param float $latitude
     * @param float $longitude
     * @param float $radius
     * @param string $unit
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeNearby($query, float $latitude, float $longitude, float $radius, string $unit = 'km')
    {
        $point = DB::raw("ST_SetSRID(ST_MakePoint($longitude, $latitude), 4326)");
        $distance = $unit === 'mi' ? $radius * 1609.34 : $radius * 1000;

        return $query->whereHas('location', function ($query) use ($point, $distance) {
            $query->whereRaw(
                "ST_DWithin(coordinates::geography, ?::geography, ?)",
                [$point, $distance]
            );
        });
    }

    /**
     * Scope a query to find models within bounds.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param array<string, float> $ne Northeast corner [lat, lng]
     * @param array<string, float> $sw Southwest corner [lat, lng]
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeInBounds($query, array $ne, array $sw)
    {
        $bounds = DB::raw(
            "ST_MakeEnvelope(
                {$sw['lng']}, {$sw['lat']},
                {$ne['lng']}, {$ne['lat']},
                4326
            )"
        );

        return $query->whereHas('location', function ($query) use ($bounds) {
            $query->whereRaw("ST_Within(coordinates::geometry, ?::geometry)", [$bounds]);
        });
    }
}