use adw::prelude::*;
use chrono::{Datelike, Local};
use outlay_core::db::Database;
use outlay_core::models::TransactionType;
use std::cell::{Cell, RefCell};
use std::rc::Rc;

use crate::icon_theme;

pub struct BudgetsView {
    pub container: gtk::Box,
    current_year: Rc<Cell<i32>>,
    current_month: Rc<Cell<u32>>,
    month_label: gtk::Label,
    refresh_month: Rc<dyn Fn(i32, u32)>,
}

impl BudgetsView {
    pub fn new(db: Rc<Database>) -> Self {
        let container = gtk::Box::new(gtk::Orientation::Vertical, 0);
        let toast_overlay = adw::ToastOverlay::new();

        let clamp = adw::Clamp::new();
        clamp.set_maximum_size(700);
        clamp.set_margin_start(16);
        clamp.set_margin_end(16);

        let inner = gtk::Box::new(gtk::Orientation::Vertical, 20);
        inner.set_margin_top(20);
        inner.set_margin_bottom(20);

        let today = Local::now().date_naive();
        let current_year = Rc::new(Cell::new(today.year()));
        let current_month = Rc::new(Cell::new(today.month()));

        // Month navigation
        let nav_box = gtk::Box::new(gtk::Orientation::Horizontal, 12);
        nav_box.set_halign(gtk::Align::Center);
        nav_box.set_margin_top(12);
        nav_box.set_margin_bottom(12);

        let prev_btn = gtk::Button::from_icon_name("go-previous-symbolic");
        prev_btn.add_css_class("flat");
        prev_btn.add_css_class("circular");
        prev_btn.set_tooltip_text(Some("Previous month"));

        let month_label = gtk::Label::new(None);
        month_label.add_css_class("month-nav-label");
        month_label.set_width_chars(18);

        let label_btn = gtk::Button::new();
        label_btn.set_child(Some(&month_label));
        label_btn.add_css_class("flat");
        label_btn.set_tooltip_text(Some("Pick a month"));

        let next_btn = gtk::Button::from_icon_name("go-next-symbolic");
        next_btn.add_css_class("flat");
        next_btn.add_css_class("circular");
        next_btn.set_tooltip_text(Some("Next month"));

        // Cycle config popover
        let cycle_popover = gtk::Popover::new();
        let cycle_box = gtk::Box::new(gtk::Orientation::Vertical, 8);
        cycle_box.set_margin_start(12);
        cycle_box.set_margin_end(12);
        cycle_box.set_margin_top(12);
        cycle_box.set_margin_bottom(12);

        let cycle_label = gtk::Label::new(Some("Budget Cycle"));
        cycle_label.add_css_class("heading");
        cycle_box.append(&cycle_label);

        let cycle_toggle_box = gtk::Box::new(gtk::Orientation::Horizontal, 0);
        cycle_toggle_box.add_css_class("linked");

        let cal_btn = gtk::ToggleButton::with_label("Calendar");
        cal_btn.set_hexpand(true);
        let pay_btn = gtk::ToggleButton::with_label("Payday");
        pay_btn.set_group(Some(&cal_btn));
        pay_btn.set_hexpand(true);
        let roll_btn = gtk::ToggleButton::with_label("Rolling");
        roll_btn.set_group(Some(&cal_btn));
        roll_btn.set_hexpand(true);

        let current_mode = db.get_budget_cycle_mode();
        match current_mode {
            outlay_core::models::BudgetCycleMode::Calendar => cal_btn.set_active(true),
            outlay_core::models::BudgetCycleMode::Payday => pay_btn.set_active(true),
            outlay_core::models::BudgetCycleMode::Rolling => roll_btn.set_active(true),
        }

        cycle_toggle_box.append(&cal_btn);
        cycle_toggle_box.append(&pay_btn);
        cycle_toggle_box.append(&roll_btn);
        cycle_box.append(&cycle_toggle_box);

        let start_day_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
        let start_day_label = gtk::Label::new(Some("Start day:"));
        let start_day_spin = gtk::SpinButton::with_range(1.0, 31.0, 1.0);
        let saved_day: f64 = db.get_setting("budget_cycle_start_day")
            .ok().flatten()
            .and_then(|s| s.parse().ok())
            .unwrap_or(1.0);
        start_day_spin.set_value(saved_day);
        start_day_row.append(&start_day_label);
        start_day_row.append(&start_day_spin);
        start_day_row.set_visible(pay_btn.is_active());
        cycle_box.append(&start_day_row);

        let rolling_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
        let rolling_label = gtk::Label::new(Some("Period days:"));
        let rolling_spin = gtk::SpinButton::with_range(7.0, 90.0, 1.0);
        let saved_days: f64 = db.get_setting("budget_cycle_days")
            .ok().flatten()
            .and_then(|s| s.parse().ok())
            .unwrap_or(30.0);
        rolling_spin.set_value(saved_days);
        rolling_row.append(&rolling_label);
        rolling_row.append(&rolling_spin);
        rolling_row.set_visible(roll_btn.is_active());
        cycle_box.append(&rolling_row);

        cycle_popover.set_child(Some(&cycle_box));

        let cycle_btn = gtk::MenuButton::new();
        cycle_btn.set_icon_name("emblem-system-symbolic");
        cycle_btn.add_css_class("flat");
        cycle_btn.set_popover(Some(&cycle_popover));
        cycle_btn.set_tooltip_text(Some("Budget cycle settings"));

        // Period tracking
        let period_start: Rc<Cell<chrono::NaiveDate>> = Rc::new(Cell::new(today));
        let period_end: Rc<Cell<chrono::NaiveDate>> = Rc::new(Cell::new(today));
        {
            let (ps, pe) = db.get_budget_period(today);
            period_start.set(ps);
            period_end.set(pe);
        }

        nav_box.append(&prev_btn);
        nav_box.append(&label_btn);
        nav_box.append(&next_btn);
        nav_box.append(&cycle_btn);

        // Summary
        let summary_label = gtk::Label::new(None);
        summary_label.add_css_class("dim-label");
        summary_label.set_halign(gtk::Align::Center);

        // Budget list
        let budget_group = adw::PreferencesGroup::builder()
            .title("BUDGETS")
            .build();

        // Add budget button
        let add_btn = gtk::Button::with_label("Add Budget");
        add_btn.add_css_class("suggested-action");
        add_btn.add_css_class("pill");
        add_btn.set_halign(gtk::Align::Center);
        add_btn.set_margin_top(8);

        // Monthly budget cap widgets (created early so nav callbacks can reference them)
        let cap_label = gtk::Label::new(None);
        cap_label.set_halign(gtk::Align::Start);
        cap_label.set_margin_start(12);
        cap_label.set_margin_end(12);
        cap_label.add_css_class("dim-label");

        let cap_bar = gtk::LevelBar::builder()
            .min_value(0.0)
            .max_value(1.0)
            .value(0.0)
            .build();
        cap_bar.set_margin_start(12);
        cap_bar.set_margin_end(12);
        cap_bar.set_margin_bottom(12);

        // Safe-to-spend labels (created early so nav callbacks can reference them)
        let safe_amount = gtk::Label::new(Some("--/day"));
        safe_amount.add_css_class("title-1");
        safe_amount.set_halign(gtk::Align::Start);
        safe_amount.set_margin_start(12);

        let safe_detail = gtk::Label::new(None);
        safe_detail.add_css_class("caption");
        safe_detail.add_css_class("dim-label");
        safe_detail.set_halign(gtk::Align::Start);
        safe_detail.set_margin_start(12);
        safe_detail.set_margin_bottom(12);

        // Initial load
        Self::update_month_label(&month_label, current_year.get(), current_month.get());
        Self::load_budgets(
            &db, &budget_group, &summary_label, &toast_overlay,
            current_year.get(), current_month.get(),
        );
        Self::update_cap_display(&db, &cap_label, &cap_bar, current_year.get(), current_month.get());

        // Spending insights (created early so nav callbacks can reference)
        let insights_group = adw::PreferencesGroup::builder()
            .title("INSIGHTS")
            .build();
        Self::load_insights(&db, &insights_group, current_year.get(), current_month.get());

        // Month picker popover
        let popover = gtk::Popover::new();
        popover.set_parent(&label_btn);

        let picker_box = gtk::Box::new(gtk::Orientation::Vertical, 8);
        picker_box.set_margin_start(12);
        picker_box.set_margin_end(12);
        picker_box.set_margin_top(12);
        picker_box.set_margin_bottom(12);

        let year_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
        year_row.set_halign(gtk::Align::Center);

        let picker_year = Rc::new(Cell::new(current_year.get()));

        let year_label_pick = gtk::Label::new(Some(&current_year.get().to_string()));
        year_label_pick.add_css_class("heading");
        year_label_pick.set_width_chars(6);

        let py_btn = gtk::Button::from_icon_name("go-previous-symbolic");
        py_btn.add_css_class("flat");
        py_btn.add_css_class("circular");
        let ny_btn = gtk::Button::from_icon_name("go-next-symbolic");
        ny_btn.add_css_class("flat");
        ny_btn.add_css_class("circular");

        year_row.append(&py_btn);
        year_row.append(&year_label_pick);
        year_row.append(&ny_btn);
        picker_box.append(&year_row);

        {
            let py = picker_year.clone();
            let yl = year_label_pick.clone();
            py_btn.connect_clicked(move |_| {
                py.set(py.get() - 1);
                yl.set_label(&py.get().to_string());
            });
        }
        {
            let py = picker_year.clone();
            let yl = year_label_pick.clone();
            ny_btn.connect_clicked(move |_| {
                py.set(py.get() + 1);
                yl.set_label(&py.get().to_string());
            });
        }

        // Shared refresh closure for picker callbacks
        let refresh_all: Rc<dyn Fn(i32, u32)> = {
            let db_ref = db.clone();
            let group_ref = budget_group.clone();
            let summary_ref = summary_label.clone();
            let toast_ref = toast_overlay.clone();
            let cap_lbl = cap_label.clone();
            let cap_b = cap_bar.clone();
            let safe_amt = safe_amount.clone();
            let safe_det = safe_detail.clone();
            let insights_ref = insights_group.clone();
            Rc::new(move |y: i32, m: u32| {
                Self::load_budgets(&db_ref, &group_ref, &summary_ref, &toast_ref, y, m);
                Self::update_cap_display(&db_ref, &cap_lbl, &cap_b, y, m);
                Self::update_safe_to_spend(&db_ref, &safe_amt, &safe_det, y, m);
                Self::load_insights(&db_ref, &insights_ref, y, m);
            })
        };

        let month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                           "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
        let month_grid = gtk::Grid::new();
        month_grid.set_row_spacing(4);
        month_grid.set_column_spacing(4);

        for i in 0..12u32 {
            let btn = gtk::Button::with_label(month_names[i as usize]);
            btn.add_css_class("flat");
            btn.set_hexpand(true);

            let y = current_year.clone();
            let m = current_month.clone();
            let py = picker_year.clone();
            let lbl = month_label.clone();
            let pop = popover.clone();
            let refresh = refresh_all.clone();
            btn.connect_clicked(move |_| {
                let new_month = i + 1;
                let new_year = py.get();
                y.set(new_year);
                m.set(new_month);
                Self::update_month_label(&lbl, new_year, new_month);
                refresh(new_year, new_month);
                pop.popdown();
            });

            month_grid.attach(&btn, (i % 4) as i32, (i / 4) as i32, 1, 1);
        }

        picker_box.append(&month_grid);

        let today_btn = gtk::Button::with_label("Today");
        today_btn.add_css_class("flat");
        {
            let y = current_year.clone();
            let m = current_month.clone();
            let lbl = month_label.clone();
            let pop = popover.clone();
            let refresh = refresh_all;
            today_btn.connect_clicked(move |_| {
                let now = chrono::Local::now().date_naive();
                y.set(now.year());
                m.set(now.month());
                Self::update_month_label(&lbl, now.year(), now.month());
                refresh(now.year(), now.month());
                pop.popdown();
            });
        }
        picker_box.append(&today_btn);

        popover.set_child(Some(&picker_box));

        {
            let py = picker_year;
            let y = current_year.clone();
            let yl = year_label_pick;
            popover.connect_show(move |_| {
                py.set(y.get());
                yl.set_label(&y.get().to_string());
            });
        }

        label_btn.connect_clicked(move |_| {
            popover.popup();
        });

        // Navigation
        {
            let db_ref = db.clone();
            let year_ref = current_year.clone();
            let month_ref = current_month.clone();
            let label_ref = month_label.clone();
            let group_ref = budget_group.clone();
            let summary_ref = summary_label.clone();
            let toast_ref = toast_overlay.clone();
            let cap_lbl = cap_label.clone();
            let cap_b = cap_bar.clone();
            let safe_amt_prev = safe_amount.clone();
            let safe_det_prev = safe_detail.clone();
            let insights_prev = insights_group.clone();
            prev_btn.connect_clicked(move |_| {
                let mut y = year_ref.get();
                let mut m = month_ref.get();
                if m == 1 { m = 12; y -= 1; } else { m -= 1; }
                year_ref.set(y);
                month_ref.set(m);
                Self::update_month_label(&label_ref, y, m);
                Self::load_budgets(&db_ref, &group_ref, &summary_ref, &toast_ref, y, m);
                Self::update_cap_display(&db_ref, &cap_lbl, &cap_b, y, m);
                Self::update_safe_to_spend(&db_ref, &safe_amt_prev, &safe_det_prev, y, m);
                Self::load_insights(&db_ref, &insights_prev, y, m);
            });
        }
        {
            let db_ref = db.clone();
            let year_ref = current_year.clone();
            let month_ref = current_month.clone();
            let label_ref = month_label.clone();
            let group_ref = budget_group.clone();
            let summary_ref = summary_label.clone();
            let toast_ref = toast_overlay.clone();
            let cap_lbl = cap_label.clone();
            let cap_b = cap_bar.clone();
            let safe_amt_next = safe_amount.clone();
            let safe_det_next = safe_detail.clone();
            let insights_next = insights_group.clone();
            next_btn.connect_clicked(move |_| {
                let mut y = year_ref.get();
                let mut m = month_ref.get();
                if m == 12 { m = 1; y += 1; } else { m += 1; }
                year_ref.set(y);
                month_ref.set(m);
                Self::update_month_label(&label_ref, y, m);
                Self::load_budgets(&db_ref, &group_ref, &summary_ref, &toast_ref, y, m);
                Self::update_cap_display(&db_ref, &cap_lbl, &cap_b, y, m);
                Self::update_safe_to_spend(&db_ref, &safe_amt_next, &safe_det_next, y, m);
                Self::load_insights(&db_ref, &insights_next, y, m);
            });
        }

        // Wire cycle mode toggle buttons
        {
            let apply_cycle = {
                let sd_row = start_day_row.clone();
                let rl_row = rolling_row.clone();
                let db_ref = db.clone();
                let label_ref = month_label.clone();
                let year_ref = current_year.clone();
                let month_ref = current_month.clone();
                let group_ref = budget_group.clone();
                let summary_ref = summary_label.clone();
                let toast_ref = toast_overlay.clone();
                let cap_lbl = cap_label.clone();
                let cap_b = cap_bar.clone();
                let safe_amt = safe_amount.clone();
                let safe_det = safe_detail.clone();
                let insights_ref = insights_group.clone();
                Rc::new(move |mode_str: &str, show_sd: bool, show_rl: bool| {
                    db_ref.set_setting("budget_cycle_mode", mode_str).ok();
                    sd_row.set_visible(show_sd);
                    rl_row.set_visible(show_rl);
                    let y = year_ref.get();
                    let m = month_ref.get();
                    Self::update_month_label(&label_ref, y, m);
                    Self::load_budgets(&db_ref, &group_ref, &summary_ref, &toast_ref, y, m);
                    Self::update_cap_display(&db_ref, &cap_lbl, &cap_b, y, m);
                    Self::update_safe_to_spend(&db_ref, &safe_amt, &safe_det, y, m);
                    Self::load_insights(&db_ref, &insights_ref, y, m);
                })
            };

            {
                let apply = apply_cycle.clone();
                cal_btn.connect_toggled(move |btn| {
                    if btn.is_active() { apply("calendar", false, false); }
                });
            }
            {
                let apply = apply_cycle.clone();
                pay_btn.connect_toggled(move |btn| {
                    if btn.is_active() { apply("payday", true, false); }
                });
            }
            {
                let apply = apply_cycle;
                roll_btn.connect_toggled(move |btn| {
                    if btn.is_active() { apply("rolling", false, true); }
                });
            }
        }
        {
            let db_ref = db.clone();
            start_day_spin.connect_value_changed(move |spin| {
                db_ref.set_setting("budget_cycle_start_day", &format!("{}", spin.value() as u32)).ok();
            });
        }
        {
            let db_ref = db.clone();
            rolling_spin.connect_value_changed(move |spin| {
                db_ref.set_setting("budget_cycle_days", &format!("{}", spin.value() as u32)).ok();
            });
        }

        // Add budget button
        {
            let db_ref = db.clone();
            let year_ref = current_year.clone();
            let month_ref = current_month.clone();
            let group_ref = budget_group.clone();
            let summary_ref = summary_label.clone();
            let toast_ref = toast_overlay.clone();
            add_btn.connect_clicked(move |btn| {
                let y = year_ref.get();
                let m = month_ref.get();
                Self::show_add_dialog(
                    btn, &db_ref, &group_ref, &summary_ref, &toast_ref, y, m,
                );
            });
        }

        // Copy from last month button
        let copy_btn = gtk::Button::with_label("Copy from last month");
        copy_btn.add_css_class("flat");
        copy_btn.add_css_class("pill");
        copy_btn.set_halign(gtk::Align::Center);
        {
            let db_ref = db.clone();
            let year_ref = current_year.clone();
            let month_ref = current_month.clone();
            let group_ref = budget_group.clone();
            let summary_ref = summary_label.clone();
            let toast_ref = toast_overlay.clone();
            copy_btn.connect_clicked(move |btn| {
                let y = year_ref.get();
                let m = month_ref.get();
                let current = Self::month_str(y, m);
                let (py, pm) = if m == 1 { (y - 1, 12) } else { (y, m - 1) };
                let prev = Self::month_str(py, pm);

                // Check if budgets already exist for current month
                let existing = db_ref.list_budgets_for_month(&current).unwrap_or_default();
                if !existing.is_empty() {
                    let alert = adw::AlertDialog::new(
                        Some("Budgets already exist"),
                        Some("This month already has budgets. Copying will overwrite them with last month's values."),
                    );
                    alert.add_response("cancel", "Cancel");
                    alert.add_response("copy", "Overwrite");
                    alert.set_response_appearance("copy", adw::ResponseAppearance::Destructive);
                    alert.set_default_response(Some("cancel"));
                    alert.set_close_response("cancel");

                    let db_copy = db_ref.clone();
                    let group_copy = group_ref.clone();
                    let summary_copy = summary_ref.clone();
                    let toast_copy = toast_ref.clone();
                    let prev_copy = prev.clone();
                    let current_copy = current.clone();
                    alert.connect_response(None, move |_, response| {
                        if response == "copy" {
                            match db_copy.copy_budgets(&prev_copy, &current_copy) {
                                Ok(()) => {
                                    let toast = adw::Toast::new("Budgets copied from last month");
                                    toast_copy.add_toast(toast);
                                    Self::load_budgets(&db_copy, &group_copy, &summary_copy, &toast_copy, y, m);
                                }
                                Err(e) => {
                                    let toast = adw::Toast::new(&format!("Error: {}", e));
                                    toast_copy.add_toast(toast);
                                }
                            }
                        }
                    });
                    alert.present(Some(btn));
                } else {
                    match db_ref.copy_budgets(&prev, &current) {
                        Ok(()) => {
                            let toast = adw::Toast::new("Budgets copied from last month");
                            toast_ref.add_toast(toast);
                            Self::load_budgets(&db_ref, &group_ref, &summary_ref, &toast_ref, y, m);
                        }
                        Err(e) => {
                            let toast = adw::Toast::new(&format!("Error: {}", e));
                            toast_ref.add_toast(toast);
                        }
                    }
                }
            });
        }

        // Button row
        let btn_row = gtk::Box::new(gtk::Orientation::Horizontal, 12);
        btn_row.set_halign(gtk::Align::Center);
        btn_row.set_margin_top(8);
        btn_row.append(&add_btn);
        btn_row.append(&copy_btn);

        // Monthly budget cap card (cap_label and cap_bar created above)
        let cap_card = gtk::Box::new(gtk::Orientation::Vertical, 6);
        cap_card.add_css_class("card");
        cap_card.set_margin_start(4);
        cap_card.set_margin_end(4);

        let cap_header = gtk::Box::new(gtk::Orientation::Horizontal, 8);
        cap_header.set_margin_top(12);
        cap_header.set_margin_start(12);
        cap_header.set_margin_end(12);
        let cap_title = gtk::Label::new(Some("Monthly Cap"));
        cap_title.add_css_class("heading");
        cap_title.set_halign(gtk::Align::Start);
        cap_title.set_hexpand(true);

        let cap_edit_btn = gtk::Button::from_icon_name("tabler-edit");
        cap_edit_btn.add_css_class("flat");
        cap_edit_btn.add_css_class("circular");
        cap_edit_btn.set_tooltip_text(Some("Set monthly cap"));
        cap_header.append(&cap_title);
        cap_header.append(&cap_edit_btn);

        cap_card.append(&cap_header);
        cap_card.append(&cap_label);
        cap_card.append(&cap_bar);

        // Edit cap dialog
        {
            let db_ref = db.clone();
            let cap_label_ref = cap_label.clone();
            let cap_bar_ref = cap_bar.clone();
            let yr = current_year.clone();
            let mo = current_month.clone();
            let group_ref = budget_group.clone();
            let summary_ref = summary_label.clone();
            let toast_ref = toast_overlay.clone();
            cap_edit_btn.connect_clicked(move |btn| {
                let dialog = adw::Dialog::builder()
                    .title("Set Monthly Cap")
                    .content_width(320)
                    .content_height(180)
                    .build();
                let toolbar = adw::ToolbarView::new();
                toolbar.add_top_bar(&adw::HeaderBar::new());

                let content = gtk::Box::new(gtk::Orientation::Vertical, 16);
                content.set_margin_top(16);
                content.set_margin_bottom(16);
                content.set_margin_start(16);
                content.set_margin_end(16);

                let current_cap = db_ref.get_setting("monthly_budget_cap")
                    .ok().flatten()
                    .unwrap_or_default();
                let entry = adw::EntryRow::builder()
                    .title("Cap Amount (0 to disable)")
                    .text(&current_cap)
                    .build();
                entry.set_input_purpose(gtk::InputPurpose::Number);
                crate::numpad::attach_numpad(&entry);

                let form = adw::PreferencesGroup::new();
                form.add(&entry);

                let save_btn = gtk::Button::with_label("Save");
                save_btn.add_css_class("suggested-action");
                save_btn.add_css_class("pill");
                save_btn.set_halign(gtk::Align::Center);

                content.append(&form);
                content.append(&save_btn);
                toolbar.set_content(Some(&content));
                dialog.set_child(Some(&toolbar));

                let db_save = db_ref.clone();
                let dialog_save = dialog.clone();
                let cap_lbl = cap_label_ref.clone();
                let cap_b = cap_bar_ref.clone();
                let yr2 = yr.clone();
                let mo2 = mo.clone();
                let group2 = group_ref.clone();
                let summary2 = summary_ref.clone();
                let toast2 = toast_ref.clone();
                save_btn.connect_clicked(move |_| {
                    let text = entry.text();
                    let val: f64 = text.trim().parse().unwrap_or(0.0);
                    let _ = db_save.set_setting("monthly_budget_cap", &format!("{:.2}", val));
                    dialog_save.close();
                    Self::update_cap_display(&db_save, &cap_lbl, &cap_b, yr2.get(), mo2.get());
                    Self::load_budgets(&db_save, &group2, &summary2, &toast2, yr2.get(), mo2.get());
                });

                dialog.present(Some(btn));
            });
        }

        // Safe-to-spend card (labels created earlier for nav callback access)
        let safe_card = gtk::Box::new(gtk::Orientation::Vertical, 4);
        safe_card.add_css_class("card");
        safe_card.set_margin_start(4);
        safe_card.set_margin_end(4);

        let safe_title = gtk::Label::new(Some("SAFE TO SPEND"));
        safe_title.add_css_class("caption");
        safe_title.add_css_class("dim-label");
        safe_title.set_halign(gtk::Align::Start);
        safe_title.set_margin_top(12);
        safe_title.set_margin_start(12);

        safe_card.append(&safe_title);
        safe_card.append(&safe_amount);
        safe_card.append(&safe_detail);

        Self::update_safe_to_spend(&db, &safe_amount, &safe_detail, current_year.get(), current_month.get());

        // Upcoming bills section
        let bills_group = adw::PreferencesGroup::builder()
            .title("UPCOMING BILLS")
            .build();
        Self::load_upcoming_bills(&db, &bills_group);

        // Sandbox / What-If mode
        let sandbox_banner = adw::Banner::new("Sandbox Mode - changes are not saved");
        sandbox_banner.set_button_label(Some("Exit"));
        sandbox_banner.set_revealed(false);
        let sandbox_active = Rc::new(Cell::new(false));
        let sandbox_values: Rc<RefCell<std::collections::HashMap<i64, f64>>> = Rc::new(RefCell::new(std::collections::HashMap::new()));

        let sandbox_btn_box = gtk::Box::new(gtk::Orientation::Horizontal, 8);
        sandbox_btn_box.set_halign(gtk::Align::Center);
        sandbox_btn_box.set_margin_bottom(4);

        let whatif_btn = gtk::ToggleButton::with_label("What-If");
        whatif_btn.add_css_class("flat");
        whatif_btn.set_tooltip_text(Some("Experiment with budget changes"));
        sandbox_btn_box.append(&whatif_btn);

        let apply_btn = gtk::Button::with_label("Apply Changes");
        apply_btn.add_css_class("suggested-action");
        apply_btn.add_css_class("pill");
        apply_btn.set_visible(false);
        sandbox_btn_box.append(&apply_btn);

        {
            let banner_ref = sandbox_banner.clone();
            let active_ref = sandbox_active.clone();
            let vals_ref = sandbox_values.clone();
            let apply_ref = apply_btn.clone();
            let db_ref = db.clone();
            let group_ref = budget_group.clone();
            let summary_ref = summary_label.clone();
            let toast_ref = toast_overlay.clone();
            let year_ref = current_year.clone();
            let month_ref = current_month.clone();
            let cap_lbl = cap_label.clone();
            let cap_b = cap_bar.clone();
            whatif_btn.connect_toggled(move |btn| {
                let is_active = btn.is_active();
                active_ref.set(is_active);
                banner_ref.set_revealed(is_active);
                apply_ref.set_visible(is_active);
                if !is_active {
                    vals_ref.borrow_mut().clear();
                    let y = year_ref.get();
                    let m = month_ref.get();
                    Self::load_budgets(&db_ref, &group_ref, &summary_ref, &toast_ref, y, m);
                    Self::update_cap_display(&db_ref, &cap_lbl, &cap_b, y, m);
                }
            });
        }

        // Banner exit button
        {
            let btn_ref = whatif_btn.clone();
            sandbox_banner.connect_button_clicked(move |_| {
                btn_ref.set_active(false);
            });
        }

        // Apply button
        {
            let db_ref = db.clone();
            let vals_ref = sandbox_values.clone();
            let btn_ref = whatif_btn.clone();
            let toast_ref = toast_overlay.clone();
            let year_ref = current_year.clone();
            let month_ref = current_month.clone();
            apply_btn.connect_clicked(move |_| {
                let y = year_ref.get();
                let m = month_ref.get();
                let month_str = Self::month_str(y, m);
                let vals = vals_ref.borrow();
                let mut count = 0;
                for (cat_id, amount) in vals.iter() {
                    db_ref.set_budget(*cat_id, &month_str, *amount, false).ok();
                    count += 1;
                }
                drop(vals);
                vals_ref.borrow_mut().clear();
                btn_ref.set_active(false);
                if count > 0 {
                    let toast = adw::Toast::new(&format!("Applied {} budget change{}", count, if count == 1 { "" } else { "s" }));
                    toast_ref.add_toast(toast);
                }
            });
        }

        inner.append(&nav_box);
        inner.append(&sandbox_banner);
        inner.append(&sandbox_btn_box);
        inner.append(&safe_card);
        inner.append(&insights_group);
        inner.append(&bills_group);
        inner.append(&cap_card);
        inner.append(&summary_label);
        inner.append(&budget_group);
        inner.append(&btn_row);

        clamp.set_child(Some(&inner));

        let scroll = gtk::ScrolledWindow::builder()
            .hscrollbar_policy(gtk::PolicyType::Never)
            .vexpand(true)
            .child(&clamp)
            .build();

        toast_overlay.set_child(Some(&scroll));
        container.append(&toast_overlay);

        let refresh_month: Rc<dyn Fn(i32, u32)> = {
            let db_ref = db.clone();
            let group_ref = budget_group;
            let summary_ref = summary_label;
            let toast_ref = toast_overlay;
            let cap_lbl = cap_label;
            let cap_b = cap_bar;
            Rc::new(move |y: i32, m: u32| {
                Self::load_budgets(&db_ref, &group_ref, &summary_ref, &toast_ref, y, m);
                Self::update_cap_display(&db_ref, &cap_lbl, &cap_b, y, m);
            })
        };

        BudgetsView {
            container, current_year, current_month, month_label, refresh_month,
        }
    }

