//   Bobby, browse SQLite files
//   Copyright (C) 2025  Hylke Bons (hello@planetpeanut.studio)
//
//   This program is free software: you can redistribute it and/or modify it
//   under the terms of the GNU General Public License v3 or any later version.


use std::cell::Ref;
use std::error::Error;

use gio::{
    Menu,
    Settings,
};

use gtk4::prelude::*;
use gtk4::{
    gdk::BUTTON_SECONDARY,
    gdk::Rectangle,
    glib::BoxedAnyObject,
    ColumnView,
    ColumnViewColumn,
    GestureClick,
    Label,
    ListTabBehavior,
    PopoverMenu,
    PickFlags,
    ScrolledWindow,
    SignalListItemFactory,
    SingleSelection,
};

use crate::bobby::prelude::*;
use crate::bobby::sqlite::cache::DatabaseCacheModel;

use super::item::{
    bind_index_list_item,
    bind_list_item,
    setup_index_list_item,
    setup_list_item,
    SYMBOL_KEY,
};


pub fn content_new(
    database: &Database,
    table: &Table,
) -> Result<ScrolledWindow, Box<dyn Error>>
{
    let settings = Settings::new("studio.planetpeanut.Bobby"); // TODO
    let monospace_font: bool = settings.get("monospace-font");

    let model = DatabaseCacheModel::from_database(database, table);
    let selection = SingleSelection::new(Some(model));

    let column_view = ColumnView::builder()
        .enable_rubberband(false)
        .focusable(true)
        .has_tooltip(true)
        .model(&selection)
        .reorderable(false)
        .show_column_separators(true)
        .show_row_separators(true)
        .single_click_activate(true)
        .tab_behavior(ListTabBehavior::Cell)
        .build();

    let row_count = database.row_count(table)?;
    let columns = database.columns(table)?;

    let mut columns = columns.clone();
    columns.insert(0, Column::default()); // Reserve for row numbers

    for (column_index, column) in columns.iter().enumerate() {
        let is_index_column = column_index == 0;
        let is_last_column = column_index == columns.len() - 1;

        let factory = SignalListItemFactory::new();

        unsafe {
            factory.set_data("column", column_index);
        }

        if is_index_column {
            factory.connect_setup(move |_factory, obj| {
                if let Err(e) = setup_index_list_item(obj) {
                    eprintln!("Failed to set up index list item: {e}");
                }
            });

            factory.connect_bind(move |_factory, obj| {
                if let Err(e) = bind_index_list_item(obj, row_count) {
                    eprintln!("Failed to bind index list item: {e}");
                }
            });
        } else {
            let primary_key = column.primary_key;

            factory.connect_setup(move |_factory, obj| {
                if let Err(e) = setup_list_item(obj, monospace_font) {
                    eprintln!("Failed to set up list item: {e}");
                }
            });

            factory.connect_bind(move |_factory, obj| {
                if let Err(e) = bind_list_item(obj, column_index, primary_key) {
                    eprintln!("Failed to bind index list item: {e}");
                }
            });
        }

        let view_column = ColumnViewColumn::builder()
            .title(&column.name)
            .id(column_index.to_string())
            .factory(&factory)
            .resizable(true)
            .expand(is_last_column)
            .build();

        if column.primary_key {
            let title = format!("{} {}", &column.name, SYMBOL_KEY);
            view_column.set_title(Some(&title));
        }

        if is_index_column {
            let margin = super::item::MARGIN;

            let label = Label::new(None); // Find out rendering char width
            label.add_css_class("monospace");
            let layout = label.create_pango_layout(Some("0"));
            let (_, char) = layout.pixel_extents();

            let width =
                margin +
                ((row_count.to_string().len() as i32 + 2) * char.width()) +
                margin;

            view_column.set_fixed_width(width);
            view_column.set_resizable(false);

            view_column.set_visible(
                settings.boolean("row-numbers")
            );

            // TODO: File GTK rendering bug
            // settings.bind(
            //     "row-numbers",
            //     &view_column,
            //     "visible"
            // ).build();
        } else {
            view_column.set_fixed_width(
                match column.affinity {
                    Affinity::BLOB(_, _) => { view_column.set_resizable(false); 128 },
                    Affinity::TEXT(_) => 192,
                    _ => 128,
                }
            );
        }

        column_view.append_column(&view_column);
    }


    let click = GestureClick::builder()
        .button(BUTTON_SECONDARY)
        .build();

    let column_view_handle = column_view.clone();

    click.connect_pressed(move |gesture, _n_presses, x, y| {
        if let Err(e) = content_clicked(gesture, x, y, &column_view_handle) {
            eprintln!("Failed to open context menu: {e}");
        }
    });


    column_view.add_controller(click);
    column_view.grab_focus();

    let scrolled_window = ScrolledWindow::new();
    scrolled_window.set_child(Some(&column_view));
    scrolled_window.set_widget_name("content");
    scrolled_window.set_vexpand(true);
    // scrolled_window.set_sensitive(false); // TODO: Disable when file changed

    Ok(scrolled_window)
}


