using VoiceCat.Interop;

namespace VoiceCat.App.Forms;

/// <summary>
/// Create or edit a channel, including its full per-channel Opus audio config.
/// Returns DialogResult.OK with Result set.
/// </summary>
public sealed class ChannelEditDialog : Form
{
    private readonly bool _isCreate;
    private readonly uint _editingId;
    private readonly List<ChannelInfo> _channels;

    private TextBox _txtName = null!;
    private TextBox _txtTopic = null!;
    private ComboBox _cboParent = null!;
    private NumericUpDown _numMaxUsers = null!;
    private NumericUpDown _numSortOrder = null!;
    private CheckBox _chkPassword = null!;
    private TextBox _txtPassword = null!;

    private ComboBox _cboMode = null!;
    private NumericUpDown _numSampleRate = null!;
    private NumericUpDown _numBitrate = null!;
    private NumericUpDown _numFrameMs = null!;
    private ComboBox _cboApplication = null!;
    private CheckBox _chkFec = null!;
    private NumericUpDown _numExpectedLoss = null!;
    private CheckBox _chkDtx = null!;
    private CheckBox _chkDred = null!;
    private NumericUpDown _numComplexity = null!;

    public ChannelEditInfo? Result { get; private set; }

    public ChannelEditDialog(IEnumerable<ChannelInfo> channels, ChannelEditInfo? existing = null)
    {
        _isCreate = existing is null;
        _editingId = existing?.Id ?? 0;
        _channels = channels.Where(c => c.Id != _editingId).ToList();

        Text = _isCreate ? "Create channel" : "Edit channel";
        FormBorderStyle = FormBorderStyle.FixedDialog;
        MaximizeBox = false;
        MinimizeBox = false;
        StartPosition = FormStartPosition.CenterParent;
        AutoScaleMode = AutoScaleMode.Font;
        ClientSize = new Size(520, 520);

        var tabs = new TabControl
        {
            Dock = DockStyle.Fill,
            TabIndex = 0,
        };

        var pageGeneral = new TabPage("General");
        BuildGeneralPage(pageGeneral, existing);
        var pageAudio = new TabPage("Audio config");
        BuildAudioPage(pageAudio, existing?.Audio);

        tabs.TabPages.Add(pageGeneral);
        tabs.TabPages.Add(pageAudio);

        var btnOk = new Button
        {
            Text = "&OK",
            DialogResult = DialogResult.OK,
            Location = new Point(350, 486),
            Size = new Size(75, 27),
            TabIndex = 100,
        };
        var btnCancel = new Button
        {
            Text = "&Cancel",
            DialogResult = DialogResult.Cancel,
            Location = new Point(431, 486),
            Size = new Size(75, 27),
            TabIndex = 101,
        };

        AcceptButton = btnOk;
        CancelButton = btnCancel;

        Controls.Add(tabs);
        Controls.Add(btnOk);
        Controls.Add(btnCancel);

        btnOk.Click += BtnOk_Click;
    }