    pub fn get_month(&self) -> (i32, u32) {
        (self.current_year.get(), self.current_month.get())
    }

    pub fn set_month(&self, year: i32, month: u32) {
        self.current_year.set(year);
        self.current_month.set(month);
        Self::update_month_label(&self.month_label, year, month);
        (self.refresh_month)(year, month);
    }

    fn update_month_label(label: &gtk::Label, year: i32, month: u32) {
        let date = chrono::NaiveDate::from_ymd_opt(year, month, 1).unwrap();
        label.set_label(&date.format("%B %Y").to_string());
    }

    fn month_str(year: i32, month: u32) -> String {
        format!("{:04}-{:02}", year, month)
    }

    fn load_budgets(
        db: &Rc<Database>,
        group: &adw::PreferencesGroup,
        summary_label: &gtk::Label,
        toast_overlay: &adw::ToastOverlay,
        year: i32,
        month: u32,
    ) {
        // Clear existing rows
        let mut rows = Vec::new();
        Self::collect_action_rows(group.upcast_ref(), &mut rows);
        for row in &rows {
            group.remove(row);
        }

        let month_str = Self::month_str(year, month);
        let budgets = db.list_budgets_for_month(&month_str).unwrap_or_default();

        if budgets.is_empty() {
            // 4.2: Prominent empty state with copy-from-last-month
            let (py, pm) = if month == 1 { (year - 1, 12) } else { (year, month - 1) };
            let prev_month_name = match pm {
                1 => "January", 2 => "February", 3 => "March",
                4 => "April", 5 => "May", 6 => "June",
                7 => "July", 8 => "August", 9 => "September",
                10 => "October", 11 => "November", 12 => "December",
                _ => "last month",
            };
            let prev_has_budgets = !db.list_budgets_for_month(&Self::month_str(py, pm))
                .unwrap_or_default()
                .is_empty();

            let empty_box = gtk::Box::new(gtk::Orientation::Vertical, 8);
            empty_box.set_halign(gtk::Align::Center);
            empty_box.set_margin_top(16);
            empty_box.set_margin_bottom(16);

            let icon = gtk::Image::from_icon_name("edit-copy-symbolic");
            icon.set_pixel_size(48);
            icon.add_css_class("dim-label");
            empty_box.append(&icon);

            let title = gtk::Label::new(Some("No budgets set for this month"));
            title.add_css_class("title-3");
            empty_box.append(&title);

            let desc = gtk::Label::new(Some("Start fresh or copy your budgets from last month"));
            desc.add_css_class("dim-label");
            empty_box.append(&desc);

            let btn_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
            btn_row.set_halign(gtk::Align::Center);
            btn_row.set_margin_top(8);

            if prev_has_budgets {
                let copy_label = format!("Copy from {}", prev_month_name);
                let copy_btn = gtk::Button::with_label(&copy_label);
                copy_btn.add_css_class("suggested-action");
                copy_btn.add_css_class("pill");
                let prev_str = Self::month_str(py, pm);
                let current_str = month_str.clone();
                let db_c = db.clone();
                let group_c = group.clone();
                let summary_c = summary_label.clone();
                let toast_c = toast_overlay.clone();
                copy_btn.connect_clicked(move |_| {
                    match db_c.copy_budgets(&prev_str, &current_str) {
                        Ok(()) => {
                            let toast = adw::Toast::new("Budgets copied from last month");
                            toast_c.add_toast(toast);
                            Self::load_budgets(&db_c, &group_c, &summary_c, &toast_c, year, month);
                        }
                        Err(e) => {
                            let toast = adw::Toast::new(&format!("Error: {}", e));
                            toast_c.add_toast(toast);
                        }
                    }
                });
                btn_row.append(&copy_btn);
            }
            empty_box.append(&btn_row);

            let empty_row = adw::ActionRow::new();
            empty_row.set_child(Some(&empty_box));
            empty_row.set_activatable(false);
            group.add(&empty_row);

            summary_label.set_label("");
            return;
        }

        let mut total_budgeted = 0.0_f64;
        let mut total_spent = 0.0_f64;

        for budget in &budgets {
            let progress = db
                .get_budget_progress(budget.category_id, &month_str)
                .unwrap_or(None);

            let cat = db.get_category(budget.category_id).ok();
            let cat_name = cat.as_ref()
                .map(|c| c.name.clone())
                .unwrap_or_else(|| "Unknown".to_string());
            let cat_icon = cat.as_ref().and_then(|c| c.icon.clone());
            let cat_color = cat.as_ref().and_then(|c| c.color.clone());

            // Check rollover if enabled
            let (effective_amt, rollover_amount) = if budget.rollover {
                db.get_budget_with_rollover(budget.category_id, &month_str)
                    .ok()
                    .flatten()
                    .unwrap_or((budget.amount, 0.0))
            } else {
                (budget.amount, 0.0)
            };

            let (_, spent, _) = progress.unwrap_or((budget.amount, 0.0, 0.0));
            let pct = if effective_amt > 0.0 { spent / effective_amt * 100.0 } else { 0.0 };
            total_budgeted += effective_amt;
            total_spent += spent;

            let remaining = effective_amt - spent;
            let rollover_suffix = if budget.rollover && rollover_amount.abs() > 0.01 {
                if rollover_amount > 0.0 {
                    format!(" (+{:.2} rollover)", rollover_amount)
                } else {
                    format!(" ({:.2} overspent)", rollover_amount)
                }
            } else {
                String::new()
            };
            let subtitle = format!("{:.0}% spent{}", pct, rollover_suffix);

            let row = adw::ActionRow::builder()
                .title(&cat_name)
                .subtitle(&subtitle)
                .activatable(true)
                .build();

            if remaining < 0.0 {
                row.add_css_class("error");
            }

            let icon_name = icon_theme::resolve_category_icon(&cat_icon, &cat_color);
            if let Some(name) = &icon_name {
                let icon = gtk::Image::from_icon_name(name);
                icon.set_pixel_size(24);
                row.add_prefix(&icon);
            }

            // Sparkline (last 4 months spending for this category)
            {
                let cat_id = budget.category_id;
                let mut values = Vec::new();
                let mut sy = year;
                let mut sm = month;
                for _ in 0..4 {
                    let v = db.get_monthly_total_for_category(sy, sm, cat_id).unwrap_or(0.0);
                    values.push(v);
                    if sm == 1 { sm = 12; sy -= 1; } else { sm -= 1; }
                }
                values.reverse();

                let sparkline = gtk::DrawingArea::new();
                sparkline.set_content_width(60);
                sparkline.set_content_height(20);
                sparkline.set_valign(gtk::Align::Center);
                sparkline.set_draw_func(move |_, cr, w, h| {
                    let max = values.iter().cloned().fold(0.0_f64, f64::max);
                    if max == 0.0 { return; }
                    let w = w as f64;
                    let h = h as f64;
                    let n = values.len();
                    if n < 2 { return; }
                    let pts: Vec<(f64, f64)> = values.iter().enumerate().map(|(i, val)| {
                        let x = (i as f64 / (n - 1) as f64) * w;
                        let y = h - (val / max) * (h - 2.0) - 1.0;
                        (x, y)
                    }).collect();
                    let smooth = crate::charts_view::monotone_subdivide(&pts, 12);
                    cr.set_line_width(1.5);
                    cr.set_source_rgba(0.4, 0.6, 0.8, 0.7);
                    if let Some(&(x, y)) = smooth.first() {
                        cr.move_to(x, y);
                        for &(sx, sy) in &smooth[1..] {
                            cr.line_to(sx, sy);
                        }
                    }
                    cr.stroke().ok();
                });
                row.add_suffix(&sparkline);
            }

            // Progress bar
            let level = gtk::LevelBar::builder()
                .min_value(0.0)
                .max_value(1.0)
                .value((pct / 100.0).min(1.0))
                .hexpand(true)
                .valign(gtk::Align::Center)
                .build();
            level.set_width_request(160);

            // Color based on percentage
            if pct >= 100.0 {
                level.add_css_class("error");
            } else if pct >= 75.0 {
                level.add_css_class("warning");
            }

            let suffix_box = gtk::Box::new(gtk::Orientation::Vertical, 4);
            suffix_box.set_valign(gtk::Align::Center);
            suffix_box.set_margin_top(8);
            suffix_box.set_margin_bottom(8);

            // Amount hierarchy: spent / total
            let amount_box = gtk::Box::new(gtk::Orientation::Horizontal, 2);
            amount_box.set_halign(gtk::Align::End);
            let spent_lbl = gtk::Label::new(Some(&format!("{:.2}", spent)));
            spent_lbl.add_css_class("budget-spent");
            let total_lbl = gtk::Label::new(Some(&format!(" / {:.2}", effective_amt)));
            total_lbl.add_css_class("budget-total");
            amount_box.append(&spent_lbl);
            amount_box.append(&total_lbl);
            suffix_box.append(&amount_box);

            // Remaining amount
            let remaining_text = if remaining >= 0.0 {
                format!("{:.2} left", remaining)
            } else {
                format!("{:.2} over", remaining.abs())
            };
            let remaining_lbl = gtk::Label::new(Some(&remaining_text));
            remaining_lbl.add_css_class("budget-remaining");
            remaining_lbl.set_halign(gtk::Align::End);
            if remaining < 0.0 {
                remaining_lbl.add_css_class("amount-expense");
            }
            suffix_box.append(&remaining_lbl);

            suffix_box.append(&level);

            // Daily pace indicator (4.4)
            let today = Local::now().date_naive();
            if today.year() == year && today.month() == month {
                let days_elapsed = today.day();
                if days_elapsed >= 3 {
                    let days_in_month = if month == 12 {
                        chrono::NaiveDate::from_ymd_opt(year + 1, 1, 1)
                    } else {
                        chrono::NaiveDate::from_ymd_opt(year, month + 1, 1)
                    }.and_then(|d| d.pred_opt()).map(|d| d.day()).unwrap_or(30);
                    let avg_daily = spent / days_elapsed as f64;
                    let budget_pace = effective_amt / days_in_month as f64;
                    let pace_text = format!(
                        "{:.2}/day (pace: {:.2}/day)",
                        avg_daily, budget_pace,
                    );
                    let pace_label = gtk::Label::new(Some(&pace_text));
                    pace_label.add_css_class("caption");
                    if avg_daily > budget_pace {
                        pace_label.add_css_class("amount-expense");
                    } else {
                        pace_label.add_css_class("amount-income");
                    }
                    suffix_box.append(&pace_label);
                }
            }

            row.add_suffix(&suffix_box);

            // Click to edit
            let budget_id = budget.id;
            let cat_id = budget.category_id;
            let current_amount = budget.amount;
            let current_rollover = budget.rollover;
            let db_ref = db.clone();
            let group_ref = group.clone();
            let summary_ref = summary_label.clone();
            let toast_ref = toast_overlay.clone();
            row.connect_activated(move |row| {
                Self::show_edit_dialog(
                    row, budget_id, cat_id, current_amount, current_rollover,
                    &db_ref, &group_ref, &summary_ref, &toast_ref, year, month,
                );
            });

            group.add(&row);
        }

        summary_label.set_label(&format!(
            "Total budgeted: {:.2} - Spent: {:.2}",
            total_budgeted, total_spent
        ));
    }

