//! Preferences dialog: stream mode, bitrate, host compositor, gamepad type, microphone,
//! capture behavior. Written back to disk when the dialog closes.

use crate::trust::Settings;
use adw::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;

/// `(0, 0)` = the native size of the monitor the window is on, resolved at connect.
const RESOLUTIONS: &[(u32, u32)] = &[
    (0, 0),
    (1280, 720),
    (1920, 1080),
    (2560, 1440),
    (3840, 2160),
];
/// `0` = the monitor's native refresh, resolved at connect.
const REFRESH: &[u32] = &[0, 30, 60, 90, 120, 144, 165, 240];
const GAMEPADS: &[&str] = &["auto", "xbox360", "dualsense", "xboxone", "dualshock4"];
const COMPOSITORS: &[&str] = &["auto", "kwin", "wlroots", "mutter", "gamescope"];

/// punktfunk's own license (MIT OR Apache-2.0), shown on the About dialog's Legal page.
const APP_LICENSE: &str = concat!(
    "punktfunk is licensed under MIT OR Apache-2.0, at your option.\n\n",
    "================================ MIT ================================\n\n",
    include_str!("../../../LICENSE-MIT"),
    "\n\n=============================== Apache-2.0 ===============================\n\n",
    include_str!("../../../LICENSE-APACHE"),
);
/// Third-party software notices for the linked Rust crates (generated by
/// scripts/gen-third-party-notices.sh; shown as a Legal section in the About dialog).
const THIRD_PARTY_NOTICES: &str = include_str!("../../../THIRD-PARTY-NOTICES.txt");

/// Show the About dialog (app license + the third-party-software Legal section).
fn show_about(parent: &impl IsA<gtk::Widget>) {
    let about = adw::AboutDialog::builder()
        .application_name("punktfunk")
        .developer_name("unom")
        .version(env!("CARGO_PKG_VERSION"))
        .website("https://git.unom.io/unom/punktfunk")
        .license_type(gtk::License::Custom)
        .license(APP_LICENSE)
        .build();
    // The native (FFmpeg/GTK/PipeWire/SDL3) components are dynamically linked under their own
    // (LGPL/Zlib/MIT) licenses; the Rust crate notices are the substantive attribution set.
    about.add_legal_section(
        "Third-party software (Rust crates)",
        None,
        gtk::License::Custom,
        Some(THIRD_PARTY_NOTICES),
    );
    about.add_legal_section(
        "Third-party software (system libraries)",
        None,
        gtk::License::Custom,
        Some(
            "This application dynamically links system libraries under their own licenses, \
             including FFmpeg (LGPL v2.1+), GTK 4 and libadwaita (LGPL v2.1+), PipeWire (MIT), \
             and SDL 3 (Zlib). Their full license texts are available from each project.",
        ),
    );
    about.present(Some(parent));
}

