use std::error::Error;

mod draw;
mod scene;

use smithay::{
    backend::{
        renderer::{
            Color32F, Frame, Renderer,
            gles::{GlesRenderer, GlesTarget, UniformName, UniformType},
        },
        winit::WinitGraphicsBackend,
    },
    utils::{Physical, Rectangle, Transform},
};

use super::app_icon::{ensure_app_icon_resources_for_node_ids, ensure_node_app_icon_resources};
use super::cluster_icon::ensure_cluster_core_icon_resources;
use super::log_rounded_shader_failure;
use super::node::{
    ensure_node_circle_resources, node_app_icon_texture_allowed,
    node_markers_need_app_icon_resources,
};
use super::screenshot_icon::ensure_screenshot_menu_icon_resources;
use crate::compositor::interaction::ResizeCtx;
use crate::compositor::root::Halley;
use crate::overlay::ensure_cluster_bloom_icon_resources;
use crate::render::bearings::ensure_bearing_icon_resources;
use crate::text::ensure_ui_text_resources;
use crate::window::prewarm_visible_active_window_offscreen_caches;
use draw::{draw_cursor_layer, draw_debug_frame_scene};
use scene::{collect_cursor_scene, collect_debug_frame_scene, prepare_debug_frame_state};

pub(crate) use draw::{draw_offscreen_textures, draw_window_borders};

const WINDOW_TEXTURE_SHADER: &str = include_str!("../shaders/window_rounded_texture.frag");
const WINDOW_SHADOW_SHADER: &str = include_str!("../shaders/window_shadow.frag");
const SURFACE_CLIP_SHADER: &str = include_str!("../shaders/surface_clipped_texture.frag");

pub(crate) fn ensure_window_texture_program(renderer: &mut GlesRenderer, st: &mut Halley) {
    if st.ui.render_state.gpu.window_texture_program.is_some()
        || st.ui.render_state.gpu.window_texture_program_failed
    {
        return;
    }

    match renderer.compile_custom_texture_shader(
        WINDOW_TEXTURE_SHADER,
        &[
            UniformName::new("rect_size", UniformType::_2f),
            UniformName::new("corner_radius", UniformType::_1f),
            UniformName::new("border_px", UniformType::_1f),
            UniformName::new("border_color", UniformType::_4f),
            UniformName::new("fill_color", UniformType::_4f),
            UniformName::new("content_alpha_scale", UniformType::_1f),
            UniformName::new("geo_offset", UniformType::_2f),
            UniformName::new("geo_size", UniformType::_2f),
        ],
    ) {
        Ok(program) => st.ui.render_state.gpu.window_texture_program = Some(program),
        Err(err) => {
            st.ui.render_state.gpu.window_texture_program_failed = true;
            log_rounded_shader_failure(
                "render/shaders/window_rounded_texture.frag",
                "window-content-clip",
                &err,
            );
        }
    }
}

fn ensure_window_shadow_program(renderer: &mut GlesRenderer, st: &mut Halley) {
    if st.ui.render_state.gpu.window_shadow_program.is_some()
        || st.ui.render_state.gpu.window_shadow_program_failed
    {
        return;
    }

    match renderer.compile_custom_texture_shader(
        WINDOW_SHADOW_SHADER,
        &[
            UniformName::new("rect_size", UniformType::_2f),
            UniformName::new("caster_size", UniformType::_2f),
            UniformName::new("caster_center", UniformType::_2f),
            UniformName::new("corner_radius", UniformType::_1f),
            UniformName::new("spread", UniformType::_1f),
            UniformName::new("shadow_radius", UniformType::_1f),
            UniformName::new("shadow_color", UniformType::_4f),
        ],
    ) {
        Ok(program) => st.ui.render_state.gpu.window_shadow_program = Some(program),
        Err(err) => {
            st.ui.render_state.gpu.window_shadow_program_failed = true;
            log_rounded_shader_failure("render/shaders/window_shadow.frag", "window-shadow", &err);
        }
    }
}

fn ensure_surface_clip_program(renderer: &mut GlesRenderer, st: &mut Halley) {
    if st.ui.render_state.gpu.surface_clip_program.is_some()
        || st.ui.render_state.gpu.surface_clip_program_failed
    {
        return;
    }

    match renderer.compile_custom_texture_shader(
        SURFACE_CLIP_SHADER,
        &[
            UniformName::new("clip_scale", UniformType::_1f),
            UniformName::new("geo_size", UniformType::_2f),
            UniformName::new("corner_radius", UniformType::_4f),
            UniformName::new("input_to_geo_row_0", UniformType::_3f),
            UniformName::new("input_to_geo_row_1", UniformType::_3f),
            UniformName::new("input_to_geo_row_2", UniformType::_3f),
        ],
    ) {
        Ok(program) => st.ui.render_state.gpu.surface_clip_program = Some(program),
        Err(err) => {
            st.ui.render_state.gpu.surface_clip_program_failed = true;
            log_rounded_shader_failure(
                "render/shaders/surface_clipped_texture.frag",
                "window-surface-clip",
                &err,
            );
        }
    }
}

