use gtk::prelude::*;
use gtk::{gdk, gio, glib};
use std::cell::{Cell, RefCell};
use std::path::PathBuf;
use std::rc::Rc;
use vte::prelude::*;

use crate::cli::{Cli, CliAction};
use crate::config::{AppSettings, config_path_for_write, run_config_command};
use crate::terminal::{LaunchConfig, TerminalPane};
use crate::theme::{THEME_NAMES, TerminalTheme};

const APP_ID: &str = "dev.lios.Terminal";

pub fn run() -> glib::ExitCode {
    let cli = match Cli::parse() {
        Ok(cli) => cli,
        Err(message) => {
            eprintln!("{message}");
            return glib::ExitCode::FAILURE;
        }
    };

    match cli.action {
        CliAction::ShowHelp => {
            print!("{}", Cli::help_text());
            glib::ExitCode::SUCCESS
        }
        CliAction::ShowVersion => {
            println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
            glib::ExitCode::SUCCESS
        }
        CliAction::Config(command) => match run_config_command(command) {
            Ok(output) => {
                print!("{output}");
                glib::ExitCode::SUCCESS
            }
            Err(message) => {
                eprintln!("{message}");
                glib::ExitCode::FAILURE
            }
        },
        CliAction::Run {
            launch,
            config_path,
            overrides,
        } => match AppSettings::load(config_path.clone(), *overrides) {
            Ok(settings) => {
                run_application(launch, settings, config_path_for_write(config_path).ok())
            }
            Err(message) => {
                eprintln!("{message}");
                glib::ExitCode::FAILURE
            }
        },
    }
}

fn run_application(
    launch: LaunchConfig,
    settings: AppSettings,
    config_path: Option<PathBuf>,
) -> glib::ExitCode {
    let application = gtk::Application::builder()
        .application_id(APP_ID)
        .flags(gio::ApplicationFlags::NON_UNIQUE)
        .build();

    application.connect_activate(move |app| {
        build_window(app, launch.clone(), settings.clone(), config_path.clone())
    });
    application.run_with_args(&[env!("CARGO_PKG_NAME")])
}

fn build_window(
    app: &gtk::Application,
    launch: LaunchConfig,
    settings: AppSettings,
    config_path: Option<PathBuf>,
) {
    let window = gtk::ApplicationWindow::builder()
        .application(app)
        .title(&settings.window.title)
        .default_width(settings.window.default_width)
        .default_height(settings.window.default_height)
        .decorated(settings.window.decorated)
        .build();

    let state = Rc::new(RefCell::new(settings));
    let pane = Rc::new(TerminalPane::new(&state.borrow().terminal));
    let terminal = pane.terminal().clone();

    install_window_actions(&window, &terminal);
    install_keyboard_shortcuts(&window, &terminal);
    install_topbar(&window, pane.clone(), state.clone(), config_path);
    keep_window_title_in_sync(&window, &terminal, state.borrow().window.title.clone());
    close_window_when_shell_exits(&window, &terminal);

    window.set_child(Some(pane.widget()));
    window.present();

    pane.spawn(launch);
    pane.focus();
}

fn install_topbar(
    window: &gtk::ApplicationWindow,
    pane: Rc<TerminalPane>,
    settings: Rc<RefCell<AppSettings>>,
    config_path: Option<PathBuf>,
) {
    if !settings.borrow().window.decorated {
        return;
    }

    let header = gtk::HeaderBar::new();
    header.set_show_title_buttons(true);

    let title = gtk::Label::new(Some("Lios"));
    title.add_css_class("heading");
    header.set_title_widget(Some(&title));

    let preferences = gtk::Button::with_label("Preferences");
    let window_for_prefs = window.clone();
    preferences.connect_clicked(move |_| {
        show_preferences_window(
            &window_for_prefs,
            pane.clone(),
            settings.clone(),
            config_path.clone(),
        );
    });
    header.pack_end(&preferences);

    window.set_titlebar(Some(&header));
}

