using System.Runtime.Versioning;
using RioJoy.Core.Editing;
using RioJoy.Core.Hid;
using RioJoy.Core.Mapping;
using RioJoy.Core.Profiles;

namespace RioJoy.Tray.Editor;

/// <summary>
/// Profile editor: shows the cockpit control panel (the functional grouping from
/// the original Win32 RIO design — MFD clusters, board columns, keypads, encoder
/// gauges, via <see cref="CockpitPanel"/>) as a clickable map, and edits the
/// selected button's label and action/modifiers/lamp. Writes back to the
/// <see cref="RioProfile"/>'s <see cref="RioProfile.OverlayLabels"/> (keyed by the
/// wallpaper region <c>b-XX</c>) and <see cref="RioProfile.Buttons"/> (the iRIO
/// word); <c>Save</c> raises <see cref="Saved"/>.
/// </summary>
[SupportedOSPlatform("windows")]
public sealed class ProfileEditorForm : Form
{
    private static readonly Dictionary<int, string> GroupOf =
        CockpitPanel.Buttons().ToDictionary(b => b.Address, b => b.Group.Title);

    private static readonly Dictionary<int, bool> LampCapableOf =
        CockpitPanel.Buttons().ToDictionary(b => b.Address, b => b.LampCapable);

    private readonly RioProfile _profile;
    private readonly PanelCanvas _canvas = new();

    private readonly Label _info = new() { AutoSize = true, Location = new Point(12, 12), MaximumSize = new Size(310, 0) };
    private readonly TextBox _labelBox = new() { Location = new Point(70, 44), Width = 230 };
    private readonly ComboBox _kindBox = new() { Location = new Point(70, 78), Width = 150, DropDownStyle = ComboBoxStyle.DropDownList };
    private readonly Label _valueLabel = new() { Text = "Key:", Location = new Point(12, 115), AutoSize = true };
    private readonly ComboBox _valueCombo = new() { Location = new Point(70, 112), Width = 230, DropDownStyle = ComboBoxStyle.DropDownList };
    private readonly CheckBox _shift = new() { Text = "Shift", Location = new Point(70, 144), AutoSize = true };
    private readonly CheckBox _ctrl = new() { Text = "Ctrl", Location = new Point(140, 144), AutoSize = true };
    private readonly CheckBox _alt = new() { Text = "Alt", Location = new Point(200, 144), AutoSize = true };
    private readonly CheckBox _ext = new() { Text = "Ext", Location = new Point(250, 144), AutoSize = true };
    private readonly CheckBox _lit = new() { Text = "Lit", Location = new Point(70, 172), AutoSize = true };
    private readonly Button _apply = new() { Text = "Apply to cell", Location = new Point(70, 206), Width = 110 };
    private readonly Button _unassign = new() { Text = "Unassign", Location = new Point(190, 206), Width = 110 };
    private readonly Button _save = new() { Text = "Save profile", Location = new Point(70, 240), Width = 110 };
    private readonly Button _close = new() { Text = "Close", Location = new Point(190, 240), Width = 80 };

    private int? _address;
    private bool _loading;

    /// <summary>A pick-list entry: what the user sees and the byte it maps to.</summary>
    private sealed record ValueItem(string Display, byte Value)
    {
        public override string ToString() => Display;
    }

    /// <summary>Raised when the user saves; the argument is the edited profile.</summary>
    public event Action<RioProfile>? Saved;

