using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using devDept.Eyeshot;
using devDept.Eyeshot.Control;
using devDept.Eyeshot.Entities;
using devDept.Geometry;
using WpfEngineeringSketch.Models.Scene;
using WpfEngineeringSketch.Tools3D;
using WpfEngineeringSketch.ViewModels;

namespace WpfEngineeringSketch.Rendering
{
    public static class SceneBinder
    {
        // Map SceneObject -> Eyeshot Entity
        private static readonly Dictionary<SceneObject, Entity> _objectMap = new Dictionary<SceneObject, Entity>();
        private static readonly Dictionary<Entity, SceneObject> _entityMap = new Dictionary<Entity, SceneObject>();
        
        // Flag to prevent infinite loops during two-way updates
        private static bool _isSyncing = false;

        public static SceneObject? GetSceneObjectFromEntity(Entity entity)
        {
            if (_entityMap.TryGetValue(entity, out var obj)) return obj;
            return null;
        }

        public static readonly DependencyProperty ViewModelProperty =
            DependencyProperty.RegisterAttached(
                "ViewModel",
                typeof(MainViewModel),
                typeof(SceneBinder),
                typeof(SceneBinder).GetProperty("ViewModel") != null ? new PropertyMetadata(null) : new PropertyMetadata(null, OnViewModelChanged));

        public static MainViewModel? GetViewModel(DependencyObject obj)
        {
            return (MainViewModel?)obj.GetValue(ViewModelProperty);
        }

        public static void SetViewModel(DependencyObject obj, MainViewModel value)
        {
            obj.SetValue(ViewModelProperty, value);
        }

