//! Multipart form handling shared by the product and category admin forms.
//!
//! Both forms submit a mix of text fields and an optional `image` file part;
//! this collects them into an easy-to-query [`MultipartForm`] and stores any
//! uploaded image through the configured storage driver.

use std::collections::HashMap;

use axum::extract::Multipart;
use loco_rs::prelude::*;

use crate::controllers::media::{detect_image_extension, store_upload, IMAGE_MAX_BYTES, IMAGE_STORAGE_DIR};

fn normalize_empty(value: Option<String>) -> Option<String> {
    value.and_then(|value| {
        let value = value.trim().to_string();
        (!value.is_empty()).then_some(value)
    })
}

/// Collected multipart form: text fields keyed by name, plus the raw bytes of
/// an `image` file part if one was uploaded (an empty file input is ignored).
pub(crate) struct MultipartForm {
    fields: HashMap<String, String>,
    pub(crate) image: Option<Vec<u8>>,
}

impl MultipartForm {
    /// Trimmed value of a text field, `None` when missing or blank.
    pub(crate) fn text(&self, key: &str) -> Option<String> {
        normalize_empty(self.fields.get(key).cloned())
    }

    /// Whether a checkbox-style field is checked.
    pub(crate) fn checked(&self, key: &str) -> bool {
        matches!(
            self.fields.get(key).map(String::as_str),
            Some("on" | "true" | "1")
        )
    }

    /// The distinct row indices `N` present among `variants[N][...]` fields,
    /// sorted ascending. Used to read the repeated variant rows of the product
    /// form (each row's fields are uniquely keyed, so the HashMap keeps them all).
    pub(crate) fn variant_indices(&self) -> Vec<usize> {
        let mut idx: Vec<usize> = self
            .fields
            .keys()
            .filter_map(|k| {
                let rest = k.strip_prefix("variants[")?;
                rest.split(']').next()?.parse::<usize>().ok()
            })
            .collect();
        idx.sort_unstable();
        idx.dedup();
        idx
    }
}

pub(crate) async fn read_multipart_form(mut multipart: Multipart) -> Result<MultipartForm> {
    let mut fields = HashMap::new();
    let mut image = None;

    while let Some(mut field) = multipart
        .next_field()
        .await
        .map_err(|err| Error::BadRequest(format!("invalid multipart data: {err}")))?
    {
        let name = field.name().unwrap_or("").to_string();
        if name == "image" {
            let mut data = Vec::new();
            while let Some(chunk) = field
                .chunk()
                .await
                .map_err(|err| Error::BadRequest(format!("invalid multipart chunk: {err}")))?
            {
                data.extend_from_slice(&chunk);
                if data.len() > IMAGE_MAX_BYTES {
                    return Err(Error::BadRequest(format!(
                        "image is larger than {} MB",
                        IMAGE_MAX_BYTES / 1024 / 1024
                    )));
                }
            }
            if !data.is_empty() {
                image = Some(data);
            }
        } else {
            let value = field
                .text()
                .await
                .map_err(|err| Error::BadRequest(format!("invalid multipart field: {err}")))?;
            fields.insert(name, value);
        }
    }

    Ok(MultipartForm { fields, image })
}

/// Store an uploaded image's bytes and return its generated filename.
pub(crate) async fn store_image(ctx: &AppContext, data: Vec<u8>) -> Result<String> {
    let extension = detect_image_extension(&data)?;
    store_upload(ctx, IMAGE_STORAGE_DIR, extension, data).await
}
