// SPDX-License-Identifier: GPL-3.0-or-later

use gtk::glib::Cast;
use gtk::{
    ffi::GTK_STYLE_PROVIDER_PRIORITY_APPLICATION,
    traits::{BoxExt, OrientableExt, StyleContextExt, WidgetExt},
    CssProvider,
};

use crate::styles::{Stylable, Style};

impl Stylable<gtk::Widget> for Style {
    fn apply(&self, widget: &gtk::Widget) {
        match self {
            Style::none(_) => {}
            Style::rounded_full(_) => {
                let css = CssProvider::new();
                css.load_from_data("* { border-radius: 100%;}");
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::bg(modi, color) => {
                let css = CssProvider::new();
                let pseudoclass = match modi {
                    crate::styles::Modifier::none => "",
                    crate::styles::Modifier::active => ":active",
                    crate::styles::Modifier::hover => ":hover",
                };
                css.load_from_data(&format!("*{pseudoclass} {{ background-color: {};}}", color));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::text(_modi, color) => {
                let css = CssProvider::new();
                css.load_from_data(&format!("* {{ color: {};}}", color));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::m(_modi, margin) => {
                widget.set_margin_bottom(*margin as i32);
                widget.set_margin_top(*margin as i32);
                widget.set_margin_start(*margin as i32);
                widget.set_margin_end(*margin as i32);
            }
            Style::mx(_modi, margin) => {
                widget.set_margin_start(*margin as i32);
                widget.set_margin_end(*margin as i32);
            }
            Style::my(_modi, margin) => {
                widget.set_margin_start(*margin as i32);
                widget.set_margin_end(*margin as i32);
            }
            Style::mt(_modi, margin) => {
                widget.set_margin_top(*margin as i32);
            }
            Style::mb(_modi, margin) => {
                widget.set_margin_bottom(*margin as i32);
            }
            Style::ml(_modi, margin) => {
                widget.set_margin_start(*margin as i32);
            }
            Style::mr(_modi, margin) => {
                widget.set_margin_end(*margin as i32);
            }
            Style::p(_modi, padding) => {
                let css = CssProvider::new();
                css.load_from_data(&format!("* {{ padding: {}px;}}", padding));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::px(_modi, padding) => {
                let css = CssProvider::new();
                css.load_from_data(&format!(
                    "* {{ padding-left: {0}px; padding-right: {0}px;}}",
                    padding
                ));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::py(_modi, padding) => {
                let css = CssProvider::new();
                css.load_from_data(&format!(
                    "* {{ padding-top: {0}px; padding-bottom: {0}px;}}",
                    padding
                ));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::pt(_modi, padding) => {
                let css = CssProvider::new();
                css.load_from_data(&format!("* {{ padding-top: {}px;}}", padding));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::pb(_modi, padding) => {
                let css = CssProvider::new();
                css.load_from_data(&format!("* {{ padding-bottom: {}px;}}", padding));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::pl(_modi, padding) => {
                let css = CssProvider::new();
                css.load_from_data(&format!("* {{ padding-left: {}px;}}", padding));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::pr(_modi, padding) => {
                let css = CssProvider::new();
                css.load_from_data(&format!("* {{ padding-right: {}px;}}", padding));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }

            Style::target(_, can_target) => {
                widget.set_can_target(*can_target);
            }

            Style::vertical(_modi) => {
                if let Some(bo) = widget.dynamic_cast_ref::<gtk::Orientable>() {
                    bo.set_orientation(gtk::Orientation::Vertical)
                }
            }
            Style::horizontal(_modi) => {
                if let Some(bo) = widget.dynamic_cast_ref::<gtk::Orientable>() {
                    bo.set_orientation(gtk::Orientation::Horizontal)
                }
            }
            Style::spacing(_modi, gap) => {
                if let Some(bo) = widget.dynamic_cast_ref::<gtk::Box>() {
                    bo.set_spacing(*gap as i32);
                }
            }
            Style::h(_modi, height) => {
                widget.set_height_request(*height as i32);
                if let Some(im) = widget.dynamic_cast_ref::<gtk::Image>() {
                    im.set_pixel_size(*height as i32);
                }
            }
            Style::w(_modi, width) => {
                widget.set_width_request(*width as i32);
            }
            Style::vexpand(_) => widget.set_vexpand(true),
            Style::hexpand(_) => widget.set_hexpand(true),
            Style::homogeneous(_) => {
                if let Some(bo) = widget.dynamic_cast_ref::<gtk::Box>() {
                    bo.set_homogeneous(true);
                }
            }
            Style::halign(_, align) => match align {
                crate::styles::Align::none => {
                    // ignore
                }
                crate::styles::Align::start => widget.set_halign(gtk::Align::Start),
                crate::styles::Align::center => widget.set_halign(gtk::Align::Center),
                crate::styles::Align::end => widget.set_halign(gtk::Align::End),
            },
            Style::valign(_, align) => match align {
                crate::styles::Align::none => {
                    // ignore
                }
                crate::styles::Align::start => widget.set_valign(gtk::Align::Start),
                crate::styles::Align::center => widget.set_valign(gtk::Align::Center),
                crate::styles::Align::end => widget.set_valign(gtk::Align::End),
            },

            Style::border_color(_, color) => {
                let css = CssProvider::new();
                css.load_from_data(&format!("* {{ border-color: {color}; }}"));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::border(_, b1, b2, b3, b4) => {
                let css = CssProvider::new();
                css.load_from_data(&format!(
                    "* {{ border-width: {b1}px {b2}px {b3}px {b4}px; }}"
                ));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::border_y(_, b) => {
                let css = CssProvider::new();
                css.load_from_data(&format!("* {{ border-width: {0}px 0px {0}px 0px; }}", b));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::border_x(_, b) => {
                let css = CssProvider::new();
                css.load_from_data(&format!("* {{ border-width: 0px {0}px 0px {0}px; }}", b));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::border_r(_, b) => {
                let css = CssProvider::new();
                css.load_from_data(&format!("* {{ border-width: 0px {0}px 0px 0px; }}", b));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::border_l(_, b) => {
                let css = CssProvider::new();
                css.load_from_data(&format!("* {{ border-width: 0px 0px {0}px 0px; }}", b));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::border_t(_, b) => {
                let css = CssProvider::new();
                css.load_from_data(&format!("* {{ border-width: {0}px 0px 0px 0px; }}", b));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::border_b(_, b) => {
                let css = CssProvider::new();
                css.load_from_data(&format!("* {{ border-width: 0px 0px 0px {0}px; }}", b));
                widget
                    .style_context()
                    .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
            }
            Style::overflow(_, orientation) => {
                if let Some(scroll) = widget.downcast_ref::<gtk::ScrolledWindow>() {
                    match orientation {
                        crate::styles::Overflow::horizontal => {
                            scroll.set_vscrollbar_policy(gtk::PolicyType::Never);
                        }
                        crate::styles::Overflow::vertical => {
                            scroll.set_hscrollbar_policy(gtk::PolicyType::Never);
                        }
                        crate::styles::Overflow::both => {
                            scroll.set_hscrollbar_policy(gtk::PolicyType::Automatic);
                            scroll.set_vscrollbar_policy(gtk::PolicyType::Automatic);
                        }
                        crate::styles::Overflow::never => {
                            scroll.set_hscrollbar_policy(gtk::PolicyType::Never);
                            scroll.set_vscrollbar_policy(gtk::PolicyType::Never);
                        }
                        crate::styles::Overflow::external => {
                            scroll.set_hscrollbar_policy(gtk::PolicyType::External);
                            scroll.set_vscrollbar_policy(gtk::PolicyType::External);
                        }
                    }
                }
            }
            Style::text_size(_, size) => match size.as_str() {
                "xs" => {
                    let css = CssProvider::new();
                    css.load_from_data("* { font-size: 0.8rem; }");
                    widget
                        .style_context()
                        .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
                }
                "l" => widget.add_css_class("title-3"),
                "xl" => widget.add_css_class("title-2"),
                "xxl" => widget.add_css_class("title-1"),
                _ => {
                    let css = CssProvider::new();
                    css.load_from_data(&format!("* {{ font-size: {0}; }}", size));
                    widget
                        .style_context()
                        .add_provider(&css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32);
                }
            },
            Style::wrap(_, wrap) => {
                if let Some(text) = widget.downcast_ref::<gtk::Label>() {
                    match wrap {
                        crate::styles::Wrap::char => {
                            text.set_wrap(true);
                            text.set_wrap_mode(gtk::gdk::pango::WrapMode::Char);
                        }
                        crate::styles::Wrap::word => {
                            text.set_wrap(true);
                            text.set_wrap_mode(gtk::gdk::pango::WrapMode::Word);
                        }
                        crate::styles::Wrap::word_char => {
                            text.set_wrap(true);
                            text.set_wrap_mode(gtk::gdk::pango::WrapMode::WordChar);
                        }
                    }
                }
            }
            Style::ellipsize(_, a) => {
                if let Some(text) = widget.downcast_ref::<gtk::Label>() {
                    match a {
                        crate::styles::Align::none => {
                            text.set_ellipsize(gtk::gdk::pango::EllipsizeMode::None);
                        }
                        crate::styles::Align::start => {
                            text.set_ellipsize(gtk::gdk::pango::EllipsizeMode::Start);
                        }
                        crate::styles::Align::center => {
                            text.set_ellipsize(gtk::gdk::pango::EllipsizeMode::Middle);
                        }
                        crate::styles::Align::end => {
                            text.set_ellipsize(gtk::gdk::pango::EllipsizeMode::End);
                        }
                    }
                }
            }
            Style::justify(_, justify) => {
                if let Some(text) = widget.downcast_ref::<gtk::Label>() {
                    match justify {
                        crate::styles::Justify::left => text.set_justify(gtk::Justification::Left),
                        crate::styles::Justify::right => {
                            text.set_justify(gtk::Justification::Right)
                        }
                        crate::styles::Justify::middle => {
                            text.set_justify(gtk::Justification::Center)
                        }
                        crate::styles::Justify::fill => text.set_justify(gtk::Justification::Fill),
                    }
                }
            }
            Style::selectable(_) => {
                if let Some(text) = widget.downcast_ref::<gtk::Label>() {
                    text.set_selectable(true);
                }
            }
        }
    }
}