        private static void OnViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is Design design)
            {
                if (e.OldValue is MainViewModel oldVm)
                {
                    design.MouseDown -= OnMouseDown;
                    design.MouseMove -= OnMouseMove;
                    design.MouseUp -= OnMouseUp;
                    design.SelectionChanged -= OnSelectionChanged;
                    oldVm.PropertyChanged -= (s, args) => OnVmPropertyChanged(s, args, design);
                }

                if (e.NewValue is MainViewModel newVm)
                {
                    design.MouseDown += OnMouseDown;
                    design.MouseMove += OnMouseMove;
                    design.MouseUp += OnMouseUp;
                    design.SelectionChanged += OnSelectionChanged;
                    
                    // Listen to tool changes
                    newVm.PropertyChanged += (s, args) => OnVmPropertyChanged(s, args, design);

                    // Initial setup
                    UpdateDesignMode(design, newVm);
                }
            }
        }

        private static void OnVmPropertyChanged(object? sender, PropertyChangedEventArgs e, Design design)
        {
            if (e.PropertyName == nameof(MainViewModel.CurrentTool))
            {
                if (sender is MainViewModel vm)
                {
                    UpdateDesignMode(design, vm);
                }
            }
        }

        private static void UpdateDesignMode(Design design, MainViewModel vm)
        {
            if (vm.CurrentTool is SelectTool3D)
            {
                // Enable standard selection and Gizmo
                design.ActionMode = actionType.SelectVisibleByPick;
                
                // Enable ObjectManipulator
                // design.ObjectManipulator.Visible = true;
                // design.ObjectManipulator.Enabled = true;

                if (design.Viewports.Count > 0)
                {
                    design.CoordinateSystemIcon.Visible = true;
                    design.ViewCubeIcon.Visible = true;
                }
            }
            else
            {
                // Disable standard selection for other tools
                design.ActionMode = actionType.None;
                // design.ObjectManipulator.Visible = false;
                // design.ObjectManipulator.Enabled = false;
                // design.ObjectManipulator.Cancel();
                
                design.Entities.ClearSelection();
            }
            design.Invalidate();
        }

        private static void OnSelectionChanged(object? sender, EventArgs e)
        {
            if (_isSyncing) return;
            if (sender is Design design)
            {
                var vm = GetViewModel(design);
                if (vm == null) return;

                _isSyncing = true;
                try
                {
                     // Sync Eyeshot selection -> ViewModel
                     // We need to match vm.SelectedObjects with design.Entities.Where(x => x.Selected)
                     
                     var selectedEntities = design.Entities.Where(x => x.Selected).ToList();
                     var selectedObjects = selectedEntities.Select(ent => _entityMap.TryGetValue(ent, out var obj) ? obj : null).Where(o => o != null).Cast<SceneObject>().ToList();

                     // Update IsSelected on all objects
                     foreach (var obj in vm.Objects)
                     {
                         bool shouldBeSelected = selectedObjects.Contains(obj);
                         if (obj.IsSelected != shouldBeSelected)
                         {
                             obj.IsSelected = shouldBeSelected;
                         }
                     }

                     // Update SelectedObjects collection
                     vm.SelectedObjects.Clear();
                     foreach(var obj in selectedObjects) vm.SelectedObjects.Add(obj);
                     
                     // Update primary selection
                     vm.SelectedObject = selectedObjects.LastOrDefault();
                }
                finally
                {
                    _isSyncing = false;
                }
            }
        }

        private static void SyncTransforms(Design design)
        {
             if (_isSyncing) return;
             _isSyncing = true;
             try
             {
                 foreach(var ent in design.Entities)
                 {
                     if (_entityMap.TryGetValue(ent, out var obj))
                     {
                          // Sync generic position if possible
                          var center = (ent.BoxMin + ent.BoxMax) / 2;
                          // This is an approximation/placeholder logic for sync
                     }
                 }
             }
             finally
             {
                 _isSyncing = false;
             }
        }
        
        private static void OnMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (sender is Design design)
            {
                var vm = GetViewModel(design);
                vm?.CurrentTool?.OnMouseDown(vm, design, e);
            }
        }

        private static void OnMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
             if (sender is Design design)
            {
                var vm = GetViewModel(design);
                vm?.CurrentTool?.OnMouseMove(vm, design, e);
            }
        }

        private static void OnMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
             if (sender is Design design)
            {
                var vm = GetViewModel(design);
                vm?.CurrentTool?.OnMouseUp(vm, design, e);
                
                // Try sync transform
                SyncTransforms(design);
            }
        }

        public static readonly DependencyProperty ObjectsProperty =
            DependencyProperty.RegisterAttached(
                "Objects",
                typeof(ObservableCollection<SceneObject>),
                typeof(SceneBinder),
                new PropertyMetadata(null, OnObjectsChanged));

        public static readonly DependencyProperty SelectedObjectProperty =
            DependencyProperty.RegisterAttached(
                "SelectedObject",
                typeof(SceneObject),
                typeof(SceneBinder),
                new PropertyMetadata(null, OnSelectedObjectChanged));

        public static ObservableCollection<SceneObject>? GetObjects(DependencyObject obj) => (ObservableCollection<SceneObject>?)obj.GetValue(ObjectsProperty);
        public static void SetObjects(DependencyObject obj, ObservableCollection<SceneObject> value) => obj.SetValue(ObjectsProperty, value);
        
        public static SceneObject? GetSelectedObject(DependencyObject obj) => (SceneObject?)obj.GetValue(SelectedObjectProperty);
        public static void SetSelectedObject(DependencyObject obj, SceneObject value) => obj.SetValue(SelectedObjectProperty, value);

        private static void OnObjectsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is Design design)
            {
                if (e.OldValue is ObservableCollection<SceneObject> oldColl)
                {
                    oldColl.CollectionChanged -= (s, args) => OnCollectionChanged(s, args, design);
                    design.Entities.Clear();
                    _objectMap.Clear();
                    _entityMap.Clear();
                }

                if (e.NewValue is ObservableCollection<SceneObject> newColl)
                {
                    newColl.CollectionChanged += (s, args) => OnCollectionChanged(s, args, design);
                    foreach (var obj in newColl) AddEntity(design, obj);
                }
                design.Invalidate();
            }
        }

        private static void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e, Design design)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (SceneObject item in e.NewItems!) AddEntity(design, item);
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (SceneObject item in e.OldItems!) RemoveEntity(design, item);
            }
            else if (e.Action == NotifyCollectionChangedAction.Reset)
            {
                design.Entities.Clear();
                _objectMap.Clear();
                _entityMap.Clear();
            }
            design.Invalidate();
        }

        private static void AddEntity(Design design, SceneObject obj)
        {
            Entity? entity = null;
            
            switch (obj.Type)
            {
                case BlockType.Cube:
                    entity = Mesh.CreateBox(1, 1, 1);
                    entity.Color = System.Drawing.Color.Blue;
                    break;
                case BlockType.Sphere:
                    entity = Mesh.CreateSphere(0.5, 16, 16);
                    entity.Color = System.Drawing.Color.Green;
                    break;
                case BlockType.Cylinder:
                    entity = Mesh.CreateCylinder(0.5, 1, 16);
                    entity.Color = System.Drawing.Color.Orange;
                    break;
                case BlockType.Column:
                    entity = Mesh.CreateBox(0.3, 0.3, 3);
                    entity.Color = System.Drawing.Color.Gray;
                    break;
                case BlockType.Beam:
                    entity = Mesh.CreateBox(5, 0.3, 0.5); // Length X, Width Y, Height Z
                    entity.Color = System.Drawing.Color.SteelBlue;
                    break;
                case BlockType.CMUWall:
                    entity = Mesh.CreateBox(0.2, 2, 3);
                    entity.Color = System.Drawing.Color.LightGray;
                    break;
                case BlockType.GridLine:
                    entity = Mesh.CreateCylinder(0.05, 10, 8); 
                    entity.Color = System.Drawing.Color.DarkGray;
                    break;
                case BlockType.UIIndicator:
                    entity = Mesh.CreateBox(0.3, 0.3, 0.3);
                    entity.Color = System.Drawing.Color.LightGreen;
                    break;
                case BlockType.Connection:
                    entity = Mesh.CreateSphere(0.2, 10, 10);
                    entity.Color = System.Drawing.Color.Red;
                    break;
            }

            if (entity != null)
            {
                ApplyTransformToEntity(entity, obj);
                
                // Bind properties
                obj.PropertyChanged += (s, e) =>
                {
                    if (_isSyncing) return; // Ignore property changes if we originated them
                    
                    if (e.PropertyName == nameof(SceneObject.Position) ||
                        e.PropertyName == nameof(SceneObject.Rotation) ||
                        e.PropertyName == nameof(SceneObject.Scale))
                    {
                        if (_objectMap.TryGetValue(obj, out var existing)) { design.Entities.Remove(existing); _objectMap.Remove(obj); _entityMap.Remove(existing); }
                        AddEntity(design, obj);
                        design.Invalidate();
                    }
                    else if (e.PropertyName == nameof(SceneObject.IsSelected))
                    {
                        if (_objectMap.TryGetValue(obj, out var ent))
                        {
                            ent.Selected = obj.IsSelected;
                        }
                        design.Invalidate();
                    }
                };

                design.Entities.Add(entity);
                _objectMap[obj] = entity;
                _entityMap[entity] = obj;
            }
        }

        private static void RemoveEntity(Design design, SceneObject obj)
        {
            if (_objectMap.TryGetValue(obj, out var entity))
            {
                design.Entities.Remove(entity);
                _objectMap.Remove(obj);
                _entityMap.Remove(entity);
            }
        }

        private static void ApplyTransformToEntity(Entity entity, SceneObject obj)
        {
            try
            {
                entity.Scale(obj.Scale.X, obj.Scale.Y, obj.Scale.Z);
                entity.Rotate(devDept.Geometry.Utility.DegToRad(obj.Rotation.X), devDept.Geometry.Vector3D.AxisX);
                entity.Rotate(devDept.Geometry.Utility.DegToRad(obj.Rotation.Y), devDept.Geometry.Vector3D.AxisY);
                entity.Rotate(devDept.Geometry.Utility.DegToRad(obj.Rotation.Z), devDept.Geometry.Vector3D.AxisZ);
                entity.Translate(obj.Position.X, obj.Position.Y, obj.Position.Z);
            }
            catch { }
        }

        private static void OnSelectedObjectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (_isSyncing) return;
            if (d is Design design && e.NewValue is SceneObject obj && _objectMap.TryGetValue(obj, out var entity))
            {
                design.Entities.ClearSelection();
                entity.Selected = true;
                design.Invalidate();
            }
             else if (d is Design d2 && e.NewValue == null)
            {
                d2.Entities.ClearSelection();
                d2.Invalidate();
            }
        }
    }
}
