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

namespace NavisworksTransport.Utils.CoordinateSystem
{
    /// <summary>
    /// 模型局部轴约定。
    /// 显式定义模型本地哪个轴是前进方向、哪个轴是上方向。
    /// </summary>
    public sealed class ModelAxisConvention
    {
        private readonly Vector3 _forwardUnitVector;
        private readonly Vector3 _upUnitVector;

        public LocalAxisDirection ForwardAxis { get; }
        public LocalAxisDirection UpAxis { get; }

        public ModelAxisConvention(LocalAxisDirection forwardAxis, LocalAxisDirection upAxis)
        {
            ForwardAxis = forwardAxis;
            UpAxis = upAxis;

            _forwardUnitVector = GetAxisVector3(forwardAxis);
            _upUnitVector = GetAxisVector3(upAxis);
            float dot = Vector3.Dot(_forwardUnitVector, _upUnitVector);
            if (Math.Abs(dot) > 1e-6)
            {
                throw new ArgumentException("ForwardAxis 和 UpAxis 不能平行或重合。");
            }
        }

        public Vector3 ForwardUnitVector => _forwardUnitVector;
        public Vector3 UpUnitVector => _upUnitVector;
        public Vector3D ForwardVector => ToNavVector(_forwardUnitVector);
        public Vector3D UpVector => ToNavVector(_upUnitVector);
        public LocalAxisDirection SideAxis => GetAxisDirection(Normalize(Vector3.Cross(_forwardUnitVector, _upUnitVector)));

        /// <summary>
        /// 根据模型局部轴约定，直接构造“本地 forward/up 对齐到目标世界 forward/up”的线性姿态。
        /// 返回矩阵的 3 列分别表示模型本地 +X/+Y/+Z 在世界中的方向。
        /// </summary>
        public Matrix3 CreateLinearTransform(Vector3D worldForward, Vector3D worldUp)
        {
            Matrix4x4 linear = CreateLinearTransform3(ToNumericsVector(worldForward), ToNumericsVector(worldUp));
            return ToNavMatrix3(linear);
        }

