use anyhow::Result;
use gtk::gdk;
use libxml::parser::Parser;
use rand::prelude::IteratorRandom;

use crate::{config, project::ProjectType};

fn validate_icon_name(icon_name: &str) -> bool {
    !icon_name.ends_with("-symbolic")
}

pub fn validate_project_name(project_name: &str, project_type: ProjectType) -> bool {
    match project_type {
        ProjectType::Symbolic => validate_icon_name(project_name),
        ProjectType::IconsSet => true,
    }
}

pub fn random_symbolics(total: usize) -> Vec<String> {
    let theme = gtk::IconTheme::default();
    let mut rng = &mut rand::thread_rng();

    let app_icon = format!("{}-symbolic", config::APP_ID);
    let icons_blacklist = vec![
        "gesture-rotate-clockwise-symbolic",
        "gesture-pinch-symbolic",
        "gesture-two-finger-swipe-left-symbolic",
        "gesture-two-finger-swipe-right-symbolic",
        "gesture-stretch-symbolic",
        "gesture-rotate-anticlockwise-symbolic",
        &app_icon,
    ];

    theme
        .icon_names()
        .iter()
        .filter(|icon| icon.ends_with("-symbolic") && !icons_blacklist.contains(&icon.as_str()))
        .map(|icon| icon.as_str().to_string())
        .choose_multiple(&mut rng, total)
        .into_iter()
        .collect::<Vec<String>>()
}

pub fn clean_svg(input: &str) -> Result<Vec<u8>> {
    let cleaning_options = svgcleaner::CleaningOptions {
        remove_unused_defs: true,
        convert_shapes: true,
        remove_title: true,
        remove_desc: true,
        remove_metadata: true,
        remove_dupl_linear_gradients: true,
        remove_dupl_radial_gradients: true,
        remove_dupl_fe_gaussian_blur: true,
        ungroup_groups: true,
        ungroup_defs: true,
        group_by_style: true,
        merge_gradients: true,
        regroup_gradient_stops: true,
        remove_invalid_stops: true,
        remove_invisible_elements: true,
        resolve_use: true,
        remove_version: true,
        remove_unreferenced_ids: true,
        trim_ids: true,
        remove_text_attributes: true,
        remove_unused_coordinates: true,
        remove_default_attributes: true,
        remove_xmlns_xlink_attribute: true,
        remove_needless_attributes: true,
        remove_gradient_attributes: true,
        join_style_attributes: svgcleaner::StyleJoinMode::None,
        apply_transform_to_gradients: true,
        apply_transform_to_shapes: true,

        paths_to_relative: true,
        remove_unused_segments: true,
        convert_segments: true,
        apply_transform_to_paths: true,

        coordinates_precision: 6,
        properties_precision: 6,
        paths_coordinates_precision: 8,
        transforms_precision: 8,
    };
    let mut document = svgcleaner::cleaner::parse_data(
        input,
        &svgcleaner::ParseOptions {
            skip_unresolved_classes: false,
            ..Default::default()
        },
    )
    .unwrap();
    let _ = svgcleaner::cleaner::clean_doc(
        &mut document,
        &cleaning_options,
        &svgcleaner::WriteOptions::default(),
    );
    let mut buf = vec![];
    svgcleaner::cleaner::write_buffer(&document, &svgcleaner::WriteOptions::default(), &mut buf);
    Ok(buf)
}

fn round_rgba(rgba: gdk::RGBA) -> (f32, f32, f32) {
    (
        (rgba.red() * 100.0).round(),
        (rgba.green() * 100.0).round(),
        (rgba.blue() * 100.0).round(),
    )
}

fn remove_fill(node: &mut libxml::tree::Node) -> Option<(f32, f32, f32)> {
    let style_attr = node.get_attribute("style").unwrap_or_default();
    let mut fill_value = None;
    if style_attr.contains("fill:") {
        let attributes = style_attr.split(';');
        let mut new_style_attr = String::default();
        for attr in attributes {
            if attr.is_empty() {
                continue;
            }
            if let Some((key, value)) = attr.split_once(':') {
                if key == "fill" && value != "none" {
                    let rgba = gdk::RGBA::parse(value).unwrap();
                    fill_value = Some(round_rgba(rgba));
                } else {
                    new_style_attr.push_str(&format!("{key}:{value};"));
                }
            }
        }
        node.set_attribute("style", &new_style_attr).unwrap();
    }
    fill_value
}

pub fn replace_classes_with_fill(xml_input: &str) -> anyhow::Result<String> {
    let parser = Parser::default();
    let document = parser.parse_string(xml_input).expect("can't parse element");
    let classes = [
        ("success", "#33d17a"),
        ("warning", "#ff7800"),
        ("error", "#e01b24"),
    ];
    if let Some(root) = document.get_root_element() {
        let nodes = root
            .findnodes("//*[@class]")
            .expect("couldn't find any style attrs");
        for mut node in nodes {
            let mut new_style_attr = String::default();
            let class_name = node.get_attribute("class").unwrap_or_default();
            let style_attrs = node.get_attribute("style").unwrap_or_default();
            for attr in style_attrs.split(';') {
                if let Some((key, value)) = attr.split_once(':') {
                    if key != "fill" {
                        new_style_attr.push_str(&format!("{key}:{value};"));
                    }
                }
            }
            for (class, color) in classes {
                if class_name == class {
                    new_style_attr.push_str(&format!("fill:{color};"));
                }
            }
            node.remove_attribute("class").unwrap();
            node.set_attribute("style", &new_style_attr).unwrap();
        }
        Ok(document.to_string())
    } else {
        anyhow::bail!("Couldn't find a root element")
    }
}

pub fn replace_fill_with_classes(xml_input: &str) -> anyhow::Result<String> {
    let parser = Parser::default();
    let document = parser.parse_string(xml_input).expect("can't parse element");
    let classes = [
        ("success", (20.0, 82.0, 48.0)),
        ("warning", (100.0, 47.0, 0.0)),
        ("error", (88.0, 11.0, 14.0)),
    ];

    if let Some(root) = document.get_root_element() {
        let style_nodes = root
            .findnodes("//*[@style]")
            .expect("couldn't find any style attrs");
        for mut node in style_nodes {
            if let Some(removed_color) = remove_fill(&mut node) {
                for (css_class, color) in classes {
                    if color == removed_color {
                        node.set_attribute("class", css_class)
                            .expect("couldn't add a css class");
                    }
                }
            }
        }
        Ok(document.to_string())
    } else {
        anyhow::bail!("Couldn't find a root element")
    }
}
