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

use crate::edit_dialog;
use crate::icon_theme;

use std::collections::HashSet;

pub struct HistoryView {
    pub container: gtk::Box,
    db: Rc<Database>,
    list_container: gtk::Box,
    toast_overlay: adw::ToastOverlay,
    current_year: Rc<Cell<i32>>,
    current_month: Rc<Cell<u32>>,
    month_label: gtk::Label,
    search_term: Rc<RefCell<String>>,
    active_categories: Rc<RefCell<HashSet<i64>>>,
    active_tags: Rc<RefCell<HashSet<String>>>,
    chips_box: gtk::FlowBox,
    chips_updating: Rc<Cell<bool>>,
    no_results_page: adw::StatusPage,
    anomaly_banner: adw::Banner,
    on_navigate_insights: Rc<RefCell<Option<Rc<dyn Fn()>>>>,
    in_selection_mode: Rc<Cell<bool>>,
    selected_ids: Rc<RefCell<HashSet<i64>>>,
    select_btn: gtk::Button,
    action_bar: gtk::ActionBar,
    action_count_label: gtk::Label,
    income_label: gtk::Label,
    expense_label: gtk::Label,
    net_label: gtk::Label,
}

impl HistoryView {
    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, 16);
        inner.set_margin_top(20);
        inner.set_margin_bottom(20);

        // -- Monthly summary cards --
        let summary_box = gtk::Box::new(gtk::Orientation::Horizontal, 12);
        summary_box.set_halign(gtk::Align::Fill);

        let income_card = Self::make_summary_card("INCOME");
        let expense_card = Self::make_summary_card("EXPENSES");
        let net_card = Self::make_summary_card("NET");

        summary_box.append(&income_card.0);
        summary_box.append(&expense_card.0);
        summary_box.append(&net_card.0);

        let income_label = income_card.1;
        let expense_label = expense_card.1;
        let net_label = net_card.1;

        // -- Selection mode state --
        let in_selection_mode = Rc::new(Cell::new(false));
        let selected_ids: Rc<RefCell<HashSet<i64>>> = Rc::new(RefCell::new(HashSet::new()));

        // -- Search entry + Select button --
        let search_box = gtk::Box::new(gtk::Orientation::Horizontal, 8);
        let search_entry = gtk::SearchEntry::new();
        search_entry.set_placeholder_text(Some("Search transactions..."));
        search_entry.set_hexpand(true);

        let select_btn = gtk::Button::with_label("Select");
        select_btn.add_css_class("flat");

        let search_term: Rc<RefCell<String>> = Rc::new(RefCell::new(String::new()));
        let active_categories: Rc<RefCell<HashSet<i64>>> = Rc::new(RefCell::new(HashSet::new()));
        let active_tags: Rc<RefCell<HashSet<String>>> = Rc::new(RefCell::new(HashSet::new()));

        // Category filter chips (wrapping)
        let chips_box = gtk::FlowBox::new();
        chips_box.set_selection_mode(gtk::SelectionMode::None);
        chips_box.set_homogeneous(false);
        chips_box.set_row_spacing(6);
        chips_box.set_column_spacing(6);
        chips_box.set_margin_top(4);
        chips_box.set_margin_bottom(4);
        chips_box.set_max_children_per_line(20);
        chips_box.set_min_children_per_line(1);

        // -- Month navigation (with picker) --
        let today = Local::now().date_naive();
        let current_year = Rc::new(Cell::new(today.year()));
        let current_month = Rc::new(Cell::new(today.month()));

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

        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(20);
        month_label.set_xalign(0.5);

        // Make month label a button with picker popover
        let month_btn = gtk::Button::new();
        month_btn.set_child(Some(&month_label));
        month_btn.add_css_class("flat");
        month_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"));

        nav_box.append(&prev_btn);
        nav_box.append(&month_btn);
        nav_box.append(&next_btn);

        // -- Transaction list container --
        let list_container = gtk::Box::new(gtk::Orientation::Vertical, 0);

        // Empty state for filtered results (2.4)
        let no_results = adw::StatusPage::builder()
            .icon_name("edit-find-symbolic")
            .title("No matching transactions")
            .description("Try a different search term or clear the filters")
            .vexpand(true)
            .build();
        no_results.set_visible(false);

        let scroll_inner = gtk::Box::new(gtk::Orientation::Vertical, 0);
        scroll_inner.append(&list_container);
        scroll_inner.append(&no_results);

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

        // Guard for chip toggled reentrancy
        let chips_updating = Rc::new(Cell::new(false));

        // -- Anomaly banner --
        let anomaly_banner = adw::Banner::new("");
        anomaly_banner.set_button_label(Some("View"));
        anomaly_banner.set_revealed(false);
        let on_navigate_insights: Rc<RefCell<Option<Rc<dyn Fn()>>>> = Rc::new(RefCell::new(None));
        {
            let nav_cb = on_navigate_insights.clone();
            anomaly_banner.connect_button_clicked(move |_| {
                if let Some(ref cb) = *nav_cb.borrow() {
                    cb();
                }
            });
        }

        // -- Month picker popover --
        let picker_popover = gtk::Popover::new();
        picker_popover.set_parent(&month_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 picker_year = Rc::new(Cell::new(current_year.get()));
            let year_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
            year_row.set_halign(gtk::Align::Center);
            let year_lbl = gtk::Label::new(Some(&current_year.get().to_string()));
            year_lbl.add_css_class("heading");
            year_lbl.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_lbl);
            year_row.append(&ny_btn);
            picker_box.append(&year_row);

            {
                let py = picker_year.clone();
                let yl = year_lbl.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_lbl.clone();
                ny_btn.connect_clicked(move |_| { py.set(py.get() + 1); yl.set_label(&py.get().to_string()); });
            }

            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 yr = current_year.clone();
                let mr = current_month.clone();
                let py = picker_year.clone();
                let ml = month_label.clone();
                let pop = picker_popover.clone();
                let db_p = db.clone();
                let lc = list_container.clone();
                let to = toast_overlay.clone();
                let cb = chips_box.clone();
                let ac = active_categories.clone();
                let at = active_tags.clone();
                let st = search_term.clone();
                let cu = chips_updating.clone();
                let nr = no_results.clone();
                let ab = anomaly_banner.clone();
                let il = income_label.clone();
                let el = expense_label.clone();
                let nl = net_label.clone();
                btn.connect_clicked(move |_| {
                    let new_m = i + 1;
                    let new_y = py.get();
                    yr.set(new_y);
                    mr.set(new_m);
                    let count = db_p.get_transaction_count_for_month(new_y, new_m).unwrap_or(0);
                    Self::update_month_label(&ml, new_y, new_m, count);
                    ac.borrow_mut().clear();
                    at.borrow_mut().clear();
                    nr.set_visible(false);
                    Self::load_month(&db_p, &lc, &to, new_y, new_m);
                    Self::rebuild_chips(&db_p, &cb, &ac, &at, &st, &lc, new_y, new_m, &cu, &nr);
                    Self::update_anomaly_banner(&db_p, &ab, new_y, new_m);
                    Self::refresh_summary(&db_p, &il, &el, &nl, new_y, new_m);
                    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 yr = current_year.clone();
                let mr = current_month.clone();
                let ml = month_label.clone();
                let pop = picker_popover.clone();
                let db_t = db.clone();
                let lc = list_container.clone();
                let to = toast_overlay.clone();
                let cb = chips_box.clone();
                let ac = active_categories.clone();
                let at = active_tags.clone();
                let st = search_term.clone();
                let cu = chips_updating.clone();
                let nr = no_results.clone();
                let ab = anomaly_banner.clone();
                let il = income_label.clone();
                let el = expense_label.clone();
                let nl = net_label.clone();
                today_btn.connect_clicked(move |_| {
                    let now = Local::now().date_naive();
                    yr.set(now.year());
                    mr.set(now.month());
                    let count = db_t.get_transaction_count_for_month(now.year(), now.month()).unwrap_or(0);
                    Self::update_month_label(&ml, now.year(), now.month(), count);
                    ac.borrow_mut().clear();
                    at.borrow_mut().clear();
                    nr.set_visible(false);
                    Self::load_month(&db_t, &lc, &to, now.year(), now.month());
                    Self::rebuild_chips(&db_t, &cb, &ac, &at, &st, &lc, now.year(), now.month(), &cu, &nr);
                    Self::update_anomaly_banner(&db_t, &ab, now.year(), now.month());
                    Self::refresh_summary(&db_t, &il, &el, &nl, now.year(), now.month());
                    pop.popdown();
                });
            }
            picker_box.append(&today_btn);
            picker_popover.set_child(Some(&picker_box));

            let py_sync = picker_year.clone();
            let yr_sync = current_year.clone();
            let yl_sync = year_lbl.clone();
            picker_popover.connect_show(move |_| {
                py_sync.set(yr_sync.get());
                yl_sync.set_label(&yr_sync.get().to_string());
            });

            month_btn.connect_clicked(move |_| { picker_popover.popup(); });
        }

        // Initial load
        let base_currency = db.get_setting("base_currency").ok().flatten().unwrap_or_else(|| "USD".to_string());
        let init_count = db.get_transaction_count_for_month(current_year.get(), current_month.get()).unwrap_or(0);
        Self::update_month_label(&month_label, current_year.get(), current_month.get(), init_count);
        Self::load_month(&db, &list_container, &toast_overlay, current_year.get(), current_month.get());
        Self::rebuild_chips(&db, &chips_box, &active_categories, &active_tags, &search_term, &list_container, current_year.get(), current_month.get(), &chips_updating, &no_results);
        Self::update_anomaly_banner(&db, &anomaly_banner, current_year.get(), current_month.get());
        Self::refresh_summary(&db, &income_label, &expense_label, &net_label, current_year.get(), current_month.get());

        // -- Navigation callbacks --
        {
            let db_ref = db.clone();
            let year_ref = current_year.clone();
            let month_ref = current_month.clone();
            let label_ref = month_label.clone();
            let list_ref = list_container.clone();
            let toast_ref = toast_overlay.clone();
            let chips_ref = chips_box.clone();
            let cats_ref = active_categories.clone();
            let tags_ref = active_tags.clone();
            let term_ref = search_term.clone();
            let upd_ref = chips_updating.clone();
            let nr_ref = no_results.clone();
            let banner_ref = anomaly_banner.clone();
            let il_ref = income_label.clone();
            let el_ref = expense_label.clone();
            let nl_ref = net_label.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);
                let count = db_ref.get_transaction_count_for_month(y, m).unwrap_or(0);
                Self::update_month_label(&label_ref, y, m, count);
                cats_ref.borrow_mut().clear();
                tags_ref.borrow_mut().clear();
                nr_ref.set_visible(false);
                Self::load_month(&db_ref, &list_ref, &toast_ref, y, m);
                Self::rebuild_chips(&db_ref, &chips_ref, &cats_ref, &tags_ref, &term_ref, &list_ref, y, m, &upd_ref, &nr_ref);
                Self::update_anomaly_banner(&db_ref, &banner_ref, y, m);
                Self::refresh_summary(&db_ref, &il_ref, &el_ref, &nl_ref, 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 list_ref = list_container.clone();
            let toast_ref = toast_overlay.clone();
            let chips_ref = chips_box.clone();
            let cats_ref = active_categories.clone();
            let tags_ref = active_tags.clone();
            let term_ref = search_term.clone();
            let upd_ref = chips_updating.clone();
            let nr_ref = no_results.clone();
            let banner_ref = anomaly_banner.clone();
            let il_ref = income_label.clone();
            let el_ref = expense_label.clone();
            let nl_ref = net_label.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);
                let count = db_ref.get_transaction_count_for_month(y, m).unwrap_or(0);
                Self::update_month_label(&label_ref, y, m, count);
                cats_ref.borrow_mut().clear();
                tags_ref.borrow_mut().clear();
                nr_ref.set_visible(false);
                Self::load_month(&db_ref, &list_ref, &toast_ref, y, m);
                Self::rebuild_chips(&db_ref, &chips_ref, &cats_ref, &tags_ref, &term_ref, &list_ref, y, m, &upd_ref, &nr_ref);
                Self::update_anomaly_banner(&db_ref, &banner_ref, y, m);
                Self::refresh_summary(&db_ref, &il_ref, &el_ref, &nl_ref, y, m);
            });
        }

        // -- Wire search --
        {
            let term_ref = search_term.clone();
            let cats_ref = active_categories.clone();
            let tags_ref = active_tags.clone();
            let list_ref = list_container.clone();
            let nr_ref = no_results.clone();
            search_entry.connect_search_changed(move |entry| {
                let text = entry.text().to_string().to_lowercase();
                *term_ref.borrow_mut() = text.clone();
                Self::apply_combined_filter(&list_ref, &text, &cats_ref.borrow(), &tags_ref.borrow(), &nr_ref);
            });
        }

        // -- Action bar for bulk operations --
        let action_bar = gtk::ActionBar::new();
        action_bar.set_revealed(false);

        let action_count_label = gtk::Label::new(Some("0 selected"));
        action_count_label.add_css_class("heading");
        action_bar.set_center_widget(Some(&action_count_label));

        let select_all_btn = gtk::Button::with_label("All");
        select_all_btn.add_css_class("flat");
        action_bar.pack_start(&select_all_btn);

        let delete_bulk_btn = gtk::Button::with_label("Delete");
        delete_bulk_btn.add_css_class("destructive-action");
        action_bar.pack_end(&delete_bulk_btn);

        let recat_btn = gtk::Button::with_label("Recategorize");
        recat_btn.add_css_class("flat");
        action_bar.pack_end(&recat_btn);

        // Wire select button toggle
        {
            let sel_mode = in_selection_mode.clone();
            let sel_ids = selected_ids.clone();
            let btn_ref = select_btn.clone();
            let bar_ref = action_bar.clone();
            let count_ref = action_count_label.clone();
            let db_ref = db.clone();
            let list_ref = list_container.clone();
            let toast_ref = toast_overlay.clone();
            let year_ref = current_year.clone();
            let month_ref = current_month.clone();
            select_btn.connect_clicked(move |_| {
                let was_selecting = sel_mode.get();
                sel_mode.set(!was_selecting);
                sel_ids.borrow_mut().clear();
                if was_selecting {
                    btn_ref.set_label("Select");
                    bar_ref.set_revealed(false);
                } else {
                    btn_ref.set_label("Cancel");
                    count_ref.set_label("0 selected");
                }
                Self::load_month(&db_ref, &list_ref, &toast_ref, year_ref.get(), month_ref.get());
            });
        }

        // Wire select all
        {
            let sel_ids = selected_ids.clone();
            let list_ref = list_container.clone();
            let bar_ref = action_bar.clone();
            let count_ref = action_count_label.clone();
            select_all_btn.connect_clicked(move |_| {
                let mut child = list_ref.first_child();
                while let Some(widget) = child {
                    // Traverse day groups -> list boxes -> rows
                    let mut inner = widget.first_child();
                    while let Some(inner_w) = inner {
                        if let Some(listbox) = inner_w.downcast_ref::<gtk::ListBox>() {
                            let mut row_widget = listbox.first_child();
                            while let Some(rw) = row_widget {
                                let name = rw.widget_name().to_string();
                                if let Some(txn_id) = Self::extract_txn_id_from_name(&name) {
                                    sel_ids.borrow_mut().insert(txn_id);
                                    // Toggle the checkbox if it exists
                                    if let Some(check) = Self::find_check_button(&rw) {
                                        check.set_active(true);
                                    }
                                }
                                row_widget = rw.next_sibling();
                            }
                        }
                        inner = inner_w.next_sibling();
                    }
                    child = widget.next_sibling();
                }
                let n = sel_ids.borrow().len();
                count_ref.set_label(&format!("{} selected", n));
                bar_ref.set_revealed(n > 0);
            });
        }

        // Wire bulk delete
        {
            let sel_ids = selected_ids.clone();
            let sel_mode = in_selection_mode.clone();
            let db_ref = db.clone();
            let list_ref = list_container.clone();
            let toast_ref = toast_overlay.clone();
            let year_ref = current_year.clone();
            let month_ref = current_month.clone();
            let bar_ref = action_bar.clone();
            let btn_ref = select_btn.clone();
            delete_bulk_btn.connect_clicked(move |w| {
                let n = sel_ids.borrow().len();
                let alert = adw::AlertDialog::new(
                    Some(&format!("Delete {} transaction{}?", n, if n == 1 { "" } else { "s" })),
                    Some("This action cannot be undone."),
                );
                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 ids = sel_ids.clone();
                let sm = sel_mode.clone();
                let db_c = db_ref.clone();
                let list_c = list_ref.clone();
                let toast_c = toast_ref.clone();
                let y = year_ref.clone();
                let m = month_ref.clone();
                let bar_c = bar_ref.clone();
                let btn_c = btn_ref.clone();
                alert.connect_response(None, move |_, response| {
                    if response == "delete" {
                        let count = ids.borrow().len();
                        for id in ids.borrow().iter() {
                            db_c.delete_transaction(*id).ok();
                        }
                        ids.borrow_mut().clear();
                        sm.set(false);
                        btn_c.set_label("Select");
                        bar_c.set_revealed(false);
                        Self::load_month(&db_c, &list_c, &toast_c, y.get(), m.get());
                        let toast = adw::Toast::new(&format!("Deleted {} transaction{}", count, if count == 1 { "" } else { "s" }));
                        toast_c.add_toast(toast);
                    }
                });
                alert.present(Some(w));
            });
        }

        // Wire bulk recategorize
        {
            let sel_ids = selected_ids.clone();
            let sel_mode = in_selection_mode.clone();
            let db_ref = db.clone();
            let list_ref = list_container.clone();
            let toast_ref = toast_overlay.clone();
            let year_ref = current_year.clone();
            let month_ref = current_month.clone();
            let bar_ref = action_bar.clone();
            let btn_ref = select_btn.clone();
            recat_btn.connect_clicked(move |w| {
                let cats = db_ref.list_categories(None).unwrap_or_default();
                if cats.is_empty() { return; }

                let alert = adw::AlertDialog::new(
                    Some("Recategorize selected transactions"),
                    Some("Choose a new category:"),
                );
                alert.add_response("cancel", "Cancel");
                for cat in &cats {
                    alert.add_response(&cat.id.to_string(), &cat.name);
                }
                alert.set_default_response(Some("cancel"));
                alert.set_close_response("cancel");

                let ids = sel_ids.clone();
                let sm = sel_mode.clone();
                let db_c = db_ref.clone();
                let list_c = list_ref.clone();
                let toast_c = toast_ref.clone();
                let y = year_ref.clone();
                let m = month_ref.clone();
                let bar_c = bar_ref.clone();
                let btn_c = btn_ref.clone();
                let cat_list = cats.clone();
                alert.connect_response(None, move |_, response| {
                    if response == "cancel" { return; }
                    if let Ok(cat_id) = response.parse::<i64>() {
                        let count = ids.borrow().len();
                        for txn_id in ids.borrow().iter() {
                            db_c.update_transaction_category(*txn_id, cat_id).ok();
                        }
                        ids.borrow_mut().clear();
                        sm.set(false);
                        btn_c.set_label("Select");
                        bar_c.set_revealed(false);
                        Self::load_month(&db_c, &list_c, &toast_c, y.get(), m.get());
                        let cat_name = cat_list.iter().find(|c| c.id == cat_id).map(|c| c.name.as_str()).unwrap_or("Unknown");
                        let toast = adw::Toast::new(&format!("Moved {} transaction{} to {}", count, if count == 1 { "" } else { "s" }, cat_name));
                        toast_c.add_toast(toast);
                    }
                });
                alert.present(Some(w));
            });
        }

        // -- Assemble --
        search_box.append(&search_entry);
        search_box.append(&select_btn);
        inner.append(&summary_box);
        inner.append(&nav_box);
        inner.append(&search_box);
        inner.append(&chips_box);
        inner.append(&anomaly_banner);
        inner.append(&scroll);

        clamp.set_child(Some(&inner));
        toast_overlay.set_child(Some(&clamp));
        container.append(&toast_overlay);
        container.append(&action_bar);

        HistoryView {
            container,
            db,
            list_container,
            toast_overlay,
            current_year,
            current_month,
            month_label,
            search_term,
            active_categories,
            active_tags,
            chips_box,
            chips_updating,
            no_results_page: no_results,
            anomaly_banner,
            on_navigate_insights,
            in_selection_mode,
            selected_ids,
            select_btn,
            action_bar,
            action_count_label,
            income_label,
            expense_label,
            net_label,
        }
    }

    pub fn refresh(&self) {
        if self.in_selection_mode.get() {
            Self::load_month_with_selection(
                &self.db,
                &self.list_container,
                &self.toast_overlay,
                self.current_year.get(),
                self.current_month.get(),
                &self.in_selection_mode,
                &self.selected_ids,
                &self.action_bar,
                &self.action_count_label,
            );
        } else {
            Self::load_month(
                &self.db,
                &self.list_container,
                &self.toast_overlay,
                self.current_year.get(),
                self.current_month.get(),
            );
        }
        Self::update_anomaly_banner(&self.db, &self.anomaly_banner, self.current_year.get(), self.current_month.get());
        Self::refresh_summary(&self.db, &self.income_label, &self.expense_label, &self.net_label, self.current_year.get(), self.current_month.get());
    }

    pub fn set_on_navigate_insights(&self, cb: Rc<dyn Fn()>) {
        *self.on_navigate_insights.borrow_mut() = Some(cb);
    }

    fn update_anomaly_banner(db: &Database, banner: &adw::Banner, year: i32, month: u32) {
        let anomalies = db.detect_anomalies(year, month);
        if anomalies.is_empty() {
            banner.set_revealed(false);
        } else {
            let msg = format!("{} spending insight{} for this month", anomalies.len(), if anomalies.len() == 1 { "" } else { "s" });
            banner.set_title(&msg);
            banner.set_revealed(true);
        }
    }

    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);
        let count = self.db.get_transaction_count_for_month(year, month).unwrap_or(0);
        Self::update_month_label(&self.month_label, year, month, count);
        self.refresh();
    }

    pub fn set_category_filter(&self, category_id: i64) {
        // Rebuild chips first so the target chip exists
        Self::rebuild_chips(
            &self.db,
            &self.chips_box,
            &self.active_categories,
            &self.active_tags,
            &self.search_term,
            &self.list_container,
            self.current_year.get(),
            self.current_month.get(),
            &self.chips_updating,
            &self.no_results_page,
        );
        // Set the filter to only show this category
        {
            let mut cats = self.active_categories.borrow_mut();
            cats.clear();
            cats.insert(category_id);
        }
        // Set guard to prevent toggle callbacks from firing during programmatic updates
        self.chips_updating.set(true);
        let mut child = self.chips_box.first_child();
        while let Some(w) = child {
            let next = w.next_sibling();
            if let Some(tb) = w.first_child().and_then(|c| c.downcast::<gtk::ToggleButton>().ok()) {
                tb.set_active(false);
            }
            child = next;
        }
        // Find and activate the matching category chip
        let cat_name = self.db.get_category(category_id)
            .map(|c| c.name)
            .unwrap_or_default();
        let mut child = self.chips_box.first_child();
        while let Some(w) = child {
            let next = w.next_sibling();
            if let Some(tb) = w.first_child().and_then(|c| c.downcast::<gtk::ToggleButton>().ok()) {
                if tb.label().as_deref() == Some(&cat_name) {
                    tb.set_active(true);
                }
            }
            child = next;
        }
        self.chips_updating.set(false);
        // Apply filter directly
        Self::apply_combined_filter(
            &self.list_container,
            &self.search_term.borrow(),
            &self.active_categories.borrow(),
            &self.active_tags.borrow(),
            &self.no_results_page,
        );
    }

    fn update_month_label(label: &gtk::Label, year: i32, month: u32, count: i64) {
        let month_name = match month {
            1 => "January", 2 => "February", 3 => "March",
            4 => "April", 5 => "May", 6 => "June",
            7 => "July", 8 => "August", 9 => "September",
            10 => "October", 11 => "November", 12 => "December",
            _ => "Unknown",
        };
        let count_text = if count > 0 {
            format!(
                " <span weight='normal' size='small' alpha='50%'>({} transaction{})</span>",
                count,
                if count == 1 { "" } else { "s" },
            )
        } else {
            String::new()
        };
        label.set_markup(&format!(
            "<span font_family='Space Grotesk' weight='bold'>{}</span> <span font_family='JetBrains Mono' weight='light'>{}</span>{}",
            month_name, year, count_text
        ));
    }

    fn load_month(
        db: &Rc<Database>,
        list_container: &gtk::Box,
        toast_overlay: &adw::ToastOverlay,
        year: i32,
        month: u32,
    ) {
        Self::load_month_inner(db, list_container, toast_overlay, year, month, None, None, None, None);
    }

    fn load_month_with_selection(
        db: &Rc<Database>,
        list_container: &gtk::Box,
        toast_overlay: &adw::ToastOverlay,
        year: i32,
        month: u32,
        in_selection_mode: &Rc<Cell<bool>>,
        selected_ids: &Rc<RefCell<HashSet<i64>>>,
        action_bar: &gtk::ActionBar,
        action_count_label: &gtk::Label,
    ) {
        Self::load_month_inner(
            db, list_container, toast_overlay, year, month,
            Some(in_selection_mode), Some(selected_ids), Some(action_bar), Some(action_count_label),
        );
    }

    fn load_month_inner(
        db: &Rc<Database>,
        list_container: &gtk::Box,
        toast_overlay: &adw::ToastOverlay,
        year: i32,
        month: u32,
        in_selection_mode: Option<&Rc<Cell<bool>>>,
        selected_ids: Option<&Rc<RefCell<HashSet<i64>>>>,
        action_bar: Option<&gtk::ActionBar>,
        action_count_label: Option<&gtk::Label>,
    ) {
        // Clear existing children
        while let Some(child) = list_container.first_child() {
            list_container.remove(&child);
        }

        let txns = match db.list_transactions_by_month(year, month) {
            Ok(t) => t,
            Err(_) => return,
        };

        if txns.is_empty() {
            let placeholder = gtk::Label::new(Some("No transactions this month"));
            placeholder.add_css_class("dim-label");
            placeholder.set_margin_top(32);
            placeholder.set_margin_bottom(32);
            list_container.append(&placeholder);
            return;
        }

        // Average spending display
        {
            let daily_avg = db.get_daily_average(year, month as u32).unwrap_or(0.0);
            let weekly_avg = db.get_weekly_average(year, month as u32).unwrap_or(0.0);
            if daily_avg > 0.0 {
                let base_currency = db.get_setting("base_currency")
                    .ok().flatten()
                    .unwrap_or_else(|| "USD".to_string());
                let avg_label = gtk::Label::new(Some(
                    &format!("Avg: {:.0} {}/day - {:.0} {}/week", daily_avg, base_currency, weekly_avg, base_currency)
                ));
                avg_label.add_css_class("caption");
                avg_label.add_css_class("dim-label");
                avg_label.set_halign(gtk::Align::Start);
                avg_label.set_margin_start(12);
                avg_label.set_margin_bottom(8);
                list_container.append(&avg_label);
            }
        }

        let today = Local::now().date_naive();
        let yesterday = today.pred_opt().unwrap_or(today);

        // Group transactions by date
        let mut current_date: Option<NaiveDate> = None;
        let mut day_transactions: Vec<&Transaction> = Vec::new();
        let mut running_balance = 0.0_f64;

        for txn in &txns {
            if current_date != Some(txn.date) {
                // Flush previous day group
                if let Some(date) = current_date {
                    let day_net = Self::calc_day_net(&day_transactions);
                    running_balance += day_net;
                    Self::add_day_group(
                        db, list_container, toast_overlay,
                        date, &day_transactions, today, yesterday,
                        year, month, running_balance,
                        in_selection_mode, selected_ids, action_bar, action_count_label,
                    );
                    day_transactions.clear();
                }
                current_date = Some(txn.date);
            }
            day_transactions.push(txn);
        }

        // Flush last day group
        if let Some(date) = current_date {
            let day_net = Self::calc_day_net(&day_transactions);
            running_balance += day_net;
            Self::add_day_group(
                db, list_container, toast_overlay,
                date, &day_transactions, today, yesterday,
                year, month, running_balance,
                in_selection_mode, selected_ids, action_bar, action_count_label,
            );
        }

        // Auto-scroll to today if viewing the current month
        if year == today.year() && month == today.month() {
            let container_ref = list_container.clone();
            glib::idle_add_local_once(move || {
                let mut child = container_ref.first_child();
                while let Some(widget) = child {
                    if widget.widget_name() == "today-header" {
                        widget.grab_focus();
                        break;
                    }
                    child = widget.next_sibling();
                }
            });
        }
    }

    fn calc_day_net(transactions: &[&Transaction]) -> f64 {
        let mut net = 0.0;
        for txn in transactions {
            match txn.transaction_type {
                TransactionType::Income => net += txn.amount * txn.exchange_rate,
                TransactionType::Expense => net -= txn.amount * txn.exchange_rate,
            }
        }
        net
    }

    fn add_day_group(
        db: &Rc<Database>,
        container: &gtk::Box,
        toast_overlay: &adw::ToastOverlay,
        date: NaiveDate,
        transactions: &[&Transaction],
        today: NaiveDate,
        yesterday: NaiveDate,
        year: i32,
        month: u32,
        running_balance: f64,
        in_selection_mode: Option<&Rc<Cell<bool>>>,
        selected_ids: Option<&Rc<RefCell<HashSet<i64>>>>,
        action_bar: Option<&gtk::ActionBar>,
        action_count_label: Option<&gtk::Label>,
    ) {
        let day_group = gtk::Box::new(gtk::Orientation::Vertical, 0);
        day_group.set_widget_name("day-group");

        // Date header
        if date == today {
            let header = gtk::Label::new(Some("TODAY"));
            header.add_css_class("date-header");
            header.set_halign(gtk::Align::Start);
            header.set_widget_name("today-header");
            day_group.append(&header);
        } else if date == yesterday {
            let header = gtk::Label::new(Some("YESTERDAY"));
            header.add_css_class("date-header");
            header.set_halign(gtk::Align::Start);
            day_group.append(&header);
        } else {
            let day_name = date.format("%A").to_string().to_uppercase();
            let header = gtk::Label::new(Some(&day_name));
            header.add_css_class("date-header");
            header.set_halign(gtk::Align::Start);
            day_group.append(&header);

            let date_detail = date.format("%-d %B").to_string();
            let detail = gtk::Label::new(Some(&date_detail));
            detail.add_css_class("date-header-detail");
            detail.set_halign(gtk::Align::Start);
            day_group.append(&detail);
        }

        // Day's transactions in a boxed-list card
        let day_list = gtk::ListBox::new();
        day_list.set_selection_mode(gtk::SelectionMode::None);
        day_list.add_css_class("boxed-list");
        day_list.add_css_class("transaction-card");

        let mut day_income = 0.0_f64;
        let mut day_expense = 0.0_f64;

        for txn in transactions {
            match txn.transaction_type {
                TransactionType::Income => day_income += txn.amount,
                TransactionType::Expense => day_expense += txn.amount,
            }

            let cat = db.get_category(txn.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());

            // Build subtitle: payee + note + tags
            let mut subtitle_parts: Vec<String> = Vec::new();
            if let Some(payee) = &txn.payee {
                if !payee.is_empty() {
                    subtitle_parts.push(payee.clone());
                }
            }
            if let Some(note) = &txn.note {
                if !note.is_empty() {
                    subtitle_parts.push(note.clone());
                }
            }
            if let Ok(tags) = db.get_transaction_tags(txn.id) {
                if !tags.is_empty() {
                    let tag_str = tags.iter().map(|t| format!("#{}", t.name)).collect::<Vec<_>>().join(" ");
                    subtitle_parts.push(tag_str);
                }
            }
            let subtitle = subtitle_parts.join(" - ");

            let amount_str = match txn.transaction_type {
                TransactionType::Expense => format!("-{:.2} {}", txn.amount, txn.currency),
                TransactionType::Income => format!("+{:.2} {}", txn.amount, txn.currency),
            };

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

            // Tag row with category ID, txn ID, and searchable text for filtering
            // Format: "catid:txnid:SEARCHTEXT"
            let payee_search = txn.payee.as_deref().unwrap_or("");
            let search_data = format!("{}:{}:{}|{}|{}|{}", txn.category_id, txn.id, cat_name, subtitle, amount_str, payee_search).to_lowercase();
            row.set_widget_name(&search_data);

            let is_selecting = in_selection_mode.map_or(false, |m| m.get());

            // Checkbox prefix (selection mode only)
            if is_selecting {
                let check = gtk::CheckButton::new();
                check.set_widget_name("selection-check");
                if let Some(ref ids) = selected_ids {
                    check.set_active(ids.borrow().contains(&txn.id));
                }
                let txn_id = txn.id;
                let ids_ref = selected_ids.cloned();
                let bar_ref = action_bar.cloned();
                let count_ref = action_count_label.cloned();
                check.connect_toggled(move |btn| {
                    if let Some(ref ids) = ids_ref {
                        if btn.is_active() {
                            ids.borrow_mut().insert(txn_id);
                        } else {
                            ids.borrow_mut().remove(&txn_id);
                        }
                        let n = ids.borrow().len();
                        if let Some(ref bar) = bar_ref {
                            bar.set_revealed(n > 0);
                        }
                        if let Some(ref label) = count_ref {
                            label.set_label(&format!("{} selected", n));
                        }
                    }
                });
                row.add_prefix(&check);
            }

            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(&amount_str));
            amount_label.add_css_class("amount-display");
            match txn.transaction_type {
                TransactionType::Expense => amount_label.add_css_class("amount-expense"),
                TransactionType::Income => amount_label.add_css_class("amount-income"),
            }
            row.add_suffix(&amount_label);

            if !is_selecting {
                // Delete button (visible on hover) - not in selection mode
                let delete_btn = gtk::Button::from_icon_name("outlay-delete");
                delete_btn.add_css_class("flat");
                delete_btn.add_css_class("circular");
                delete_btn.set_valign(gtk::Align::Center);
                delete_btn.set_visible(false);
                delete_btn.set_tooltip_text(Some("Delete transaction"));
                {
                    let txn_id = txn.id;
                    let db_del = db.clone();
                    let container_del = container.clone();
                    let toast_del = toast_overlay.clone();
                    let year_del = year;
                    let month_del = month;
                    delete_btn.connect_clicked(move |btn| {
                        let alert = adw::AlertDialog::new(
                            Some("Delete this transaction?"),
                            Some("This action cannot be undone."),
                        );
                        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_c = db_del.clone();
                        let list_c = container_del.clone();
                        let toast_c = toast_del.clone();
                        alert.connect_response(None, move |_, response| {
                            if response == "delete" {
                                if db_c.delete_transaction(txn_id).is_ok() {
                                    let toast = adw::Toast::new("Transaction deleted");
                                    toast_c.add_toast(toast);
                                    Self::load_month(&db_c, &list_c, &toast_c, year_del, month_del);
                                }
                            }
                        });
                        alert.present(Some(btn));
                    });
                }
                row.add_suffix(&delete_btn);

                // Show delete button on hover
                {
                    let motion = gtk::EventControllerMotion::new();
                    let del_ref = delete_btn.clone();
                    motion.connect_enter(move |_, _, _| { del_ref.set_visible(true); });
                    let del_ref2 = delete_btn;
                    motion.connect_leave(move |_| { del_ref2.set_visible(false); });
                    row.add_controller(motion);
                }

                // Arrow to indicate clickability
                let arrow = gtk::Image::from_icon_name("outlay-next");
                arrow.add_css_class("dim-label");
                row.add_suffix(&arrow);

                // Connect row activation to edit dialog
                let txn_id = txn.id;
                let db_ref = db.clone();
                let container_ref = container.clone();
                let toast_ref = toast_overlay.clone();
                let year_copy = year;
                let month_copy = month;
                row.connect_activated(move |row| {
                    let db_c = db_ref.clone();
                    let list_c = container_ref.clone();
                    let toast_c = toast_ref.clone();
                    edit_dialog::show_edit_dialog(row, txn_id, &db_ref, &toast_ref, move || {
                        Self::load_month(&db_c, &list_c, &toast_c, year_copy, month_copy);
                    });
                });
            } else {
                // In selection mode: row activation toggles checkbox
                row.connect_activated(move |row| {
                    if let Some(check) = Self::find_check_button(row.upcast_ref::<gtk::Widget>()) {
                        check.set_active(!check.is_active());
                    }
                });
            }

            day_list.append(&row);
        }

        day_group.append(&day_list);

        // Day total
        let net = day_income - day_expense;
        let net_str = if net >= 0.0 {
            format!("Net: +{:.2}", net)
        } else {
            format!("Net: {:.2}", net)
        };

        let total_label = gtk::Label::new(Some(&net_str));
        total_label.set_halign(gtk::Align::End);
        total_label.set_margin_top(4);
        total_label.set_margin_bottom(12);
        total_label.set_margin_end(8);
        total_label.add_css_class("day-net");
        if net >= 0.0 {
            total_label.add_css_class("amount-income");
        } else {
            total_label.add_css_class("amount-expense");
        }
        day_group.append(&total_label);

        let balance_str = format!(
            "Balance: {}{}",
            if running_balance >= 0.0 { "+" } else { "" },
            format!("{:.2}", running_balance)
        );
        let balance_label = gtk::Label::new(Some(&balance_str));
        balance_label.set_halign(gtk::Align::End);
        balance_label.set_margin_end(8);
        balance_label.set_margin_bottom(12);
        balance_label.add_css_class("day-net");
        balance_label.set_opacity(0.75);
        day_group.append(&balance_label);

        container.append(&day_group);
    }

    fn rebuild_chips(
        db: &Rc<Database>,
        chips_box: &gtk::FlowBox,
        active_categories: &Rc<RefCell<HashSet<i64>>>,
        active_tags: &Rc<RefCell<HashSet<String>>>,
        search_term: &Rc<RefCell<String>>,
        list_container: &gtk::Box,
        year: i32,
        month: u32,
        updating: &Rc<Cell<bool>>,
        no_results_page: &adw::StatusPage,
    ) {
        // Clear existing chips
        while let Some(child) = chips_box.first_child() {
            chips_box.remove(&child);
        }

        // Collect unique categories from this month's transactions
        let txns = match db.list_transactions_by_month(year, month) {
            Ok(t) => t,
            Err(_) => return,
        };
        if txns.is_empty() {
            return;
        }

        let mut seen_cats: Vec<(i64, String)> = Vec::new();
        for txn in &txns {
            if !seen_cats.iter().any(|(id, _)| *id == txn.category_id) {
                let name = db.get_category(txn.category_id)
                    .map(|c| c.name)
                    .unwrap_or_else(|_| "Unknown".to_string());
                seen_cats.push((txn.category_id, name));
            }
        }

        // Collect tags present this month
        let month_tags = db.list_tags_for_month(year, month).unwrap_or_default();

        // "All" chip
        let all_btn = gtk::ToggleButton::with_label("All");
        all_btn.set_active(true);
        all_btn.add_css_class("pill");
        {
            let cats_ref = active_categories.clone();
            let tags_ref = active_tags.clone();
            let term_ref = search_term.clone();
            let list_ref = list_container.clone();
            let chips_ref = chips_box.clone();
            let guard = updating.clone();
            let nr_ref = no_results_page.clone();
            all_btn.connect_toggled(move |btn| {
                if guard.get() { return; }
                if btn.is_active() {
                    guard.set(true);
                    cats_ref.borrow_mut().clear();
                    tags_ref.borrow_mut().clear();
                    // Deactivate all other chips (FlowBox wraps in FlowBoxChild)
                    let mut child = chips_ref.first_child();
                    while let Some(w) = child {
                        let next = w.next_sibling();
                        if let Some(tb) = w.first_child().and_then(|c| c.downcast::<gtk::ToggleButton>().ok()) {
                            if tb.label().as_deref() != Some("All") {
                                tb.set_active(false);
                            }
                        }
                        child = next;
                    }
                    guard.set(false);
                    Self::apply_combined_filter(&list_ref, &term_ref.borrow(), &cats_ref.borrow(), &tags_ref.borrow(), &nr_ref);
                }
            });
        }
        chips_box.append(&all_btn);

        // Category chips
        for (cat_id, cat_name) in &seen_cats {
            let btn = gtk::ToggleButton::with_label(cat_name);
            btn.add_css_class("pill");
            let cat_id = *cat_id;
            let cats_ref = active_categories.clone();
            let tags_ref = active_tags.clone();
            let term_ref = search_term.clone();
            let list_ref = list_container.clone();
            let chips_ref = chips_box.clone();
            let guard = updating.clone();
            let nr_ref = no_results_page.clone();
            btn.connect_toggled(move |btn| {
                if guard.get() { return; }
                guard.set(true);
                {
                    let mut cats = cats_ref.borrow_mut();
                    if btn.is_active() {
                        cats.insert(cat_id);
                    } else {
                        cats.remove(&cat_id);
                    }
                }
                if btn.is_active() {
                    // Deactivate "All" chip (FlowBox wraps in FlowBoxChild)
                    if let Some(first_fbc) = chips_ref.first_child() {
                        if let Some(all) = first_fbc.first_child().and_then(|c| c.downcast::<gtk::ToggleButton>().ok()) {
                            all.set_active(false);
                        }
                    }
                } else {
                    // If nothing active, re-activate "All"
                    if cats_ref.borrow().is_empty() && tags_ref.borrow().is_empty() {
                        if let Some(first_fbc) = chips_ref.first_child() {
                            if let Some(all) = first_fbc.first_child().and_then(|c| c.downcast::<gtk::ToggleButton>().ok()) {
                                all.set_active(true);
                            }
                        }
                    }
                }
                guard.set(false);
                Self::apply_combined_filter(&list_ref, &term_ref.borrow(), &cats_ref.borrow(), &tags_ref.borrow(), &nr_ref);
            });
            chips_box.append(&btn);
        }

        // Tag chips (outlined style to distinguish from category chips)
        for tag in &month_tags {
            let label = format!("#{}", tag.name);
            let btn = gtk::ToggleButton::with_label(&label);
            btn.add_css_class("pill");
            btn.add_css_class("outlined");
            let tag_name = tag.name.clone();
            let cats_ref = active_categories.clone();
            let tags_ref = active_tags.clone();
            let term_ref = search_term.clone();
            let list_ref = list_container.clone();
            let chips_ref = chips_box.clone();
            let guard = updating.clone();
            let nr_ref = no_results_page.clone();
            btn.connect_toggled(move |btn| {
                if guard.get() { return; }
                guard.set(true);
                {
                    let mut tags = tags_ref.borrow_mut();
                    if btn.is_active() {
                        tags.insert(tag_name.clone());
                    } else {
                        tags.remove(&tag_name);
                    }
                }
                if btn.is_active() {
                    if let Some(first_fbc) = chips_ref.first_child() {
                        if let Some(all) = first_fbc.first_child().and_then(|c| c.downcast::<gtk::ToggleButton>().ok()) {
                            all.set_active(false);
                        }
                    }
                } else {
                    if cats_ref.borrow().is_empty() && tags_ref.borrow().is_empty() {
                        if let Some(first_fbc) = chips_ref.first_child() {
                            if let Some(all) = first_fbc.first_child().and_then(|c| c.downcast::<gtk::ToggleButton>().ok()) {
                                all.set_active(true);
                            }
                        }
                    }
                }
                guard.set(false);
                Self::apply_combined_filter(&list_ref, &term_ref.borrow(), &cats_ref.borrow(), &tags_ref.borrow(), &nr_ref);
            });
            chips_box.append(&btn);
        }
    }

    fn apply_combined_filter(
        list_container: &gtk::Box,
        search_text: &str,
        active_cats: &HashSet<i64>,
        active_tags: &HashSet<String>,
        no_results_page: &adw::StatusPage,
    ) {
        let no_text_filter = search_text.is_empty();
        let no_cat_filter = active_cats.is_empty();
        let no_tag_filter = active_tags.is_empty();
        let mut any_result_visible = false;

        let mut child = list_container.first_child();
        while let Some(widget) = child {
            let next = widget.next_sibling();
            if widget.widget_name() == "day-group" {
                if no_text_filter && no_cat_filter && no_tag_filter {
                    widget.set_visible(true);
                    any_result_visible = true;
                    if let Some(day_box) = widget.downcast_ref::<gtk::Box>() {
                        let mut inner = day_box.first_child();
                        while let Some(w) = inner {
                            if let Some(listbox) = w.downcast_ref::<gtk::ListBox>() {
                                let mut row_idx = 0;
                                while let Some(row) = listbox.row_at_index(row_idx) {
                                    row.set_visible(true);
                                    row_idx += 1;
                                }
                            }
                            inner = w.next_sibling();
                        }
                    }
                } else {
                    let mut any_visible = false;
                    if let Some(day_box) = widget.downcast_ref::<gtk::Box>() {
                        let mut inner = day_box.first_child();
                        while let Some(w) = inner {
                            if let Some(listbox) = w.downcast_ref::<gtk::ListBox>() {
                                let mut row_idx = 0;
                                while let Some(row) = listbox.row_at_index(row_idx) {
                                    let data = row.widget_name().to_string();
                                    // Parse "catid:txnid:searchtext"
                                    let (cat_id_str, rest) = data.split_once(':').unwrap_or(("", &data));
                                    let cat_id: i64 = cat_id_str.parse().unwrap_or(-1);
                                    let search_part = rest.split_once(':').map_or(rest, |(_, s)| s);

                                    let text_match = no_text_filter || search_part.contains(search_text);
                                    let cat_match = no_cat_filter || active_cats.contains(&cat_id);
                                    let tag_match = no_tag_filter || active_tags.iter().any(|t| {
                                        search_part.contains(&format!("#{}", t).to_lowercase())
                                    });
                                    let visible = text_match && cat_match && tag_match;
                                    row.set_visible(visible);
                                    if visible {
                                        any_visible = true;
                                    }
                                    row_idx += 1;
                                }
                            }
                            inner = w.next_sibling();
                        }
                    }
                    widget.set_visible(any_visible);
                    if any_visible {
                        any_result_visible = true;
                    }
                }
            }
            child = next;
        }

        // Show/hide empty state for filtered results
        let has_filter = !no_text_filter || !no_cat_filter || !no_tag_filter;
        no_results_page.set_visible(has_filter && !any_result_visible);
    }

    fn extract_txn_id_from_name(name: &str) -> Option<i64> {
        // Format: "catid:txnid:searchtext"
        let rest = name.split_once(':')?.1;
        let txn_id_str = rest.split_once(':')?.0;
        txn_id_str.parse().ok()
    }

    fn find_check_button(widget: &gtk::Widget) -> Option<gtk::CheckButton> {
        let mut child = widget.first_child();
        while let Some(w) = child {
            if let Some(check) = w.downcast_ref::<gtk::CheckButton>() {
                return Some(check.clone());
            }
            // Recurse into child containers
            if let Some(found) = Self::find_check_button(&w) {
                return Some(found);
            }
            child = w.next_sibling();
        }
        None
    }

    fn make_summary_card(label_text: &str) -> (gtk::Box, gtk::Label) {
        let card = gtk::Box::new(gtk::Orientation::Vertical, 4);
        card.add_css_class("card");
        card.add_css_class("summary-card");
        card.set_hexpand(true);

        let label = gtk::Label::new(Some(label_text));
        label.add_css_class("summary-card-label");
        label.set_halign(gtk::Align::Start);

        let amount = gtk::Label::new(Some("0.00"));
        amount.add_css_class("summary-card-amount");
        amount.set_halign(gtk::Align::Start);

        card.append(&label);
        card.append(&amount);

        (card, amount)
    }

    fn refresh_summary(
        db: &Database,
        income_label: &gtk::Label,
        expense_label: &gtk::Label,
        net_label: &gtk::Label,
        year: i32,
        month: u32,
    ) {
        let base_currency = db.get_setting("base_currency").ok().flatten().unwrap_or_else(|| "USD".to_string());
        let income = db.get_monthly_total(year, month, TransactionType::Income).unwrap_or(0.0);
        let expense = db.get_monthly_total(year, month, TransactionType::Expense).unwrap_or(0.0);
        let net = income - expense;

        income_label.set_label(&format!("+{:.2} {}", income, base_currency));
        income_label.remove_css_class("amount-income");
        income_label.remove_css_class("amount-expense");
        income_label.add_css_class("amount-income");

        expense_label.set_label(&format!("-{:.2} {}", expense, base_currency));
        expense_label.remove_css_class("amount-income");
        expense_label.remove_css_class("amount-expense");
        expense_label.add_css_class("amount-expense");

        if net >= 0.0 {
            net_label.set_label(&format!("+{:.2} {}", net, base_currency));
            net_label.remove_css_class("amount-expense");
            net_label.add_css_class("amount-income");
        } else {
            net_label.set_label(&format!("{:.2} {}", net, base_currency));
            net_label.remove_css_class("amount-income");
            net_label.add_css_class("amount-expense");
        }
    }
}
