using RioJoy.Core.Calibration;
using RioJoy.Core.Editing;

namespace RioJoy.Tray.Editor;

/// <summary>
/// Interactive cockpit control-panel map: draws the functional groups (via
/// <see cref="PanelView"/>) and raises <see cref="AddressSelected"/> when a button
/// is clicked. Sized to the full panel so the host scrolls it.
/// </summary>
internal sealed class PanelCanvas : Control
{
    private static readonly IList<PanelButton> AllButtons = CockpitPanel.Buttons();

    public PanelCanvas()
    {
        DoubleBuffered = true;
        BackColor = Color.FromArgb(28, 28, 28);
        Size = PanelView.GridSize(AllButtons);
    }

    /// <summary>Resolves the display label for an address (from the profile).</summary>
    public Func<int, string?>? LabelProvider { get; set; }

    /// <summary>Resolves the assigned-function caption for an address (empty = unmapped).</summary>
    public Func<int, string?>? FunctionProvider { get; set; }

    /// <summary>Whether an address's lamp is lit (the binding's IsLit flag) — drives the lamp shade.</summary>
    public Func<int, bool>? LitProvider { get; set; }

    /// <summary>Resolves a <c>[JoyStick]</c> setting's current on/off value.</summary>
    public Func<JoyStickSetting, bool>? CalibrationProvider { get; set; }

    private readonly HashSet<int> _livePressed = new();
    private AxisReadout? _axes;

    public int? SelectedAddress { get; private set; }

    public event Action<int>? AddressSelected;

    /// <summary>Mark a RIO address as live-pressed (or released) for the on-panel indicator.</summary>
    public void SetLivePressed(int address, bool pressed)
    {
        if (pressed ? _livePressed.Add(address) : _livePressed.Remove(address))
            Invalidate();
    }

    /// <summary>
    /// Show the latest calibrated readout on the encoder-gauge strip. Arrives at
    /// the analog poll rate (~18 Hz), so only the strip is repainted, and only when a
    /// value actually changed.
    /// </summary>
    public void SetAxes(AxisReadout axes)
    {
        if (_axes is { } p &&
            p.Axes.X == axes.Axes.X && p.Axes.Y == axes.Axes.Y && p.Axes.Z == axes.Axes.Z &&
            p.Axes.Rx == axes.Axes.Rx && p.Axes.Ry == axes.Axes.Ry && p.Axes.Rz == axes.Axes.Rz &&
            p.LeftPedal == axes.LeftPedal && p.RightPedal == axes.RightPedal)
            return;
        _axes = axes;
        Invalidate(new Rectangle(0, 0, Width, PanelView.TopStrip));
    }

    /// <summary>Raised when a <c>[JoyStick]</c> toggle cell is clicked.</summary>
    public event Action<JoyStickSetting>? CalibrationToggled;

    public void Select(int? address)
    {
        SelectedAddress = address;
        Invalidate();
    }

    public void Refresh(int? address)
    {
        SelectedAddress = address;
        Invalidate();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        PanelView.Paint(
            e.Graphics, AllButtons, CockpitPanel.Groups,
            a => LabelProvider?.Invoke(a),
            a => FunctionProvider?.Invoke(a),
            a => LitProvider?.Invoke(a) ?? false,
            _livePressed.Contains,
            _axes,
            SelectedAddress);
        PanelView.PaintCalibration(e.Graphics, s => CalibrationProvider?.Invoke(s) ?? false);
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);

        if (PanelView.HitTestCalibration(e.Location) is { } cal)
        {
            CalibrationToggled?.Invoke(cal.Setting);
            Invalidate();
            return;
        }

        PanelButton? hit = PanelView.HitTest(AllButtons, e.Location);
        if (hit is not null)
        {
            SelectedAddress = hit.Address;
            AddressSelected?.Invoke(hit.Address);
            Invalidate();
        }
    }
}
