using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Autodesk.Navisworks.Api;

namespace NavisworksTransport.Utils.GeometryAnalysis
{
    /// <summary>
    /// 基于端面上的三个种子点，从真实三角几何中识别端面并估计几何中心。
    /// 算法核心使用纯 Vector3，便于单测独立运行。
    /// </summary>
    public static class AssemblyEndFaceAnalyzer
    {
        private const double MinimumSeedTriangleAreaSquared = 1e-8;
        private const double PlaneDistanceTolerance = 1e-3;
        private const double NormalAlignmentCosineThreshold = 0.98;
        private const double MinimumProjectedExtent = 1e-6;

        public static EndFaceAnalysisResult Analyze(ModelItem modelItem, Point3D seedPoint1, Point3D seedPoint2, Point3D seedPoint3)
        {
            if (modelItem == null)
            {
                throw new ArgumentNullException(nameof(modelItem));
            }

            List<AnalysisTriangle3> triangles = GeometryHelper.ExtractTriangles(new[] { modelItem })
                .Select(ConvertTriangle)
                .ToList();

            return Analyze(triangles, ToVector3(seedPoint1), ToVector3(seedPoint2), ToVector3(seedPoint3));
        }

        public static EndFaceAnalysisResult Analyze(
            IEnumerable<AnalysisTriangle3> triangles,
            Vector3 seedPoint1,
            Vector3 seedPoint2,
            Vector3 seedPoint3)
        {
            if (triangles == null)
            {
                throw new ArgumentNullException(nameof(triangles));
            }

            List<AnalysisTriangle3> triangleList = triangles.ToList();
            if (triangleList.Count == 0)
            {
                return EndFaceAnalysisResult.Failure("没有可用的三角几何数据。");
            }

            PlaneDefinition seedPlane;
            if (!TryCreatePlane(seedPoint1, seedPoint2, seedPoint3, out seedPlane))
            {
                return EndFaceAnalysisResult.Failure("三个端面点近似共线，无法拟合端面平面。");
            }

            var candidateTriangles = new List<AnalysisTriangle3>();
            var candidatePoints = new HashSet<Vector3>(new Vector3EqualityComparer());
            double maxPlaneDeviation = 0.0;

            foreach (AnalysisTriangle3 triangle in triangleList)
            {
                Vector3 triangleNormal;
                if (!TryGetTriangleNormal(triangle, out triangleNormal))
                {
                    continue;
                }

                double normalAlignment = Math.Abs(Vector3.Dot(triangleNormal, seedPlane.Normal));
                if (normalAlignment < NormalAlignmentCosineThreshold)
                {
                    continue;
                }

                double distance1 = Math.Abs(GetSignedDistanceToPlane(seedPlane, triangle.Point1));
                double distance2 = Math.Abs(GetSignedDistanceToPlane(seedPlane, triangle.Point2));
                double distance3 = Math.Abs(GetSignedDistanceToPlane(seedPlane, triangle.Point3));
                double triangleDeviation = Math.Max(distance1, Math.Max(distance2, distance3));
                if (triangleDeviation > PlaneDistanceTolerance)
                {
                    continue;
                }

                candidateTriangles.Add(triangle);
                candidatePoints.Add(ProjectPointToPlane(seedPlane, triangle.Point1));
                candidatePoints.Add(ProjectPointToPlane(seedPlane, triangle.Point2));
                candidatePoints.Add(ProjectPointToPlane(seedPlane, triangle.Point3));
                maxPlaneDeviation = Math.Max(maxPlaneDeviation, triangleDeviation);
            }

            if (candidateTriangles.Count == 0 || candidatePoints.Count < 3)
            {
                return EndFaceAnalysisResult.Failure("没有识别到足够的端面共面三角形。");
            }

            PlaneDefinition refinedPlane = RefinePlane(seedPlane, candidatePoints);
            PlaneBasis planeBasis = CreatePlaneBasis(refinedPlane);

            double minU = double.MaxValue;
            double maxU = double.MinValue;
            double minV = double.MaxValue;
            double maxV = double.MinValue;

            foreach (Vector3 candidatePoint in candidatePoints)
            {
                Vector2 projected = ProjectToPlane2D(candidatePoint, planeBasis);
                minU = Math.Min(minU, projected.X);
                maxU = Math.Max(maxU, projected.X);
                minV = Math.Min(minV, projected.Y);
                maxV = Math.Max(maxV, projected.Y);
            }

            double extentU = maxU - minU;
            double extentV = maxV - minV;
            if (extentU < MinimumProjectedExtent || extentV < MinimumProjectedExtent)
            {
                return EndFaceAnalysisResult.Failure("识别到的端面分布过窄，无法稳定求中心。");
            }

            Vector3 center = ProjectFromPlane2D((minU + maxU) / 2.0, (minV + maxV) / 2.0, planeBasis);

            return EndFaceAnalysisResult.Success(
                center,
                refinedPlane.Normal,
                candidateTriangles.Count,
                candidatePoints.Count,
                maxPlaneDeviation,
                $"端面识别完成: 三角形={candidateTriangles.Count}, 顶点={candidatePoints.Count}, 偏差={maxPlaneDeviation:F6}");
        }