    public ProfileEditorForm(RioProfile profile)
    {
        _profile = profile ?? throw new ArgumentNullException(nameof(profile));

        Text = $"RIOJoy — Edit profile: {profile.Name}";
        ClientSize = new Size(1320, 820);
        StartPosition = FormStartPosition.CenterScreen;
        MinimumSize = new Size(900, 500);

        _kindBox.Items.AddRange(Enum.GetNames<RioRouteKind>());
        _kindBox.SelectedIndexChanged += OnKindChanged;

        _canvas.LabelProvider = a => _profile.OverlayLabels.TryGetValue(RegionFor(a), out string? t) ? t : null;
        _canvas.FunctionProvider = a => _profile.Buttons.TryGetValue(a, out int w) ? ButtonBinding.FromWord((ushort)w).Describe() : null;
        _canvas.LitProvider = a => _profile.Buttons.TryGetValue(a, out int w) && ButtonBinding.FromWord((ushort)w).IsLit;
        _canvas.AddressSelected += OnAddressSelected;

        var scroller = new Panel { Dock = DockStyle.Fill, AutoScroll = true, BackColor = Color.FromArgb(28, 28, 28) };
        scroller.Controls.Add(_canvas);

        Panel editPanel = BuildEditPanel();

        Controls.Add(scroller);
        Controls.Add(editPanel);

        _apply.Click += (_, _) => ApplyToCell();
        _unassign.Click += (_, _) => Unassign();
        _save.Click += (_, _) => { ApplyToCell(); Saved?.Invoke(_profile); };
        _close.Click += (_, _) => Close();

        SetEditingEnabled(false);
        _info.Text = "Click a button to edit it.";
    }

    /// <summary>The selected RIO address (for tests / offline rendering).</summary>
    public int? SelectedAddress => _address;

    private Panel BuildEditPanel()
    {
        var panel = new Panel { Dock = DockStyle.Right, Width = 330, Padding = new Padding(8), BorderStyle = BorderStyle.FixedSingle };

        panel.Controls.Add(_info);
        panel.Controls.Add(new Label { Text = "Label:", Location = new Point(12, 47), AutoSize = true });
        panel.Controls.Add(_labelBox);
        panel.Controls.Add(new Label { Text = "Action:", Location = new Point(12, 81), AutoSize = true });
        panel.Controls.Add(_kindBox);
        panel.Controls.Add(_valueLabel);
        panel.Controls.Add(_valueCombo);
        panel.Controls.AddRange(new Control[] { _shift, _ctrl, _alt, _ext, _lit, _apply, _unassign, _save, _close });

        return panel;
    }

    private void OnAddressSelected(int addr)
    {
        _address = addr;
        string region = RegionFor(addr);
        string group = GroupOf.TryGetValue(addr, out string? gn) ? gn : "—";
        _info.Text = $"Address 0x{addr:X2}    {group}    (wallpaper {region})";

        ButtonBinding b = ButtonBinding.FromWord(
            _profile.Buttons.TryGetValue(addr, out int w) ? (ushort)w : (ushort)0);

        _loading = true;
        _labelBox.Text = _profile.OverlayLabels.TryGetValue(region, out string? label) ? label : string.Empty;
        _kindBox.SelectedItem = b.Kind.ToString();
        PopulateValues(b.Kind);
        SelectValue(b.Value);
        _shift.Checked = b.Shift;
        _ctrl.Checked = b.Ctrl;
        _alt.Checked = b.Alt;
        _ext.Checked = b.Extended;
        _lit.Checked = b.IsLit;
        _lit.Visible = LampCapableOf.GetValueOrDefault(addr); // keypads have no lamp
        _loading = false;

        SetEditingEnabled(true);
    }

    private void OnKindChanged(object? sender, EventArgs e)
    {
        if (_loading) return;
        PopulateValues(SelectedKind());
        if (_valueCombo.Items.Count > 0) _valueCombo.SelectedIndex = 0;
        UpdateModifierAvailability();
    }

