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

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

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

        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;

            // Standard PCC Depth = 15cm (0.15m)
            double pccDepth = 0.15;

            // ==========================================
            // PART A: PCC Under All Foundations (30cm offset in L & W)
            // ==========================================
            var foundations = collector.Where(e => e.Category != null && e.Category.Id.IntegerValue == (int)BuiltInCategory.OST_StructuralFoundation).ToList();

            foreach (var elem in foundations)
            {
                string fName = elem.Name;
                GetElementDimensions(elem, out double lengthMeters, out double widthMeters, out double thicknessMeters);

                double finalLen = lengthMeters + 0.3; // 30cm offset
                double finalWid = widthMeters + 0.3;  // 30cm offset

                double qty = finalLen * finalWid * pccDepth;
                totalQty += qty;

                rows.Add(new EstimateRowModel
                {
                    SNo = "",
                    ItemOfWork = $"   -> PCC under Foundation: {fName} [ID: {elem.Id}]",
                    NosOfFlats = "1",
                    NosOfInstance = "1",
                    Length = finalLen.ToString("F2"),
                    Width = finalWid.ToString("F2"),
                    HeightDepth = pccDepth.ToString("F2"),
                    Qty = qty.ToString("F2"),
                    Unit = "Cum",
                    Remarks = $"PCC 1:4:8 under foundation with 30cm offset (L+0.3m, W+0.3m), depth 15cm."
                });
            }

            // ==========================================
            // PART B: PCC Under All Rooms (Inner Dimensions)
            // ==========================================
            var rooms = new FilteredElementCollector(doc)
                .OfCategory(BuiltInCategory.OST_Rooms)
                .WhereElementIsNotElementType()
                .Cast<Room>()
                .Where(r => r.Area > 0)
                .ToList();

            foreach (var room in rooms)
            {
                string roomName = room.Name ?? "Room";
                GetRoomInternalDimensions(room, out double rLen, out double rWid);

                double qty = rLen * rWid * pccDepth;
                totalQty += qty;

                rows.Add(new EstimateRowModel
                {
                    SNo = "",
                    ItemOfWork = $"   -> PCC under Room: {roomName} [{room.Number}]",
                    NosOfFlats = "1",
                    NosOfInstance = "1",
                    Length = rLen.ToString("F2"),
                    Width = rWid.ToString("F2"),
                    HeightDepth = pccDepth.ToString("F2"),
                    Qty = qty.ToString("F2"),
                    Unit = "Cum",
                    Remarks = $"PCC 1:4:8 under room inner dimensions, depth 15cm."
                });
            }

            // ==========================================
            // PART C: PCC Under Plinth Beams (Filtered between Bottom of Plinth & Top of Plinth)
            // ==========================================
            double bottomPlinthZ = _levels.BottomOfPlinth;
            double topPlinthZ = _levels.TopOfPlinth;

            // Fallback if levels are not set properly
            if (bottomPlinthZ == 0 && topPlinthZ == 0)
            {
                bottomPlinthZ = 0.0;
                topPlinthZ = 0.6;
            }

            var structuralFraming = collector.Where(e => e.Category != null && e.Category.Id.IntegerValue == (int)BuiltInCategory.OST_StructuralFraming).ToList();

            foreach (var beam in structuralFraming)
            {
                // Check if beam Z coordinate lies between Bottom of Plinth and Top of Plinth
                double beamZ = GetElementZCoordinate(beam);
                if (beamZ < (bottomPlinthZ - 0.2) || beamZ > (topPlinthZ + 0.2))
                {
                    continue; // Exclude beams outside plinth level range
                }

                string beamName = beam.Name;
                double cutLength = GetBeamCutLength(beam);
                double bWid = GetParamVal(beam, BuiltInParameter.STRUCTURAL_SECTION_COMMON_WIDTH);

                if (cutLength <= 0) cutLength = 3.0; // Fallback length
                if (bWid <= 0) bWid = 0.3;          // Fallback width

                double finalBeamLen = cutLength;          // Cut Length
                double finalBeamWid = bWid + 0.05;        // 5cm (0.05m) offset in width

                double qty = finalBeamLen * finalBeamWid * pccDepth;
                totalQty += qty;

                rows.Add(new EstimateRowModel
                {
                    SNo = "",
                    ItemOfWork = $"   -> PCC under Plinth Beam: {beamName} [ID: {beam.Id}]",
                    NosOfFlats = "1",
                    NosOfInstance = "1",
                    Length = finalBeamLen.ToString("F2"),
                    Width = finalBeamWid.ToString("F2"),
                    HeightDepth = pccDepth.ToString("F2"),
                    Qty = qty.ToString("F2"),
                    Unit = "Cum",
                    Remarks = $"PCC under plinth beam (Plinth Level Z: {beamZ:F2}m) with cut length ({finalBeamLen:F2}m), width + 5cm offset ({finalBeamWid:F2}m)."
                });
            }

            if (rows.Count == 0)
            {
                rows.Add(new EstimateRowModel
                {
                    SNo = "",
                    ItemOfWork = "   -> [No matching elements found for PCC Item 3]",
                    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 void GetElementDimensions(Element elem, out double lengthMeters, out double widthMeters, out double thicknessMeters)
        {
            lengthMeters = 1.5; widthMeters = 1.5; thicknessMeters = 0.4;
            Parameter lenParam = elem.LookupParameter("Length") ?? elem.LookupParameter("Foundation Length");
            Parameter widthParam = elem.LookupParameter("Width") ?? elem.LookupParameter("Foundation Width");
            Parameter thickParam = elem.LookupParameter("Thickness") ?? elem.LookupParameter("Foundation Thickness") ?? elem.LookupParameter("Depth");

            if (lenParam != null) lengthMeters = UnitUtils.ConvertFromInternalUnits(lenParam.AsDouble(), UnitTypeId.Meters);
            if (widthParam != null) widthMeters = UnitUtils.ConvertFromInternalUnits(widthParam.AsDouble(), UnitTypeId.Meters);
            if (thickParam != null) thicknessMeters = UnitUtils.ConvertFromInternalUnits(thickParam.AsDouble(), UnitTypeId.Meters);
        }

        private void GetRoomInternalDimensions(Room room, out double lengthMeters, out double widthMeters)
        {
            lengthMeters = 4.0; widthMeters = 3.0;
            BoundingBoxXYZ box = room.get_BoundingBox(null);
            if (box != null)
            {
                lengthMeters = UnitUtils.ConvertFromInternalUnits(Math.Abs(box.Max.X - box.Min.X), UnitTypeId.Meters);
                widthMeters = UnitUtils.ConvertFromInternalUnits(Math.Abs(box.Max.Y - box.Min.Y), UnitTypeId.Meters);
            }
        }

        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 GetParamVal(Element e, BuiltInParameter param)
        {
            Parameter p = e.get_Parameter(param);
            if (p != null && p.HasValue) return UnitUtils.ConvertFromInternalUnits(p.AsDouble(), UnitTypeId.Meters);
            return 0.0;
        }
    }
}