using System.ComponentModel;
using Microsoft.AspNetCore.Components;
using OpenCvSharp;
using VisionBuilder.UI.Blazor.Helpers;
using VisionBuilder.UI.Common.ViewModel;

namespace VisionBuilder.UI.Blazor.Components;

public partial class PreviewWindow : ComponentBase, IDisposable
{
    [Parameter] public PreviewVM? ViewModel { get; set; }

    private string _dataUri = string.Empty;
    private bool _isError;
    private DateTime _lastRenderTime = DateTime.MinValue;
    private Timer? _pendingTimer;
    private PreviewVM? _subscribedVm;
    private bool _disposed;

    protected override void OnParametersSet()
    {
        if (_subscribedVm != ViewModel)
        {
            Unsubscribe();
            Subscribe();
        }
    }

    private void Subscribe()
    {
        _subscribedVm = ViewModel;
        if (ViewModel != null)
            ViewModel.PropertyChanged += OnPropertyChanged;
    }

    private void Unsubscribe()
    {
        if (_subscribedVm != null)
            _subscribedVm.PropertyChanged -= OnPropertyChanged;
        _subscribedVm = null;
    }

    private async void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName != nameof(PreviewVM.ImagePreview) && e.PropertyName != nameof(PreviewVM.IsError))
            return;

        var now = DateTime.UtcNow;
        var elapsed = (now - _lastRenderTime).TotalMilliseconds;

        if (elapsed < 100)
        {
            // Schedule a render for when the throttle window expires,
            // so we always show the latest frame.
            _pendingTimer?.Dispose();
            _pendingTimer = new Timer(_ => RenderLatest(), null,
                (int)(100 - elapsed) + 1, Timeout.Infinite);
            return;
        }

        await RenderCurrentFrame();
    }

    private async void RenderLatest()
    {
        _pendingTimer?.Dispose();
        _pendingTimer = null;
        await RenderCurrentFrame();
    }

    private async Task RenderCurrentFrame()
    {
        if (_disposed) return;

        _lastRenderTime = DateTime.UtcNow;

        var mat = ViewModel?.ImagePreview;
        var isError = ViewModel?.IsError ?? false;

        string dataUri = string.Empty;
        if (mat != null && !mat.Empty())
        {
            dataUri = mat.ToBase64DataUri();
        }

        _dataUri = dataUri;
        _isError = isError;

        await InvokeAsync(StateHasChanged);
    }

    public void Dispose()
    {
        _disposed = true;
        _pendingTimer?.Dispose();
        Unsubscribe();
    }
}
