using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;

namespace Nodify.Calculator
{
    public class CalculatorViewModel : ObservableObject
    {
        public bool IsLoading { get; set; }

        public CalculatorViewModel()
        {
            CreateConnectionCommand = new DelegateCommand<ConnectorViewModel>(
                _ => CreateConnection(PendingConnection.Source, PendingConnection.Target),
                _ => CanCreateConnection(PendingConnection.Source, PendingConnection.Target));
            StartConnectionCommand = new DelegateCommand<ConnectorViewModel>(_ => PendingConnection.IsVisible = true, (c) => c != null && !(c.IsConnected && c.IsInput));
            DisconnectConnectorCommand = new DelegateCommand<ConnectorViewModel>(DisconnectConnector);
            DeleteSelectionCommand = new DelegateCommand(DeleteSelection);
            GroupSelectionCommand = new DelegateCommand(GroupSelectedOperations, () => SelectedOperations.Count > 0);

            Connections.WhenAdded(c =>
            {
                c.Input.IsConnected = true;
                c.Output.IsConnected = true;

                c.Input.Value = c.Output.Value;

                c.Output.ValueObservers.Add(c.Input);

                // Dynamic Split node: populate outputs when a model connects to the Square input
                if (!IsLoading) HandleSplitNodeConnected(c);

                // Dynamic Copy node: adapt connectors when something connects
                if (!IsLoading) HandleCopyNodeConnected(c);

                // Dynamic Take node: adapt connectors when a list connects
                if (!IsLoading) HandleTakeNodeConnected(c);
            })
            .WhenRemoved(c =>
            {
                var ic = Connections.Count(con => con.Input == c.Input || con.Output == c.Input);
                var oc = Connections.Count(con => con.Input == c.Output || con.Output == c.Output);

                if (ic == 0)
                {
                    c.Input.IsConnected = false;
                }

                if (oc == 0)
                {
                    c.Output.IsConnected = false;
                }

                c.Output.ValueObservers.Remove(c.Input);

                // Dynamic Split node: clear outputs when model disconnects
                HandleSplitNodeDisconnected(c);

                // Dynamic Copy node: reset on disconnect
                HandleCopyNodeDisconnected(c);

                // Dynamic Take node: reset on disconnect
                HandleTakeNodeDisconnected(c);
            });

            Operations.WhenAdded(x =>
            {
                x.Input.WhenRemoved(RemoveConnection);
                if (!IsLoading) x.NodeId = (Operations.Count + 1).ToString();
                Debug.WriteLine($"Currently adding the node with node id : {x.NodeId} , Title : {x.Title}");
                if (x is CalculatorInputOperationViewModel ci)
                {
                    ci.Output.WhenRemoved(RemoveConnection);
                }

                if (x is TakeOperationViewModel takeVm)
                {
                    takeVm.DisconnectNthAction = () =>
                    {
                        if (takeVm.NthConnector != null)
                            DisconnectConnector(takeVm.NthConnector);
                    };
                }

                void RemoveConnection(ConnectorViewModel i)
                {
                    var c = Connections.Where(con => con.Input == i || con.Output == i).ToArray();
                    c.ForEach(con => Connections.Remove(con));
                }
            })
            .WhenRemoved(x =>
            {
                foreach (var input in x.Input)
                {
                    DisconnectConnector(input);
                }

                foreach (var item in x.Output)
                {
                    DisconnectConnector(item);
                }

                
            });

            OperationsMenu = new OperationsMenuViewModel(this);
        }

        private NodifyObservableCollection<OperationViewModel> _operations = new NodifyObservableCollection<OperationViewModel>();
        public NodifyObservableCollection<OperationViewModel> Operations
        {
            get => _operations;
            set => SetProperty(ref _operations, value);
        }

        private NodifyObservableCollection<OperationViewModel> _selectedOperations = new NodifyObservableCollection<OperationViewModel>();
        public NodifyObservableCollection<OperationViewModel> SelectedOperations
        {
            get => _selectedOperations;
            set => SetProperty(ref _selectedOperations, value);
        }

        public NodifyObservableCollection<ConnectionViewModel> Connections { get; } = new NodifyObservableCollection<ConnectionViewModel>();
        public PendingConnectionViewModel PendingConnection { get; set; } = new PendingConnectionViewModel();
        public OperationsMenuViewModel OperationsMenu { get; set; }

        public INodifyCommand StartConnectionCommand { get; }
        public INodifyCommand CreateConnectionCommand { get; }
        public INodifyCommand DisconnectConnectorCommand { get; }
        public INodifyCommand DeleteSelectionCommand { get; }
        public INodifyCommand GroupSelectionCommand { get; }

