//! The central image display — fit-to-window, zoom, pan, and rotation.
//!
//! Rendering strategy:
//! - `FitToWindow`: image widget is `Length::Fill` + `ContentFit::Contain`.
//!   iced handles all centering and scaling automatically. No manual math.
//! - `ActualSize` / `CustomZoom`: image is `Length::Fixed(w)` + `Length::Fixed(h)`,
//!   wrapped in a `scrollable` so overflow is handled with scrollbars/drag.
//!   Scroll wheel is consumed by the scrollable (pan) instead of navigating.
//!
//! Rotation is applied at decode time by pre-rotating the RGBA buffer. This
//! works around iced 0.13's lack of native image rotation.

use std::sync::Arc;

use iced::widget::{container, image, mouse_area, scrollable, text};
use iced::{Element, Length, Size};

use super::theme;
use crate::codec::{DecodedImage, rotate_rgba};

/// The zoom mode the image view is currently in.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ZoomMode {
    FitToWindow,
    ActualSize,
    Custom(f32),
}

impl ZoomMode {
    pub fn factor(&self, viewport: Size, image_size: Size) -> f32 {
        match self {
            ZoomMode::FitToWindow => {
                if image_size.width <= 0.0 || image_size.height <= 0.0 {
                    return 1.0;
                }
                let sx = viewport.width / image_size.width;
                let sy = viewport.height / image_size.height;
                sx.min(sy).min(1.0)
            }
            ZoomMode::ActualSize => 1.0,
            ZoomMode::Custom(f) => *f,
        }
    }

    pub fn pct(&self) -> u32 {
        match self {
            ZoomMode::FitToWindow => 0,
            ZoomMode::ActualSize => 100,
            ZoomMode::Custom(f) => (f * 100.0).round() as u32,
        }
    }

    pub fn is_fit(&self) -> bool {
        matches!(self, ZoomMode::FitToWindow)
    }
}

/// Messages emitted by the image view.
#[derive(Debug, Clone)]
pub enum ImageMessage {
    /// User right-clicked (position comes from cursor subscription).
    RightClicked,
}

/// State for the image view.
#[derive(Debug, Clone)]
pub struct ImageView {
    /// The ORIGINAL decoded image (before rotation).
    pub original: Option<Arc<DecodedImage>>,
    /// The handle that iced renders (after rotation applied).
    pub handle: Option<Arc<image::Handle>>,
    /// Current zoom mode.
    pub zoom: ZoomMode,
    /// Rotation in degrees (0, 90, 180, 270).
    pub rotation: i32,
}

impl Default for ImageView {
    fn default() -> Self {
        Self {
            original: None,
            handle: None,
            zoom: ZoomMode::FitToWindow,
            rotation: 0,
        }
    }
}

impl ImageView {
    /// Set the current image. Resets zoom to the given default mode and pan.
    /// Keeps rotation.
    pub fn set_image(&mut self, img: Arc<DecodedImage>, default_zoom: ZoomMode) {
        self.original = Some(img);
        self.zoom = default_zoom;
        self.rebuild_handle();
    }

    pub fn clear(&mut self) {
        self.original = None;
        self.handle = None;
        self.zoom = ZoomMode::FitToWindow;
    }

    /// Rebuild the iced handle from the original image + current rotation.
    fn rebuild_handle(&mut self) {
        let Some(img) = &self.original else {
            return;
        };
        let (pixels, w, h) =
            rotate_rgba(&img.pixels, img.width, img.height, self.rotation);
        self.handle = Some(Arc::new(image::Handle::from_rgba(w, h, pixels)));
    }

    pub fn zoom_in(&mut self) {
        self.zoom = match self.zoom {
            ZoomMode::FitToWindow => ZoomMode::Custom(1.1),
            ZoomMode::ActualSize => ZoomMode::Custom(1.1),
            ZoomMode::Custom(f) => ZoomMode::Custom((f * 1.1).min(16.0)),
        };
    }

