// Copyright © Selmo Technology GmbH. All Rights Reserved

using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Novus.Connectivity.Common;
using SelmoStudio.Hmi.Common.SelmoProject.Ids;
using SelmoStudio.Hmi.Common.SelmoProject.ProcessValue;
using SelmoStudio.Hmi.Plugin.Model.WellKnown;
using SelmoStudio.Hmi.Server.ProcessValue.Services;
using SelmoStudio.Hmi.Server.SelmoHmi.Model.Dtos;
using SelmoStudio.Hmi.Server.SelmoHmi.Model.Ids;
using SelmoStudio.Hmi.Server.SelmoHmi.Parameter.Model.Dto;
using SelmoStudio.Hmi.Server.SelmoHmi.Plant.Model.Ids;
using SelmoStudio.Hmi.Server.SelmoHmi.ProjectInformation.Service;
using SelmoStudio.Hmi.Server.SelmoHmi.Sequence.Model;
using SelmoStudio.Hmi.Server.SelmoHmi.Sequence.Model.Dto;
using SelmoStudio.Hmi.Server.SelmoHmi.Sequence.Model.Ids;
using SelmoStudio.Hmi.Server.SelmoHmi.Sequence.Parameters;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared.Component;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared.Component.CustomArgs;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared.Model;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared.Service;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared.WellKnownConstants;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared.WellKnownTypes;
using SelmoStudio.Hmi.Server.Shared.Resources;
using System.Collections.Concurrent;

namespace SelmoStudio.Hmi.Server.SelmoHmi.Sequence.Components
{
    public partial class SequenceContentComponent : IBaseProcessValueComponent, IAsyncDisposable
    {
        private const int ContentInfoContainerMinWidth = 700;

        private ContentInfo _currentContentInfo => _projectInformationService.CurrentContentInfo;
        private List<ContentInfo> _contentInfos;
        private IReadOnlyList<ProjectItemCollectionSequence> _sequences;
        private bool _showDropDownControls;

        private bool _showManualControls;
        private bool _showParameter;
        private bool _isSingleViewMode;
        private bool _showStepTimeMonitoring;
        private bool _showLogicDiagram;
        private double _contentInfoContainerWidth;

        private ProcessValuesSequence _sequenceValueInfos;
        private short StepCounter { get; set; } = 0;

        private ElementReference _contentInfoContainerRef;

        private DotNetObjectReference<IComponent> _dotNetRef;

        private ConcurrentDictionary<SelmoHmiZoneId, bool> _outputZones = new();

        public Action OnContentInfoChanged;

        [Inject]
        private ILogger<SequenceContentComponent> _logger { get; set; }

        [Inject]
        private IProjectService _projectService { get; set; }

        [Inject]
        private IProjectInformationService _projectInformationService { get; set; }

        [Inject]
        private IProcessValueInfoService _processValueInfoService { get; set; }

        [Inject]
        private IViewportResizeService _viewportService { get; set; }

        [Parameter]
        public string Id { get; set; }

        [Parameter]
        public bool ShowOverview { get; set; }

        [Parameter]
        public Action<bool> OnSequenceStateChanged { get; set; }

        #region Init/Lifecycle

        protected override async Task OnInitializedAsync()
        {
            _projectInformationService.SequenceUpdated += OnCurrentSequenceChanged;

            await base.OnInitializedAsync();

            _logger.LogInformation($"Finished {nameof(OnInitializedAsync)} in {nameof(SequenceContentComponent)}");
        }

        private bool _isRendered;

        public bool IsRendered
        {
            get
            {
                return _isRendered;
            }
        }

        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                _dotNetRef = DotNetObjectReference.Create((IComponent)this);
                await _viewportService.Initialize(_dotNetRef, _contentInfoContainerRef);
            }

            _contentInfoContainerWidth = await _viewportService.GetWidthAsync(_contentInfoContainerRef);