        private void DisconnectConnector(ConnectorViewModel connector)
        {
            var connections = Connections.Where(c => c.Input == connector || c.Output == connector).ToList();
            connections.ForEach(c => Connections.Remove(c));
        }

        internal bool CanCreateConnection(ConnectorViewModel source, ConnectorViewModel? target)
            => target == null || (source != target && 
                                  (source.Shape == target.Shape || 
                                   ((source.IsCopyConnector || target.IsCopyConnector) && source.Shape != ConnectorShape.Triangle && target.Shape != ConnectorShape.Triangle)) &&
                                  !source.IsConnected &&
                                  !target.IsConnected &&
                                  source.IsInput != target.IsInput &&
                                  AreDataTypesCompatible(source.DataType, target.DataType) &&
                                  !IsTakeListBlockedConnection(source, target));

        private static bool AreDataTypesCompatible(string sourceType, string targetType)
        {
            // If either side has no DataType set, allow connection (generic/untyped connector)
            if (string.IsNullOrEmpty(sourceType) || string.IsNullOrEmpty(targetType))
                return true;
            return string.Equals(sourceType, targetType, System.StringComparison.OrdinalIgnoreCase);
        }

        private static bool IsTakeListBlockedConnection(ConnectorViewModel source, ConnectorViewModel target)
        {
            // If one side is a TAKE list connector, the other must have a list/array DataType
            var takeConn = source.IsTakeListConnector ? source : target.IsTakeListConnector ? target : null;
            if (takeConn == null) return false;

            var otherConn = takeConn == source ? target : source;
            var dt = (otherConn.DataType ?? "").ToLower();
            return !(dt.StartsWith("list<") || dt.EndsWith("[]"));
        }

        internal void OpenGetSetVariable(Point TargetLocation, string className)
        {
            OperationsMenu.OpenOnlyGetSetVariable(TargetLocation, className);
            OperationsMenu.Closed += OnOperationsMenuClosed;
        }

        internal void OpenGetSetForVariable(Point targetLocation, OperationInfoViewModel variableInfo)
        {
            OperationsMenu.OpenGetSetForVariable(targetLocation, variableInfo);
            OperationsMenu.Closed += OnOperationsMenuClosed;
        }

        internal void CreateConnection(ConnectorViewModel source, ConnectorViewModel? target)
        {
            if (target == null)
            {
                PendingConnection.IsVisible = true;
                OperationsMenu.OpenAt(PendingConnection.TargetLocation);
                OperationsMenu.Closed += OnOperationsMenuClosed;
                return;
            }

            var input = source.IsInput ? source : target;
            var output = target.IsInput ? source : target;

            PendingConnection.IsVisible = false;

            DisconnectConnector(input);

            Connections.Add(new ConnectionViewModel
            {
                Input = input,
                Output = output,
                InputNodeId = source.Operation.NodeId,
                OutputNodeId = target.Operation.NodeId

            });

            Debug.WriteLine($"Creating a connection between input node id: {source.Operation.NodeId} and :{target.Operation.NodeId}");
        }

        private void OnOperationsMenuClosed()
        {
            PendingConnection.IsVisible = false;
            OperationsMenu.Closed -= OnOperationsMenuClosed;
        }

        private void DeleteSelection()
        {
            var selected = SelectedOperations.ToList();
            selected.ForEach(o => Operations.Remove(o));
        }

        private void GroupSelectedOperations()
        {
            var selected = SelectedOperations.ToList();
            var bounding = selected.GetBoundingBox(50);

            Operations.Add(new OperationGroupViewModel
            {
                Title = "Operations",
                Location = bounding.Location,
                GroupSize = new Size(bounding.Width, bounding.Height)
            });
        }

