//! Media-widget presentation: control layout, texture caching, cover-art
//! loading and hit-testing. Reads the model on [`MprisState`].

use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Point, Rectangle, Size};

use crate::drawing::{ForgeElement, RenderTexture, Texture};
use crate::output::Canvas;
use crate::text::TextRenderer;

use super::MprisState;

const TITLE_COLOR: [u8; 4] = [255, 255, 255, 255];
const ARTIST_COLOR: [u8; 4] = [180, 180, 180, 255];
const CONTROL_COLOR: [u8; 4] = [255, 255, 255, 255];
const CONTROL_HEIGHT: i32 = 80;
const CONTROL_WIDTH: i32 = 300;
const CONTROL_PADDING: i32 = 12;

impl MprisState {
    /// Handle tap on media controls. Returns true if handled.
    pub fn handle_tap(&self, canvas: &Canvas, point: Point<f64, Logical>, on_lockscreen: bool) -> bool {
        if !self.media.available {
            return false;
        }

        let bounds = self.control_bounds(canvas, on_lockscreen);
        if !bounds.to_f64().contains(point) {
            return false;
        }

        let relative_x = point.x - bounds.loc.x as f64;
        let third = bounds.size.w as f64 / 3.0;

        if relative_x < third {
            Self::previous();
        } else if relative_x < 2.0 * third {
            Self::play_pause();
        } else {
            Self::next();
        }

        true
    }

    fn control_bounds(&self, canvas: &Canvas, on_lockscreen: bool) -> Rectangle<i32, Logical> {
        let size = canvas.size();
        let w = if canvas.is_mobile() { size.w - 24 } else { CONTROL_WIDTH };
        let x = (size.w - w) / 2;
        let y = if on_lockscreen {
            size.h / 4 + 80
        } else {
            // In quick settings - below sliders and toggles
            size.h - CONTROL_HEIGHT - 20
        };
        Rectangle::new(Point::from((x, y)), Size::from((w, CONTROL_HEIGHT)))
    }

    fn update_textures(
        &mut self,
        renderer: &mut GlesRenderer,
        canvas: &Canvas,
        text_renderer: &mut TextRenderer,
    ) {
        let scale = canvas.scale();
        if !self.dirty && (self.last_scale - scale).abs() < 0.01 {
            return;
        }
        self.dirty = false;
        self.last_scale = scale;

        if !self.media.available {
            self.title_texture = None;
            self.artist_texture = None;
            self.controls_texture = None;
            self.cover_texture = None;
            self.cover_loaded_url = None;
            return;
        }

        // Load cover art if URL changed
        if self.media.art_url != self.cover_loaded_url {
            self.cover_loaded_url = self.media.art_url.clone();
            self.cover_texture = None;
            if let Some(url) = &self.media.art_url {
                self.cover_texture = load_cover_art(renderer, scale, url);
            }
        }

        let title_font = crate::text::text_px(14.0, scale).round();
        let artist_font = crate::text::text_px(12.0, scale).round();
        let control_font = crate::text::text_px(18.0, scale).round();

        // Title
        if !self.media.title.is_empty() {
            let (buf, w, h) = text_renderer.render_line(&self.media.title, title_font, TITLE_COLOR);
            if w > 0 && h > 0 {
                self.title_texture = Some(RenderTexture::new(
                    Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
                ));
            }
        } else {
            self.title_texture = None;
        }

        // Artist
        if !self.media.artist.is_empty() {
            let (buf, w, h) = text_renderer.render_line(&self.media.artist, artist_font, ARTIST_COLOR);
            if w > 0 && h > 0 {
                self.artist_texture = Some(RenderTexture::new(
                    Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
                ));
            }
        } else {
            self.artist_texture = None;
        }

        // Controls text: "<<  |>/||  >>"
        let play_sym = if self.media.playing { "||" } else { ">" };
        let controls = format!("<<    {play_sym}    >>");
        let (buf, w, h) = text_renderer.render_line(&controls, control_font, CONTROL_COLOR);
        if w > 0 && h > 0 {
            self.controls_texture = Some(RenderTexture::new(
                Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
            ));
        }
    }

