pub mod adjustments;
pub mod metadata;
pub mod rename;
pub mod resize;
pub mod watermark;

use serde::{Deserialize, Serialize};

use crate::types::{Dimensions, ImageFormat, QualityPreset};

// --- Resize ---

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ResizeConfig {
    ByWidth(u32),
    ByHeight(u32),
    FitInBox {
        max: Dimensions,
        allow_upscale: bool,
    },
    Exact(Dimensions),
}

impl ResizeConfig {
    pub fn target_for(&self, original: Dimensions) -> Dimensions {
        if original.width == 0 || original.height == 0 {
            return original;
        }
        let result = match self {
            Self::ByWidth(w) => {
                if *w == 0 {
                    return original;
                }
                let scale = *w as f64 / original.width as f64;
                Dimensions {
                    width: *w,
                    height: (original.height as f64 * scale).round().max(1.0) as u32,
                }
            }
            Self::ByHeight(h) => {
                if *h == 0 {
                    return original;
                }
                let scale = *h as f64 / original.height as f64;
                Dimensions {
                    width: (original.width as f64 * scale).round().max(1.0) as u32,
                    height: *h,
                }
            }
            Self::FitInBox { max, allow_upscale } => {
                original.fit_within(*max, *allow_upscale)
            }
            Self::Exact(dims) => {
                if dims.width == 0 || dims.height == 0 {
                    return original;
                }
                *dims
            }
        };
        Dimensions {
            width: result.width.max(1),
            height: result.height.max(1),
        }
    }
}

// --- Resize Algorithm ---

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum ResizeAlgorithm {
    Lanczos3,
    CatmullRom,
    Bilinear,
    Nearest,
}

impl Default for ResizeAlgorithm {
    fn default() -> Self {
        Self::Lanczos3
    }
}