        private void HandleSplitNodeConnected(ConnectionViewModel c)
        {
            // Determine which side is the Split node's input (Square connector)
            var splitInput = c.Input.Shape == ConnectorShape.Square ? c.Input : null;
            if (splitInput == null) splitInput = c.Output.Shape == ConnectorShape.Square ? c.Output : null;
            if (splitInput == null) return;

            var splitOp = splitInput.Operation;
            if (splitOp is not SystemOperationViewModel sysVm || sysVm.SystemOperationType != SystemOperations.SPLIT)
                return;

            // The other end is the source — find which model class it comes from
            var sourceConnector = (c.Input == splitInput) ? c.Output : c.Input;
            var sourceOp = sourceConnector.Operation;

            // Determine class name from the source operation or connector
            string className = null;
            if (sourceOp is SystemOperationViewModel srcSys)
            {
                var title = srcSys.Title ?? "";
                if (title.StartsWith("GET ")) className = title.Substring(4).Trim();
                else if (title.StartsWith("SET ")) className = title.Split(' ').ElementAtOrDefault(1);
            }
            else if (sourceOp is APIOperationViewModel apiVm)
            {
                // API node: get response model class name
                className = apiVm.ResponseModelClassName;
                // Strip List<> wrapper if present
                if (!string.IsNullOrEmpty(className) && className.StartsWith("List<") && className.EndsWith(">"))
                    className = className.Substring(5, className.Length - 6);
            }

            // Fallback: try to get class name from the source connector's DataType
            if (string.IsNullOrEmpty(className) && !string.IsNullOrEmpty(sourceConnector.DataType))
            {
                var dt = sourceConnector.DataType;
                if (dt.StartsWith("List<") && dt.EndsWith(">"))
                    dt = dt.Substring(5, dt.Length - 6);
                // If it's not a primitive type, treat as class name
                if (!new[] { "string", "int", "double", "bool", "object" }.Contains(dt.ToLower()))
                    className = dt;
            }

            if (string.IsNullOrEmpty(className)) return;

            // Read model .cs file and parse properties
            var customModelDir = Path.Combine(ProjectManager.ProjectDirectory, "CustomModels");
            var filePath = Path.Combine(customModelDir, $"{className}.cs");
            if (!File.Exists(filePath)) return;

            var fileContent = File.ReadAllText(filePath);
            var properties = OperationFactory.GetPropertiesFromClassPublic(fileContent);
            if (properties == null || properties.Count == 0) return;

            // Remove any existing dynamic outputs (except flow connectors)
            var toRemove = splitOp.Output.Where(o => o.Shape != ConnectorShape.Triangle).ToList();
            toRemove.ForEach(o =>
            {
                DisconnectConnector(o);
                splitOp.Output.Remove(o);
            });

            // Add property outputs
            foreach (var prop in properties)
            {
                var propColor = ConnectorViewModel.GetColorForType(prop.Type);
                splitOp.Output.Add(new ConnectorViewModel
                {
                    Title = $"{prop.Name} ({prop.Type})",
                    IsInput = false,
                    Shape = ConnectorShape.Circle,
                    ConnectorColor = propColor,
                    DataType = prop.Type.ToLower()
                });
            }

            sysVm.Title = $"Split {className}";
        }

        private void HandleSplitNodeDisconnected(ConnectionViewModel c)
        {
            var splitInput = c.Input.Shape == ConnectorShape.Square ? c.Input : null;
            if (splitInput == null) splitInput = c.Output.Shape == ConnectorShape.Square ? c.Output : null;
            if (splitInput == null) return;

            var splitOp = splitInput.Operation;
            if (splitOp is not SystemOperationViewModel sysVm || sysVm.SystemOperationType != SystemOperations.SPLIT)
                return;

            // Check if the Square input still has any connection
            var stillConnected = Connections.Any(con => con.Input == splitInput || con.Output == splitInput);
            if (stillConnected) return;

            // Remove all dynamic output connectors and their connections
            var toRemove = splitOp.Output.Where(o => o.Shape != ConnectorShape.Triangle).ToList();
            toRemove.ForEach(o =>
            {
                DisconnectConnector(o);
                splitOp.Output.Remove(o);
            });

            sysVm.Title = "Split";
        }

        private void HandleCopyNodeConnected(ConnectionViewModel c)
        {
            // Find the COPY node's input connector
            ConnectorViewModel copyConnector = null;
            ConnectorViewModel sourceConnector = null;

            if (c.Input.IsCopyConnector)
            {
                copyConnector = c.Input;
                sourceConnector = c.Output;
            }
            else if (c.Output.IsCopyConnector)
            {
                copyConnector = c.Output;
                sourceConnector = c.Input;
            }

            if (copyConnector == null) return;

            var copyOp = copyConnector.Operation;
            if (copyOp is not SystemOperationViewModel sysVm || sysVm.SystemOperationType != SystemOperations.COPY)
                return;

            // Adapt the input connector to match the source
            copyConnector.Shape = sourceConnector.Shape;
            copyConnector.ConnectorColor = sourceConnector.RawColor;
            copyConnector.DataType = sourceConnector.DataType;

            // Remove any existing outputs and their connections
            var toRemove = copyOp.Output.ToList();
            toRemove.ForEach(o =>
            {
                DisconnectConnector(o);
                copyOp.Output.Remove(o);
            });

            // Create 2 output copy connectors matching the source
            for (int i = 0; i < 2; i++)
            {
                copyOp.Output.Add(new ConnectorViewModel
                {
                    Title = sourceConnector.Title ?? "",
                    IsInput = false,
                    Shape = sourceConnector.Shape,
                    ConnectorColor = sourceConnector.RawColor,
                    DataType = sourceConnector.DataType,
                    IsCopyConnector = true
                });
            }
        }