        /// <summary>
        /// 单点+法向量分析端面。用于取面工具得到法向量后替代手工三点拾取。
        /// </summary>
        /// <param name="triangles">模型三角面片</param>
        /// <param name="hitPoint">命中点</param>
        /// <param name="normal">面法向量（需归一化）</param>
        public static EndFaceAnalysisResult Analyze(
            IEnumerable<AnalysisTriangle3> triangles,
            Vector3 hitPoint,
            Vector3 normal)
        {
            if (triangles == null)
                throw new ArgumentNullException(nameof(triangles));

            if (normal.LengthSquared() < 1e-12f)
                return EndFaceAnalysisResult.Failure("法向量长度过小。");

            var triangleList = triangles.ToList();
            if (triangleList.Count == 0)
                return EndFaceAnalysisResult.Failure("没有可用的三角几何数据。");

            var seedPlane = new PlaneDefinition(Vector3.Normalize(normal), Vector3.Dot(normal, hitPoint));

            var candidateTriangles = new List<AnalysisTriangle3>();
            var candidatePoints = new HashSet<Vector3>(new Vector3EqualityComparer());
            double maxPlaneDeviation = 0.0;

            foreach (var triangle in triangleList)
            {
                if (!TryGetTriangleNormal(triangle, out Vector3 triangleNormal))
                    continue;

                if (Math.Abs(Vector3.Dot(triangleNormal, seedPlane.Normal)) < NormalAlignmentCosineThreshold)
                    continue;

                double d1 = Math.Abs(GetSignedDistanceToPlane(seedPlane, triangle.Point1));
                double d2 = Math.Abs(GetSignedDistanceToPlane(seedPlane, triangle.Point2));
                double d3 = Math.Abs(GetSignedDistanceToPlane(seedPlane, triangle.Point3));
                double triangleDeviation = Math.Max(d1, Math.Max(d2, d3));
                if (triangleDeviation > PlaneDistanceTolerance)
                    continue;

                candidateTriangles.Add(triangle);
                candidatePoints.Add(ProjectPointToPlane(seedPlane, triangle.Point1));
                candidatePoints.Add(ProjectPointToPlane(seedPlane, triangle.Point2));
                candidatePoints.Add(ProjectPointToPlane(seedPlane, triangle.Point3));
                maxPlaneDeviation = Math.Max(maxPlaneDeviation, triangleDeviation);
            }

            if (candidateTriangles.Count == 0 || candidatePoints.Count < 3)
                return EndFaceAnalysisResult.Failure("没有识别到足够的端面共面三角形。");

            PlaneDefinition refinedPlane = RefinePlane(seedPlane, candidatePoints);
            PlaneBasis planeBasis = CreatePlaneBasis(refinedPlane);

            double minU = double.MaxValue;
            double maxU = double.MinValue;
            double minV = double.MaxValue;
            double maxV = double.MinValue;

            foreach (Vector3 candidatePoint in candidatePoints)
            {
                Vector2 projected = ProjectToPlane2D(candidatePoint, planeBasis);
                minU = Math.Min(minU, projected.X);
                maxU = Math.Max(maxU, projected.X);
                minV = Math.Min(minV, projected.Y);
                maxV = Math.Max(maxV, projected.Y);
            }

            double extentU = maxU - minU;
            double extentV = maxV - minV;
            if (extentU < MinimumProjectedExtent || extentV < MinimumProjectedExtent)
            {
                return EndFaceAnalysisResult.Failure("识别到的端面分布过窄，无法稳定求中心。");
            }

            Vector3 center = ProjectFromPlane2D((minU + maxU) / 2.0, (minV + maxV) / 2.0, planeBasis);

            return EndFaceAnalysisResult.Success(
                center,
                refinedPlane.Normal,
                candidateTriangles.Count,
                candidatePoints.Count,
                maxPlaneDeviation,
                $"端面识别完成（单点法向）: 三角形={candidateTriangles.Count}, 顶点={candidatePoints.Count}, 偏差={maxPlaneDeviation:F6}");
        }