            SetSingleViewMode();
            await base.OnAfterRenderAsync(firstRender);

            _isRendered = true;
        }

        protected override async Task OnParametersSetAsync()
        {
            if (_sequences != _projectInformationService.Sequences)
            {
                _sequences = _projectInformationService.Sequences;
                _logger.LogInformation($"Set {nameof(_sequences)} in {nameof(SequenceContentComponent)}");

                _contentInfos = GetContentInfos();

                if (_currentContentInfo == null)
                {
                    SetInitialContentInfo();
                }
            }
            RefreshSubPages();

            await base.OnParametersSetAsync();
        }

        #endregion Init/Lifecycle

        #region OnChange

        [JSInvokable]
        public async Task OnWidthChange(double width)
        {
            _contentInfoContainerWidth = width;

            SetSingleViewMode();

            await InvokeAsync(() => StateHasChanged());
        }

        private void OnCurrentSequenceChanged()
        {
            SetInitialContentInfo();

            StateChanged(false);

            InvokeAsync(() => StateHasChanged());
        }

        private void StateChanged(bool showOverview)
        {
            ShowOverview = showOverview;

            OnSequenceStateChanged?.Invoke(showOverview);
        }

        #endregion OnChange

        private void SetSingleViewMode()
        {
            _isSingleViewMode = _contentInfoContainerWidth <= ContentInfoContainerMinWidth;
        }

        #region Handle Process Values

        private void RefreshValue(IProcessValue processValue)
        {
            if (processValue.Value == null)
            {
                _logger.LogError($"Cannot {nameof(RefreshValue)} '{processValue.ValueInfo.Address.FullPath}'");
                return;
            }

            if (processValue.ValueInfo.Id.Equals(_sequenceValueInfos.Status.StepCount.Id))
            {
                SetStepCounterValue(processValue);
            }
            else if (_sequenceValueInfos.StandardWaitingFor.Select(wf => wf.Value.Id).Contains(processValue.ValueInfo.Id))
            {
                UpdateWaitingStandardWaitingForInfos(processValue);
            }
            else if (_sequenceValueInfos.OutputZones.Select(o => o.Value.Id).Contains(processValue.ValueInfo.Id))
            {
                var zoneProcessValueInfo = _sequenceValueInfos.OutputZones.FirstOrDefault(o => o.Value.Id.Equals(processValue.ValueInfo.Id));

                if (zoneProcessValueInfo.Value.DataType == typeof(bool))
                {
                    _outputZones.AddOrUpdate(zoneProcessValueInfo.Key, (bool)processValue.Value, (_, _) => (bool)processValue.Value);
                }
            }
            else
            {
                var zoneId = CheckValueInfoInZoneInfo(processValue.ValueInfo.Id);
                if (!zoneId.Guid.Equals(Guid.Empty))
                {
                    UpdateWaitingForZoneList(processValue, zoneId);
                }
            }
        }

        private SelmoHmiZoneId CheckValueInfoInZoneInfo(ProcessValueId id)
        {
            foreach (var zoneInfo in _sequenceValueInfos.ZoneInfos)
            {
                if (zoneInfo.Value.WaitingActive.Id.Equals(id))
                {
                    return zoneInfo.Key;
                }
            }
            return new(Guid.Empty);
        }

        private List<ZoneDto> GetActiveOutputZones()
        {
            var activeOutputZones = new List<ZoneDto>();
            var activeOutputZonesIds = _outputZones.Where(z => z.Value).Select(z => z.Key).ToList();

            foreach (var id in activeOutputZonesIds)
            {
                var zone = _projectInformationService.CurrentSequence.ZoneCollection.Zones.FirstOrDefault(z => z.Id.Guid.Equals(id.Guid));

                if (zone != null)
                {
                    activeOutputZones.Add(zone);
                }
            }

            return activeOutputZones.OrderBy(z => z.ZoneValueIndex).ToList();
        }

        private void SetStepCounterValue(IProcessValue value)
        {
            if (value.Value == null)
                return;
            if (short.TryParse(value.Value.ToString(), out short result))
            {
                StepCounter = result;
            }
        }

        #endregion Handle Process Values

        #region Sequence Content Handling

        private void SetInitialContentInfo()
        {
            _projectInformationService.SelectContentInfo(GetInitialContentInfo());

            _showManualControls = false;
            _showParameter = false;

            ClearActiveHistory();
        }

        private ContentInfo GetInitialContentInfo()
        {
            return _contentInfos.FirstOrDefault() ?? new ContentInfo();
        }

        private ContentInfoId? _activeContentInfoId;
        private Queue<ContentInfoId> _activeContentInfoIdsHistory = new(capacity: 2);

        private void UpdateActiveHistory(ContentInfoId contentInfoId, bool isActive)
        {
            var temp = new Queue<ContentInfoId>(capacity: 2);

            foreach (var existing in _activeContentInfoIdsHistory)
            {
                if (existing != contentInfoId && temp.Count < 2)
                {
                    temp.Enqueue(existing);
                }
            }

            if (isActive)
            {
                temp.Enqueue(contentInfoId);
            }

            _activeContentInfoIdsHistory = temp;
            _activeContentInfoId = _activeContentInfoIdsHistory.Any() ? _activeContentInfoIdsHistory.Last() : null;
        }

        private void ClearActiveHistory()
        {
            _activeContentInfoIdsHistory.Clear();
            _activeContentInfoId = null;
        }

        #region Inner Content

        private void ChangeContent(ContentInfo contentInfo)
        {
            _projectInformationService.SelectContentInfo(contentInfo);

            if (contentInfo.Id.Guid.Equals(WellKnownElementIds.SequenceContentComponent_ManualControlContentInfoId))
            {
                if (_isSingleViewMode)
                {
                    _showManualControls = true;
                }
                else
                {
                    _showManualControls = !_showManualControls;
                }

                UpdateActiveHistory(contentInfo.Id, _showManualControls);

                _showStepTimeMonitoring = false;
                _showLogicDiagram = false;
            }
            else if (contentInfo.Id.Guid.Equals(WellKnownElementIds.SequenceContentComponent_ParameterContentInfoId))
            {
                if (_isSingleViewMode)
                {
                    _showParameter = true;
                }
                else
                {
                    _showParameter = !_showParameter;
                }

                UpdateActiveHistory(contentInfo.Id, _showParameter);

                _showStepTimeMonitoring = false;
                _showLogicDiagram = false;
            }
            else if (contentInfo.Id.Guid.Equals(WellKnownElementIds.SequenceContentComponent_StepTimeMonitoringContentInfoId))
            {
                _showManualControls = false;
                _showParameter = false;
                _showStepTimeMonitoring = !_showStepTimeMonitoring;
                _showLogicDiagram = false;
                ClearActiveHistory();
            }
            else if (contentInfo.Id.Guid.Equals(WellKnownElementIds.SequenceContentComponent_LogicDiagramContentInfoId))
            {
                _showManualControls = false;
                _showParameter = false;
                _showStepTimeMonitoring = false;
                _showLogicDiagram = !_showLogicDiagram;
                ClearActiveHistory();
            }

            RefreshSubPages();

            _projectInformationService.ClearParameters();
        }

        private bool LogicDiagramExists = false; //#3649 Hide because current step of dissolved repeater cannot be shown

        //private bool LogicDiagramExists =>
        //    _projectInformationService.CurrentSequence.LogicDiagram?.Steps.Any() ?? false;

        #endregion Inner Content

        private List<ContentInfo> GetContentInfos()
        {
            return new List<ContentInfo>()
            {
                new ContentInfo()
                {
                    Id = new ContentInfoId(WellKnownElementIds.SequenceContentComponent_ManualControlContentInfoId),
                    Description = HmiResource.ManualControls,
                    IconName = "manualControlIcon",
                },
                new ContentInfo()
                {
                    Id = new ContentInfoId(WellKnownElementIds.SequenceContentComponent_ParameterContentInfoId),
                    Description = HmiResource.Parameters,
                    IconName = "parameterIcon",
                    ContentType = typeof(SequenceControlGroupsComponent),
                    Parameter = new Dictionary<string, Type>()
                    {
                        { "ParameterGroups", typeof(Dictionary<string, List<ParameterDto>>)},
                        { "ParameterLookUpId", typeof(Guid)},
                        { "ShowManualControls", typeof(bool)},
                        { "Headline", typeof(string)},
                        { "ShowParameters", typeof(bool)},
                        { "SequenceId", typeof(SequenceId)},
                        { "HardwareZoneId", typeof(HardwareZoneId)},
                        { "PlantId", typeof(PlantId)},
                        { "ParameterSource", typeof(WellKnownParameterSource)},
                        { "OnParameterChanged", typeof(EventCallback<SequenceParameterChangedEventArgs>)},
                        { "Id", typeof(string)}
                    }
                },
                new ContentInfo()
                {
                    Id = new ContentInfoId(WellKnownElementIds.SequenceContentComponent_StepTimeMonitoringContentInfoId),
                    Description = "Step Times",//_localizer[nameof(HmiResource.StepTimes)],
                    IconName = "timerOutlineIcon",
                    ContentType = typeof(SequenceControlGroupsComponent),
                    Parameter = new Dictionary<string, Type>()
                    {
                    }
                },
                 //#3649 Hide because current step of dissolved repeater cannot be shown
                //new ContentInfo()
                //{
                //    Id = new ContentInfoId(WellKnownElementIds.SequenceContentComponent_LogicDiagramContentInfoId),
                //    Description = HmiResource.Steps,
                //    IconName = "logicDiagramIcon",
                //    ContentType = typeof(LogicLayerDiagramComponent),
                //    Parameter = new Dictionary<string, Type>()
                //    {
                //        { "Diagram", typeof(LogicDiagram)},
                //        { "ActiveStep", typeof(LogicDiagramStep)},
                //        { "Width", typeof(int) },
                //        { "WidthUnit", typeof(WellKnownLengthUnits) },
                //        { "Height", typeof(int) },
                //        { "HeightUnit", typeof(WellKnownLengthUnits) },
                //        { "Id", typeof(string)}
                //    },
                //    Disabled = false
                //}
            };
        }

        #endregion Sequence Content Handling

        private void ToggleDropDownControls()
        {
            _showDropDownControls = !_showDropDownControls;
        }

        #region Waiting Information

        private List<SelmoHmiWaitingForId> _standardWaitingForIds = new();
        private List<(SelmoHmiZoneId, ProcessValueId)> _waitingForZoneId = new();

        private void UpdateWaitingForZoneList(IProcessValue value, SelmoHmiZoneId id)
        {
            if (value.Value is bool val)
            {
                if (val)
                {
                    if (!_waitingForZoneId.Contains((id, value.ValueInfo.Id)))
                        _waitingForZoneId.Add((id, value.ValueInfo.Id));

                    return;
                }
                _waitingForZoneId.Remove((id, value.ValueInfo.Id));
            }
        }

        private void UpdateWaitingStandardWaitingForInfos(IProcessValue value)
        {
            if (value.Value is bool val)
            {
                var _waitingForInfo = _sequenceValueInfos.StandardWaitingFor.First(wf => wf.Value.Id.Equals(value.ValueInfo.Id));
                if (val)
                {
                    if (!_standardWaitingForIds.Contains(_waitingForInfo.Key))
                        _standardWaitingForIds.Add(_waitingForInfo.Key);

                    return;
                }

                _standardWaitingForIds.Remove(_waitingForInfo.Key);
            }
        }

        private List<string> GetWaitingInformation(List<SelmoHmiWaitingForId> waitingForToShowIds)
        {
            if (_waitingForData == null)
                return new();

            return _waitingForData.GetWaitingForInfo(_standardWaitingForIds, _waitingForZoneId);
        }

        #endregion Waiting Information

        private LogicDiagramStep GetCurrentLogicDiagramStep()
        {
            var stepId = GetCorrectStepId(StepCounter, _projectInformationService.CurrentSequence.SequenceInfo.StepOrder.ToDictionary());
            var currentStep = _projectInformationService.CurrentSequence
                                .LogicDiagram.Steps.FirstOrDefault(s => s.StepId.Guid.Equals(stepId));

            if ((currentStep == null || currentStep.Equals(Guid.Empty)))
                return null;

            return currentStep;
        }

        private Guid GetCorrectStepId(int count, Dictionary<SelmoHmiStepId, int> stepOrder)
        {
            var stepId = stepOrder.FirstOrDefault(x => x.Value == count).Key;

            if (stepId == null || stepId.Guid.Equals(Guid.Empty))
                return Guid.Empty;

            return stepId.Guid;
        }

        #region ComponentHandling

        public event EventHandler<ComponentEventArgs> ComponentChangeEvent;

        private WaitingForData _waitingForData;

        public List<IProcessValueInfo> GetValueInfos()
        {
            _waitingForData = new(_projectInformationService.CurrentSequence.ZoneCollection.Zones);

            if (_sequences == null || _projectInformationService.CurrentSequence.SequenceInfo == null)
            {
                return new();
            }

            var currentSequenceId = _projectInformationService.CurrentSequence.SequenceInfo.Id;

            var values = _processValueInfoService.CreateSequenceRegistrationStepCounterAndZones(currentSequenceId).ValueInfos;

            _sequenceValueInfos = _processValueInfoService.GetProcessValueInfo(currentSequenceId);

            var valueList = values.ToList();

            _componentValueInfos = values.ToList();

            var valuesForStatus = _processValueInfoService
                .CreateSeqStatusRegistration(_projectInformationService.Sequences.ToList()).ValueInfos;

            _lookUpTableSeqStatus = _processValueInfoService
                .GetLookUpTableForSeqStatus(_projectInformationService.Sequences.ToList());

            _statusProcessValues = valuesForStatus.ToList();

            foreach (var subComponent in SubComponents)
            {
                if (subComponent.IsRendered)
                {
                    valueList.AddRange(subComponent.GetValueInfos());
                }
            }

            return valueList;
        }

        private List<IProcessValueInfo> _componentValueInfos = new();

        private List<IProcessValueInfo> _statusProcessValues = new();
        private Dictionary<SequenceId, ProcessValuesSequenceStatus> _lookUpTableSeqStatus = new();
        private Dictionary<ProcessValueId, IProcessValue> _lookUpTableSeqStatusValues = new();

        public void Update(IDictionary<ProcessValueId, IProcessValue> updateValues)
        {
            if (!IsRendered)
                return;

            foreach (var processValue in _componentValueInfos)
            {
                if (updateValues.ContainsKey(processValue.Id))
                {
                    RefreshValue(updateValues[processValue.Id]);
                }
            }

            foreach (var subComponent in SubComponents)
            {
                if (subComponent.IsRendered)
                {
                    subComponent.Update(updateValues);
                }
            }

            foreach (var processValue in _statusProcessValues)
            {
                if (updateValues.ContainsKey(processValue.Id))
                {
                    CheckValue(updateValues[processValue.Id]);
                }
            }

            var currentSequenceId = _projectInformationService.CurrentSequence.SequenceInfo.Id;

            var infos = _lookUpTableSeqStatus[currentSequenceId];

            _operationMode = StatusHelper.GetOperationMode(infos, _lookUpTableSeqStatusValues);
        }

        private void CheckValue(IProcessValue value)
        {
            foreach (var item in _lookUpTableSeqStatus)
            {
                if (item.Value.Contains(value.ValueInfo.Id))
                {
                    _lookUpTableSeqStatusValues[value.ValueInfo.Id] = value;
                }
            }
        }

        public void TriggerComponentChangeEvent(ButtonEventArgs e, ProcessValueId id, bool val)
        {
            if (e == null)
                return;

            var processValue = GetValueInfo(id);
            if (processValue == null)
                return;

            var processValueHandle = new ProcessValueHandleInfo()
            {
                ProcessValueHandle = ProcessValueHandle.Switch,
                ProcessValueInfo = processValue,
                Value = val
            };

            ComponentChangeEvent?.Invoke(this, ComponentEventArgs.Create(e, processValueHandle));
        }

        private IProcessValueInfo GetValueInfo(ProcessValueId id)
        {
            if (id.Equals(_sequenceValueInfos.Status.AutomaticReleased.Id))
                return _sequenceValueInfos.Status.AutomaticReleased;
            if (id.Equals(_sequenceValueInfos.TriggerAutomaticRelease.Id))
                return _sequenceValueInfos.TriggerAutomaticRelease;
            return null;
        }

        #region Sub Pages

        private SequenceSelectionComponent SequenceSelectionComp = new();
        private SequenceControlGroupsComponent SequenceControlGroupsComp = new();
        private SequenceTimeComponent SequenceTimeComp = new();
        private SequenceListViewComponent SequenceListViewComp = new();
        private SequenceControlComponent SequenceControlComp = new();

        private void RefreshSubPages()
        {
            UnsubscribeToAllComponents();
            AddSubComponents();
            SubscribeToAllSubPages();
        }

        private void AddSubComponents()
        {
            SubComponents = new()
            {
                SequenceSelectionComp,
                SequenceControlGroupsComp,
                SequenceTimeComp,
                SequenceControlComp,
                SequenceListViewComp
            };
        }

        private void UnsubscribeToAllComponents()
        {
            if (SubComponents == null) return;

            foreach (var subComponent in SubComponents)
            {
                subComponent.ComponentChangeEvent -= SubpageComponentEventTriggered;
            }
        }

        private void SubscribeToAllSubPages()
        {
            foreach (var subComponent in SubComponents)
            {
                subComponent.ComponentChangeEvent += SubpageComponentEventTriggered;
            }
        }

        private void SubpageComponentEventTriggered(object? sender, ComponentEventArgs e)
        {
            ComponentChangeEvent?.Invoke(sender, e);
        }

        private List<IBaseProcessValueComponent> SubComponents;

        private OperationMode _operationMode;
        public string WaitingForColor => GetWaitingForColor(_operationMode);

        private string GetWaitingForColor(OperationMode operationMode)
        {
            switch (operationMode)
            {
                case OperationMode.Automation:
                    return WellKnownStyleConstants.AutomationColor;

                case OperationMode.AutomationSelected:
                    return WellKnownStyleConstants.AutomationColor;

                case OperationMode.Manual:
                    return WellKnownStyleConstants.ManualColor;

                case OperationMode.Error:
                    return WellKnownStyleConstants.ErrorColor;

                default:
                    return "gray";
            }
        }

        #endregion Sub Pages

        #endregion ComponentHandling

        public async ValueTask DisposeAsync()
        {
            if (_projectInformationService != null)
            {
                _projectInformationService.SequenceUpdated -= OnCurrentSequenceChanged;
            }

            if (_viewportService != null)
            {
                try
                {
                    await _viewportService.DisposeAsync();
                }
                catch (JSDisconnectedException)
                {
                    _logger?.LogInformation("JS runtime disconnected during viewport service cleanup");
                }
                catch (ObjectDisposedException)
                {
                    _logger?.LogInformation("Viewport service already disposed");
                }
                _viewportService = null;
            }

            if (_dotNetRef != null)
            {
                _dotNetRef.Dispose();
                _dotNetRef = null;
            }

            UnsubscribeToAllComponents();
        }
    }
}