namespace ZB.MOM.WW.CBDD.Core.Indexing.Internal;

/// <summary>
///     Basic spatial point (Latitude/Longitude)
///     Internal primitive for R-Tree logic.
/// </summary>
internal record struct GeoPoint(double Latitude, double Longitude)
{
    /// <summary>
    ///     Gets an empty point at coordinate origin.
    /// </summary>
    public static GeoPoint Empty => new(0, 0);
}

/// <summary>
///     Minimum Bounding Box (MBR) for spatial indexing
///     Internal primitive for R-Tree logic.
/// </summary>
internal record struct GeoBox(double MinLat, double MinLon, double MaxLat, double MaxLon)
{
    /// <summary>
    ///     Gets an empty bounding box sentinel value.
    /// </summary>
    public static GeoBox Empty => new(double.MaxValue, double.MaxValue, double.MinValue, double.MinValue);

    /// <summary>
    ///     Gets the area of this bounding box.
    /// </summary>
    public double Area => Math.Max(0, MaxLat - MinLat) * Math.Max(0, MaxLon - MinLon);

    /// <summary>
    ///     Determines whether this box contains the specified point.
    /// </summary>
    /// <param name="point">The point to test.</param>
    /// <returns><see langword="true" /> if the point is inside this box; otherwise, <see langword="false" />.</returns>
    public bool Contains(GeoPoint point)
    {
        return point.Latitude >= MinLat && point.Latitude <= MaxLat &&
               point.Longitude >= MinLon && point.Longitude <= MaxLon;
    }

    /// <summary>
    ///     Determines whether this box intersects another box.
    /// </summary>
    /// <param name="other">The other box to test.</param>
    /// <returns><see langword="true" /> if the boxes intersect; otherwise, <see langword="false" />.</returns>
    public bool Intersects(GeoBox other)
    {
        return !(other.MinLat > MaxLat || other.MaxLat < MinLat ||
                 other.MinLon > MaxLon || other.MaxLon < MinLon);
    }

    /// <summary>
    ///     Creates a box that contains a single point.
    /// </summary>
    /// <param name="point">The point to convert.</param>
    /// <returns>A bounding box containing the specified point.</returns>
    public static GeoBox FromPoint(GeoPoint point)
    {
        return new GeoBox(point.Latitude, point.Longitude, point.Latitude, point.Longitude);
    }

    /// <summary>
    ///     Expands this box to include the specified point.
    /// </summary>
    /// <param name="point">The point to include.</param>
    /// <returns>A new expanded bounding box.</returns>
    public GeoBox ExpandTo(GeoPoint point)
    {
        return new GeoBox(
            Math.Min(MinLat, point.Latitude),
            Math.Min(MinLon, point.Longitude),
            Math.Max(MaxLat, point.Latitude),
            Math.Max(MaxLon, point.Longitude));
    }

    /// <summary>
    ///     Expands this box to include the specified box.
    /// </summary>
    /// <param name="other">The box to include.</param>
    /// <returns>A new expanded bounding box.</returns>
    public GeoBox ExpandTo(GeoBox other)
    {
        return new GeoBox(
            Math.Min(MinLat, other.MinLat),
            Math.Min(MinLon, other.MinLon),
            Math.Max(MaxLat, other.MaxLat),
            Math.Max(MaxLon, other.MaxLon));
    }
}