        /// <summary>
        /// 单点+法向量分析端面的 Navisworks 兼容重载。
        /// </summary>
        public static EndFaceAnalysisResult Analyze(ModelItem modelItem, Point3D hitPoint, Vector3D normal)
        {
            if (modelItem == null)
                throw new ArgumentNullException(nameof(modelItem));

            var triangles = GeometryHelper.ExtractTriangles(new[] { modelItem })
                .Select(ConvertTriangle)
                .ToList();

            return Analyze(triangles, ToVector3(hitPoint), new Vector3((float)normal.X, (float)normal.Y, (float)normal.Z));
        }

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

        public static Vector3 OrientNormalTowardTarget(Vector3 normal, Vector3 originPoint, Vector3 targetPoint)
        {
            if (normal.LengthSquared() < 1e-12f)
            {
                throw new ArgumentException("端面法向长度过小，无法定向。", nameof(normal));
            }

            Vector3 normalizedNormal = Vector3.Normalize(normal);
            Vector3 targetDirection = targetPoint - originPoint;
            if (targetDirection.LengthSquared() < 1e-12f)
            {
                return normalizedNormal;
            }

            Vector3 normalizedTargetDirection = Vector3.Normalize(targetDirection);
            if (Vector3.Dot(normalizedNormal, normalizedTargetDirection) < 0f)
            {
                normalizedNormal = -normalizedNormal;
            }

            return normalizedNormal;
        }

        private static AnalysisTriangle3 ConvertTriangle(Triangle3D triangle)
        {
            return new AnalysisTriangle3(ToVector3(triangle.Point1), ToVector3(triangle.Point2), ToVector3(triangle.Point3));
        }

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

        private static bool TryCreatePlane(Vector3 point1, Vector3 point2, Vector3 point3, out PlaneDefinition plane)
        {
            Vector3 edge1 = point2 - point1;
            Vector3 edge2 = point3 - point1;
            Vector3 normal = Vector3.Cross(edge1, edge2);
            if (normal.LengthSquared() < MinimumSeedTriangleAreaSquared)
            {
                plane = default(PlaneDefinition);
                return false;
            }

            Vector3 normalizedNormal = Vector3.Normalize(normal);
            plane = new PlaneDefinition(normalizedNormal, Vector3.Dot(normalizedNormal, point1));
            return true;
        }

        private static bool TryGetTriangleNormal(AnalysisTriangle3 triangle, out Vector3 normal)
        {
            Vector3 edge1 = triangle.Point2 - triangle.Point1;
            Vector3 edge2 = triangle.Point3 - triangle.Point1;
            Vector3 cross = Vector3.Cross(edge1, edge2);
            if (cross.LengthSquared() < 1e-12)
            {
                normal = Vector3.Zero;
                return false;
            }

            normal = Vector3.Normalize(cross);
            return true;
        }

        private static PlaneDefinition RefinePlane(PlaneDefinition seedPlane, IEnumerable<Vector3> candidatePoints)
        {
            double averageOffset = candidatePoints.Average(point => Vector3.Dot(seedPlane.Normal, point));
            return new PlaneDefinition(seedPlane.Normal, averageOffset);
        }

        private static double GetSignedDistanceToPlane(PlaneDefinition plane, Vector3 point)
        {
            return Vector3.Dot(plane.Normal, point) - plane.Offset;
        }

        private static Vector3 ProjectPointToPlane(PlaneDefinition plane, Vector3 point)
        {
            float signedDistance = (float)GetSignedDistanceToPlane(plane, point);
            return point - plane.Normal * signedDistance;
        }

