pub mod configpicker;
mod fontpicker;
pub mod notification;

use std::time::Duration;

use cosmic_text::{Color, FontSystem, SwashCache};
use image::Rgba;
use ratatui_image::{
    FontSize,
    picker::{Capability, Picker, ProtocolType, cap_parser::QueryStdioOptions},
};

use crate::{
    config::{self, UserConfig},
    error::Error,
};
use fontpicker::interactive_font_picker;
use what_terminal_font::detect_terminal_font;

pub struct FontRenderer {
    pub font_size: FontSize, // Terminal font-size, not rendered font-size.
    pub font_name: String,
    pub font_system: FontSystem,
    pub font_color: Color,
    pub swash_cache: SwashCache,
    pub background_color: Option<Rgba<u8>>,
}

impl FontRenderer {
    pub fn new(
        font_system: FontSystem,
        swash_cache: SwashCache,
        font_name: String,
        font_size: FontSize,
        font_color: Option<Color>,
        background_color: Option<Rgba<u8>>,
    ) -> Self {
        FontRenderer {
            font_size,
            font_name,
            font_system,
            font_color: font_color.unwrap_or_else(|| Color::rgba(255, 255, 255, 255)),
            swash_cache,
            background_color,
        }
    }
}

pub enum SetupResult {
    Aborted,
    TextSizing(Picker),
    AsciiArt(Picker),
    Complete(Picker, Box<FontRenderer>),
}

static JETBRAINS_MONO: &[u8] = include_bytes!("fonts/JetBrainsMonoNerdFont-Regular.ttf");
static CASCADIA_CODE: &[u8] = include_bytes!("fonts/CascadiaCode-Regular.otf");

pub fn setup_graphics(
    config: &mut UserConfig,
    force_font_setup: bool,
    no_cap_checks: bool,
    debug_override_protocol_type: Option<ProtocolType>,
) -> Result<SetupResult, Error> {
    let (mut picker, background_color) = if no_cap_checks {
        (Picker::halfblocks(), None)
    } else {
        print!("Detecting supported graphics protocols...");
        let picker = Picker::from_query_stdio_with_options(QueryStdioOptions {
            timeout: config
                .stdio_query_timeout_ms
                .map(Duration::from_millis)
                .unwrap_or_else(|| QueryStdioOptions::default().timeout),
            text_sizing_protocol: !config.ignore_text_sizing_protocol.unwrap_or(false),
            terminal_background_color_osc: true,
            ..Default::default()
        })?;
        println!(" {:?}.", picker.protocol_type());
        let mut bg = None;
        if picker.protocol_type() == ProtocolType::Sixel {
            for cap in picker.capabilities() {
                if let Capability::Background(r, g, b) = cap {
                    bg = Some(Rgba::from([*r, *g, *b, 255]));
                }
            }
        }
        (picker, bg)
    };

    let has_text_size_protocol = picker
        .capabilities()
        .contains(&Capability::TextSizingProtocol);
    if has_text_size_protocol {
        return Ok(SetupResult::TextSizing(picker));
    }

    if picker.protocol_type() == ProtocolType::Halfblocks {
        return Ok(SetupResult::AsciiArt(picker));
    }

    let mut font_system = FontSystem::new_with_fonts([
        cosmic_text::fontdb::Source::Binary(std::sync::Arc::new(JETBRAINS_MONO)),
        cosmic_text::fontdb::Source::Binary(std::sync::Arc::new(CASCADIA_CODE)),
    ]);
    let db = font_system.db_mut();
    db.load_system_fonts();

    let all_font_families: Vec<String> = db
        .faces()
        .map(|faceinfo| faceinfo.families[0].0.clone())
        .collect();

    let config_font_family = if force_font_setup {
        println!("Forced font setup");
        None
    } else {
        config.font_family.as_ref().and_then(|font_family| {
            // Ensure this font exists
            if all_font_families.contains(font_family) {
                return Some(font_family);
            }
            println!("Configured font not found: {font_family}");
            None
        })
    };

    let font_name = match config_font_family {
        Some(font_family) => font_family.clone(),
        None => {
            let terminal_font = detect_terminal_font();

            if !force_font_setup
                && let Ok(terminal_font) = &terminal_font
                && all_font_families.contains(terminal_font)
            {
                config::store_font_family(config, terminal_font.clone())?;
                notification::interactive_notification("Font has been written to config file.")?;
                terminal_font.to_owned()
            } else {
                match interactive_font_picker(db, &mut picker, terminal_font.ok()) {
                    Ok(Some(setup_font_family)) => {
                        config::store_font_family(config, setup_font_family.clone())?;
                        notification::interactive_notification(
                            "Font has been written to config file.",
                        )?;
                        setup_font_family
                    }
                    Ok(None) => return Ok(SetupResult::Aborted),
                    Err(err) => return Err(err),
                }
            }
        }
    };

    let font_size = picker.font_size();

    if let Some(debug_override_protocol_type) = debug_override_protocol_type {
        log::warn!("debug_override_protocol_type set to {debug_override_protocol_type:?}");
        picker.set_protocol_type(debug_override_protocol_type);
    }

    Ok(SetupResult::Complete(
        picker,
        Box::new(FontRenderer::new(
            font_system,
            SwashCache::new(),
            font_name,
            font_size,
            config.theme.as_ref().and_then(|theme| {
                theme.header_color.map(|ratatui_color| match ratatui_color {
                    ratatui::style::Color::Rgb(r, g, b) => Color::rgba(r, g, b, 255),
                    _ => Color::rgba(255, 255, 255, 255),
                })
            }),
            background_color,
        )),
    ))
}
