using System;
using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Rendering;
using ClaudeDo.Ui.ViewModels.Modals;

namespace ClaudeDo.Ui.Views.Controls;

/// <summary>
/// Usage bar with three draggable stage markers: soft (throttle to 2 slots), hard (1 slot) and gate
/// (queue paused). Positions are computed against the control's real width — no hardcoded track
/// size — and the drag math lives in <see cref="UsageThresholdDrag"/> so it stays testable.
/// Values are written back through TwoWay bindings while dragging; <see cref="CommitCommand"/>
/// fires once on release, which is when the host persists them.
/// A row without thresholds (plan-dependent scoped buckets) renders as a plain read-only bar.
/// </summary>
public sealed class UsageGaugeBar : Control, ICustomHitTest
{
    /// <summary>How close the pointer has to be to grab a marker.</summary>
    private const double GrabRadiusPx = 12;

    private const double TrackHeightPx = 10;
    private const double MarkerWidthPx = 2;

    public static readonly StyledProperty<double> PercentProperty =
        AvaloniaProperty.Register<UsageGaugeBar, double>(nameof(Percent));

    public static readonly StyledProperty<bool> IsWarnProperty =
        AvaloniaProperty.Register<UsageGaugeBar, bool>(nameof(IsWarn));

    public static readonly StyledProperty<int?> SoftPctProperty =
        AvaloniaProperty.Register<UsageGaugeBar, int?>(
            nameof(SoftPct), defaultBindingMode: BindingMode.TwoWay);

    public static readonly StyledProperty<int?> HardPctProperty =
        AvaloniaProperty.Register<UsageGaugeBar, int?>(
            nameof(HardPct), defaultBindingMode: BindingMode.TwoWay);

    public static readonly StyledProperty<int?> GatePctProperty =
        AvaloniaProperty.Register<UsageGaugeBar, int?>(
            nameof(GatePct), defaultBindingMode: BindingMode.TwoWay);

    public static readonly StyledProperty<IBrush?> TrackBrushProperty =
        AvaloniaProperty.Register<UsageGaugeBar, IBrush?>(nameof(TrackBrush));

    public static readonly StyledProperty<IBrush?> FillBrushProperty =
        AvaloniaProperty.Register<UsageGaugeBar, IBrush?>(nameof(FillBrush));

    public static readonly StyledProperty<IBrush?> WarnFillBrushProperty =
        AvaloniaProperty.Register<UsageGaugeBar, IBrush?>(nameof(WarnFillBrush));

    public static readonly StyledProperty<IBrush?> SoftMarkerBrushProperty =
        AvaloniaProperty.Register<UsageGaugeBar, IBrush?>(nameof(SoftMarkerBrush));

    public static readonly StyledProperty<IBrush?> HardMarkerBrushProperty =
        AvaloniaProperty.Register<UsageGaugeBar, IBrush?>(nameof(HardMarkerBrush));

    public static readonly StyledProperty<IBrush?> GateMarkerBrushProperty =
        AvaloniaProperty.Register<UsageGaugeBar, IBrush?>(nameof(GateMarkerBrush));

    public static readonly StyledProperty<ICommand?> CommitCommandProperty =
        AvaloniaProperty.Register<UsageGaugeBar, ICommand?>(nameof(CommitCommand));

    static UsageGaugeBar()
    {
        AffectsRender<UsageGaugeBar>(
            PercentProperty, IsWarnProperty, SoftPctProperty, HardPctProperty, GatePctProperty,
            TrackBrushProperty, FillBrushProperty, WarnFillBrushProperty,
            SoftMarkerBrushProperty, HardMarkerBrushProperty, GateMarkerBrushProperty);
    }

    private UsageThresholdDrag.Stage? _dragging;

    public double Percent
    {
        get => GetValue(PercentProperty);
        set => SetValue(PercentProperty, value);
    }

    public bool IsWarn
    {
        get => GetValue(IsWarnProperty);
        set => SetValue(IsWarnProperty, value);
    }

    public int? SoftPct
    {
        get => GetValue(SoftPctProperty);
        set => SetValue(SoftPctProperty, value);
    }

    public int? HardPct
    {
        get => GetValue(HardPctProperty);
        set => SetValue(HardPctProperty, value);
    }

    public int? GatePct
    {
        get => GetValue(GatePctProperty);
        set => SetValue(GatePctProperty, value);
    }

    public IBrush? TrackBrush
    {
        get => GetValue(TrackBrushProperty);
        set => SetValue(TrackBrushProperty, value);
    }

    public IBrush? FillBrush
    {
        get => GetValue(FillBrushProperty);
        set => SetValue(FillBrushProperty, value);
    }

    public IBrush? WarnFillBrush
    {
        get => GetValue(WarnFillBrushProperty);
        set => SetValue(WarnFillBrushProperty, value);
    }

