use adw::prelude::*;
use std::cell::Cell;
use pixstrip_core::config::{AppConfig, ErrorBehavior, OverwriteBehavior, SkillLevel};
use pixstrip_core::storage::ConfigStore;

pub fn build_settings_dialog() -> adw::PreferencesDialog {
    let dialog = adw::PreferencesDialog::builder()
        .title("Settings")
        .build();

    let config_store = ConfigStore::new();
    let config = match config_store.load() {
        Ok(c) => c,
        Err(e) => {
            eprintln!("Failed to load config, using defaults: {}", e);
            AppConfig::default()
        }
    };

    // General page
    let general_page = adw::PreferencesPage::builder()
        .title("General")
        .icon_name("preferences-system-symbolic")
        .build();

    let output_group = adw::PreferencesGroup::builder()
        .title("Output")
        .build();

    // Output mode: subfolder or fixed path
    let output_mode_row = adw::ComboRow::builder()
        .title("Default output location")
        .subtitle("Where processed images are saved by default")
        .use_subtitle(true)
        .build();
    let output_mode_model = gtk::StringList::new(&[
        "Subfolder next to originals",
        "Fixed output folder",
    ]);
    output_mode_row.set_model(Some(&output_mode_model));
    output_mode_row.set_list_factory(Some(&crate::steps::full_text_list_factory()));
    output_mode_row.set_selected(if config.output_fixed_path.is_some() { 1 } else { 0 });

    let subfolder_row = adw::EntryRow::builder()
        .title("Default output subfolder")
        .text(&config.output_subfolder)
        .visible(config.output_fixed_path.is_none())
        .build();

    let fixed_path_row = adw::ActionRow::builder()
        .title("Fixed output folder")
        .subtitle(
            config.output_fixed_path
                .as_deref()
                .unwrap_or("No folder selected"),
        )
        .activatable(true)
        .visible(config.output_fixed_path.is_some())
        .build();
    let fp_icon = gtk::Image::from_icon_name("folder-open-symbolic");
    fp_icon.set_accessible_role(gtk::AccessibleRole::Presentation);
    fixed_path_row.add_prefix(&fp_icon);

    let choose_fixed_btn = gtk::Button::builder()
        .icon_name("document-open-symbolic")
        .tooltip_text("Choose output folder")
        .valign(gtk::Align::Center)
        .build();
    choose_fixed_btn.add_css_class("flat");
    choose_fixed_btn.update_property(&[
        gtk::accessible::Property::Label("Choose output folder"),
    ]);
    fixed_path_row.add_suffix(&choose_fixed_btn);

    // Shared state for fixed path
    let fixed_path_state: std::rc::Rc<std::cell::RefCell<Option<String>>> =
        std::rc::Rc::new(std::cell::RefCell::new(config.output_fixed_path.clone()));

    // Wire output mode toggle
    {
        let sf = subfolder_row.clone();
        let fp = fixed_path_row.clone();
        output_mode_row.connect_selected_notify(move |row| {
            let is_fixed = row.selected() == 1;
            sf.set_visible(!is_fixed);
            fp.set_visible(is_fixed);
        });
    }

    // Wire fixed path chooser
    {
        let fps = fixed_path_state.clone();
        let fpr = fixed_path_row.clone();
        choose_fixed_btn.connect_clicked(move |btn| {
            let fps = fps.clone();
            let fpr = fpr.clone();
            let dialog = gtk::FileDialog::builder()
                .title("Choose Output Folder")
                .modal(true)
                .build();
            if let Some(window) = btn.root().and_then(|r| r.downcast::<gtk::Window>().ok()) {
                dialog.select_folder(Some(&window), gtk::gio::Cancellable::NONE, move |result| {
                    if let Ok(file) = result
                        && let Some(path) = file.path()
                    {
                        let path_str = path.display().to_string();
                        fpr.set_subtitle(&path_str);
                        *fps.borrow_mut() = Some(path_str);
                    }
                });
            }
        });
    }

    let overwrite_row = adw::ComboRow::builder()
        .title("Default overwrite behavior")
        .subtitle("What to do when output files already exist")
        .use_subtitle(true)
        .build();
    let overwrite_model = gtk::StringList::new(&[
        "Ask before overwriting",
        "Auto-rename with suffix",
        "Always overwrite",
        "Skip existing files",
    ]);
    overwrite_row.set_model(Some(&overwrite_model));
    overwrite_row.set_list_factory(Some(&crate::steps::full_text_list_factory()));
    overwrite_row.set_selected(match config.overwrite_behavior {
        OverwriteBehavior::Ask => 0,
        OverwriteBehavior::AutoRename => 1,
        OverwriteBehavior::Overwrite => 2,
        OverwriteBehavior::Skip => 3,
    });

    let remember_row = adw::SwitchRow::builder()
        .title("Remember last-used settings")
        .subtitle("Restore wizard state on next launch")
        .active(config.remember_settings)
        .build();

    output_group.add(&output_mode_row);
    output_group.add(&subfolder_row);
    output_group.add(&fixed_path_row);
    output_group.add(&overwrite_row);
    output_group.add(&remember_row);
    general_page.add(&output_group);

    let ui_group = adw::PreferencesGroup::builder()
        .title("Interface")
        .build();

    let skill_row = adw::ComboRow::builder()
        .title("Detail level")
        .subtitle("Controls how many options are visible by default")
        .use_subtitle(true)
        .build();
    let skill_model = gtk::StringList::new(&["Simple", "Detailed"]);
    skill_row.set_model(Some(&skill_model));
    skill_row.set_list_factory(Some(&crate::steps::full_text_list_factory()));
    skill_row.set_selected(match config.skill_level {
        SkillLevel::Simple => 0,
        SkillLevel::Detailed => 1,
    });

    let reset_button = gtk::Button::builder()
        .label("Reset to Defaults")
        .halign(gtk::Align::Start)
        .margin_top(8)
        .build();
    reset_button.add_css_class("destructive-action");

    // Reset welcome wizard / tutorial
    let reset_welcome_state: std::rc::Rc<Cell<bool>> = std::rc::Rc::new(Cell::new(false));

    let reset_welcome_row = adw::ActionRow::builder()
        .title("Reset welcome wizard")
        .subtitle("Show the setup wizard and tutorial again on next launch")
        .build();

    let reset_welcome_btn = gtk::Button::builder()
        .label("Reset")
        .valign(gtk::Align::Center)
        .build();
    reset_welcome_btn.add_css_class("destructive-action");

    {
        let rws = reset_welcome_state.clone();
        let row = reset_welcome_row.clone();
        reset_welcome_btn.connect_clicked(move |btn| {
            rws.set(true);
            btn.set_sensitive(false);
            row.set_subtitle("Will show on next launch");
        });
    }

    reset_welcome_row.add_suffix(&reset_welcome_btn);

    ui_group.add(&skill_row);
    ui_group.add(&reset_welcome_row);
    general_page.add(&ui_group);

    // File Manager Integration
    let fm_group = adw::PreferencesGroup::builder()
        .title("File Manager Integration")
        .description("Add 'Process with Pixstrip' to your file manager's right-click menu")
        .build();

    use pixstrip_core::fm_integration::FileManager;
    let file_managers = [
        (FileManager::Nautilus, "org.gnome.Nautilus"),
        (FileManager::Nemo, "org.nemo.Nemo"),
        (FileManager::Thunar, "thunar"),
        (FileManager::Dolphin, "org.kde.dolphin"),
    ];

    let mut found_fm = false;
    for (fm, desktop_id) in &file_managers {
        let is_installed = gtk::gio::AppInfo::all()
            .iter()
            .any(|info| {
                info.id()
                    .map(|id| id.as_str().contains(desktop_id))
                    .unwrap_or(false)
            });

        if is_installed {
            found_fm = true;
            let already_installed = fm.is_installed();
            let row = adw::SwitchRow::builder()
                .title(fm.name())
                .subtitle(format!("Add right-click menu to {}", fm.name()))
                .active(already_installed)
                .build();

            let fm_copy = *fm;
            let reverting = std::rc::Rc::new(std::cell::Cell::new(false));
            let reverting_clone = reverting.clone();
            row.connect_active_notify(move |row| {
                if reverting_clone.get() {
                    return;
                }
                let result = if row.is_active() {
                    fm_copy.install()
                } else {
                    fm_copy.uninstall()
                };
                if let Err(e) = result {
                    eprintln!("File manager integration error for {}: {}", fm_copy.name(), e);
                    reverting_clone.set(true);
                    row.set_active(!row.is_active());
                    reverting_clone.set(false);
                }
            });

            fm_group.add(&row);
        }
    }

    if !found_fm {
        let row = adw::ActionRow::builder()
            .title("No supported file managers detected")
            .subtitle("Nautilus, Nemo, Thunar, and Dolphin are supported")
            .build();
        row.add_prefix(&gtk::Image::from_icon_name("dialog-information-symbolic"));
        fm_group.add(&row);
    }

    general_page.add(&fm_group);

    // Reset defaults group
    let reset_group = adw::PreferencesGroup::new();
    reset_group.add(&reset_button);
    general_page.add(&reset_group);

    dialog.add(&general_page);

    // Processing page
    let processing_page = adw::PreferencesPage::builder()
        .title("Processing")
        .icon_name("system-run-symbolic")
        .build();

    let threads_group = adw::PreferencesGroup::builder()
        .title("Performance")
        .build();

    let threads_row = adw::ComboRow::builder()
        .title("Processing threads")
        .subtitle("Auto uses all available CPU cores")
        .use_subtitle(true)
        .build();
    let threads_model = gtk::StringList::new(&["Auto", "1", "2", "4", "8"]);
    threads_row.set_model(Some(&threads_model));
    threads_row.set_list_factory(Some(&crate::steps::full_text_list_factory()));
    threads_row.set_selected(match config.thread_count {
        pixstrip_core::config::ThreadCount::Auto => 0,
        pixstrip_core::config::ThreadCount::Manual(1) => 1,
        pixstrip_core::config::ThreadCount::Manual(2) => 2,
        pixstrip_core::config::ThreadCount::Manual(n) if n <= 4 => 3,
        pixstrip_core::config::ThreadCount::Manual(_) => 4,
    });

    let error_row = adw::ComboRow::builder()
        .title("On error")
        .subtitle("What to do when an image fails to process")
        .use_subtitle(true)
        .build();
    let error_model = gtk::StringList::new(&["Skip and continue", "Pause on error"]);
    error_row.set_model(Some(&error_model));
    error_row.set_list_factory(Some(&crate::steps::full_text_list_factory()));
    error_row.set_selected(match config.error_behavior {
        ErrorBehavior::SkipAndContinue => 0,
        ErrorBehavior::PauseOnError => 1,
    });

    threads_group.add(&threads_row);
    threads_group.add(&error_row);
    processing_page.add(&threads_group);
    dialog.add(&processing_page);

    // Accessibility page
    let a11y_page = adw::PreferencesPage::builder()
        .title("Accessibility")
        .icon_name("preferences-desktop-accessibility-symbolic")
        .build();

    let a11y_group = adw::PreferencesGroup::builder()
        .title("Visual Preferences")
        .description("Override system settings for this app only")
        .build();

    let contrast_row = adw::SwitchRow::builder()
        .title("High contrast")
        .subtitle("Increase visual contrast throughout the app")
        .active(config.high_contrast)
        .build();

    let large_text_row = adw::SwitchRow::builder()
        .title("Large text")
        .subtitle("Increase text size throughout the app")
        .active(config.large_text)
        .build();

    let motion_row = adw::SwitchRow::builder()
        .title("Reduced motion")
        .subtitle("Minimize animations and transitions")
        .active(config.reduced_motion)
        .build();

    // Wire high contrast to apply immediately
    {
        let original_theme: std::rc::Rc<std::cell::RefCell<Option<gtk::glib::GString>>> =
            std::rc::Rc::new(std::cell::RefCell::new(
                gtk::Settings::default().and_then(|s| s.gtk_theme_name())
            ));
        let orig_theme = original_theme.clone();
        contrast_row.connect_active_notify(move |row| {
            if let Some(settings) = gtk::Settings::default() {
                if row.is_active() {
                    // Capture current theme before switching (if not already captured)
                    let mut saved = orig_theme.borrow_mut();
                    if saved.is_none() {
                        *saved = settings.gtk_theme_name();
                    }
                    settings.set_gtk_theme_name(Some("HighContrast"));
                } else {
                    let saved = orig_theme.borrow();
                    settings.set_gtk_theme_name(saved.as_deref());
                }
            }
        });
    }

    // Wire large text to apply immediately
    {
        // Capture the initial DPI at construction so we can restore it later
        let initial_dpi = gtk::Settings::default()
            .map(|s| s.gtk_xft_dpi())
            .unwrap_or(0);
        let original_dpi: std::rc::Rc<Cell<i32>> = std::rc::Rc::new(Cell::new(initial_dpi));
        let orig_dpi = original_dpi.clone();
        large_text_row.connect_active_notify(move |row| {
            if let Some(settings) = gtk::Settings::default() {
                if row.is_active() {
                    // Store original DPI before modifying (refresh if not yet set)
                    let current_dpi = settings.gtk_xft_dpi();
                    if current_dpi > 0 {
                        orig_dpi.set(current_dpi);
                        settings.set_gtk_xft_dpi(current_dpi * 5 / 4);
                    }
                } else {
                    // Restore the original DPI (only if we actually changed it)
                    let saved = orig_dpi.get();
                    if saved > 0 {
                        settings.set_gtk_xft_dpi(saved);
                    }
                }
            }
        });
    }

    // Wire reduced motion to apply immediately
    {
        motion_row.connect_active_notify(move |row| {
            if let Some(settings) = gtk::Settings::default() {
                settings.set_gtk_enable_animations(!row.is_active());
            }
        });
    }

    a11y_group.add(&contrast_row);
    a11y_group.add(&large_text_row);
    a11y_group.add(&motion_row);
    a11y_page.add(&a11y_group);
    dialog.add(&a11y_page);

    // Notifications page
    let notify_page = adw::PreferencesPage::builder()
        .title("Notifications")
        .icon_name("preferences-system-notifications-symbolic")
        .build();

    let notify_group = adw::PreferencesGroup::builder()
        .title("Completion")
        .build();

    let desktop_notify_row = adw::SwitchRow::builder()
        .title("Desktop notification")
        .subtitle("Show notification when processing completes")
        .active(config.notify_on_completion)
        .build();

    let sound_row = adw::SwitchRow::builder()
        .title("Completion sound")
        .subtitle("Play a sound when processing completes")
        .active(config.play_completion_sound)
        .build();

    let auto_open_row = adw::SwitchRow::builder()
        .title("Auto-open output folder")
        .subtitle("Open the output folder in file manager when done")
        .active(config.auto_open_output)
        .build();

    notify_group.add(&desktop_notify_row);
    notify_group.add(&sound_row);
    notify_group.add(&auto_open_row);
    notify_page.add(&notify_group);
    dialog.add(&notify_page);

    // Watch Folders page
    let watch_page = adw::PreferencesPage::builder()
        .title("Watch Folders")
        .icon_name("folder-visiting-symbolic")
        .build();

    let watch_group = adw::PreferencesGroup::builder()
        .title("Monitored Folders")
        .description("Automatically process images added to these folders")
        .build();

    let watch_list = gtk::ListBox::builder()
        .selection_mode(gtk::SelectionMode::None)
        .css_classes(["boxed-list"])
        .build();
    watch_list.set_widget_name("watch-folder-list");
    watch_list.update_property(&[
        gtk::accessible::Property::Label("Configured watch folders for automatic processing"),
    ]);

    // Shared state for watch folders
    let watch_folders_state: std::rc::Rc<std::cell::RefCell<Vec<pixstrip_core::watcher::WatchFolder>>> =
        std::rc::Rc::new(std::cell::RefCell::new(config.watch_folders.clone()));

    // Build preset names list for dropdown
    let builtin_presets = pixstrip_core::preset::Preset::all_builtins();
    let preset_names: Vec<String> = builtin_presets.iter().map(|p| p.name.clone()).collect();

    // Populate existing watch folders
    {
        let folders = watch_folders_state.borrow();
        for folder in folders.iter() {
            let row = build_watch_folder_row(folder, &preset_names, &watch_folders_state, &watch_list);
            watch_list.append(&row);
        }
    }

    // Empty state
    let empty_label = gtk::Label::builder()
        .label("No watch folders configured.\nAdd a folder to start automatic processing.")
        .css_classes(["dim-label"])
        .halign(gtk::Align::Center)
        .margin_top(16)
        .margin_bottom(16)
        .justify(gtk::Justification::Center)
        .build();
    empty_label.set_visible(config.watch_folders.is_empty());

    watch_group.add(&watch_list);
    watch_group.add(&empty_label);

    // Add folder button
    let add_button = gtk::Button::builder()
        .label("Add Watch Folder")
        .halign(gtk::Align::Start)
        .margin_top(8)
        .build();
    add_button.add_css_class("suggested-action");
    add_button.add_css_class("pill");

    {
        let wfs = watch_folders_state.clone();
        let wl = watch_list.clone();
        let el = empty_label.clone();
        let pnames = preset_names.clone();
        add_button.connect_clicked(move |btn| {
            let wfs = wfs.clone();
            let wl = wl.clone();
            let el = el.clone();
            let pnames = pnames.clone();
            let dialog = gtk::FileDialog::builder()
                .title("Choose Watch Folder")
                .modal(true)
                .build();

            if let Some(window) = btn.root().and_then(|r| r.downcast::<gtk::Window>().ok()) {
                dialog.select_folder(Some(&window), gtk::gio::Cancellable::NONE, move |result| {
                    if let Ok(file) = result
                        && let Some(path) = file.path()
                    {
                        let new_folder = pixstrip_core::watcher::WatchFolder {
                            path: path.clone(),
                            preset_name: "Blog Photos".to_string(),
                            recursive: false,
                            active: true,
                        };
                        let row = build_watch_folder_row(&new_folder, &pnames, &wfs, &wl);
                        wl.append(&row);
                        wfs.borrow_mut().push(new_folder);
                        el.set_visible(false);
                    }
                });
            }
        });
    }

    let add_group = adw::PreferencesGroup::new();
    add_group.add(&add_button);

    watch_page.add(&watch_group);
    watch_page.add(&add_group);
    dialog.add(&watch_page);

    // Wire reset button
    {
        let subfolder = subfolder_row.clone();
        let overwrite = overwrite_row.clone();
        let remember = remember_row.clone();
        let skill = skill_row.clone();
        let threads = threads_row.clone();
        let error = error_row.clone();
        let contrast = contrast_row.clone();
        let large_text = large_text_row.clone();
        let motion = motion_row.clone();
        let notify = desktop_notify_row.clone();
        let sound = sound_row.clone();
        let auto_open = auto_open_row.clone();
        let output_mode = output_mode_row.clone();
        let fps_reset = fixed_path_state.clone();
        let wfs_reset = watch_folders_state.clone();
        let wl_reset = watch_list.clone();
        let el_reset = empty_label.clone();
        reset_button.connect_clicked(move |_| {
            let defaults = AppConfig::default();
            subfolder.set_text(&defaults.output_subfolder);
            overwrite.set_selected(0);
            remember.set_active(defaults.remember_settings);
            skill.set_selected(0);
            threads.set_selected(0);
            error.set_selected(0);
            contrast.set_active(defaults.high_contrast);
            large_text.set_active(defaults.large_text);
            motion.set_active(defaults.reduced_motion);
            notify.set_active(defaults.notify_on_completion);
            sound.set_active(defaults.play_completion_sound);
            auto_open.set_active(defaults.auto_open_output);
            output_mode.set_selected(0);
            *fps_reset.borrow_mut() = None;
            // Clear watch folders
            wfs_reset.borrow_mut().clear();
            wl_reset.remove_all();
            el_reset.set_visible(true);
        });
    }

    // Preserve history settings from current config (not exposed in UI yet)
    let hist_max_entries = config.history_max_entries;
    let hist_max_days = config.history_max_days;

    // Save settings when the dialog closes
    dialog.connect_closed(move |_| {
        let welcome_reset = reset_welcome_state.get();
        let new_config = AppConfig {
            first_run_complete: !welcome_reset,
            tutorial_complete: !welcome_reset,
            output_subfolder: subfolder_row.text().to_string(),
            output_fixed_path: if output_mode_row.selected() == 1 {
                fixed_path_state.borrow().clone()
            } else {
                None
            },
            overwrite_behavior: match overwrite_row.selected() {
                1 => OverwriteBehavior::AutoRename,
                2 => OverwriteBehavior::Overwrite,
                3 => OverwriteBehavior::Skip,
                _ => OverwriteBehavior::Ask,
            },
            remember_settings: remember_row.is_active(),
            skill_level: match skill_row.selected() {
                1 => SkillLevel::Detailed,
                _ => SkillLevel::Simple,
            },
            thread_count: match threads_row.selected() {
                1 => pixstrip_core::config::ThreadCount::Manual(1),
                2 => pixstrip_core::config::ThreadCount::Manual(2),
                3 => pixstrip_core::config::ThreadCount::Manual(4),
                4 => pixstrip_core::config::ThreadCount::Manual(8),
                _ => pixstrip_core::config::ThreadCount::Auto,
            },
            error_behavior: match error_row.selected() {
                1 => ErrorBehavior::PauseOnError,
                _ => ErrorBehavior::SkipAndContinue,
            },
            notify_on_completion: desktop_notify_row.is_active(),
            play_completion_sound: sound_row.is_active(),
            auto_open_output: auto_open_row.is_active(),
            high_contrast: contrast_row.is_active(),
            large_text: large_text_row.is_active(),
            reduced_motion: motion_row.is_active(),
            history_max_entries: hist_max_entries,
            history_max_days: hist_max_days,
            watch_folders: watch_folders_state.borrow().clone(),
        };

        let store = ConfigStore::new();
        let _ = store.save(&new_config);
    });

    dialog
}

