using System;
using System.Numerics;
using Autodesk.Navisworks.Api;

namespace NavisworksTransport.Utils.CoordinateSystem
{
    /// <summary>
    /// 吊装路径在宿主坐标与 Canonical Space 之间的坐标语义辅助。
    /// 统一表达“沿宿主 up 的高程”和“宿主水平面投影”，避免把世界 Z 误当成向上轴。
    /// </summary>
    public static class HoistingCoordinateHelper
    {
        public static double GetElevation(Point3D hostPoint, HostCoordinateAdapter adapter)
        {
            if (hostPoint == null)
            {
                throw new ArgumentNullException(nameof(hostPoint));
            }

            return GetElevation(ToVector3(hostPoint), adapter);
        }

        public static double GetElevation(Vector3 hostPoint, HostCoordinateAdapter adapter)
        {
            if (adapter == null)
            {
                throw new ArgumentNullException(nameof(adapter));
            }

            return adapter.ToCanonicalPoint3(hostPoint).Z;
        }

        public static Point3D SetElevation(Point3D hostPoint, double elevation, HostCoordinateAdapter adapter)
        {
            if (hostPoint == null)
            {
                throw new ArgumentNullException(nameof(hostPoint));
            }

            return ToPoint3D(SetElevation3(ToVector3(hostPoint), elevation, adapter));
        }

        public static Vector3 SetElevation3(Vector3 hostPoint, double elevation, HostCoordinateAdapter adapter)
        {
            if (adapter == null)
            {
                throw new ArgumentNullException(nameof(adapter));
            }

            Vector3 canonicalPoint = adapter.ToCanonicalPoint3(hostPoint);
            Vector3 canonicalResult = new Vector3(canonicalPoint.X, canonicalPoint.Y, (float)elevation);
            return adapter.FromCanonicalPoint3(canonicalResult);
        }

        public static Point3D OffsetAlongUp(Point3D hostPoint, double offset, HostCoordinateAdapter adapter)
        {
            return ToPoint3D(OffsetAlongUp3(ToVector3(hostPoint), offset, adapter));
        }

        public static Vector3 OffsetAlongUp3(Vector3 hostPoint, double offset, HostCoordinateAdapter adapter)
        {
            return SetElevation3(hostPoint, GetElevation(hostPoint, adapter) + offset, adapter);
        }

        public static Point3D ProjectToAerialElevation(Point3D groundPoint, Point3D aerialReferencePoint, HostCoordinateAdapter adapter)
        {
            return ToPoint3D(ProjectToAerialElevation3(ToVector3(groundPoint), ToVector3(aerialReferencePoint), adapter));
        }

        public static Vector3 ProjectToAerialElevation3(Vector3 groundPoint, Vector3 aerialReferencePoint, HostCoordinateAdapter adapter)
        {
            return SetElevation3(groundPoint, GetElevation(aerialReferencePoint, adapter), adapter);
        }

        public static double GetRelativeHeight(Point3D groundPoint, Point3D aerialPoint, HostCoordinateAdapter adapter)
        {
            return GetRelativeHeight3(ToVector3(groundPoint), ToVector3(aerialPoint), adapter);
        }

        public static double GetRelativeHeight3(Vector3 groundPoint, Vector3 aerialPoint, HostCoordinateAdapter adapter)
        {
            return GetElevation(aerialPoint, adapter) - GetElevation(groundPoint, adapter);
        }

        public static Point3D CreateHorizontalTurnPoint(
            Point3D currentPoint,
            Point3D nextPoint,
            bool keepCurrentFirstHorizontalAxis,
            HostCoordinateAdapter adapter)
        {
            if (currentPoint == null)
            {
                throw new ArgumentNullException(nameof(currentPoint));
            }

            if (nextPoint == null)
            {
                throw new ArgumentNullException(nameof(nextPoint));
            }

            if (adapter == null)
            {
                throw new ArgumentNullException(nameof(adapter));
            }

            return ToPoint3D(CreateHorizontalTurnPoint3(
                ToVector3(currentPoint),
                ToVector3(nextPoint),
                keepCurrentFirstHorizontalAxis,
                adapter));
        }

        public static Vector3 CreateHorizontalTurnPoint3(
            Vector3 currentPoint,
            Vector3 nextPoint,
            bool keepCurrentFirstHorizontalAxis,
            HostCoordinateAdapter adapter)
        {
            if (adapter == null)
            {
                throw new ArgumentNullException(nameof(adapter));
            }

            Vector3 canonicalCurrent = adapter.ToCanonicalPoint3(currentPoint);
            Vector3 canonicalNext = adapter.ToCanonicalPoint3(nextPoint);

            Vector3 canonicalIntermediate = keepCurrentFirstHorizontalAxis
                ? new Vector3(canonicalCurrent.X, canonicalNext.Y, canonicalCurrent.Z)
                : new Vector3(canonicalNext.X, canonicalCurrent.Y, canonicalCurrent.Z);

            return adapter.FromCanonicalPoint3(canonicalIntermediate);
        }

        public static Vector3 GetHorizontalDirection3(Vector3 fromPoint, Vector3 toPoint, HostCoordinateAdapter adapter)
        {
            if (adapter == null)
            {
                throw new ArgumentNullException(nameof(adapter));
            }

            Vector3 canonicalFrom = adapter.ToCanonicalPoint3(fromPoint);
            Vector3 canonicalTo = adapter.ToCanonicalPoint3(toPoint);
            Vector3 canonicalHorizontal = new Vector3(
                canonicalTo.X - canonicalFrom.X,
                canonicalTo.Y - canonicalFrom.Y,
                0.0f);

            float horizontalLength = canonicalHorizontal.Length();
            if (horizontalLength < 1e-6f)
            {
                return Vector3.Zero;
            }

            Vector3 canonicalDirection = canonicalHorizontal / horizontalLength;
            return adapter.FromCanonicalVector3(canonicalDirection);
        }

        public static Point3D ExtendVerticalTransitionForObjectSpace(
            Point3D hostPoint,
            double objectHeight,
            bool isLowerPoint,
            HostCoordinateAdapter adapter)
        {
            if (hostPoint == null)
            {
                throw new ArgumentNullException(nameof(hostPoint));
            }

            return ToPoint3D(ExtendVerticalTransitionForObjectSpace3(
                ToVector3(hostPoint),
                objectHeight,
                isLowerPoint,
                adapter));
        }

        public static Vector3 ExtendVerticalTransitionForObjectSpace3(
            Vector3 hostPoint,
            double objectHeight,
            bool isLowerPoint,
            HostCoordinateAdapter adapter)
        {
            if (adapter == null)
            {
                throw new ArgumentNullException(nameof(adapter));
            }

            if (!isLowerPoint || Math.Abs(objectHeight) < 1e-9)
            {
                return hostPoint;
            }

            return OffsetAlongUp3(hostPoint, -objectHeight, adapter);
        }

        private static Vector3 ToVector3(Point3D point)
        {
            return new Vector3((float)point.X, (float)point.Y, (float)point.Z);
        }

        private static Point3D ToPoint3D(Vector3 point)
        {
            return new Point3D(point.X, point.Y, point.Z);
        }
    }
}