    private void BuildGeneralPage(TabPage page, ChannelEditInfo? existing)
    {
        int y = 16;
        int labelWidth = 110;
        int inputX = 128;

        AddLabel(page, "&Name:", 12, y, labelWidth);
        _txtName = new TextBox
        {
            Location = new Point(inputX, y - 2),
            Size = new Size(360, 23),
            Text = existing?.Name ?? "",
            TabIndex = 1,
        };
        page.Controls.Add(_txtName);
        y += 36;

        AddLabel(page, "&Topic:", 12, y, labelWidth);
        _txtTopic = new TextBox
        {
            Location = new Point(inputX, y - 2),
            Size = new Size(360, 23),
            Text = existing?.Topic ?? "",
            TabIndex = 2,
        };
        page.Controls.Add(_txtTopic);
        y += 36;

        AddLabel(page, "&Parent channel:", 12, y, labelWidth);
        _cboParent = new ComboBox
        {
            Location = new Point(inputX, y - 2),
            Size = new Size(360, 23),
            DropDownStyle = ComboBoxStyle.DropDownList,
            TabIndex = 3,
        };
        _cboParent.Items.Add(new ChannelItem("(root)", 0));
        foreach (var ch in _channels.OrderBy(c => c.Name))
            _cboParent.Items.Add(new ChannelItem(ch.Name, ch.Id));
        SelectParent(existing?.ParentId ?? 0);
        page.Controls.Add(_cboParent);
        y += 36;

        AddLabel(page, "&Max users:", 12, y, labelWidth);
        _numMaxUsers = new NumericUpDown
        {
            Location = new Point(inputX, y - 2),
            Size = new Size(120, 23),
            Minimum = 0,
            Maximum = 10000,
            Value = existing?.MaxUsers ?? 0,
            TabIndex = 4,
        };
        page.Controls.Add(_numMaxUsers);
        AddLabel(page, "(0 = unlimited)", inputX + 128, y, 100);
        y += 36;

        AddLabel(page, "&Sort order:", 12, y, labelWidth);
        _numSortOrder = new NumericUpDown
        {
            Location = new Point(inputX, y - 2),
            Size = new Size(120, 23),
            Minimum = 0,
            Maximum = uint.MaxValue,
            Value = existing?.SortOrder ?? 0,
            TabIndex = 5,
        };
        page.Controls.Add(_numSortOrder);
        y += 36;

        _chkPassword = new CheckBox
        {
            Text = "&Password protected",
            Location = new Point(inputX, y),
            AutoSize = true,
            Checked = existing?.PasswordProtected ?? false,
            TabIndex = 6,
        };
        page.Controls.Add(_chkPassword);
        y += 28;

        _txtPassword = new TextBox
        {
            Location = new Point(inputX, y),
            Size = new Size(360, 23),
            UseSystemPasswordChar = true,
            Enabled = _chkPassword.Checked,
            Text = existing?.Password ?? "",
            TabIndex = 7,
        };
        page.Controls.Add(_txtPassword);
        _chkPassword.CheckedChanged += (_, _) => _txtPassword.Enabled = _chkPassword.Checked;
    }

    private void BuildAudioPage(TabPage page, AudioConfigInfo? audio)
    {
        audio ??= new AudioConfigInfo(0, false, 48000, 0, 20, 0, true, 0, false, 10, false);

        int y = 16;
        int labelWidth = 150;
        int inputX = 168;

        AddLabel(page, "Codec (0 = Opus):", 12, y, labelWidth);
        var numCodec = new NumericUpDown
        {
            Location = new Point(inputX, y - 2),
            Size = new Size(120, 23),
            Minimum = 0,
            Maximum = 0,
            Value = audio.Codec,
            Enabled = false,
        };
        page.Controls.Add(numCodec);
        y += 34;

        AddLabel(page, "&Mode:", 12, y, labelWidth);
        _cboMode = new ComboBox
        {
            Location = new Point(inputX, y - 2),
            Size = new Size(160, 23),
            DropDownStyle = ComboBoxStyle.DropDownList,
            TabIndex = 10,
        };
        _cboMode.Items.AddRange(["Mono", "Stereo"]);
        _cboMode.SelectedIndex = audio.Stereo ? 1 : 0;
        page.Controls.Add(_cboMode);
        y += 34;

        AddLabel(page, "Sample &rate (Hz):", 12, y, labelWidth);
        _numSampleRate = new NumericUpDown
        {
            Location = new Point(inputX, y - 2),
            Size = new Size(120, 23),
            Minimum = 8000,
            Maximum = 96000,
            Value = audio.SampleRate,
            TabIndex = 11,
        };
        page.Controls.Add(_numSampleRate);
        y += 34;

        AddLabel(page, "&Bitrate (bps, 0 = default):", 12, y, labelWidth);
        _numBitrate = new NumericUpDown
        {
            Location = new Point(inputX, y - 2),
            Size = new Size(120, 23),
            Minimum = 0,
            Maximum = 512000,
            Increment = 1000,
            Value = audio.BitrateBps,
            TabIndex = 12,
        };
        page.Controls.Add(_numBitrate);
        y += 34;

        AddLabel(page, "Frame &ms:", 12, y, labelWidth);
        _numFrameMs = new NumericUpDown
        {
            Location = new Point(inputX, y - 2),
            Size = new Size(120, 23),
            Minimum = 5,
            Maximum = 120,
            Value = audio.FrameMs,
            TabIndex = 13,
        };
        page.Controls.Add(_numFrameMs);
        y += 34;

        AddLabel(page, "&Application:", 12, y, labelWidth);
        _cboApplication = new ComboBox
        {
            Location = new Point(inputX, y - 2),
            Size = new Size(160, 23),
            DropDownStyle = ComboBoxStyle.DropDownList,
            TabIndex = 14,
        };
        _cboApplication.Items.AddRange(["VoIP", "Audio", "Low delay"]);
        _cboApplication.SelectedIndex = (int)Math.Min(2, audio.Application);
        page.Controls.Add(_cboApplication);
        y += 34;

        AddLabel(page, "Expected packet loss (%):", 12, y, labelWidth);
        _numExpectedLoss = new NumericUpDown
        {
            Location = new Point(inputX, y - 2),
            Size = new Size(120, 23),
            Minimum = 0,
            Maximum = 100,
            Value = audio.ExpectedPacketLoss,
            TabIndex = 15,
        };
        page.Controls.Add(_numExpectedLoss);
        y += 34;

        AddLabel(page, "Com&plexity (0–10):", 12, y, labelWidth);
        _numComplexity = new NumericUpDown
        {
            Location = new Point(inputX, y - 2),
            Size = new Size(120, 23),
            Minimum = 0,
            Maximum = 10,
            Value = audio.Complexity,
            TabIndex = 16,
        };
        page.Controls.Add(_numComplexity);
        y += 34;

        _chkFec = new CheckBox
        {
            Text = "&FEC",
            Location = new Point(inputX, y),
            AutoSize = true,
            Checked = audio.Fec,
            TabIndex = 17,
        };
        page.Controls.Add(_chkFec);
        y += 28;

        _chkDtx = new CheckBox
        {
            Text = "&DTX",
            Location = new Point(inputX, y),
            AutoSize = true,
            Checked = audio.Dtx,
            TabIndex = 18,
        };
        page.Controls.Add(_chkDtx);
        y += 28;

        _chkDred = new CheckBox
        {
            Text = "D&RED (deep redundancy)",
            Location = new Point(inputX, y),
            AutoSize = true,
            Checked = audio.Dred,
            TabIndex = 19,
        };
        page.Controls.Add(_chkDred);
    }

