using System;
using System.Threading;
using System.Threading.Tasks;
using TeamsISO.Engine.Controller;
using TeamsISO.Engine.Domain;

namespace TeamsISO.App.WinUI.ViewModels;

/// <summary>
/// Slim per-row view model for the WinUI 3 host. Drops the WPF-specific
/// surfaces (thumbnail WriteableBitmap, clipboard, PreviewWindow, snapshot
/// PNG encoder) that don't translate without a separate composition path.
/// The essential operator-facing properties stay: display name, source
/// codec, signal state, audio level, ISO toggle.
///
/// Thumbnails and per-row snapshot/preview come back in a follow-up
/// commit alongside Microsoft.UI.Xaml.Media.Imaging.WriteableBitmap +
/// SoftwareBitmap encoding.
/// </summary>
public sealed class ParticipantViewModel : ObservableObject
{
    private readonly IIsoController _controller;
    private Participant _participant;
    private bool _isEnabled;
    private bool _isProcessing;
    private bool _isActiveSpeaker;
    private double _displayedAudioLevel;
    private string _stateLabel = "—";
    private string _outputName = string.Empty;
    private string _sourceCodec = string.Empty;

    public ParticipantViewModel(IIsoController controller, Participant participant)
    {
        _controller = controller;
        _participant = participant;
        ToggleIsoCommand = new AsyncRelayCommand(ToggleIsoAsync, () => !_isProcessing);
        RefreshFromParticipant();
    }

    public Guid Id => _participant.Id;

    public string DisplayName => string.IsNullOrWhiteSpace(_participant.DisplayName)
        ? _participant.Id.ToString().Substring(0, 8)
        : _participant.DisplayName;

    public string Initials
    {
        get
        {
            var name = DisplayName;
            if (string.IsNullOrWhiteSpace(name)) return "??";
            var parts = name.Split(' ', StringSplitOptions.RemoveEmptyEntries);
            if (parts.Length == 0) return name.Substring(0, Math.Min(2, name.Length)).ToUpperInvariant();
            if (parts.Length == 1) return parts[0].Substring(0, Math.Min(2, parts[0].Length)).ToUpperInvariant();
            return (parts[0][0].ToString() + parts[^1][0]).ToUpperInvariant();
        }
    }

    public string SourceCodec
    {
        get => _sourceCodec;
        private set => SetField(ref _sourceCodec, value);
    }

    public bool IsOnline => _participant.CurrentSource is not null;

    public bool IsEnabled
    {
        get => _isEnabled;
        private set
        {
            if (SetField(ref _isEnabled, value))
            {
                OnPropertyChanged(nameof(IsoStateLabel));
            }
        }
    }

    public bool IsProcessing
    {
        get => _isProcessing;
        private set
        {
            if (SetField(ref _isProcessing, value))
                ToggleIsoCommand.RaiseCanExecuteChanged();
        }
    }

    public bool IsActiveSpeaker
    {
        get => _isActiveSpeaker;
        internal set => SetField(ref _isActiveSpeaker, value);
    }

    public double DisplayedAudioLevel
    {
        get => _displayedAudioLevel;
        private set => SetField(ref _displayedAudioLevel, value);
    }

    /// <summary>Display label for the ISO pill: LIVE / OFF / ERROR / NO SIGNAL / STARTING.</summary>
    public string IsoStateLabel => _stateLabel;

    /// <summary>
    /// Output name resolved from the operator's template. For now, a
    /// deterministic TEAMSISO_{firstname_lowercase} fallback to keep
    /// the demo running without the OutputNameTemplate plumbing.
    /// </summary>
    public string OutputName
    {
        get => _outputName;
        private set => SetField(ref _outputName, value);
    }

    public AsyncRelayCommand ToggleIsoCommand { get; }

    /// <summary>Engine emits an updated Participant — refresh derived fields.</summary>
    public void Update(Participant updated)
    {
        _participant = updated;
        RefreshFromParticipant();
        OnPropertyChanged(nameof(DisplayName));
        OnPropertyChanged(nameof(Initials));
        OnPropertyChanged(nameof(IsOnline));
        OnPropertyChanged(nameof(OutputName));
    }

    private void RefreshFromParticipant()
    {
        // Output name: TEAMSISO_<firstname-lowercased-with-underscores>.
        // Mirrors the engine's default template loosely. Will replace
        // with the operator's template once OutputNameTemplate moves
        // into the engine layer (currently it's WPF-host-side).
        var name = DisplayName.Replace(' ', '_').ToLowerInvariant();
        OutputName = "TEAMSISO_" + name;

        // Source codec line: "MS Teams · WxH · fps". When no source is
        // attached yet, show a placeholder.
        var src = _participant.CurrentSource;
        SourceCodec = src is null
            ? "Offline"
            : "MS Teams · awaiting frame";
    }

    /// <summary>
    /// Called from MainViewModel's 1Hz stats tick. Updates audio level
    /// with attack/decay, state label from the engine's IsoHealthStats.
    /// </summary>
    public void UpdateStats(IsoHealthStats stats)
    {
        if (stats.PeakAudioLevel > _displayedAudioLevel)
        {
            _displayedAudioLevel = stats.PeakAudioLevel;
        }
        else
        {
            _displayedAudioLevel *= 0.7;
            if (_displayedAudioLevel < 0.01) _displayedAudioLevel = 0;
        }
        OnPropertyChanged(nameof(DisplayedAudioLevel));

        var newLabel = stats.State switch
        {
            IsoState.Receiving => "LIVE",
            IsoState.Sending => "LIVE",
            IsoState.NoSignal => "NO SIGNAL",
            IsoState.Error => "ERROR",
            IsoState.Idle => IsEnabled ? "STARTING" : "OFF",
            _ => "OFF",
        };

        if (stats.IncomingWidth > 0 && stats.IncomingHeight > 0)
        {
            SourceCodec = $"MS Teams · {stats.IncomingWidth}×{stats.IncomingHeight} · {stats.IncomingFps:0} fps";
        }

        if (_stateLabel != newLabel)
        {
            _stateLabel = newLabel;
            OnPropertyChanged(nameof(IsoStateLabel));
        }
    }

    private async Task ToggleIsoAsync()
    {
        IsProcessing = true;
        try
        {
            if (IsEnabled)
            {
                await _controller.DisableIsoAsync(Id, CancellationToken.None);
                IsEnabled = false;
            }
            else
            {
                await _controller.EnableIsoAsync(Id, OutputName, CancellationToken.None);
                IsEnabled = true;
            }
        }
        catch
        {
            // Failures surface in the engine alerts stream; don't crash the UI here.
        }
        finally
        {
            IsProcessing = false;
        }
    }
}