pub fn show(
    parent: &impl IsA<gtk::Widget>,
    settings: Rc<RefCell<Settings>>,
    gamepads: &crate::gamepad::GamepadService,
) {
    let page = adw::PreferencesPage::new();

    let stream = adw::PreferencesGroup::builder().title("Stream").build();
    let res_names: Vec<String> = RESOLUTIONS
        .iter()
        .map(|&(w, h)| {
            if w == 0 {
                "Native display".to_string()
            } else {
                format!("{w} × {h}")
            }
        })
        .collect();
    let res_row = adw::ComboRow::builder()
        .title("Resolution")
        .subtitle("The host creates a virtual output at exactly this size")
        .model(&gtk::StringList::new(
            &res_names.iter().map(String::as_str).collect::<Vec<_>>(),
        ))
        .build();
    let hz_names: Vec<String> = REFRESH
        .iter()
        .map(|&r| {
            if r == 0 {
                "Native".to_string()
            } else {
                format!("{r} Hz")
            }
        })
        .collect();
    let hz_row = adw::ComboRow::builder()
        .title("Refresh rate")
        .model(&gtk::StringList::new(
            &hz_names.iter().map(String::as_str).collect::<Vec<_>>(),
        ))
        .build();
    let bitrate_row = adw::SpinRow::with_range(0.0, 3000.0, 5.0);
    bitrate_row.set_title("Bitrate");
    bitrate_row.set_subtitle("Mbit/s · 0 = host default · run a speed test before going high");
    let compositor_row = adw::ComboRow::builder()
        .title("Host compositor")
        .subtitle("Advisory — the host falls back to auto-detect when unavailable")
        .model(&gtk::StringList::new(&[
            "Automatic",
            "KWin",
            "wlroots (Sway/Hyprland)",
            "Mutter (GNOME)",
            "gamescope",
        ]))
        .build();
    stream.add(&res_row);
    stream.add(&hz_row);
    stream.add(&bitrate_row);
    stream.add(&compositor_row);

    let input = adw::PreferencesGroup::builder().title("Input").build();
    // Which physical controller forwards as pad 0: automatic = the most recently
    // connected; pinning survives until the app exits (Swift parity).
    let pads = gamepads.pads();
    let mut pad_names = vec!["Automatic (most recent)".to_string()];
    pad_names.extend(pads.iter().map(|p| {
        let kind = p.kind_label();
        if kind.is_empty() {
            p.name.clone()
        } else {
            format!("{} · {kind}", p.name)
        }
    }));
    let forward_row = adw::ComboRow::builder()
        .title("Forwarded controller")
        .subtitle(if pads.is_empty() {
            "No controllers detected"
        } else {
            "Exactly one controller is forwarded to the host"
        })
        .model(&gtk::StringList::new(
            &pad_names.iter().map(String::as_str).collect::<Vec<_>>(),
        ))
        .build();
    let pinned_i = gamepads
        .pinned()
        .and_then(|id| pads.iter().position(|p| p.id == id))
        .map_or(0, |i| i + 1);
    forward_row.set_selected(pinned_i as u32);
    {
        let svc = gamepads.clone();
        let ids: Vec<u32> = pads.iter().map(|p| p.id).collect();
        forward_row.connect_selected_notify(move |row| {
            let sel = row.selected() as usize;
            svc.set_pinned(if sel == 0 {
                None
            } else {
                ids.get(sel - 1).copied()
            });
        });
    }
    let pad_row = adw::ComboRow::builder()
        .title("Gamepad type")
        .subtitle("The virtual pad the host creates — Automatic matches the physical pad")
        .model(&gtk::StringList::new(&[
            "Automatic",
            "Xbox 360",
            "DualSense",
            "Xbox One",
            "DualShock 4",
        ]))
        .build();
    let inhibit_row = adw::SwitchRow::builder()
        .title("Capture system shortcuts")
        .subtitle("Forward Alt+Tab, Super, … to the host while input is captured")
        .build();
    input.add(&forward_row);
    input.add(&pad_row);
    input.add(&inhibit_row);

    let audio = adw::PreferencesGroup::builder().title("Audio").build();
    let surround_row = adw::ComboRow::builder()
        .title("Audio channels")
        .subtitle("Request stereo or surround (the host downmixes if its output has fewer)")
        .model(&gtk::StringList::new(&[
            "Stereo",
            "5.1 Surround",
            "7.1 Surround",
        ]))
        .build();
    audio.add(&surround_row);
    let mic_row = adw::SwitchRow::builder()
        .title("Stream microphone")
        .subtitle("Send the default input device to the host's virtual microphone")
        .build();
    audio.add(&mic_row);

    let about = adw::PreferencesGroup::builder().title("About").build();
    let licenses_row = adw::ActionRow::builder()
        .title("Third-party licenses")
        .subtitle("Open-source software used by punktfunk")
        .activatable(true)
        .build();
    licenses_row.add_suffix(&gtk::Image::from_icon_name("go-next-symbolic"));
    {
        let about_parent: gtk::Widget = parent.clone().upcast();
        licenses_row.connect_activated(move |_| show_about(&about_parent));
    }
    about.add(&licenses_row);

    page.add(&stream);
    page.add(&input);
    page.add(&audio);
    page.add(&about);

    // Seed from the current settings.
    {
        let s = settings.borrow();
        let res_i = RESOLUTIONS
            .iter()
            .position(|&(w, h)| w == s.width && h == s.height)
            .unwrap_or(0);
        res_row.set_selected(res_i as u32);
        let hz_i = REFRESH.iter().position(|&r| r == s.refresh_hz).unwrap_or(0);
        hz_row.set_selected(hz_i as u32);
        bitrate_row.set_value(f64::from(s.bitrate_kbps) / 1000.0);
        let pad_i = GAMEPADS.iter().position(|&g| g == s.gamepad).unwrap_or(0);
        pad_row.set_selected(pad_i as u32);
        let comp_i = COMPOSITORS
            .iter()
            .position(|&c| c == s.compositor)
            .unwrap_or(0);
        compositor_row.set_selected(comp_i as u32);
        inhibit_row.set_active(s.inhibit_shortcuts);
        mic_row.set_active(s.mic_enabled);
        surround_row.set_selected(match s.audio_channels {
            6 => 1,
            8 => 2,
            _ => 0,
        });
    }

    let dialog = adw::PreferencesDialog::new();
    dialog.set_title("Preferences");
    dialog.add(&page);
    dialog.connect_closed(move |_| {
        let mut s = settings.borrow_mut();
        let (w, h) = RESOLUTIONS[(res_row.selected() as usize).min(RESOLUTIONS.len() - 1)];
        (s.width, s.height) = (w, h);
        s.refresh_hz = REFRESH[(hz_row.selected() as usize).min(REFRESH.len() - 1)];
        s.bitrate_kbps = (bitrate_row.value() * 1000.0) as u32;
        s.gamepad = GAMEPADS[(pad_row.selected() as usize).min(GAMEPADS.len() - 1)].to_string();
        s.compositor = COMPOSITORS[(compositor_row.selected() as usize).min(COMPOSITORS.len() - 1)]
            .to_string();
        s.inhibit_shortcuts = inhibit_row.is_active();
        s.mic_enabled = mic_row.is_active();
        s.audio_channels = match surround_row.selected() {
            1 => 6,
            2 => 8,
            _ => 2,
        };
        s.save();
    });
    dialog.present(Some(parent));
}