    private static void AddLabel(Control parent, string text, int x, int y, int width)
    {
        parent.Controls.Add(new Label
        {
            Text = text,
            Location = new Point(x, y),
            Size = new Size(width, 17),
            TextAlign = System.Drawing.ContentAlignment.MiddleLeft,
        });
    }

    private void SelectParent(uint parentId)
    {
        for (int i = 0; i < _cboParent.Items.Count; i++)
        {
            if (_cboParent.Items[i] is ChannelItem item && item.Id == parentId)
            {
                _cboParent.SelectedIndex = i;
                return;
            }
        }
        _cboParent.SelectedIndex = 0;
    }

    private void BtnOk_Click(object? sender, EventArgs e)
    {
        string name = _txtName.Text.Trim();
        if (string.IsNullOrEmpty(name))
        {
            MessageBox.Show(this, "Channel name is required.", "VoiceCat",
                MessageBoxButtons.OK, MessageBoxIcon.Warning);
            DialogResult = DialogResult.None;
            return;
        }

        uint parentId = (_cboParent.SelectedItem as ChannelItem)?.Id ?? 0;
        var audio = new AudioConfigInfo(
            Codec: 0,
            Stereo: _cboMode.SelectedIndex == 1,
            SampleRate: (uint)_numSampleRate.Value,
            BitrateBps: (uint)_numBitrate.Value,
            FrameMs: (uint)_numFrameMs.Value,
            Application: (uint)_cboApplication.SelectedIndex,
            Fec: _chkFec.Checked,
            ExpectedPacketLoss: (uint)_numExpectedLoss.Value,
            Dtx: _chkDtx.Checked,
            Complexity: (uint)_numComplexity.Value,
            Dred: _chkDred.Checked);

        Result = new ChannelEditInfo(
            Id: _editingId,
            ParentId: parentId,
            Name: name,
            Topic: _txtTopic.Text.Trim(),
            PasswordProtected: _chkPassword.Checked,
            Password: _chkPassword.Checked ? _txtPassword.Text : null,
            MaxUsers: (uint)_numMaxUsers.Value,
            SortOrder: (uint)_numSortOrder.Value,
            Audio: audio);
    }

    private sealed class ChannelItem(string name, uint id)
    {
        public uint Id { get; } = id;
        public override string ToString() => name;
    }
}