    pub fn textures(
        &mut self,
        textures: &mut Vec<ForgeElement>,
        canvas: &Canvas,
        renderer: &mut GlesRenderer,
        text_renderer: &mut TextRenderer,
        on_lockscreen: bool,
    ) {
        if !self.media.available {
            return;
        }

        self.update_textures(renderer, canvas, text_renderer);

        let scale = canvas.scale();
        let bounds = self.control_bounds(canvas, on_lockscreen);
        let physical_bounds = bounds.to_physical_precise_round(scale);

        // Cover art (left side of control area)
        let text_x_offset = if let Some(cover) = &self.cover_texture {
            let art_size = bounds.size.h - CONTROL_PADDING * 2;
            let art_rect: Rectangle<i32, Logical> = Rectangle::new(
                Point::from((bounds.loc.x + CONTROL_PADDING, bounds.loc.y + CONTROL_PADDING)),
                Size::from((art_size, art_size)),
            );
            let art_phys = art_rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, cover.clone(), art_phys.loc, art_phys, None, scale);
            art_size + CONTROL_PADDING
        } else {
            0
        };

        let mut text_y = bounds.loc.y + CONTROL_PADDING;

        // Title
        if let Some(title) = &self.title_texture {
            let ts = title.size();
            let tx = bounds.loc.x + CONTROL_PADDING + text_x_offset;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((tx, text_y)), ts);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, title.clone(), physical.loc, physical, None, scale);
            text_y += ts.h + 2;
        }

        // Artist
        if let Some(artist) = &self.artist_texture {
            let as_ = artist.size();
            let ax = bounds.loc.x + CONTROL_PADDING + text_x_offset;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((ax, text_y)), as_);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, artist.clone(), physical.loc, physical, None, scale);
        }

        // Controls (centered)
        if let Some(controls) = &self.controls_texture {
            let cs = controls.size();
            let cx = bounds.loc.x + (bounds.size.w - cs.w) / 2;
            let cy = bounds.loc.y + bounds.size.h - cs.h - CONTROL_PADDING;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((cx, cy)), cs);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, controls.clone(), physical.loc, physical, None, scale);
        }

        // Background last: the element list is front-to-back, so the panel
        // fill sits behind the art, text and controls above.
        ForgeElement::add_element(
            textures,
            self.bg_texture.clone(),
            physical_bounds.loc,
            physical_bounds,
            None,
            scale,
        );
    }
}

/// Load cover art from a file:// URL and create a texture.
fn load_cover_art(renderer: &mut GlesRenderer, scale: f64, url: &str) -> Option<RenderTexture> {
    let path = if let Some(p) = url.strip_prefix("file://") {
        p.to_string()
    } else {
        return None;
    };

    let img = image::open(&path).ok()?.into_rgba8();
    let (iw, ih) = img.dimensions();
    let size = 56u32; // logical cover art size
    let phys_size = ((size as f64 * scale) as u32).max(1);

    // Resize with nearest-neighbor
    let sx = iw as f64 / phys_size as f64;
    let sy = ih as f64 / phys_size as f64;
    let byte_count = (phys_size as usize)
        .checked_mul(phys_size as usize)
        .and_then(|n| n.checked_mul(4))?;
    let mut buf = vec![0u8; byte_count];
    for y in 0..phys_size {
        for x in 0..phys_size {
            let src_x = (x as f64 * sx).min((iw - 1) as f64) as u32;
            let src_y = (y as f64 * sy).min((ih - 1) as f64) as u32;
            let src_off = ((src_y * iw + src_x) * 4) as usize;
            let dst_off = ((y * phys_size + x) * 4) as usize;
            buf[dst_off..dst_off + 4].copy_from_slice(&img.as_raw()[src_off..src_off + 4]);
        }
    }

    let tex = Texture::from_buffer(renderer, scale, &buf, phys_size as i32, phys_size as i32, true);
    Some(RenderTexture::new(tex))
}