#[allow(deprecated)]
fn show_preferences_window(
    parent: &gtk::ApplicationWindow,
    pane: Rc<TerminalPane>,
    settings: Rc<RefCell<AppSettings>>,
    config_path: Option<PathBuf>,
) {
    let snapshot = settings.borrow().clone();
    let dialog = gtk::Window::builder()
        .title("Lios Preferences")
        .transient_for(parent)
        .default_width(460)
        .default_height(420)
        .build();

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

    let theme_combo = gtk::ComboBoxText::new();
    for theme in THEME_NAMES {
        theme_combo.append(Some(theme), theme);
    }
    theme_combo.set_active_id(Some(&snapshot.terminal.theme_name));
    content.append(&preference_row("Theme", &theme_combo));

    let image_entry = gtk::Entry::new();
    image_entry.set_placeholder_text(Some("/path/to/background.jpg"));
    if let Some(image) = &snapshot.terminal.background.image {
        image_entry.set_text(&image.to_string_lossy());
    }
    content.append(&preference_row("Background image", &image_entry));

    let image_opacity = opacity_spin(snapshot.terminal.background.image_opacity);
    content.append(&preference_row("Image opacity", &image_opacity));

    let terminal_opacity = opacity_spin(snapshot.terminal.background.terminal_opacity);
    content.append(&preference_row("Terminal opacity", &terminal_opacity));

    let overlay_entry = gtk::Entry::new();
    overlay_entry.set_placeholder_text(Some("#7c3aed"));
    if let Some(color) = &snapshot.terminal.background.overlay_color {
        overlay_entry.set_text(&color_to_hex(color));
    }
    content.append(&preference_row("Overlay color", &overlay_entry));

    let overlay_opacity = opacity_spin(snapshot.terminal.background.overlay_opacity);
    content.append(&preference_row("Overlay opacity", &overlay_opacity));

    let random_overlay = gtk::Switch::new();
    random_overlay.set_active(snapshot.terminal.background.random_overlay);
    content.append(&preference_row("Random overlay", &random_overlay));

    let decorated = gtk::Switch::new();
    decorated.set_active(snapshot.window.decorated);
    content.append(&preference_row("Show topbar", &decorated));

    let status = gtk::Label::new(None);
    status.set_wrap(true);
    status.set_xalign(0.0);
    content.append(&status);

    let apply = gtk::Button::with_label("Apply and Save");
    let parent_for_apply = parent.clone();
    apply.connect_clicked(move |_| {
        let mut next = settings.borrow().clone();
        let theme_name = theme_combo
            .active_id()
            .map(|value| value.to_string())
            .unwrap_or_else(|| next.terminal.theme_name.clone());

        let theme = match TerminalTheme::named(&theme_name) {
            Ok(theme) => theme,
            Err(message) => {
                status.set_text(&message);
                return;
            }
        };

        let overlay_text = overlay_entry.text().trim().to_string();
        let overlay_color = if overlay_text.is_empty() {
            None
        } else {
            match TerminalTheme::parse_color(&overlay_text) {
                Ok(color) => Some(color),
                Err(message) => {
                    status.set_text(&message);
                    return;
                }
            }
        };

        let image_text = image_entry.text().trim().to_string();
        next.terminal.theme_name = theme_name;
        next.terminal.theme = theme;
        next.terminal.background.image = if image_text.is_empty() {
            None
        } else {
            Some(PathBuf::from(image_text))
        };
        next.terminal.background.image_opacity = image_opacity.value();
        next.terminal.background.terminal_opacity = terminal_opacity.value();
        next.terminal.background.overlay_color = overlay_color;
        next.terminal.background.overlay_opacity = overlay_opacity.value();
        next.terminal.background.random_overlay = random_overlay.is_active();
        next.window.decorated = decorated.is_active();

        pane.apply_config(&next.terminal);
        parent_for_apply.set_decorated(next.window.decorated);
        if !next.window.decorated {
            parent_for_apply.set_titlebar(None::<&gtk::Widget>);
        }

        if let Some(path) = &config_path {
            if let Err(message) = next.persist(path) {
                status.set_text(&message);
                return;
            }
            status.set_text(&format!("Saved {}", path.display()));
        } else {
            status.set_text(
                "Applied for this session. Set HOME or XDG_CONFIG_HOME to save preferences.",
            );
        }

        *settings.borrow_mut() = next;
    });
    content.append(&apply);

    dialog.set_child(Some(&content));
    dialog.present();
}

fn preference_row(label: &str, control: &impl IsA<gtk::Widget>) -> gtk::Box {
    let row = gtk::Box::new(gtk::Orientation::Horizontal, 12);
    let row_label = gtk::Label::new(Some(label));
    row_label.set_width_chars(18);
    row_label.set_xalign(0.0);
    control.as_ref().set_hexpand(true);
    row.append(&row_label);
    row.append(control);
    row
}

fn opacity_spin(value: f64) -> gtk::SpinButton {
    let spin = gtk::SpinButton::with_range(0.0, 1.0, 0.05);
    spin.set_digits(2);
    spin.set_value(value.clamp(0.0, 1.0));
    spin
}

fn color_to_hex(color: &gdk::RGBA) -> String {
    let red = (color.red().clamp(0.0, 1.0) * 255.0).round() as u8;
    let green = (color.green().clamp(0.0, 1.0) * 255.0).round() as u8;
    let blue = (color.blue().clamp(0.0, 1.0) * 255.0).round() as u8;
    format!("#{red:02x}{green:02x}{blue:02x}")
}