fn content_clicked(
    gesture: &GestureClick,
    x: f64,
    y: f64,
    column_view: &ColumnView,
) -> Result<(), Box<dyn Error>>
{
    let model = column_view
        .model()
        .ok_or("Missing model on ColumnView")?;

    let single_selection = model
        .downcast::<SingleSelection>()
        .map_err(|_| "Model is not a gtk4::SingleSelection")?;

    let picked = column_view
        .pick(x, y, PickFlags::NON_TARGETABLE)
        .ok_or("Could not pick Widget")?;

    // TODO: Also handle label parent if internal margin clicked
    let label = picked
        .downcast::<Label>()
        .map_err(|_| "Widget is not a gtk4::Label")?;

    let row = single_selection.selected() as usize;
    let col = label.widget_name().parse::<usize>()?;

    if let Some(col) = col.checked_sub(1) {
        context_menu_open(gesture, col, row, x, y);
    }

    Ok(())
}


fn context_menu_open(gesture: &GestureClick, col_index: usize, row_index: usize, x: f64, y: f64) {
    if let Some(widget) = gesture.widget() {
        let menu = Menu::new();

        menu.append(
            Some("Copy"),
            Some(&format!("win.copy-val::{}:{}", row_index, col_index)),
        );

        menu.append(
            Some("Copy Row"),
            Some("win.copy-row")
        );

        // TODO: Also prepend column headers in Markdown mode
        // menu.append(
        //     Some("Copy Rows"),
        //     Some(&format!("win.copy-rows::{}", row_index))
        // );

        let popover = PopoverMenu::builder()
            .has_arrow(false)
            .menu_model(&menu)
            .pointing_to(&Rectangle::new(x as i32, y as i32, 0, 0))
            .build();

        // TODO: GTK warning when switching tables after opening context menu
        popover.set_parent(&widget);
        popover.popup();
    }
}


pub fn get_row(column_view: ColumnView, position: usize) -> Option<Row> {
    let model = column_view.model()?;
    let selection = model.downcast_ref::<SingleSelection>()?;

    let item = selection
        .item(position as u32)
        .and_then(|o| o.downcast::<BoxedAnyObject>().ok())?;

    let row: Ref<Row> = item.borrow();
    let row = row.clone();

    Some(row)
}


/// HACK: Force a redraw of all columns to prevent separator glitch
pub fn content_force_redraw(column_view: &ColumnView) {
    let columns_model = column_view.columns();
    let num_cols = columns_model.n_items();
    let mut cols: Vec<gtk4::ColumnViewColumn> = Vec::new();

    for i in 0..num_cols {
        if let Some(obj) = columns_model.item(i) {
            if let Some(c) = obj.downcast_ref::<ColumnViewColumn>() {
                cols.push(c.clone());
            }
        }
    }

    for c in &cols {
        c.set_visible(false);
    }

    // column_view.queue_resize();

    for c in &cols {
        c.set_visible(true);
    }
}