fn build_watch_folder_row(
    folder: &pixstrip_core::watcher::WatchFolder,
    preset_names: &[String],
    watch_state: &std::rc::Rc<std::cell::RefCell<Vec<pixstrip_core::watcher::WatchFolder>>>,
    list_box: &gtk::ListBox,
) -> adw::ExpanderRow {
    let display_path = folder.path.file_name()
        .and_then(|n| n.to_str())
        .unwrap_or_else(|| folder.path.to_str().unwrap_or("Unknown"));

    let row = adw::ExpanderRow::builder()
        .title(display_path)
        .subtitle(&folder.path.display().to_string())
        .show_enable_switch(true)
        .enable_expansion(folder.active)
        .build();
    row.add_prefix(&gtk::Image::from_icon_name("folder-visiting-symbolic"));

    // Preset selector
    let preset_row = adw::ComboRow::builder()
        .title("Linked Preset")
        .subtitle("Preset to apply to new images")
        .use_subtitle(true)
        .build();
    let preset_model = gtk::StringList::new(
        &preset_names.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
    );
    preset_row.set_model(Some(&preset_model));
    preset_row.set_list_factory(Some(&crate::steps::full_text_list_factory()));

    // Set selected to matching preset
    let selected_idx = preset_names.iter()
        .position(|n| *n == folder.preset_name)
        .unwrap_or(0);
    preset_row.set_selected(selected_idx as u32);

    // Recursive toggle
    let recursive_row = adw::SwitchRow::builder()
        .title("Include Subfolders")
        .subtitle("Monitor subfolders recursively")
        .active(folder.recursive)
        .build();

    // Remove button
    let remove_row = adw::ActionRow::builder()
        .title("Remove This Folder")
        .build();
    remove_row.add_prefix(&gtk::Image::from_icon_name("user-trash-symbolic"));
    let remove_btn = gtk::Button::builder()
        .icon_name("edit-delete-symbolic")
        .tooltip_text("Remove watch folder")
        .valign(gtk::Align::Center)
        .build();
    remove_btn.add_css_class("flat");
    remove_btn.add_css_class("error");
    remove_row.add_suffix(&remove_btn);

    row.add_row(&preset_row);
    row.add_row(&recursive_row);
    row.add_row(&remove_row);

    // Wire enable toggle
    let folder_path = folder.path.clone();
    {
        let wfs = watch_state.clone();
        let fp = folder_path.clone();
        row.connect_enable_expansion_notify(move |r| {
            let mut folders = wfs.borrow_mut();
            if let Some(f) = folders.iter_mut().find(|f| f.path == fp) {
                f.active = r.enables_expansion();
            }
        });
    }

    // Wire preset change
    {
        let wfs = watch_state.clone();
        let fp = folder_path.clone();
        let pnames = preset_names.to_vec();
        preset_row.connect_selected_notify(move |r| {
            let mut folders = wfs.borrow_mut();
            if let Some(f) = folders.iter_mut().find(|f| f.path == fp) {
                f.preset_name = pnames.get(r.selected() as usize)
                    .cloned()
                    .unwrap_or_default();
            }
        });
    }

    // Wire recursive toggle
    {
        let wfs = watch_state.clone();
        let fp = folder_path.clone();
        recursive_row.connect_active_notify(move |r| {
            let mut folders = wfs.borrow_mut();
            if let Some(f) = folders.iter_mut().find(|f| f.path == fp) {
                f.recursive = r.is_active();
            }
        });
    }

    // Wire remove button
    {
        let wfs = watch_state.clone();
        let lb = list_box.clone();
        let fp = folder_path;
        let r = row.clone();
        remove_btn.connect_clicked(move |_| {
            wfs.borrow_mut().retain(|f| f.path != fp);
            lb.remove(&r);
        });
    }

    row
}
