using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Revit.DB;
using RevitEstimate.Models;

namespace RevitEstimate.Calculators
{
    public class Item23Calculator : IItemCalculator
    {
        private ProjectLevels _levels;

        public Item23Calculator(ProjectLevels levels = null)
        {
            _levels = levels ?? new ProjectLevels();
        }

        private class FloorLevelInfo
        {
            public string Name { get; set; }
            public double BottomZ { get; set; }
            public double TopZ { get; set; }
            public double Height => Math.Abs(TopZ - BottomZ);
        }

        public List<EstimateRowModel> Calculate(List<Element> collector, out double totalQty)
        {
            totalQty = 0.0;
            var rows = new List<EstimateRowModel>();
            var wallRows = new List<EstimateRowModel>();
            var deductionRows = new List<EstimateRowModel>();

            Document doc = collector.FirstOrDefault()?.Document;
            if (doc == null) return rows;

            // ==========================================
            // SETUP ACTIVE FLOORS FROM LEVEL WINDOW
            // ==========================================
            var activeFloors = new List<FloorLevelInfo>();
            activeFloors.Add(new FloorLevelInfo { Name = "GF", BottomZ = _levels.TopOfPlinth, TopZ = _levels.SlabTop });
            double currentTop = _levels.SlabTop;

            if (_levels.FirstFloorSlabTop != -8888 && _levels.FirstFloorSlabTop != 0)
            {
                activeFloors.Add(new FloorLevelInfo { Name = "FF", BottomZ = currentTop, TopZ = _levels.FirstFloorSlabTop });
                currentTop = _levels.FirstFloorSlabTop;
            }
            if (_levels.SecondFloorSlabTop != -8888 && _levels.SecondFloorSlabTop != 0)
            {
                activeFloors.Add(new FloorLevelInfo { Name = "SF", BottomZ = currentTop, TopZ = _levels.SecondFloorSlabTop });
                currentTop = _levels.SecondFloorSlabTop;
            }
            if (_levels.ThirdFloorSlabTop != -8888 && _levels.ThirdFloorSlabTop != 0)
            {
                activeFloors.Add(new FloorLevelInfo { Name = "TF", BottomZ = currentTop, TopZ = _levels.ThirdFloorSlabTop });
                currentTop = _levels.ThirdFloorSlabTop;
            }
            if (_levels.TerraceRoofTop != -8888 && _levels.TerraceRoofTop != 0)
            {
                activeFloors.Add(new FloorLevelInfo { Name = "Terrace", BottomZ = currentTop, TopZ = _levels.TerraceRoofTop });
            }

            // ==========================================
            // PART 1: SPLIT WALLS FLOOR BY FLOOR (INNER & BOTH SIDES PLASTER)
            // ==========================================
            var walls = collector.Where(e => e.Category != null && e.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Walls).Cast<Wall>().ToList();

            var validWallFloorMap = new Dictionary<string, int>();

            foreach (var wall in walls)
            {
                double wallThickness = GetWallThickness(wall);

                // Thick walls (>= 230mm) -> Inner side only (1 instance)
                // Thin walls (< 229mm) -> Both sides (2 instances)
                int plasterInstances = (wallThickness >= 0.22) ? 1 : 2;

                BoundingBoxXYZ box = wall.get_BoundingBox(null);
                if (box == null) continue;

                double wallBotZ = UnitUtils.ConvertFromInternalUnits(box.Min.Z, UnitTypeId.Meters);
                double wallTopZ = UnitUtils.ConvertFromInternalUnits(box.Max.Z, UnitTypeId.Meters);

                foreach (var floor in activeFloors)
                {
                    double overlapBot = Math.Max(wallBotZ, floor.BottomZ);
                    double overlapTop = Math.Min(wallTopZ, floor.TopZ);
                    double overlapHeight = overlapTop - overlapBot;

                    if (overlapHeight > 0.05)
                    {
                        double cutLength = GetWallLength(wall);
                        if (cutLength <= 0) cutLength = 3.0;

                        double qty = cutLength * overlapHeight * plasterInstances;
                        totalQty += qty;

                        string mapKey = $"{wall.Id}_{floor.Name}";
                        validWallFloorMap[mapKey] = plasterInstances;

                        string sideText = (plasterInstances == 2) ? "Both Sides" : "Inner Side";

                        wallRows.Add(new EstimateRowModel
                        {
                            SNo = "",
                            ItemOfWork = $"   -> Plaster ({sideText}) on {(wallThickness * 1000):F0}mm Wall ({floor.Name}): {wall.Name} [ID: {wall.Id}]",
                            NosOfFlats = "1",
                            NosOfInstance = plasterInstances.ToString(),
                            Length = cutLength.ToString("F2"),
                            Width = "",
                            HeightDepth = overlapHeight.ToString("F2"),
                            Qty = qty.ToString("F2"),
                            Unit = "Sqm",
                            Remarks = $"Plaster Area ({sideText}) in {floor.Name} (L={cutLength:F2}m x H={overlapHeight:F2}m)."
                        });
                    }
                }
            }

            // ==========================================
            // PART 2: DEDUCTIONS FOR DOORS & WINDOWS (With proper [ID: ...] for Smart Naming)
            // ==========================================
            var doorsAndWindows = collector
                .Where(e => e is FamilyInstance && e.Category != null &&
                           (e.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Doors || e.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Windows))
                .Cast<FamilyInstance>().ToList();

            foreach (var inst in doorsAndWindows)
            {
                if (inst.Host != null)
                {
                    BoundingBoxXYZ box = inst.get_BoundingBox(null);
                    if (box == null) continue;

                    double instCenterZ = UnitUtils.ConvertFromInternalUnits((box.Min.Z + box.Max.Z) / 2.0, UnitTypeId.Meters);
                    var floor = activeFloors.FirstOrDefault(f => instCenterZ >= (f.BottomZ - 0.5) && instCenterZ <= (f.TopZ + 0.5));

                    if (floor != null)
                    {
                        string mapKey = $"{inst.Host.Id}_{floor.Name}";
                        if (validWallFloorMap.ContainsKey(mapKey))
                        {
                            int hostInstances = validWallFloorMap[mapKey];

                            double width = GetDimensionFromTypeOrInstance(inst, "Width");
                            if (width == 0) width = GetDimensionFromTypeOrInstance(inst, "Rough Width");
                            if (width == 0) width = 1.0;

                            double height = GetDimensionFromTypeOrInstance(inst, "Height");
                            if (height == 0) height = GetDimensionFromTypeOrInstance(inst, "Rough Height");
                            if (height == 0) height = 2.1;

                            double deductionQty = -1.0 * (width * height * hostInstances);
                            totalQty += deductionQty;

                            deductionRows.Add(new EstimateRowModel
                            {
                                SNo = "",
                                ItemOfWork = $"   -> Deduct Plaster {inst.Category.Name} ({floor.Name}): {inst.Name} [ID: {inst.Id}]",
                                NosOfFlats = "-",
                                NosOfInstance = hostInstances.ToString(),
                                Length = width.ToString("F2"),
                                Width = "",
                                HeightDepth = height.ToString("F2"),
                                Qty = deductionQty.ToString("F2"),
                                Unit = "Sqm",
                                Remarks = $"Deducted from {floor.Name} segment of wall [ID: {inst.Host.Id}] ({hostInstances} side/s)."
                            });
                        }
                    }
                }
            }

            // ==========================================
            // COMPILE FINAL ROWS
            // ==========================================
            if (wallRows.Count > 0)
            {
                rows.AddRange(wallRows);
                if (deductionRows.Count > 0)
                {
                    rows.Add(new EstimateRowModel { SNo = "", ItemOfWork = "   -> --- DEDUCTIONS FOR DOORS & WINDOWS ---", NosOfFlats = "-", NosOfInstance = "-", Length = "-", Width = "-", HeightDepth = "-", Qty = "-", Unit = "-", Remarks = "Negative areas applied." });
                    rows.AddRange(deductionRows);
                }
            }
            else
            {
                rows.Add(new EstimateRowModel { SNo = "", ItemOfWork = "   -> [No walls found for Item 23]", NosOfFlats = "-", NosOfInstance = "0", Length = "-", Width = "", HeightDepth = "-", Qty = "0.00", Unit = "-", Remarks = "No elements detected." });
            }

            return rows;
        }