        private static PlaneBasis CreatePlaneBasis(PlaneDefinition plane)
        {
            Vector3 reference = Math.Abs(plane.Normal.Z) < 0.9f
                ? new Vector3(0.0f, 0.0f, 1.0f)
                : new Vector3(1.0f, 0.0f, 0.0f);
            Vector3 axisU = Vector3.Normalize(Vector3.Cross(reference, plane.Normal));
            Vector3 axisV = Vector3.Normalize(Vector3.Cross(plane.Normal, axisU));
            Vector3 origin = plane.Normal * (float)plane.Offset;
            return new PlaneBasis(origin, axisU, axisV);
        }

        private static Vector2 ProjectToPlane2D(Vector3 point, PlaneBasis basis)
        {
            Vector3 vector = point - basis.Origin;
            return new Vector2(Vector3.Dot(vector, basis.AxisU), Vector3.Dot(vector, basis.AxisV));
        }

        private static Vector3 ProjectFromPlane2D(double u, double v, PlaneBasis basis)
        {
            return basis.Origin + basis.AxisU * (float)u + basis.AxisV * (float)v;
        }

        private struct PlaneDefinition
        {
            public PlaneDefinition(Vector3 normal, double offset)
            {
                Normal = normal;
                Offset = offset;
            }

            public Vector3 Normal { get; }
            public double Offset { get; }
        }

        private struct PlaneBasis
        {
            public PlaneBasis(Vector3 origin, Vector3 axisU, Vector3 axisV)
            {
                Origin = origin;
                AxisU = axisU;
                AxisV = axisV;
            }

            public Vector3 Origin { get; }
            public Vector3 AxisU { get; }
            public Vector3 AxisV { get; }
        }

        private sealed class Vector3EqualityComparer : IEqualityComparer<Vector3>
        {
            private const float Tolerance = 1e-5f;

            public bool Equals(Vector3 x, Vector3 y)
            {
                return Math.Abs(x.X - y.X) <= Tolerance &&
                       Math.Abs(x.Y - y.Y) <= Tolerance &&
                       Math.Abs(x.Z - y.Z) <= Tolerance;
            }

            public int GetHashCode(Vector3 obj)
            {
                int x = (int)Math.Round(obj.X / Tolerance);
                int y = (int)Math.Round(obj.Y / Tolerance);
                int z = (int)Math.Round(obj.Z / Tolerance);
                unchecked
                {
                    int hash = 17;
                    hash = hash * 31 + x;
                    hash = hash * 31 + y;
                    hash = hash * 31 + z;
                    return hash;
                }
            }
        }
    }

    public struct AnalysisTriangle3
    {
        public AnalysisTriangle3(Vector3 point1, Vector3 point2, Vector3 point3)
        {
            Point1 = point1;
            Point2 = point2;
            Point3 = point3;
        }

        public Vector3 Point1 { get; }
        public Vector3 Point2 { get; }
        public Vector3 Point3 { get; }
    }

    public sealed class EndFaceAnalysisResult
    {
        private EndFaceAnalysisResult(
            bool isReliable,
            Vector3 center,
            Vector3 normal,
            int candidateTriangleCount,
            int candidateVertexCount,
            double maxPlaneDeviation,
            string diagnosticMessage)
        {
            IsReliable = isReliable;
            Center = center;
            Normal = normal;
            CandidateTriangleCount = candidateTriangleCount;
            CandidateVertexCount = candidateVertexCount;
            MaxPlaneDeviation = maxPlaneDeviation;
            DiagnosticMessage = diagnosticMessage;
        }

        public bool IsReliable { get; }
        public Vector3 Center { get; }
        public Vector3 Normal { get; }
        public int CandidateTriangleCount { get; }
        public int CandidateVertexCount { get; }
        public double MaxPlaneDeviation { get; }
        public string DiagnosticMessage { get; }

        public static EndFaceAnalysisResult Success(
            Vector3 center,
            Vector3 normal,
            int candidateTriangleCount,
            int candidateVertexCount,
            double maxPlaneDeviation,
            string diagnosticMessage)
        {
            return new EndFaceAnalysisResult(
                true,
                center,
                normal,
                candidateTriangleCount,
                candidateVertexCount,
                maxPlaneDeviation,
                diagnosticMessage);
        }

        public static EndFaceAnalysisResult Failure(string diagnosticMessage)
        {
            return new EndFaceAnalysisResult(
                false,
                Vector3.Zero,
                Vector3.Zero,
                0,
                0,
                0.0,
                diagnosticMessage);
        }
    }
}