    public IBrush? SoftMarkerBrush
    {
        get => GetValue(SoftMarkerBrushProperty);
        set => SetValue(SoftMarkerBrushProperty, value);
    }

    public IBrush? HardMarkerBrush
    {
        get => GetValue(HardMarkerBrushProperty);
        set => SetValue(HardMarkerBrushProperty, value);
    }

    public IBrush? GateMarkerBrush
    {
        get => GetValue(GateMarkerBrushProperty);
        set => SetValue(GateMarkerBrushProperty, value);
    }

    public ICommand? CommitCommand
    {
        get => GetValue(CommitCommandProperty);
        set => SetValue(CommitCommandProperty, value);
    }

    private bool IsAdjustable => SoftPct is not null && HardPct is not null && GatePct is not null;

    // Custom hit test (point is in local coordinates): the control draws itself, so the whole
    // rectangle takes the pointer — not just the pixels the track happens to cover.
    public bool HitTest(Point point) => new Rect(Bounds.Size).Contains(point);

    public override void Render(DrawingContext context)
    {
        var width = Bounds.Width;
        var height = Bounds.Height;
        if (width <= 0 || height <= 0) return;

        var top = Math.Max(0, (height - TrackHeightPx) / 2);
        var trackHeight = Math.Min(TrackHeightPx, height);
        var radius = trackHeight / 2;

        // Transparent full-bounds fill keeps the grab area the whole control, not just the track.
        context.FillRectangle(Brushes.Transparent, new Rect(0, 0, width, height));

        if (TrackBrush is { } track)
            context.DrawRectangle(track, null, new RoundedRect(new Rect(0, top, width, trackHeight), radius));

        var fillWidth = width * Math.Clamp(Percent, 0, 100) / 100.0;
        var fill = IsWarn ? WarnFillBrush ?? FillBrush : FillBrush;
        if (fillWidth > 0 && fill is not null)
            context.DrawRectangle(fill, null, new RoundedRect(new Rect(0, top, fillWidth, trackHeight), radius));

        DrawMarker(context, SoftPct, SoftMarkerBrush, width, height);
        DrawMarker(context, HardPct, HardMarkerBrush, width, height);
        DrawMarker(context, GatePct, GateMarkerBrush, width, height);
    }

    private static void DrawMarker(DrawingContext context, int? percent, IBrush? brush, double width, double height)
    {
        if (percent is not { } value || brush is null) return;

        var x = Math.Clamp(width * Math.Clamp(value, 0, 100) / 100.0 - MarkerWidthPx / 2, 0, Math.Max(0, width - MarkerWidthPx));
        context.FillRectangle(brush, new Rect(x, 0, MarkerWidthPx, height));
    }

    protected override void OnPointerPressed(PointerPressedEventArgs e)
    {
        base.OnPointerPressed(e);
        if (!IsAdjustable) return;

        var percent = PercentAt(e.GetPosition(this).X);
        _dragging = UsageThresholdDrag.Nearest(
            SoftPct!.Value, HardPct!.Value, GatePct!.Value, percent, GrabTolerancePercent());
        if (_dragging is null) return;

        e.Pointer.Capture(this);
        ApplyDrag(_dragging.Value, percent);
        e.Handled = true;
    }

    protected override void OnPointerMoved(PointerEventArgs e)
    {
        base.OnPointerMoved(e);
        if (!IsAdjustable) return;

        var percent = PercentAt(e.GetPosition(this).X);

        if (_dragging is { } stage)
        {
            ApplyDrag(stage, percent);
            e.Handled = true;
            return;
        }

        var hover = UsageThresholdDrag.Nearest(
            SoftPct!.Value, HardPct!.Value, GatePct!.Value, percent, GrabTolerancePercent());
        Cursor = new Cursor(hover is null ? StandardCursorType.Arrow : StandardCursorType.SizeWestEast);
    }

    protected override void OnPointerReleased(PointerReleasedEventArgs e)
    {
        base.OnPointerReleased(e);
        if (_dragging is null) return;

        _dragging = null;
        e.Pointer.Capture(null);
        e.Handled = true;

        if (CommitCommand is { } command && command.CanExecute(null))
            command.Execute(null);
    }

    private void ApplyDrag(UsageThresholdDrag.Stage stage, double percent)
    {
        var (soft, hard, gate) = UsageThresholdDrag.Apply(
            SoftPct!.Value, HardPct!.Value, GatePct!.Value, stage, percent);

        SoftPct = soft;
        HardPct = hard;
        GatePct = gate;
    }

    private double PercentAt(double x) => Bounds.Width <= 0 ? 0 : Math.Clamp(x / Bounds.Width * 100.0, 0, 100);

    private double GrabTolerancePercent() => Bounds.Width <= 0 ? 0 : GrabRadiusPx / Bounds.Width * 100.0;
}
