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

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

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

        // Helper class to store active floor data
        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>();

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

            // ==========================================
            // SETUP ACTIVE FLOORS FROM LEVEL WINDOW
            // ==========================================
            var activeFloors = new List<FloorLevelInfo>();

            // 1. Ground Floor (Always Active)
            activeFloors.Add(new FloorLevelInfo { Name = "GF", BottomZ = _levels.TopOfPlinth, TopZ = _levels.SlabTop });
            double currentTop = _levels.SlabTop;

            // 2. First Floor
            if (_levels.FirstFloorSlabTop != -8888 && _levels.FirstFloorSlabTop != 0)
            {
                activeFloors.Add(new FloorLevelInfo { Name = "FF", BottomZ = currentTop, TopZ = _levels.FirstFloorSlabTop });
                currentTop = _levels.FirstFloorSlabTop;
            }

            // 3. Second Floor
            if (_levels.SecondFloorSlabTop != -8888 && _levels.SecondFloorSlabTop != 0)
            {
                activeFloors.Add(new FloorLevelInfo { Name = "SF", BottomZ = currentTop, TopZ = _levels.SecondFloorSlabTop });
                currentTop = _levels.SecondFloorSlabTop;
            }

            // 4. Third Floor
            if (_levels.ThirdFloorSlabTop != -8888 && _levels.ThirdFloorSlabTop != 0)
            {
                activeFloors.Add(new FloorLevelInfo { Name = "TF", BottomZ = currentTop, TopZ = _levels.ThirdFloorSlabTop });
                currentTop = _levels.ThirdFloorSlabTop;
            }

            // 5. Terrace
            if (_levels.TerraceRoofTop != -8888 && _levels.TerraceRoofTop != 0)
            {
                activeFloors.Add(new FloorLevelInfo { Name = "Terrace", BottomZ = currentTop, TopZ = _levels.TerraceRoofTop });
            }

            // Helper function to find which floor an element belongs to
            FloorLevelInfo GetFloorForElement(double zCoord)
            {
                // Give a 0.5m margin for precision issues
                return activeFloors.FirstOrDefault(f => zCoord >= (f.BottomZ - 0.5) && zCoord <= (f.TopZ + 0.5));
            }

            // ==========================================
            // PART 1: COLUMNS (Height = TopZ - BottomZ of that floor)
            // ==========================================
            var columns = collector.Where(e => e.Category != null && e.Category.Id.IntegerValue == (int)BuiltInCategory.OST_StructuralColumns).ToList();

            foreach (var col in columns)
            {
                double zCoord = GetElementZCoordinate(col);
                var floor = GetFloorForElement(zCoord);

                if (floor == null) continue; // Skip columns outside enabled floors

                string colName = col.Name;
                double bWid = GetParameterValueByName(col, "b") ?? GetParameterValueByName(col, "Width") ?? 0.3;
                double hDep = GetParameterValueByName(col, "h") ?? GetParameterValueByName(col, "Depth") ?? 0.3;

                double colHeight = floor.Height;
                if (colHeight <= 0) colHeight = 3.0;

                double qty = bWid * hDep * colHeight; // Volume
                totalQty += qty;

                rows.Add(new EstimateRowModel
                {
                    SNo = "",
                    ItemOfWork = $"   -> RCC Column ({floor.Name}): {colName} [ID: {col.Id}]",
                    NosOfFlats = "1",
                    NosOfInstance = "1",
                    Length = colHeight.ToString("F2"),
                    Width = bWid.ToString("F2"),
                    HeightDepth = hDep.ToString("F2"),
                    Qty = qty.ToString("F2"),
                    Unit = "Cum",
                    Remarks = $"Column height based on {floor.Name} levels ({floor.BottomZ:F2}m to {floor.TopZ:F2}m)."
                });
            }

            // ==========================================
            // PART 2: BEAMS (Lintels then Slab Beams per floor)
            // ==========================================
            var allBeams = collector.Where(e => e.Category != null && e.Category.Id.IntegerValue == (int)BuiltInCategory.OST_StructuralFraming).ToList();
            var upperBeams = allBeams.Where(b => GetElementZCoordinate(b) > _levels.TopOfPlinth + 0.3).OrderBy(b => GetElementZCoordinate(b)).ToList();

            foreach (var beam in upperBeams)
            {
                double zCoord = GetElementZCoordinate(beam);
                var floor = GetFloorForElement(zCoord);

                if (floor == null) continue;

                // Check if it's near the Slab top for that specific floor
                string beamType = (zCoord >= floor.TopZ - 0.4) ? "Slab Beam" : "Lintel Beam";

                string beamName = beam.Name;
                double cutLength = GetBeamCutLength(beam);
                double bWid = GetParameterValueByName(beam, "b") ?? GetParameterValueByName(beam, "Width") ?? 0.3;
                double hDep = GetParameterValueByName(beam, "h") ?? GetParameterValueByName(beam, "Depth") ?? 0.3;

                if (cutLength <= 0) cutLength = 3.0;

                double qty = cutLength * bWid * hDep;
                totalQty += qty;

                rows.Add(new EstimateRowModel
                {
                    SNo = "",
                    ItemOfWork = $"   -> RCC {beamType} ({floor.Name}): {beamName} [ID: {beam.Id}]",
                    NosOfFlats = "1",
                    NosOfInstance = "1",
                    Length = cutLength.ToString("F2"),
                    Width = bWid.ToString("F2"),
                    HeightDepth = hDep.ToString("F2"),
                    Qty = qty.ToString("F2"),
                    Unit = "Cum",
                    Remarks = $"{beamType} volume in {floor.Name} (Cut Length x b x h)."
                });
            }

            // ==========================================
            // PART 3: SLABS (Area x Thickness per floor)
            // ==========================================
            var floors = collector.Where(e => e.Category != null && e.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Floors).ToList();
            var slabFloors = floors.Where(f => GetElementZCoordinate(f) > _levels.TopOfPlinth + 0.5).OrderBy(f => GetElementZCoordinate(f)).ToList();

            foreach (var slab in slabFloors)
            {
                double zCoord = GetElementZCoordinate(slab);
                var floor = GetFloorForElement(zCoord);

                if (floor == null) continue;

                string slabName = slab.Name;
                double area = GetFloorArea(slab);
                double thickness = GetFloorThickness(slab);

                double qty = area * thickness;
                totalQty += qty;

                rows.Add(new EstimateRowModel
                {
                    SNo = "",
                    ItemOfWork = $"   -> RCC Slab ({floor.Name}): {slabName} [ID: {slab.Id}]",
                    NosOfFlats = "1",
                    NosOfInstance = "1",
                    Length = area.ToString("F2"),
                    Width = "-",
                    HeightDepth = thickness.ToString("F2"),
                    Qty = qty.ToString("F2"),
                    Unit = "Cum",
                    Remarks = $"Slab volume for {floor.Name} (Area {area:F2} Sqm x Thickness {thickness:F2}m)."
                });
            }

            // Fallback message
            if (rows.Count == 0)
            {
                rows.Add(new EstimateRowModel
                {
                    SNo = "",
                    ItemOfWork = "   -> [No RCC Columns, Beams, or Slabs found in enabled floors]",
                    NosOfFlats = "-",
                    NosOfInstance = "0",
                    Length = "-",
                    Width = "-",
                    HeightDepth = "-",
                    Qty = "0.00",
                    Unit = "-",
                    Remarks = "No elements detected."
                });
            }

            return rows;
        }

        private double GetBeamCutLength(Element beam)
        {
            Parameter cutLenParam = beam.LookupParameter("Cut Length") ?? beam.get_Parameter(BuiltInParameter.INSTANCE_LENGTH_PARAM);
            if (cutLenParam != null && cutLenParam.HasValue) return UnitUtils.ConvertFromInternalUnits(cutLenParam.AsDouble(), UnitTypeId.Meters);

            if (beam.Location is LocationCurve locCurve && locCurve.Curve != null) return UnitUtils.ConvertFromInternalUnits(locCurve.Curve.Length, UnitTypeId.Meters);

            Parameter lenParam = beam.get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH) ?? beam.LookupParameter("Length");
            if (lenParam != null && lenParam.HasValue) return UnitUtils.ConvertFromInternalUnits(lenParam.AsDouble(), UnitTypeId.Meters);

            return 3.0;
        }

        private double? GetParameterValueByName(Element elem, string paramName)
        {
            Parameter param = elem.LookupParameter(paramName);
            if (param == null && elem.Document.GetElement(elem.GetTypeId()) is ElementType elemType)
            {
                param = elemType.LookupParameter(paramName);
            }
            if (param != null && param.HasValue)
            {
                return UnitUtils.ConvertFromInternalUnits(param.AsDouble(), UnitTypeId.Meters);
            }
            return null;
        }

        private double GetElementZCoordinate(Element elem)
        {
            BoundingBoxXYZ box = elem.get_BoundingBox(null);
            if (box != null) return UnitUtils.ConvertFromInternalUnits((box.Min.Z + box.Max.Z) / 2.0, UnitTypeId.Meters);
            return 0.0;
        }

        private double GetFloorArea(Element floor)
        {
            Parameter areaParam = floor.get_Parameter(BuiltInParameter.HOST_AREA_COMPUTED) ?? floor.LookupParameter("Area");
            if (areaParam != null && areaParam.HasValue)
            {
                return UnitUtils.ConvertFromInternalUnits(areaParam.AsDouble(), UnitTypeId.SquareMeters);
            }
            return 10.0;
        }

        private double GetFloorThickness(Element floor)
        {
            Parameter thickParam = floor.get_Parameter(BuiltInParameter.FLOOR_ATTR_THICKNESS_PARAM) ?? floor.LookupParameter("Thickness");
            if (thickParam != null && thickParam.HasValue)
            {
                return UnitUtils.ConvertFromInternalUnits(thickParam.AsDouble(), UnitTypeId.Meters);
            }

            if (floor is Floor fObj && fObj.FloorType != null)
            {
                Parameter typeThick = fObj.FloorType.get_Parameter(BuiltInParameter.FLOOR_ATTR_THICKNESS_PARAM);
                if (typeThick != null && typeThick.HasValue) return UnitUtils.ConvertFromInternalUnits(typeThick.AsDouble(), UnitTypeId.Meters);
            }
            return 0.15;
        }
    }
}