// --- Rotation / Flip ---

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Rotation {
    None,
    Cw90,
    Cw180,
    Cw270,
    AutoOrient,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Flip {
    None,
    Horizontal,
    Vertical,
}

// --- Convert ---

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConvertConfig {
    KeepOriginal,
    SingleFormat(ImageFormat),
    FormatMapping(Vec<(ImageFormat, ImageFormat)>),
}

impl ConvertConfig {
    pub fn output_format(&self, input: ImageFormat) -> ImageFormat {
        match self {
            Self::KeepOriginal => input,
            Self::SingleFormat(fmt) => *fmt,
            Self::FormatMapping(map) => {
                map.iter()
                    .find(|(from, _)| *from == input)
                    .map(|(_, to)| *to)
                    .unwrap_or(input)
            }
        }
    }

    /// Returns true if this conversion will change at least some file extensions.
    pub fn changes_extension(&self) -> bool {
        !matches!(self, Self::KeepOriginal)
    }
}

// --- Compress ---

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CompressConfig {
    Preset(QualityPreset),
    Custom {
        jpeg_quality: Option<u8>,
        png_level: Option<u8>,
        webp_quality: Option<f32>,
        avif_quality: Option<f32>,
    },
}

// --- Metadata ---

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MetadataConfig {
    StripAll,
    KeepAll,
    Privacy,
    Custom {
        strip_gps: bool,
        strip_camera: bool,
        strip_software: bool,
        strip_timestamps: bool,
        strip_copyright: bool,
    },
}

impl MetadataConfig {
    pub fn should_strip_gps(&self) -> bool {
        match self {
            Self::StripAll => true,
            Self::KeepAll => false,
            Self::Privacy => true,
            Self::Custom { strip_gps, .. } => *strip_gps,
        }
    }

    pub fn should_strip_camera(&self) -> bool {
        match self {
            Self::StripAll => true,
            Self::KeepAll => false,
            Self::Privacy => true,
            Self::Custom { strip_camera, .. } => *strip_camera,
        }
    }

    pub fn should_strip_copyright(&self) -> bool {
        match self {
            Self::StripAll => true,
            Self::KeepAll => false,
            Self::Privacy => false,
            Self::Custom { strip_copyright, .. } => *strip_copyright,
        }
    }

    pub fn should_strip_software(&self) -> bool {
        match self {
            Self::StripAll => true,
            Self::KeepAll => false,
            Self::Privacy => true,
            Self::Custom { strip_software, .. } => *strip_software,
        }
    }

    pub fn should_strip_timestamps(&self) -> bool {
        match self {
            Self::StripAll => true,
            Self::KeepAll => false,
            Self::Privacy => false,
            Self::Custom { strip_timestamps, .. } => *strip_timestamps,
        }
    }
}

// --- Watermark ---

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum WatermarkPosition {
    TopLeft,
    TopCenter,
    TopRight,
    MiddleLeft,
    Center,
    MiddleRight,
    BottomLeft,
    BottomCenter,
    BottomRight,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WatermarkConfig {
    Text {
        text: String,
        position: WatermarkPosition,
        font_size: f32,
        opacity: f32,
        color: [u8; 4],
        font_family: Option<String>,
        rotation: Option<WatermarkRotation>,
        tiled: bool,
        margin: u32,
    },
    Image {
        path: std::path::PathBuf,
        position: WatermarkPosition,
        opacity: f32,
        scale: f32,
        rotation: Option<WatermarkRotation>,
        tiled: bool,
        margin: u32,
    },
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum WatermarkRotation {
    Degrees45,
    DegreesNeg45,
    Degrees90,
    Custom(f32),
}

// --- Adjustments ---

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdjustmentsConfig {
    pub brightness: i32,
    pub contrast: i32,
    pub saturation: i32,
    pub sharpen: bool,
    pub grayscale: bool,
    pub sepia: bool,
    pub crop_aspect_ratio: Option<(f64, f64)>,
    pub trim_whitespace: bool,
    pub canvas_padding: u32,
}

impl AdjustmentsConfig {
    pub fn is_noop(&self) -> bool {
        self.brightness == 0
            && self.contrast == 0
            && self.saturation == 0
            && !self.sharpen
            && !self.grayscale
            && !self.sepia
            && self.crop_aspect_ratio.is_none()
            && !self.trim_whitespace
            && self.canvas_padding == 0
    }
}

// --- Overwrite Action (concrete action, no "Ask" variant) ---

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum OverwriteAction {
    AutoRename,
    Overwrite,
    Skip,
}

impl Default for OverwriteAction {
    fn default() -> Self {
        Self::AutoRename
    }
}

// --- Rename ---

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RenameConfig {
    pub prefix: String,
    pub suffix: String,
    pub counter_start: u32,
    pub counter_padding: u32,
    #[serde(default)]
    pub counter_enabled: bool,
    /// 0=before prefix, 1=before name, 2=after name, 3=after suffix, 4=replace name
    #[serde(default = "default_counter_position")]
    pub counter_position: u32,
    pub template: Option<String>,
    /// 0=none, 1=lowercase, 2=uppercase, 3=title case
    pub case_mode: u32,
    /// 0=none, 1=underscore, 2=hyphen, 3=dot, 4=camelcase, 5=remove
    #[serde(default)]
    pub replace_spaces: u32,
    /// 0=keep all, 1=filesystem-safe, 2=web-safe, 3=hyphens+underscores, 4=hyphens only, 5=alphanumeric only
    #[serde(default)]
    pub special_chars: u32,
    pub regex_find: String,
    pub regex_replace: String,
}

fn default_counter_position() -> u32 { 3 }

impl RenameConfig {
    /// Returns true if this rename config would actually change any filename.
    pub fn changes_filename(&self) -> bool {
        !self.prefix.is_empty()
            || !self.suffix.is_empty()
            || self.counter_enabled
            || !self.regex_find.is_empty()
            || self.case_mode > 0
            || self.replace_spaces > 0
            || self.special_chars > 0
            || self.template.is_some()
    }

    /// Pre-compile the regex for batch use. Call once before a loop of apply_simple_compiled.
    pub fn compile_regex(&self) -> Option<regex::Regex> {
        rename::compile_rename_regex(&self.regex_find)
    }

    pub fn apply_simple(&self, original_name: &str, extension: &str, index: u32) -> String {
        // 1. Apply regex find-and-replace on the original name
        let working_name = rename::apply_regex_replace(original_name, &self.regex_find, &self.regex_replace);

        // 2. Apply space replacement
        let working_name = rename::apply_space_replacement(&working_name, self.replace_spaces);

        // 3. Apply special character filtering
        let working_name = rename::apply_special_chars(&working_name, self.special_chars);

        // 4. Build counter string
        let counter_str = if self.counter_enabled {
            let counter = self.counter_start.saturating_add(index.saturating_sub(1));
            let padding = (self.counter_padding as usize).min(10);
            format!("{:0>width$}", counter, width = padding)
        } else {
            String::new()
        };

        let has_counter = self.counter_enabled && !counter_str.is_empty();

        // 5. Assemble parts based on counter position
        // Positions: 0=before prefix, 1=before name, 2=after name, 3=after suffix, 4=replace name
        let mut result = String::new();

        if has_counter && self.counter_position == 0 {
            result.push_str(&counter_str);
            result.push('_');
        }

        result.push_str(&self.prefix);

        if has_counter && self.counter_position == 1 {
            result.push_str(&counter_str);
            result.push('_');
        }

        if has_counter && self.counter_position == 4 {
            result.push_str(&counter_str);
        } else {
            result.push_str(&working_name);
        }

        if has_counter && self.counter_position == 2 {
            result.push('_');
            result.push_str(&counter_str);
        }

        result.push_str(&self.suffix);

        if has_counter && self.counter_position == 3 {
            result.push('_');
            result.push_str(&counter_str);
        }

        // 6. Apply case conversion
        let result = rename::apply_case_conversion(&result, self.case_mode);

        format!("{}.{}", result, extension)
    }

    /// Like apply_simple but uses a pre-compiled regex (avoids recompiling per file).
    pub fn apply_simple_compiled(&self, original_name: &str, extension: &str, index: u32, compiled_re: Option<&regex::Regex>) -> String {
        // 1. Apply regex find-and-replace on the original name
        let working_name = match compiled_re {
            Some(re) => rename::apply_regex_replace_compiled(original_name, re, &self.regex_replace),
            None => original_name.to_string(),
        };

        // 2. Apply space replacement
        let working_name = rename::apply_space_replacement(&working_name, self.replace_spaces);

        // 3. Apply special character filtering
        let working_name = rename::apply_special_chars(&working_name, self.special_chars);

        // 4. Build counter string
        let counter_str = if self.counter_enabled {
            let counter = self.counter_start.saturating_add(index.saturating_sub(1));
            let padding = (self.counter_padding as usize).min(10);
            format!("{:0>width$}", counter, width = padding)
        } else {
            String::new()
        };

        let has_counter = self.counter_enabled && !counter_str.is_empty();

        // 5. Assemble parts based on counter position
        let mut result = String::new();

        if has_counter && self.counter_position == 0 {
            result.push_str(&counter_str);
            result.push('_');
        }

        result.push_str(&self.prefix);

        if has_counter && self.counter_position == 1 {
            result.push_str(&counter_str);
            result.push('_');
        }

        if has_counter && self.counter_position == 4 {
            result.push_str(&counter_str);
        } else {
            result.push_str(&working_name);
        }

        if has_counter && self.counter_position == 2 {
            result.push('_');
            result.push_str(&counter_str);
        }

        result.push_str(&self.suffix);

        if has_counter && self.counter_position == 3 {
            result.push('_');
            result.push_str(&counter_str);
        }

        // 6. Apply case conversion
        let result = rename::apply_case_conversion(&result, self.case_mode);

        format!("{}.{}", result, extension)
    }
}