    fn show_add_dialog(
        parent: &gtk::Button,
        db: &Rc<Database>,
        group: &adw::PreferencesGroup,
        summary_label: &gtk::Label,
        toast_overlay: &adw::ToastOverlay,
        year: i32,
        month: u32,
    ) {
        let dialog = adw::Dialog::builder()
            .title("Add Budget")
            .content_width(360)
            .content_height(300)
            .build();

        let toolbar = adw::ToolbarView::new();
        toolbar.add_top_bar(&adw::HeaderBar::new());

        let content = gtk::Box::new(gtk::Orientation::Vertical, 16);
        content.set_margin_top(16);
        content.set_margin_bottom(16);
        content.set_margin_start(16);
        content.set_margin_end(16);

        // Category selector
        let cats = db
            .list_categories(Some(TransactionType::Expense))
            .unwrap_or_default();
        let cat_ids: Rc<RefCell<Vec<i64>>> = Rc::new(RefCell::new(Vec::new()));
        let cat_model = gtk::StringList::new(&[]);
        for cat in &cats {
            let icon_name = icon_theme::resolve_category_icon(&cat.icon, &cat.color);
            let entry = match &icon_name {
                Some(icon) => format!("{}\t{}", icon, cat.name),
                None => cat.name.clone(),
            };
            cat_model.append(&entry);
            cat_ids.borrow_mut().push(cat.id);
        }
        let cat_row = adw::ComboRow::builder()
            .title("Category")
            .model(&cat_model)
            .build();
        cat_row.set_factory(Some(&Self::make_category_factory()));
        cat_row.set_list_factory(Some(&Self::make_category_factory()));

        let amount_row = adw::EntryRow::builder()
            .title("Budget Amount")
            .build();
        amount_row.set_input_purpose(gtk::InputPurpose::Number);
        crate::numpad::attach_numpad(&amount_row);

        let rollover_row = adw::SwitchRow::builder()
            .title("Rollover")
            .subtitle("Carry unspent budget to next month")
            .active(false)
            .build();

        let form = adw::PreferencesGroup::new();
        form.add(&cat_row);
        form.add(&amount_row);
        form.add(&rollover_row);

        let save_btn = gtk::Button::with_label("Save");
        save_btn.add_css_class("suggested-action");
        save_btn.add_css_class("pill");
        save_btn.set_halign(gtk::Align::Center);

        content.append(&form);
        content.append(&save_btn);
        toolbar.set_content(Some(&content));
        dialog.set_child(Some(&toolbar));

        {
            let db_ref = db.clone();
            let dialog_ref = dialog.clone();
            let group_ref = group.clone();
            let summary_ref = summary_label.clone();
            let toast_ref = toast_overlay.clone();
            let ids_ref = cat_ids.clone();
            let rollover_ref = rollover_row.clone();
            save_btn.connect_clicked(move |_| {
                let amount: f64 = match amount_row.text().trim().parse() {
                    Ok(v) if v > 0.0 => v,
                    _ => {
                        let toast = adw::Toast::new("Please enter a valid amount");
                        toast_ref.add_toast(toast);
                        return;
                    }
                };

                let idx = cat_row.selected() as usize;
                let ids = ids_ref.borrow();
                let cat_id = match ids.get(idx) {
                    Some(&id) => id,
                    None => return,
                };

                let rollover = rollover_ref.is_active();
                let month_str = Self::month_str(year, month);
                match db_ref.set_budget(cat_id, &month_str, amount, rollover) {
                    Ok(()) => {
                        dialog_ref.close();
                        let toast = adw::Toast::new("Budget added");
                        toast_ref.add_toast(toast);
                        Self::load_budgets(&db_ref, &group_ref, &summary_ref, &toast_ref, year, month);
                    }
                    Err(e) => {
                        let toast = adw::Toast::new(&format!("Error: {}", e));
                        toast_ref.add_toast(toast);
                    }
                }
            });
        }

        dialog.present(Some(parent));
    }