    pub fn zoom_out(&mut self) {
        self.zoom = match self.zoom {
            ZoomMode::FitToWindow => ZoomMode::Custom(0.9),
            ZoomMode::ActualSize => ZoomMode::Custom(0.9),
            ZoomMode::Custom(f) => ZoomMode::Custom((f * 0.9).max(0.05)),
        };
    }

    pub fn fit_to_window(&mut self) {
        self.zoom = ZoomMode::FitToWindow;
    }

    pub fn actual_size(&mut self) {
        self.zoom = ZoomMode::ActualSize;
    }

    pub fn rotate_cw(&mut self) {
        self.rotation = (self.rotation + 90) % 360;
        self.rebuild_handle();
    }

    pub fn rotate_ccw(&mut self) {
        self.rotation = (self.rotation + 270) % 360;
        self.rebuild_handle();
    }

    /// The displayed image dimensions (after rotation).
    pub fn displayed_dimensions(&self) -> Option<(u32, u32)> {
        let img = self.original.as_ref()?;
        if self.rotation % 180 == 0 {
            Some((img.width, img.height))
        } else {
            Some((img.height, img.width))
        }
    }

    /// Render the image view.
    /// `viewport` is the available size for the image area (already minus chrome).
    pub fn view(&self, _viewport: Size) -> Element<'_, ImageMessage> {
        // Guard: no image loaded → render the empty-state placeholder.
        let (handle, img) = match (&self.handle, &self.original) {
            (Some(h), Some(i)) => (h, i),
            _ => return self.view_empty(),
        };

        let (iw, ih) = if self.rotation % 180 == 0 {
            (img.width as f32, img.height as f32)
        } else {
            (img.height as f32, img.width as f32)
        };

        let image_element: Element<'_, ImageMessage> = match self.zoom {
            ZoomMode::FitToWindow => {
                container(
                    image(handle.as_ref().clone())
                        .width(Length::Fill)
                        .height(Length::Fill)
                        .content_fit(iced::ContentFit::Contain),
                )
                .width(Length::Fill)
                .height(Length::Fill)
                .into()
            }
            ZoomMode::ActualSize | ZoomMode::Custom(_) => {
                let factor = self.zoom.factor(
                    Size::new(f32::MAX, f32::MAX),
                    Size::new(iw, ih),
                );
                let dw = iw * factor;
                let dh = ih * factor;

                // The image widget is the direct child of the scrollable
                // (no intermediate Fill container, which collapses inside
                // scrollable and hides the image). ContentFit::Contain scales
                // the image to fill the Fixed box while preserving aspect
                // ratio — since dw/dh matches iw/ih, the image fills exactly.
                scrollable(
                    image(handle.as_ref().clone())
                        .width(Length::Fixed(dw))
                        .height(Length::Fixed(dh))
                        .content_fit(iced::ContentFit::Contain),
                )
                .direction(scrollable::Direction::Both {
                    horizontal: scrollable::Scrollbar::new().width(6).scroller_width(6),
                    vertical: scrollable::Scrollbar::new().width(6).scroller_width(6),
                })
                .width(Length::Fill)
                .height(Length::Fill)
                .into()
            }
        };

        // Wrap in mouse_area to capture right-clicks for context menu.
        let with_mouse = mouse_area(image_element)
            .on_right_press(ImageMessage::RightClicked);

        container(with_mouse)
            .width(Length::Fill)
            .height(Length::Fill)
            .style(|_| container::Style {
                background: Some(theme::BG_IMAGE_AREA.into()),
                ..Default::default()
            })
            .into()
    }

    /// Empty-state placeholder shown before any folder is opened.
    fn view_empty(&self) -> Element<'_, ImageMessage> {
        container(
            text("No image — press O to open a folder")
                .color(theme::TEXT_SECONDARY)
                .size(16),
        )
        .width(Length::Fill)
        .height(Length::Fill)
        .align_x(iced::Alignment::Center)
        .align_y(iced::Alignment::Center)
        .style(|_| container::Style {
            background: Some(theme::BG_IMAGE_AREA.into()),
            ..Default::default()
        })
        .into()
    }
}