pub(crate) fn draw_debug_frame(
    backend: &mut WinitGraphicsBackend<GlesRenderer>,
    st: &mut Halley,
    resize_preview: Option<ResizeCtx>,
    hover_node: Option<halley_core::field::NodeId>,
    preview_hover_node: Option<halley_core::field::NodeId>,
) -> Result<(), Box<dyn Error>> {
    let size = backend.window_size();
    let damage = Rectangle::<i32, Physical>::from_size(size);
    {
        let (renderer, mut framebuffer) = backend.bind()?;
        draw_debug_frame_to_target(
            renderer,
            &mut framebuffer,
            size,
            st,
            resize_preview,
            hover_node,
            preview_hover_node,
            None,
            None,
            Transform::Flipped180,
        )?;
    }
    backend.submit(Some(&[damage]))?;
    Ok(())
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn draw_debug_frame_to_target(
    renderer: &mut GlesRenderer,
    framebuffer: &mut GlesTarget<'_>,
    size: smithay::utils::Size<i32, Physical>,
    st: &mut Halley,
    resize_preview: Option<ResizeCtx>,
    hover_node: Option<halley_core::field::NodeId>,
    preview_hover_node: Option<halley_core::field::NodeId>,
    cursor_screen: Option<(f32, f32)>,
    cursor_image: Option<&smithay::input::pointer::CursorImageStatus>,
    frame_transform: Transform,
) -> Result<(), Box<dyn Error>> {
    ensure_node_circle_resources(renderer, st)?;
    ensure_window_texture_program(renderer, st);
    ensure_window_shadow_program(renderer, st);
    ensure_surface_clip_program(renderer, st);

    let prepared = prepare_debug_frame_state(st, size);
    prewarm_visible_active_window_offscreen_caches(renderer, st, prepared.now);

    let scene = collect_debug_frame_scene(
        renderer,
        st,
        size,
        resize_preview,
        hover_node,
        preview_hover_node,
        prepared.now,
    );
    ensure_node_app_icon_resources(renderer, st, &scene.render_nodes)?;
    if node_markers_need_app_icon_resources(st.runtime.tuning.node_show_app_icons) {
        ensure_cluster_core_icon_resources(renderer, st)?;
    }
    ensure_screenshot_menu_icon_resources(renderer, st)?;
    let current_monitor = st.model.monitor_state.current_monitor.clone();
    if st.runtime.tuning.tile_queue_show_icons
        && node_app_icon_texture_allowed(st.runtime.tuning.node_show_app_icons, false)
    {
        let overflow_ids = st
            .model
            .cluster_state
            .cluster_overflow_members
            .get(current_monitor.as_str())
            .into_iter()
            .flat_map(|ids| ids.iter().copied())
            .chain(
                st.input
                    .interaction_state
                    .cluster_overflow_drag_preview
                    .as_ref()
                    .filter(|preview| preview.monitor == current_monitor)
                    .map(|preview| preview.member_id),
            )
            .chain(
                st.model
                    .cluster_state
                    .cluster_overflow_promotion_anim
                    .get(current_monitor.as_str())
                    .map(|anim| anim.member_id),
            )
            .collect::<Vec<_>>();
        ensure_app_icon_resources_for_node_ids(renderer, st, overflow_ids.into_iter())?;
    }
    if node_app_icon_texture_allowed(st.runtime.tuning.node_show_app_icons, false)
        && let Some(candidate_ids) = st
            .input
            .interaction_state
            .focus_cycle_session
            .as_ref()
            .map(|session| session.candidates.clone())
    {
        ensure_app_icon_resources_for_node_ids(renderer, st, candidate_ids.into_iter())?;
    }
    ensure_cluster_bloom_icon_resources(renderer, st, current_monitor.as_str())?;
    ensure_bearing_icon_resources(renderer, st, current_monitor.as_str())?;
    ensure_ui_text_resources(renderer, st)?;
    let cursor = collect_cursor_scene(renderer, cursor_screen, cursor_image);
    let mut frame = renderer.render(framebuffer, size, frame_transform)?;
    frame.clear(Color32F::new(0.04, 0.05, 0.06, 1.0), &[prepared.damage])?;

    draw_debug_frame_scene(&mut frame, st, size, &prepared, &scene, hover_node)?;
    draw_cursor_layer(
        &mut frame,
        prepared.damage,
        cursor_screen,
        &cursor,
        &st.runtime.tuning.cursor,
    )?;

    let _ = frame.finish()?;
    crate::compositor::workspace::state::process_pending_manual_collapses_for_monitor(
        st,
        current_monitor.as_str(),
        prepared.now,
    );
    Ok(())
}