    fn show_edit_dialog(
        parent: &adw::ActionRow,
        budget_id: i64,
        cat_id: i64,
        current_amount: f64,
        current_rollover: bool,
        db: &Rc<Database>,
        group: &adw::PreferencesGroup,
        summary_label: &gtk::Label,
        toast_overlay: &adw::ToastOverlay,
        year: i32,
        month: u32,
    ) {
        let dialog = adw::Dialog::builder()
            .title("Edit Budget")
            .content_width(360)
            .content_height(300)
            .build();

        let toolbar = adw::ToolbarView::new();
        toolbar.add_top_bar(&adw::HeaderBar::new());

        let content = gtk::Box::new(gtk::Orientation::Vertical, 16);
        content.set_margin_top(16);
        content.set_margin_bottom(16);
        content.set_margin_start(16);
        content.set_margin_end(16);

        let amount_row = adw::EntryRow::builder()
            .title("Budget Amount")
            .text(&format!("{:.2}", current_amount))
            .build();
        amount_row.set_input_purpose(gtk::InputPurpose::Number);
        crate::numpad::attach_numpad(&amount_row);

        let rollover_row = adw::SwitchRow::builder()
            .title("Rollover")
            .subtitle("Carry unspent budget to next month")
            .active(current_rollover)
            .build();

        let form = adw::PreferencesGroup::new();
        form.add(&amount_row);
        form.add(&rollover_row);

        let btn_box = gtk::Box::new(gtk::Orientation::Horizontal, 12);
        btn_box.set_halign(gtk::Align::Center);

        let delete_btn = gtk::Button::with_label("Delete");
        delete_btn.add_css_class("destructive-action");
        delete_btn.add_css_class("pill");

        let save_btn = gtk::Button::with_label("Save");
        save_btn.add_css_class("suggested-action");
        save_btn.add_css_class("pill");

        btn_box.append(&delete_btn);
        btn_box.append(&save_btn);

        content.append(&form);
        content.append(&btn_box);
        toolbar.set_content(Some(&content));
        dialog.set_child(Some(&toolbar));

        // Save
        {
            let db_ref = db.clone();
            let dialog_ref = dialog.clone();
            let group_ref = group.clone();
            let summary_ref = summary_label.clone();
            let toast_ref = toast_overlay.clone();
            let amount_ref = amount_row.clone();
            let rollover_ref = rollover_row.clone();
            save_btn.connect_clicked(move |_| {
                let amount: f64 = match amount_ref.text().trim().parse() {
                    Ok(v) if v > 0.0 => v,
                    _ => {
                        let toast = adw::Toast::new("Please enter a valid amount");
                        toast_ref.add_toast(toast);
                        return;
                    }
                };

                let rollover = rollover_ref.is_active();
                let month_str = Self::month_str(year, month);
                match db_ref.set_budget(cat_id, &month_str, amount, rollover) {
                    Ok(()) => {
                        dialog_ref.close();
                        let toast = adw::Toast::new("Budget updated");
                        toast_ref.add_toast(toast);
                        Self::load_budgets(&db_ref, &group_ref, &summary_ref, &toast_ref, year, month);
                    }
                    Err(e) => {
                        let toast = adw::Toast::new(&format!("Error: {}", e));
                        toast_ref.add_toast(toast);
                    }
                }
            });
        }

        // Delete
        {
            let db_ref = db.clone();
            let dialog_ref = dialog.clone();
            let group_ref = group.clone();
            let summary_ref = summary_label.clone();
            let toast_ref = toast_overlay.clone();
            delete_btn.connect_clicked(move |btn| {
                let alert = adw::AlertDialog::new(
                    Some("Delete this budget?"),
                    Some("This will remove the budget for this category."),
                );
                alert.add_response("cancel", "Cancel");
                alert.add_response("delete", "Delete");
                alert.set_response_appearance("delete", adw::ResponseAppearance::Destructive);
                alert.set_default_response(Some("cancel"));
                alert.set_close_response("cancel");

                let db_del = db_ref.clone();
                let dialog_del = dialog_ref.clone();
                let group_del = group_ref.clone();
                let summary_del = summary_ref.clone();
                let toast_del = toast_ref.clone();
                alert.connect_response(None, move |_, response| {
                    if response == "delete" {
                        match db_del.delete_budget(budget_id) {
                            Ok(()) => {
                                dialog_del.close();
                                let toast = adw::Toast::new("Budget deleted");
                                toast_del.add_toast(toast);
                                Self::load_budgets(&db_del, &group_del, &summary_del, &toast_del, year, month);
                            }
                            Err(e) => {
                                let toast = adw::Toast::new(&format!("Error: {}", e));
                                toast_del.add_toast(toast);
                            }
                        }
                    }
                });

                alert.present(Some(btn));
            });
        }

        dialog.present(Some(parent));
    }

