using System;
using System.Numerics;

namespace NavisworksTransport.Utils.GeometryAnalysis
{
    public sealed class AssemblyInstallationReferenceResult
    {
        public Vector3 OpticalAxisBasePoint { get; set; }
        public Vector3 OpticalAxisDirection { get; set; }
        public Vector3 PickPoint { get; set; }
        public Vector3 SecondaryPickPoint { get; set; }
        public Vector3 PlaneNormal { get; set; }
        public Vector3 PlaneSpanDirection { get; set; }
        public float OffsetDistance { get; set; }
        public Vector3 InstallLineBasePoint { get; set; }
        public Vector3 AnchorPoint { get; set; }
    }

    public static class AssemblyInstallationReferenceBuilder
    {
        private const float MinimumOffsetDistance = 1e-4f;

        public static AssemblyInstallationReferenceResult Build(
            Vector3 opticalAxisBasePoint,
            Vector3 opticalAxisDirection,
            Vector3 pickPoint)
        {
            float axisLength = opticalAxisDirection.Length();
            if (axisLength < 1e-6f)
            {
                throw new InvalidOperationException("光轴方向长度过小，无法计算安装参考。");
            }

            Vector3 axisDir = opticalAxisDirection / axisLength;
            Vector3 axisProjection = ProjectPointToLine(pickPoint, opticalAxisBasePoint, axisDir);
            Vector3 rawOffset = pickPoint - axisProjection;
            float offsetDistance = rawOffset.Length();
            if (offsetDistance < MinimumOffsetDistance)
            {
                throw new InvalidOperationException("参考安装点距离光轴过小，无法确定安装参考面，请重新选择。");
            }

            Vector3 planeNormal = rawOffset / offsetDistance;
            Vector3 planeSpanDirection = Vector3.Cross(axisDir, planeNormal);
            float spanLength = planeSpanDirection.Length();
            if (spanLength < 1e-6f)
            {
                throw new InvalidOperationException("安装参考面横向方向计算失败，请重新选择参考安装点。");
            }

            planeSpanDirection /= spanLength;

            Vector3 installLineBasePoint = opticalAxisBasePoint + planeNormal * offsetDistance;
            Vector3 anchorPoint = ProjectPointToLine(pickPoint, installLineBasePoint, axisDir);

            return new AssemblyInstallationReferenceResult
            {
                OpticalAxisBasePoint = opticalAxisBasePoint,
                OpticalAxisDirection = axisDir,
                PickPoint = pickPoint,
                SecondaryPickPoint = pickPoint,
                PlaneNormal = planeNormal,
                PlaneSpanDirection = planeSpanDirection,
                OffsetDistance = offsetDistance,
                InstallLineBasePoint = installLineBasePoint,
                AnchorPoint = anchorPoint
            };
        }

        public static AssemblyInstallationReferenceResult Build(
            Vector3 opticalAxisBasePoint,
            Vector3 opticalAxisDirection,
            Vector3 firstPickPoint,
            Vector3 secondPickPoint)
        {
            float axisLength = opticalAxisDirection.Length();
            if (axisLength < 1e-6f)
            {
                throw new InvalidOperationException("光轴方向长度过小，无法计算安装参考。");
            }

            Vector3 axisDir = opticalAxisDirection / axisLength;
            Vector3 pickSegment = secondPickPoint - firstPickPoint;
            Vector3 axisAlignedComponent = axisDir * Vector3.Dot(pickSegment, axisDir);
            Vector3 planeSpanDirection = pickSegment - axisAlignedComponent;
            float spanLength = planeSpanDirection.Length();
            if (spanLength < MinimumOffsetDistance)
            {
                throw new InvalidOperationException("两次安装点拾取几乎与光轴平行，无法确定安装参考面，请重新选择。");
            }

            planeSpanDirection /= spanLength;

            Vector3 midpoint = (firstPickPoint + secondPickPoint) * 0.5f;
            Vector3 planeNormal = Vector3.Cross(axisDir, planeSpanDirection);
            float normalLength = planeNormal.Length();
            if (normalLength < 1e-6f)
            {
                throw new InvalidOperationException("安装参考面法向计算失败，请重新选择。");
            }

            planeNormal /= normalLength;

            Vector3 midpointOffset = midpoint - opticalAxisBasePoint;
            float signedOffset = Vector3.Dot(midpointOffset, planeNormal);
            if (Math.Abs(signedOffset) < MinimumOffsetDistance)
            {
                throw new InvalidOperationException("安装面中点距离光轴过小，无法确定安装参考面，请重新选择。");
            }

            if (signedOffset < 0f)
            {
                planeNormal = -planeNormal;
                signedOffset = -signedOffset;
            }

            float offsetDistance = signedOffset;
            Vector3 installLineBasePoint = opticalAxisBasePoint + planeNormal * offsetDistance;
            Vector3 anchorPoint = ProjectPointToLine(midpoint, installLineBasePoint, axisDir);

            return new AssemblyInstallationReferenceResult
            {
                OpticalAxisBasePoint = opticalAxisBasePoint,
                OpticalAxisDirection = axisDir,
                PickPoint = firstPickPoint,
                SecondaryPickPoint = secondPickPoint,
                PlaneNormal = planeNormal,
                PlaneSpanDirection = planeSpanDirection,
                OffsetDistance = offsetDistance,
                InstallLineBasePoint = installLineBasePoint,
                AnchorPoint = anchorPoint
            };
        }