    // Fill the value picker with friendly choices for the chosen routing kind.
    private void PopulateValues(RioRouteKind kind)
    {
        _valueLabel.Text = kind switch
        {
            RioRouteKind.Keyboard => "Key:",
            RioRouteKind.Joystick => "Button:",
            RioRouteKind.Hat => "Direction:",
            RioRouteKind.Mouse => "Action:",
            RioRouteKind.RioCommand => "Command:",
            _ => "Value:",
        };

        _valueCombo.BeginUpdate();
        _valueCombo.Items.Clear();
        switch (kind)
        {
            case RioRouteKind.Keyboard:
                foreach (KeyName k in KeyCatalog.Entries)
                    _valueCombo.Items.Add(new ValueItem(k.Name, k.Value));
                break;
            case RioRouteKind.Joystick:
                for (int btn = 1; btn <= RioHidReport.ButtonCount; btn++)
                    _valueCombo.Items.Add(new ValueItem($"Button {btn}", (byte)btn));
                break;
            case RioRouteKind.Hat:
                foreach (RioHat h in new[] { RioHat.Up, RioHat.Right, RioHat.Down, RioHat.Left })
                    _valueCombo.Items.Add(new ValueItem(h.ToString(), (byte)(int)h));
                break;
            case RioRouteKind.Mouse:
                foreach (MouseAction m in Enum.GetValues<MouseAction>())
                    _valueCombo.Items.Add(new ValueItem(m.ToString(), (byte)m));
                break;
            case RioRouteKind.RioCommand:
                foreach (RioCommandCode c in Enum.GetValues<RioCommandCode>())
                    _valueCombo.Items.Add(new ValueItem(c.ToString(), (byte)c));
                break;
        }
        _valueCombo.EndUpdate();
        UpdateModifierAvailability();
    }

    private void SelectValue(byte value)
    {
        for (int i = 0; i < _valueCombo.Items.Count; i++)
        {
            if (_valueCombo.Items[i] is ValueItem v && v.Value == value)
            {
                _valueCombo.SelectedIndex = i;
                return;
            }
        }
        int idx = _valueCombo.Items.Add(new ValueItem($"0x{value:X2}", value));
        _valueCombo.SelectedIndex = idx;
    }

    private void UpdateModifierAvailability()
    {
        bool keyboard = SelectedKind() == RioRouteKind.Keyboard;
        foreach (Control c in new Control[] { _shift, _ctrl, _alt, _ext })
            c.Enabled = keyboard;
    }

    private RioRouteKind SelectedKind() =>
        Enum.TryParse(_kindBox.SelectedItem?.ToString(), out RioRouteKind k) ? k : RioRouteKind.Keyboard;

    private void ApplyToCell()
    {
        if (_address is not { } addr) return;

        string region = RegionFor(addr);
        string label = _labelBox.Text.Trim();
        if (string.IsNullOrEmpty(label))
            _profile.OverlayLabels.Remove(region);
        else
            _profile.OverlayLabels[region] = label;

        RioRouteKind kind = SelectedKind();
        bool keyboard = kind == RioRouteKind.Keyboard;
        var binding = new ButtonBinding
        {
            Kind = kind,
            Value = _valueCombo.SelectedItem is ValueItem vi ? vi.Value : (byte)0,
            Shift = keyboard && _shift.Checked,
            Ctrl = keyboard && _ctrl.Checked,
            Alt = keyboard && _alt.Checked,
            Extended = keyboard && _ext.Checked,
            IsLit = LampCapableOf.GetValueOrDefault(addr) && _lit.Checked, // keypads never lit
        };

        ushort word = binding.ToWord();
        if (word == 0)
            _profile.Buttons.Remove(addr);
        else
            _profile.Buttons[addr] = word;

        _canvas.Refresh(addr); // update lamp shade + label on the panel
    }

    // Clear the selected button's action mapping (the label is left untouched).
    private void Unassign()
    {
        if (_address is not { } addr) return;
        _profile.Buttons.Remove(addr);

        _loading = true;
        _kindBox.SelectedItem = RioRouteKind.Keyboard.ToString();
        PopulateValues(RioRouteKind.Keyboard);
        if (_valueCombo.Items.Count > 0) _valueCombo.SelectedIndex = 0;
        _shift.Checked = _ctrl.Checked = _alt.Checked = _ext.Checked = _lit.Checked = false;
        _loading = false;

        _canvas.Refresh(addr);
    }

    private void SetEditingEnabled(bool enabled)
    {
        foreach (Control c in new Control[] { _labelBox, _kindBox, _valueCombo, _shift, _ctrl, _alt, _ext, _lit, _apply, _unassign })
            c.Enabled = enabled;
        if (enabled) UpdateModifierAvailability();
    }

    private static string RegionFor(int address) => $"b-{address:X2}";

    /// <summary>Select an address programmatically (for tests / offline preview).</summary>
    public void SelectAddress(int address)
    {
        _canvas.Select(address);
        OnAddressSelected(address);
    }
}