    fn update_cap_display(db: &Database, label: &gtk::Label, bar: &gtk::LevelBar, year: i32, month: u32) {
        let cap: f64 = db.get_setting("monthly_budget_cap")
            .ok().flatten()
            .and_then(|s| s.parse().ok())
            .unwrap_or(0.0);

        if cap <= 0.0 {
            label.set_label("No monthly cap set");
            bar.set_value(0.0);
            // Remove old warning/error classes
            bar.remove_css_class("warning");
            bar.remove_css_class("error");
            return;
        }

        let spent = db.get_monthly_total(year, month, TransactionType::Expense).unwrap_or(0.0);
        let pct = if cap > 0.0 { spent / cap * 100.0 } else { 0.0 };
        label.set_label(&format!("Spent: {:.2} / {:.2} ({:.0}%)", spent, cap, pct));
        bar.set_value((spent / cap).min(1.0));

        bar.remove_css_class("warning");
        bar.remove_css_class("error");
        if pct >= 100.0 {
            bar.add_css_class("error");
        } else if pct >= 75.0 {
            bar.add_css_class("warning");
        }
    }

    fn update_safe_to_spend(
        db: &Database,
        amount_label: &gtk::Label,
        detail_label: &gtk::Label,
        year: i32,
        month: u32,
    ) {
        let month_str = Self::month_str(year, month);
        match db.get_safe_to_spend(&month_str) {
            Ok((daily, remaining, days_left)) => {
                if daily > 0.0 {
                    let base_currency = db
                        .get_setting("base_currency")
                        .ok()
                        .flatten()
                        .unwrap_or_else(|| "USD".to_string());
                    amount_label.set_label(&format!("{:.2} {}/day", daily, base_currency));
                    detail_label.set_label(&format!(
                        "{:.2} remaining over {} day{}",
                        remaining,
                        days_left,
                        if days_left == 1 { "" } else { "s" },
                    ));

                    amount_label.remove_css_class("amount-income");
                    amount_label.remove_css_class("amount-expense");
                    if remaining > 0.0 {
                        amount_label.add_css_class("amount-income");
                    } else {
                        amount_label.add_css_class("amount-expense");
                    }
                } else {
                    amount_label.set_label("Over budget");
                    amount_label.remove_css_class("amount-income");
                    amount_label.add_css_class("amount-expense");
                    detail_label.set_label("No remaining budget this month");
                }
            }
            Err(_) => {
                amount_label.set_label("--/day");
                detail_label.set_label("Set budgets to see daily allowance");
            }
        }
    }