fn install_window_actions(window: &gtk::ApplicationWindow, terminal: &vte::Terminal) {
    let menu = gio::Menu::new();
    menu.append(Some("Copy"), Some("win.copy"));
    menu.append(Some("Copy as HTML"), Some("win.copy-html"));
    menu.append(Some("Paste"), Some("win.paste"));
    menu.append(Some("Select All"), Some("win.select-all"));

    let copy = gio::SimpleAction::new("copy", None);
    let terminal_for_copy = terminal.clone();
    copy.connect_activate(move |_, _| {
        terminal_for_copy.copy_clipboard_format(vte::Format::Text);
    });
    window.add_action(&copy);

    let copy_html = gio::SimpleAction::new("copy-html", None);
    let terminal_for_copy_html = terminal.clone();
    copy_html.connect_activate(move |_, _| {
        terminal_for_copy_html.copy_clipboard_format(vte::Format::Html);
    });
    window.add_action(&copy_html);

    let paste = gio::SimpleAction::new("paste", None);
    let terminal_for_paste = terminal.clone();
    paste.connect_activate(move |_, _| {
        terminal_for_paste.paste_clipboard();
    });
    window.add_action(&paste);

    let select_all = gio::SimpleAction::new("select-all", None);
    let terminal_for_select_all = terminal.clone();
    select_all.connect_activate(move |_, _| {
        terminal_for_select_all.select_all();
    });
    window.add_action(&select_all);

    terminal.set_context_menu_model(Some(&menu));
}

fn install_keyboard_shortcuts(window: &gtk::ApplicationWindow, terminal: &vte::Terminal) {
    let zoom = Rc::new(Cell::new(1.0));
    let controller = gtk::EventControllerKey::new();
    controller.set_propagation_phase(gtk::PropagationPhase::Capture);

    let terminal_for_keys = terminal.clone();
    let zoom_for_keys = zoom.clone();
    controller.connect_key_pressed(move |_, key, _, state| {
        let Some(character) = key.to_unicode().map(|ch| ch.to_ascii_lowercase()) else {
            return glib::Propagation::Proceed;
        };

        let has_control = state.contains(gdk::ModifierType::CONTROL_MASK);
        let has_shift = state.contains(gdk::ModifierType::SHIFT_MASK);

        if has_control && has_shift {
            match character {
                'c' => {
                    terminal_for_keys.copy_clipboard_format(vte::Format::Text);
                    return glib::Propagation::Stop;
                }
                'v' => {
                    terminal_for_keys.paste_clipboard();
                    return glib::Propagation::Stop;
                }
                'a' => {
                    terminal_for_keys.select_all();
                    return glib::Propagation::Stop;
                }
                _ => {}
            }
        }

        if has_control {
            match character {
                '+' | '=' => {
                    update_zoom(&terminal_for_keys, &zoom_for_keys, 1.1);
                    return glib::Propagation::Stop;
                }
                '-' => {
                    update_zoom(&terminal_for_keys, &zoom_for_keys, 1.0 / 1.1);
                    return glib::Propagation::Stop;
                }
                '0' => {
                    zoom_for_keys.set(1.0);
                    terminal_for_keys.set_font_scale(1.0);
                    return glib::Propagation::Stop;
                }
                _ => {}
            }
        }

        glib::Propagation::Proceed
    });

    window.add_controller(controller);
}

fn update_zoom(terminal: &vte::Terminal, zoom: &Cell<f64>, multiplier: f64) {
    let next = (zoom.get() * multiplier).clamp(0.5, 2.5);
    zoom.set(next);
    terminal.set_font_scale(next);
}

fn keep_window_title_in_sync(
    window: &gtk::ApplicationWindow,
    terminal: &vte::Terminal,
    fallback_title: String,
) {
    let window_for_title = window.clone();
    terminal.connect_window_title_changed(move |terminal| {
        let title = terminal_window_title(terminal)
            .filter(|title| !title.trim().is_empty())
            .unwrap_or_else(|| fallback_title.clone());
        window_for_title.set_title(Some(&title));
    });
}

#[allow(deprecated)]
fn terminal_window_title(terminal: &vte::Terminal) -> Option<String> {
    terminal.window_title().map(|title| title.to_string())
}

fn close_window_when_shell_exits(window: &gtk::ApplicationWindow, terminal: &vte::Terminal) {
    let window_for_exit = window.clone();
    terminal.connect_child_exited(move |_, _| {
        window_for_exit.close();
    });
}