        private void HandleCopyNodeDisconnected(ConnectionViewModel c)
        {
            ConnectorViewModel copyConnector = null;

            if (c.Input.IsCopyConnector && c.Input.IsInput)
                copyConnector = c.Input;
            else if (c.Output.IsCopyConnector && c.Output.IsInput)
                copyConnector = c.Output;

            if (copyConnector == null) return;

            var copyOp = copyConnector.Operation;
            if (copyOp is not SystemOperationViewModel sysVm || sysVm.SystemOperationType != SystemOperations.COPY)
                return;

            // Check if input still has a connection
            var stillConnected = Connections.Any(con => con.Input == copyConnector || con.Output == copyConnector);
            if (stillConnected) return;

            // Reset input connector to universal
            copyConnector.Shape = ConnectorShape.Circle;
            copyConnector.ConnectorColor = System.Drawing.Color.White;
            copyConnector.DataType = string.Empty;

            // Remove all outputs and their connections
            var toRemove = copyOp.Output.ToList();
            toRemove.ForEach(o =>
            {
                DisconnectConnector(o);
                copyOp.Output.Remove(o);
            });
        }

        private void HandleTakeNodeConnected(ConnectionViewModel c)
        {
            // Find the TAKE node's list input (IsCopyConnector on input side)
            ConnectorViewModel takeListInput = null;
            ConnectorViewModel sourceConnector = null;

            if (c.Input.IsCopyConnector && c.Input.IsInput)
            {
                var op = c.Input.Operation;
                if (op is TakeOperationViewModel)
                {
                    takeListInput = c.Input;
                    sourceConnector = c.Output;
                }
            }
            else if (c.Output.IsCopyConnector && c.Output.IsInput)
            {
                var op = c.Output.Operation;
                if (op is TakeOperationViewModel)
                {
                    takeListInput = c.Output;
                    sourceConnector = c.Input;
                }
            }

            if (takeListInput == null || sourceConnector == null) return;

            var takeOp = (TakeOperationViewModel)takeListInput.Operation;

            // Adapt the list input to match source shape/color
            takeListInput.Shape = sourceConnector.Shape;
            takeListInput.ConnectorColor = sourceConnector.RawColor;
            takeListInput.DataType = sourceConnector.DataType;

            // Determine the element type from a list type (e.g. "List<ClassName>" -> "ClassName")
            var elementType = sourceConnector.DataType ?? "";
            if (elementType.StartsWith("List<", System.StringComparison.OrdinalIgnoreCase) && elementType.EndsWith(">"))
            {
                elementType = elementType.Substring(5, elementType.Length - 6);
            }

            var elementColor = ConnectorViewModel.GetColorForType(elementType);

            // Check if the element type is a model class (has a .cs file in CustomModels)
            bool isModelType = false;
            var modelFilePath = Path.Combine(ProjectManager.ProjectDirectory, "CustomModels", $"{elementType}.cs");
            if (File.Exists(modelFilePath))
                isModelType = true;

            // Remove existing outputs
            var toRemove = takeOp.Output.ToList();
            toRemove.ForEach(o =>
            {
                DisconnectConnector(o);
                takeOp.Output.Remove(o);
            });

            // Add single output for the element type — use Square shape for model types
            takeOp.Output.Add(new ConnectorViewModel
            {
                Title = "Element",
                IsInput = false,
                Shape = isModelType ? ConnectorShape.Square : ConnectorShape.Circle,
                ConnectorColor = isModelType ? System.Drawing.Color.MediumPurple : elementColor,
                DataType = elementType
            });
        }

        private void HandleTakeNodeDisconnected(ConnectionViewModel c)
        {
            ConnectorViewModel takeListInput = null;

            if (c.Input.IsCopyConnector && c.Input.IsInput && c.Input.Operation is TakeOperationViewModel)
                takeListInput = c.Input;
            else if (c.Output.IsCopyConnector && c.Output.IsInput && c.Output.Operation is TakeOperationViewModel)
                takeListInput = c.Output;

            if (takeListInput == null) return;

            var takeOp = (TakeOperationViewModel)takeListInput.Operation;

            // Check if input still has a connection
            var stillConnected = Connections.Any(con => con.Input == takeListInput || con.Output == takeListInput);
            if (stillConnected) return;

            // Reset list input to universal
            takeListInput.Shape = ConnectorShape.Circle;
            takeListInput.ConnectorColor = System.Drawing.Color.White;
            takeListInput.DataType = string.Empty;

            // Remove all outputs
            var toRemove = takeOp.Output.ToList();
            toRemove.ForEach(o =>
            {
                DisconnectConnector(o);
                takeOp.Output.Remove(o);
            });
        }
    }
}

