using VoiceCat.App.Audio;

namespace VoiceCat.App.Forms;

/// <summary>
/// Modal dialog for selecting which apps' audio to share.
/// Returns the chosen <see cref="AppAudioScope"/> via <see cref="ChosenScope"/>,
/// or <see cref="DialogResult.Cancel"/> if the user dismissed without sharing.
/// </summary>
public sealed class AppAudioPickerDialog : Form
{
    private readonly RadioButton  _rdoAll;
    private readonly RadioButton  _rdoOnly;
    private readonly RadioButton  _rdoExcept;
    private readonly CheckBox      _chkExcludeSelf;
    private readonly ListView     _appList;
    private readonly Label        _lblApps;

    // Snapshot taken when the dialog opens (refresh on open, not on every check change).
    private IReadOnlyList<AudioAppInfo> _apps = [];

    public AppAudioScope? ChosenScope { get; private set; }

    public AppAudioPickerDialog()
    {
        // ── Radio buttons ───────────────────────────────────────────────────
        _rdoAll = new RadioButton
        {
            Text      = "&Entire desktop",
            Checked   = true,
            Location  = new Point(12, 12),
            Size      = new Size(360, 20),
            TabIndex  = 0,
        };
        _rdoOnly = new RadioButton
        {
            Text     = "&Only selected apps",
            Location = new Point(12, 36),
            Size     = new Size(360, 20),
            TabIndex = 1,
        };
        _rdoExcept = new RadioButton
        {
            Text     = "All apps e&xcept selected",
            Location = new Point(12, 60),
            Size     = new Size(360, 20),
            TabIndex = 2,
        };

        _rdoAll.CheckedChanged    += OnModeChanged;
        _rdoOnly.CheckedChanged   += OnModeChanged;
        _rdoExcept.CheckedChanged += OnModeChanged;

        // ── Self-exclude (echo prevention) ───────────────────────────────────
        // Captures the whole desktop minus VoiceCat's own playback, so the channel
        // doesn't echo back the voices you're already hearing. Only applies to the
        // entire-desktop mode (the per-app modes already exclude this app's tree).
        _chkExcludeSelf = new CheckBox
        {
            Text     = "E&xclude VoiceCat's own audio (prevents echo)",
            Checked  = true,
            Location = new Point(28, 84),
            Size     = new Size(344, 20),
            TabIndex = 3,
        };

        // ── App list ────────────────────────────────────────────────────────
        _lblApps = new Label
        {
            Text     = "Apps with active audio sessions:",
            AutoSize = true,
            Location = new Point(12, 112),
            Visible  = false,
            TabIndex = 4,
        };

        _appList = new ListView
        {
            Location      = new Point(12, 134),
            Size          = new Size(360, 158),
            CheckBoxes    = true,
            View          = View.List,
            Visible       = false,
            TabIndex      = 5,
            FullRowSelect = true,
        };
        // Exclude mode supports only one target process tree (WASAPI EXCLUDE takes a single
        // PID), so enforce a single check while "All apps except selected" is active.
        _appList.ItemCheck += OnAppItemCheck;

        // ── Buttons ─────────────────────────────────────────────────────────
        var btnShare = new Button
        {
            Text         = "&Share",
            DialogResult = DialogResult.OK,
            Location     = new Point(216, 308),
            Size         = new Size(75, 27),
            TabIndex     = 6,
        };
        var btnCancel = new Button
        {
            Text         = "&Cancel",
            DialogResult = DialogResult.Cancel,
            Location     = new Point(297, 308),
            Size         = new Size(75, 27),
            TabIndex     = 7,
        };
        btnShare.Click += OnShareClick;

        AcceptButton  = btnShare;
        CancelButton  = btnCancel;
        AutoScaleMode = AutoScaleMode.Font;
        ClientSize    = new Size(384, 348);
        Controls.AddRange([_rdoAll, _rdoOnly, _rdoExcept, _chkExcludeSelf, _lblApps, _appList,
                           btnShare, btnCancel]);
        FormBorderStyle = FormBorderStyle.FixedDialog;
        MaximizeBox     = false;
        MinimizeBox     = false;
        StartPosition   = FormStartPosition.CenterParent;
        Text            = "Share App Audio";
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        RefreshAppList();
    }

    private void RefreshAppList()
    {
        _apps = AudioSessionEnumerator.GetAudioApps();
        _appList.Items.Clear();
        foreach (var app in _apps)
            _appList.Items.Add(new ListViewItem($"{app.DisplayName}  (PID {app.Pid})") { Tag = app.Pid });
    }

    private void OnModeChanged(object? sender, EventArgs e)
    {
        bool showList = _rdoOnly.Checked || _rdoExcept.Checked;
        _lblApps.Visible = showList;
        _appList.Visible = showList;
        _lblApps.Text = _rdoExcept.Checked
            ? "App to exclude (everything else is shared):"
            : "Apps with active audio sessions:";

        // Self-exclude only applies to entire-desktop; the per-app modes already exclude
        // this app's own tree (Include) or spend the single EXCLUDE slot on the chosen app.
        _chkExcludeSelf.Enabled = _rdoAll.Checked;

        if (showList && _appList.Items.Count == 0)
            RefreshAppList();

        // Switching into exclude mode: collapse any multi-selection down to a single item.
        if (_rdoExcept.Checked)
            KeepSingleCheck(firstCheckedOnly: true);
    }

    // In exclude mode the list behaves like radio buttons: checking one item clears the rest.
    private bool _suppressItemCheck;

    private void OnAppItemCheck(object? sender, ItemCheckEventArgs e)
    {
        if (_suppressItemCheck || !_rdoExcept.Checked || e.NewValue != CheckState.Checked)
            return;

        _suppressItemCheck = true;
        foreach (ListViewItem item in _appList.Items)
            if (item.Index != e.Index && item.Checked)
                item.Checked = false;
        _suppressItemCheck = false;
    }

    // Leave at most one item checked (the first), unchecking the rest.
    private void KeepSingleCheck(bool firstCheckedOnly)
    {
        _suppressItemCheck = true;
        bool kept = false;
        foreach (ListViewItem item in _appList.Items)
        {
            if (!item.Checked) continue;
            if (firstCheckedOnly && !kept) { kept = true; continue; }
            item.Checked = false;
        }
        _suppressItemCheck = false;
    }

    private void OnShareClick(object? sender, EventArgs e)
    {
        if (_rdoAll.Checked)
        {
            ChosenScope = new EntireDesktop(_chkExcludeSelf.Checked);
            return;
        }

        var checkedPids   = new List<int>();
        var checkedNames  = new List<string>();
        foreach (ListViewItem item in _appList.CheckedItems)
        {
            if (item.Tag is int pid)
            {
                checkedPids.Add(pid);
                checkedNames.Add(item.Text);
            }
        }

        if (checkedPids.Count == 0)
        {
            MessageBox.Show(
                "Select at least one app, or choose 'Entire desktop'.",
                "No apps selected",
                MessageBoxButtons.OK,
                MessageBoxIcon.Warning);
            DialogResult = DialogResult.None; // prevent close
            return;
        }

        ChosenScope = _rdoOnly.Checked
            ? new OnlyApps(checkedPids, checkedNames)
            : new AllExceptApps(checkedPids, checkedNames);
    }
}