        private double GetDimensionFromTypeOrInstance(FamilyInstance inst, string paramName)
        {
            if (inst.Symbol != null) { Parameter typeParam = inst.Symbol.LookupParameter(paramName); if (typeParam != null && typeParam.HasValue) return UnitUtils.ConvertFromInternalUnits(typeParam.AsDouble(), UnitTypeId.Meters); }
            Parameter instParam = inst.LookupParameter(paramName);
            if (instParam != null && instParam.HasValue) return UnitUtils.ConvertFromInternalUnits(instParam.AsDouble(), UnitTypeId.Meters);
            return 0;
        }

        private double GetWallLength(Wall wall)
        {
            Parameter lenParam = wall.get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH) ?? wall.LookupParameter("Length");
            if (lenParam != null && lenParam.HasValue) return UnitUtils.ConvertFromInternalUnits(lenParam.AsDouble(), UnitTypeId.Meters);
            if (wall.Location is LocationCurve locCurve && locCurve.Curve != null) return UnitUtils.ConvertFromInternalUnits(locCurve.Curve.Length, UnitTypeId.Meters);
            return 3.0;
        }

        private double GetWallThickness(Wall wall)
        {
            WallType wallType = wall.WallType;
            if (wallType != null) { CompoundStructure cs = wallType.GetCompoundStructure(); if (cs != null) return UnitUtils.ConvertFromInternalUnits(cs.GetWidth(), UnitTypeId.Meters); Parameter widthParam = wallType.LookupParameter("Width"); if (widthParam != null && widthParam.HasValue) return UnitUtils.ConvertFromInternalUnits(widthParam.AsDouble(), UnitTypeId.Meters); }
            Parameter instWidth = wall.LookupParameter("Width");
            if (instWidth != null && instWidth.HasValue) return UnitUtils.ConvertFromInternalUnits(instWidth.AsDouble(), UnitTypeId.Meters);
            return 0.23;
        }
    }
}