using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace ytLive.Models;

public enum BorderAnimation
{
    None,
    Pulse,
    Chase,
    Rainbow,
    Shimmer,
    MarchingAnts,
    Glow,
    Electricity,
    Sparkles
}

/// <summary>
/// A thing rendered in a scene: a multi-instance Source (image/background/text)
/// or a singleton resource's per-scene config (webcam). Carries the shared
/// layout + clip/mirror + border surface the preview pipeline renders from.
/// </summary>
public abstract class SceneElement : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void Raise([CallerMemberName] string? name = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    protected bool Set<T>(ref T field, T value, [CallerMemberName] string? name = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        Raise(name);
        return true;
    }

    public string Id { get; init; } = Guid.NewGuid().ToString();

    private string _name = string.Empty;
    public string Name { get => _name; set => Set(ref _name, value); }

    /// <summary>True only for singleton resources (webcam). Gates webcam-only UI.</summary>
    public virtual bool IsWebcam => false;

    /// <summary>True only for image sources — the raw preview renderer's discriminator.</summary>
    public virtual bool IsImageSource => false;

    private bool _isVisible = true;
    public bool IsVisible { get => _isVisible; set => Set(ref _isVisible, value); }

    private bool _isEditing;
    public bool IsEditing { get => _isEditing; set => Set(ref _isEditing, value); }

    private ClipShape _clipShape = ClipShape.Traditional;
    public ClipShape ClipShape
    {
        get => _clipShape;
        set
        {
            if (Set(ref _clipShape, value))
                Raise(nameof(ShapeButtonText));
        }
    }

    // The rectangular dimensions before a Round toggle, so switching back restores
    // the aspect instead of staying locked to the square Round used. Persisted so
    // the restore survives a reload of a Round element.
    private double? _rectWidth;
    private double? _rectHeight;

    public double? RectWidth { get => _rectWidth; set => Set(ref _rectWidth, value); }
    public double? RectHeight { get => _rectHeight; set => Set(ref _rectHeight, value); }

    public void ToggleClipShape()
    {
        if (ClipShape == ClipShape.Traditional)
        {
            RectWidth = Width;
            RectHeight = Height;
            ClipShape = ClipShape.Round;
        }
        else
        {
            ClipShape = ClipShape.Traditional;
            if (RectWidth is { } rw && RectHeight is { } rh)
            {
                Width = rw;
                Height = rh;
                RectWidth = null;
                RectHeight = null;
            }
        }
    }

    private bool _isMirrored;
    public bool IsMirrored
    {
        get => _isMirrored;
        set
        {
            if (Set(ref _isMirrored, value))
            {
                Raise(nameof(MirrorScale));
                Raise(nameof(MirrorButtonText));
            }
        }
    }

    public double MirrorScale => IsMirrored ? -1 : 1;
    public string MirrorButtonText => IsMirrored ? "Unmirror" : "Mirror";
    public string ShapeButtonText => ClipShape == ClipShape.Round ? "Rect" : "Round";

    // Live camera frames: the shared WriteableBitmap owned by CameraManager.
    private WriteableBitmap? _videoImageSource;
    public WriteableBitmap? VideoImageSource
    {
        get => _videoImageSource;
        set
        {
            if (Set(ref _videoImageSource, value))
                Raise(nameof(DisplaySource));
        }
    }

    /// <summary>What the preview shows: live frames (webcam) or a static image (sources).</summary>
    public abstract ImageSource? DisplaySource { get; }

    // Position/transform (per-scene usage)
    private double _x;
    public double X { get => _x; set => Set(ref _x, value); }

    private double _y;
    public double Y { get => _y; set => Set(ref _y, value); }

    private double _width;
    public double Width
    {
        get => _width;
        set
        {
            if (Set(ref _width, value))
                Raise(nameof(RoundBorderSize));
        }
    }

    private double _height;
    public double Height
    {
        get => _height;
        set
        {
            if (Set(ref _height, value))
                Raise(nameof(RoundBorderSize));
        }
    }

    private double _opacity = 1.0;
    public double Opacity { get => _opacity; set => Set(ref _opacity, value); }

    /// <summary>Round clip/border diameter = the shorter dimension (true circle).</summary>
    public double RoundBorderSize => Math.Min(Width, Height);

    // Border (OBS-style): #RRGGBB color, alpha opacity, pixel width. Off by default.
    private string _borderColor = string.Empty;
    public string BorderColor
    {
        get => _borderColor;
        set
        {
            if (Set(ref _borderColor, value))
            {
                Raise(nameof(HasBorder));
                Raise(nameof(BorderBrush));
            }
        }
    }

    private double _borderOpacity = 1.0;
    public double BorderOpacity
    {
        get => _borderOpacity;
        set
        {
            if (Set(ref _borderOpacity, value))
            {
                Raise(nameof(HasBorder));
                Raise(nameof(BorderBrush));
            }
        }
    }

    private int _borderWidth;
    public int BorderWidth
    {
        get => _borderWidth;
        set
        {
            if (Set(ref _borderWidth, value))
            {
                Raise(nameof(HasBorder));
                Raise(nameof(BorderBrush));
            }
        }
    }

    private BorderAnimation _borderAnimation = BorderAnimation.None;
    public BorderAnimation BorderAnimation { get => _borderAnimation; set => Set(ref _borderAnimation, value); }

    public bool HasBorder
    {
        get
        {
            if (BorderWidth <= 0) return false;
            return TryGetBorderColor(out _, out _, out _);
        }
    }

    public Brush? BorderBrush
    {
        get
        {
            if (!HasBorder || !TryGetBorderColor(out var r, out var g, out var b)) return null;
            var alpha = (byte)Math.Round(Math.Clamp(BorderOpacity, 0, 1) * 255);
            return new SolidColorBrush(Color.FromArgb(alpha, r, g, b));
        }
    }

    public bool TryGetBorderColor(out byte r, out byte g, out byte b)
    {
        r = g = b = 0;
        var hex = BorderColor.Trim().TrimStart('#');
        if (hex.Length != 6) return false;
        return byte.TryParse(hex.AsSpan(0, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out r)
            && byte.TryParse(hex.AsSpan(2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out g)
            && byte.TryParse(hex.AsSpan(4, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out b);
    }
}