        public Rotation3D CreateRotation(Vector3D worldForward, Vector3D worldUp)
        {
            Quaternion quaternion = CreateQuaternion(ToNumericsVector(worldForward), ToNumericsVector(worldUp));
            return new Rotation3D(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
        }

        public Matrix4x4 CreateLinearTransform3(Vector3 worldForward, Vector3 worldUp)
        {
            Vector3 normalizedWorldForward = Normalize(worldForward);
            Vector3 normalizedWorldUp = Normalize(worldUp);
            Vector3 worldRemaining = Normalize(Vector3.Cross(normalizedWorldForward, normalizedWorldUp));

            Vector3 localRemaining = Normalize(Vector3.Cross(_forwardUnitVector, _upUnitVector));

            Vector3 worldX = ResolveWorldAxisForLocalPositiveAxis(
                0,
                normalizedWorldForward,
                normalizedWorldUp,
                worldRemaining,
                localRemaining);
            Vector3 worldY = ResolveWorldAxisForLocalPositiveAxis(
                1,
                normalizedWorldForward,
                normalizedWorldUp,
                worldRemaining,
                localRemaining);
            Vector3 worldZ = ResolveWorldAxisForLocalPositiveAxis(
                2,
                normalizedWorldForward,
                normalizedWorldUp,
                worldRemaining,
                localRemaining);

            return new Matrix4x4(
                worldX.X, worldY.X, worldZ.X, 0f,
                worldX.Y, worldY.Y, worldZ.Y, 0f,
                worldX.Z, worldY.Z, worldZ.Z, 0f,
                0f, 0f, 0f, 1f);
        }

        public Quaternion CreateQuaternion(Vector3 worldForward, Vector3 worldUp)
        {
            return Quaternion.CreateFromRotationMatrix(CreateLinearTransform3(worldForward, worldUp));
        }

        /// <summary>
        /// 当前项目里，真实模型默认遵循宿主 up 语义：
        /// - Z-up 宿主 => 本地 X 前进，本地 Z 向上
        /// - Y-up 宿主 => 本地 X 前进，本地 Y 向上
        /// </summary>
        public static ModelAxisConvention CreateDefaultForHost(CoordinateSystemType hostType)
        {
            switch (hostType)
            {
                case CoordinateSystemType.YUp:
                    return new ModelAxisConvention(LocalAxisDirection.PositiveX, LocalAxisDirection.PositiveY);
                case CoordinateSystemType.ZUp:
                default:
                    return new ModelAxisConvention(LocalAxisDirection.PositiveX, LocalAxisDirection.PositiveZ);
            }
        }

        /// <summary>
        /// 虚拟物体资源的本地轴约定。
        /// 该约定仅描述资源文件本身的 local forward/up，不代表当前宿主文档的坐标系。
        /// </summary>
        public static ModelAxisConvention CreateVirtualObjectAssetConvention()
        {
            return new ModelAxisConvention(LocalAxisDirection.PositiveX, LocalAxisDirection.PositiveZ);
        }

        /// <summary>
        /// 参考杆资源的本地轴约定。
        /// 当前资源约定长度轴沿本地 +X，截面 up 沿本地 +Z。
        /// </summary>
        public static ModelAxisConvention CreateReferenceRodAssetConvention()
        {
            return new ModelAxisConvention(LocalAxisDirection.PositiveX, LocalAxisDirection.PositiveZ);
        }

        /// <summary>
        /// Rail 资源的本地轴约定。
        /// 当前资源约定前进方向沿本地 +X，向上沿本地 +Z。
        /// </summary>
        public static ModelAxisConvention CreateRailAssetConvention()
        {
            return new ModelAxisConvention(LocalAxisDirection.PositiveX, LocalAxisDirection.PositiveZ);
        }

        public double GetForwardSize(BoundingBox3D bounds)
        {
            return GetAxisExtent(bounds, ForwardAxis);
        }

        public double GetUpSize(BoundingBox3D bounds)
        {
            return GetAxisExtent(bounds, UpAxis);
        }

        public double GetSideSize(BoundingBox3D bounds)
        {
            return GetAxisExtent(bounds, SideAxis);
        }

        /// <summary>
        /// 根据 forward/side/up 语义尺寸，构造资源本地 X/Y/Z 的缩放向量。
        /// </summary>
        public Vector3D CreateScaleVector(double forwardSize, double sideSize, double upSize)
        {
            Vector3 scale = CreateScaleVector3(forwardSize, sideSize, upSize);
            return new Vector3D(scale.X, scale.Y, scale.Z);
        }

        public Vector3 CreateScaleVector3(double forwardSize, double sideSize, double upSize)
        {
            float scaleX = (float)ResolveLocalAxisScale(0, forwardSize, sideSize, upSize);
            float scaleY = (float)ResolveLocalAxisScale(1, forwardSize, sideSize, upSize);
            float scaleZ = (float)ResolveLocalAxisScale(2, forwardSize, sideSize, upSize);
            return new Vector3(scaleX, scaleY, scaleZ);
        }

        private static Vector3 GetAxisVector3(LocalAxisDirection axis)
        {
            switch (axis)
            {
                case LocalAxisDirection.PositiveX: return new Vector3(1f, 0f, 0f);
                case LocalAxisDirection.PositiveY: return new Vector3(0f, 1f, 0f);
                case LocalAxisDirection.PositiveZ: return new Vector3(0f, 0f, 1f);
                case LocalAxisDirection.NegativeX: return new Vector3(-1f, 0f, 0f);
                case LocalAxisDirection.NegativeY: return new Vector3(0f, -1f, 0f);
                case LocalAxisDirection.NegativeZ: return new Vector3(0f, 0f, -1f);
                default:
                    throw new ArgumentOutOfRangeException(nameof(axis), axis, null);
            }
        }

        private Vector3 ResolveWorldAxisForLocalPositiveAxis(
            int localAxisIndex,
            Vector3 worldForward,
            Vector3 worldUp,
            Vector3 worldRemaining,
            Vector3 localRemaining)
        {
            if (MatchesAxis(ForwardAxis, localAxisIndex, out int forwardSign))
            {
                return Scale(worldForward, forwardSign);
            }

            if (MatchesAxis(UpAxis, localAxisIndex, out int upSign))
            {
                return Scale(worldUp, upSign);
            }

            LocalAxisDirection remainingAxis = GetAxisDirection(localRemaining);
            if (MatchesAxis(remainingAxis, localAxisIndex, out int remainingSign))
            {
                return Scale(worldRemaining, remainingSign);
            }

            throw new InvalidOperationException("无法为模型局部轴构造世界姿态矩阵。");
        }

        private static bool MatchesAxis(LocalAxisDirection axis, int localAxisIndex, out int sign)
        {
            switch (axis)
            {
                case LocalAxisDirection.PositiveX:
                    sign = 1;
                    return localAxisIndex == 0;
                case LocalAxisDirection.NegativeX:
                    sign = -1;
                    return localAxisIndex == 0;
                case LocalAxisDirection.PositiveY:
                    sign = 1;
                    return localAxisIndex == 1;
                case LocalAxisDirection.NegativeY:
                    sign = -1;
                    return localAxisIndex == 1;
                case LocalAxisDirection.PositiveZ:
                    sign = 1;
                    return localAxisIndex == 2;
                case LocalAxisDirection.NegativeZ:
                    sign = -1;
                    return localAxisIndex == 2;
                default:
                    throw new ArgumentOutOfRangeException(nameof(axis), axis, null);
            }
        }

        private static LocalAxisDirection GetAxisDirection(Vector3 vector)
        {
            if (IsApproximately(vector, 1, 0, 0)) return LocalAxisDirection.PositiveX;
            if (IsApproximately(vector, -1, 0, 0)) return LocalAxisDirection.NegativeX;
            if (IsApproximately(vector, 0, 1, 0)) return LocalAxisDirection.PositiveY;
            if (IsApproximately(vector, 0, -1, 0)) return LocalAxisDirection.NegativeY;
            if (IsApproximately(vector, 0, 0, 1)) return LocalAxisDirection.PositiveZ;
            if (IsApproximately(vector, 0, 0, -1)) return LocalAxisDirection.NegativeZ;

            throw new InvalidOperationException("模型局部第三轴不是标准正交轴，无法确定局部轴约定。");
        }

        private double ResolveLocalAxisScale(int localAxisIndex, double forwardSize, double sideSize, double upSize)
        {
            if (MatchesAxis(ForwardAxis, localAxisIndex, out _))
            {
                return forwardSize;
            }

            if (MatchesAxis(UpAxis, localAxisIndex, out _))
            {
                return upSize;
            }

            if (MatchesAxis(SideAxis, localAxisIndex, out _))
            {
                return sideSize;
            }

            throw new InvalidOperationException("无法为模型局部轴解析缩放尺寸。");
        }

        private static bool IsApproximately(Vector3 vector, double x, double y, double z)
        {
            return Math.Abs(vector.X - x) < 1e-6 &&
                   Math.Abs(vector.Y - y) < 1e-6 &&
                   Math.Abs(vector.Z - z) < 1e-6;
        }

        private static Vector3 Scale(Vector3 vector, int sign)
        {
            return sign == 1
                ? vector
                : new Vector3(-vector.X, -vector.Y, -vector.Z);
        }

        private static Vector3 Normalize(Vector3 vector)
        {
            float lengthSquared = vector.LengthSquared();
            if (lengthSquared < 1e-12f)
            {
                throw new InvalidOperationException("无法为模型局部轴构造有效的正交侧向。");
            }

            return Vector3.Normalize(vector);
        }

        private static Matrix3 ToNavMatrix3(Matrix4x4 matrix)
        {
            return new Matrix3(
                matrix.M11, matrix.M12, matrix.M13,
                matrix.M21, matrix.M22, matrix.M23,
                matrix.M31, matrix.M32, matrix.M33);
        }

        private static Vector3 ToNumericsVector(Vector3D vector)
        {
            return new Vector3((float)vector.X, (float)vector.Y, (float)vector.Z);
        }

        private static Vector3D ToNavVector(Vector3 vector)
        {
            return new Vector3D(vector.X, vector.Y, vector.Z);
        }

        private static double GetAxisExtent(BoundingBox3D bounds, LocalAxisDirection axis)
        {
            switch (axis)
            {
                case LocalAxisDirection.PositiveX:
                case LocalAxisDirection.NegativeX:
                    return bounds.Max.X - bounds.Min.X;
                case LocalAxisDirection.PositiveY:
                case LocalAxisDirection.NegativeY:
                    return bounds.Max.Y - bounds.Min.Y;
                case LocalAxisDirection.PositiveZ:
                case LocalAxisDirection.NegativeZ:
                    return bounds.Max.Z - bounds.Min.Z;
                default:
                    throw new ArgumentOutOfRangeException(nameof(axis), axis, null);
            }
        }
    }
}