    fn collect_action_rows(widget: &gtk::Widget, rows: &mut Vec<adw::ActionRow>) {
        if let Some(row) = widget.downcast_ref::<adw::ActionRow>() {
            rows.push(row.clone());
            return;
        }
        let mut child = widget.first_child();
        while let Some(c) = child {
            Self::collect_action_rows(&c, rows);
            child = c.next_sibling();
        }
    }

    fn make_category_factory() -> gtk::SignalListItemFactory {
        let factory = gtk::SignalListItemFactory::new();
        factory.connect_setup(|_, item| {
            let item = item.downcast_ref::<gtk::ListItem>().unwrap();
            let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 8);
            let icon = gtk::Image::new();
            icon.set_pixel_size(20);
            let label = gtk::Label::new(None);
            hbox.append(&icon);
            hbox.append(&label);
            item.set_child(Some(&hbox));
        });
        factory.connect_bind(|_, item| {
            let item = item.downcast_ref::<gtk::ListItem>().unwrap();
            let string_obj = item.item().and_downcast::<gtk::StringObject>().unwrap();
            let text = string_obj.string();
            let hbox = item.child().and_downcast::<gtk::Box>().unwrap();
            let icon = hbox.first_child().and_downcast::<gtk::Image>().unwrap();
            let label = icon.next_sibling().and_downcast::<gtk::Label>().unwrap();
            if let Some((icon_name, name)) = text.split_once('\t') {
                icon.set_icon_name(Some(icon_name));
                icon.set_visible(true);
                label.set_label(name);
            } else {
                icon.set_visible(false);
                label.set_label(&text);
            }
        });
        factory
    }

    fn load_insights(db: &Database, group: &adw::PreferencesGroup, year: i32, month: u32) {
        let mut rows = Vec::new();
        Self::collect_action_rows(group.upcast_ref(), &mut rows);
        for row in &rows {
            group.remove(row);
        }

        let insights = db.detect_anomalies(year, month);
        if insights.is_empty() {
            let row = adw::ActionRow::builder()
                .title("No spending anomalies this month")
                .build();
            row.add_css_class("dim-label");
            group.add(&row);
        } else {
            for (message, _amount, _cat_id) in &insights {
                let row = adw::ActionRow::builder()
                    .title(message)
                    .build();

                let icon = gtk::Image::from_icon_name("outlay-charts");
                icon.set_pixel_size(20);
                row.add_prefix(&icon);

                group.add(&row);
            }
        }
    }

    fn load_upcoming_bills(db: &Database, group: &adw::PreferencesGroup) {
        let mut rows = Vec::new();
        Self::collect_action_rows(group.upcast_ref(), &mut rows);
        for row in &rows {
            group.remove(row);
        }

        let bills = db.list_upcoming_bills(14).unwrap_or_default();
        if bills.is_empty() {
            let row = adw::ActionRow::builder()
                .title("No upcoming bills")
                .build();
            row.add_css_class("dim-label");
            group.add(&row);
        } else {
            let today = chrono::Local::now().date_naive();
            for (rec, next_date) in &bills {
                let cat = db.get_category(rec.category_id).ok();
                let cat_name = cat.as_ref()
                    .map(|c| c.name.clone())
                    .unwrap_or_else(|| "Unknown".to_string());
                let cat_icon = cat.as_ref().and_then(|c| c.icon.clone());
                let cat_color = cat.as_ref().and_then(|c| c.color.clone());

                let days_until = (*next_date - today).num_days();
                let due_text = if days_until == 0 {
                    "Due today".to_string()
                } else if days_until == 1 {
                    "Due tomorrow".to_string()
                } else {
                    format!("Due in {} days", days_until)
                };

                let row = adw::ActionRow::builder()
                    .title(&cat_name)
                    .subtitle(&format!("{} - {}", due_text, next_date.format("%b %d")))
                    .build();

                let icon_name = icon_theme::resolve_category_icon(&cat_icon, &cat_color);
                if let Some(name) = &icon_name {
                    let icon = gtk::Image::from_icon_name(name);
                    icon.set_pixel_size(24);
                    row.add_prefix(&icon);
                }

                let amount_label = gtk::Label::new(Some(&format!("-{:.2} {}", rec.amount, rec.currency)));
                amount_label.add_css_class("amount-display");
                amount_label.add_css_class("amount-expense");
                row.add_suffix(&amount_label);

                if days_until <= 1 {
                    row.add_css_class("error");
                }

                group.add(&row);
            }
        }
    }
}