        /// <summary>
        /// 单点+法向量构建安装参考。用于取面工具替代两点手动拾取。
        /// </summary>
        public static AssemblyInstallationReferenceResult BuildFromFaceNormal(
            Vector3 opticalAxisBasePoint,
            Vector3 opticalAxisDirection,
            Vector3 pickPoint,
            Vector3 faceNormal)
        {
            float axisLength = opticalAxisDirection.Length();
            if (axisLength < 1e-6f)
                throw new InvalidOperationException("光轴方向长度过小，无法计算安装参考。");

            Vector3 axisDir = opticalAxisDirection / axisLength;

            // 将面法向投影到垂直于轴的平面
            float axialComponent = Vector3.Dot(faceNormal, axisDir);
            Vector3 planeNormal = faceNormal - axisDir * axialComponent;
            float normalLength = planeNormal.Length();
            if (normalLength < 1e-6f)
                throw new InvalidOperationException("面法向与光轴平行，无法确定安装参考面，请选择其他面。");
            planeNormal /= normalLength;

            Vector3 axisProjection = ProjectPointToLine(pickPoint, opticalAxisBasePoint, axisDir);
            Vector3 rawOffset = pickPoint - axisProjection;
            float signedOffset = Vector3.Dot(rawOffset, planeNormal);
            if (signedOffset < 0f)
            {
                planeNormal = -planeNormal;
                signedOffset = -signedOffset;
            }
            if (signedOffset < MinimumOffsetDistance)
                throw new InvalidOperationException("安装点距离光轴过小，无法确定安装参考面，请重新选择。");

            Vector3 planeSpanDirection = Vector3.Cross(axisDir, planeNormal);
            float spanLength = planeSpanDirection.Length();
            if (spanLength < 1e-6f)
                throw new InvalidOperationException("安装参考面横向方向计算失败。");
            planeSpanDirection /= spanLength;

            Vector3 installLineBasePoint = opticalAxisBasePoint + planeNormal * signedOffset;
            Vector3 anchorPoint = ProjectPointToLine(pickPoint, installLineBasePoint, axisDir);

            return new AssemblyInstallationReferenceResult
            {
                OpticalAxisBasePoint = opticalAxisBasePoint,
                OpticalAxisDirection = axisDir,
                PickPoint = pickPoint,
                SecondaryPickPoint = pickPoint,
                PlaneNormal = planeNormal,
                PlaneSpanDirection = planeSpanDirection,
                OffsetDistance = signedOffset,
                InstallLineBasePoint = installLineBasePoint,
                AnchorPoint = anchorPoint
            };
        }

        private static Vector3 ProjectPointToLine(Vector3 point, Vector3 linePoint, Vector3 lineDirection)
        {
            Vector3 pointVector = point - linePoint;
            float projectionLength = Vector3.Dot(pointVector, lineDirection);
            return linePoint + lineDirection * projectionLength;
        }
    }
}
