use std::fmt;
use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ImageFormat {
    Jpeg,
    Png,
    WebP,
    Avif,
    Gif,
    Tiff,
    Bmp,
}

impl ImageFormat {
    pub fn from_extension(ext: &str) -> Option<Self> {
        match ext.to_lowercase().as_str() {
            "jpg" | "jpeg" => Some(Self::Jpeg),
            "png" => Some(Self::Png),
            "webp" => Some(Self::WebP),
            "avif" => Some(Self::Avif),
            "gif" => Some(Self::Gif),
            "tiff" | "tif" => Some(Self::Tiff),
            "bmp" => Some(Self::Bmp),
            _ => None,
        }
    }

    pub fn extension(&self) -> &'static str {
        match self {
            Self::Jpeg => "jpg",
            Self::Png => "png",
            Self::WebP => "webp",
            Self::Avif => "avif",
            Self::Gif => "gif",
            Self::Tiff => "tiff",
            Self::Bmp => "bmp",
        }
    }
}

#[derive(Debug, Clone)]
pub struct ImageSource {
    pub path: PathBuf,
    pub original_format: Option<ImageFormat>,
}

impl ImageSource {
    pub fn from_path(path: impl AsRef<Path>) -> Self {
        let path = path.as_ref().to_path_buf();
        let original_format = path
            .extension()
            .and_then(|ext| ext.to_str())
            .and_then(ImageFormat::from_extension);
        Self {
            path,
            original_format,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Dimensions {
    pub width: u32,
    pub height: u32,
}

impl Dimensions {
    pub fn aspect_ratio(&self) -> f64 {
        if self.height == 0 {
            return 1.0;
        }
        self.width as f64 / self.height as f64
    }

    pub fn fit_within(self, max: Dimensions, allow_upscale: bool) -> Dimensions {
        if self.width == 0 || self.height == 0 || max.width == 0 || max.height == 0 {
            return self;
        }

        if !allow_upscale && self.width <= max.width && self.height <= max.height {
            return self;
        }

        let scale_w = max.width as f64 / self.width as f64;
        let scale_h = max.height as f64 / self.height as f64;
        let scale = scale_w.min(scale_h);

        if !allow_upscale && scale >= 1.0 {
            return self;
        }

        Dimensions {
            width: (self.width as f64 * scale).round().max(1.0) as u32,
            height: (self.height as f64 * scale).round().max(1.0) as u32,
        }
    }
}

impl fmt::Display for Dimensions {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}x{}", self.width, self.height)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum QualityPreset {
    Maximum,
    High,
    Medium,
    Low,
    WebOptimized,
}

impl QualityPreset {
    pub fn jpeg_quality(&self) -> u8 {
        match self {
            Self::Maximum => 95,
            Self::High => 85,
            Self::Medium => 72,
            Self::Low => 55,
            Self::WebOptimized => 65,
        }
    }

    pub fn png_level(&self) -> u8 {
        match self {
            Self::Maximum => 2,
            Self::High => 3,
            Self::Medium => 4,
            Self::Low => 6,
            Self::WebOptimized => 5,
        }
    }

    pub fn webp_quality(&self) -> f32 {
        match self {
            Self::Maximum => 95.0,
            Self::High => 85.0,
            Self::Medium => 72.0,
            Self::Low => 50.0,
            Self::WebOptimized => 75.0,
        }
    }

    pub fn webp_effort(&self) -> u8 {
        match self {
            Self::Maximum => 6,
            Self::High => 5,
            Self::Medium => 4,
            Self::Low => 3,
            Self::WebOptimized => 4,
        }
    }

    pub fn avif_quality(&self) -> u8 {
        match self {
            Self::Maximum => 80,
            Self::High => 63,
            Self::Medium => 50,
            Self::Low => 35,
            Self::WebOptimized => 40,
        }
    }

    pub fn avif_speed(&self) -> u8 {
        match self {
            Self::Maximum => 4,
            Self::High => 6,
            Self::Medium => 6,
            Self::Low => 8,
            Self::WebOptimized => 8,
        }
    }

    pub fn label(&self) -> &'static str {
        match self {
            Self::Maximum => "Maximum",
            Self::High => "High",
            Self::Medium => "Medium",
            Self::Low => "Low",
            Self::WebOptimized => "Web-optimized",
        }
    }
}
