//! Procedural macros for Mediarium.
//!
//! # `#[string_enum]`
//!
//! Attribute macro applied to an enum. Each variant may carry
//! `#[display("some_value")]` from [`parse-display`], otherwise a snake_case
//! variant name is used. The macro rewrites the enum in-place and, injects
//! `#[derive(Display, FromStr)]`, when the enum itself carries a
//! `#[sea_orm(...)]` attribute, also injects `#[sea_orm(string_value =
//! "some_value")]` on every variant so that `DeriveActiveEnum` sees the right
//! string values without duplication.
//!
//! When no `#[sea_orm(...)]` attribute is present on the enum, only
//! `#[display("...")]` is emitted on variants - useful for plain display/parse
//! enums that are not stored in the database.
//!
//! ## Example - with SeaORM
//!
//! ```rust,ignore
//! #[string_enum]
//! #[derive(Clone, Debug, Default, DeriveActiveEnum, EnumIter)]
//! #[sea_orm(rs_type = "String", db_type = "Text")]
//! pub enum MediaType {
//!     #[default]
//!     Movie,
//!     Series,
//! }
//! ```
//!
//! ## Example - display/parse only (no SeaORM)
//!
//! ```rust,ignore
//! #[string_enum]
//! #[derive(Clone, Debug, PartialEq)]
//! pub enum Lang {
//!     #[display("zh")]
//!     Chinese,
//!     #[display("en")]
//!     English,
//! }
//! ```

mod db_settings;
mod deref;
mod string_enum;

use proc_macro::TokenStream;
use syn::{DeriveInput, parse_macro_input};

pub(crate) fn snake_case(s: &str) -> String {
    let mut out = String::new();

    for (i, ch) in s.chars().enumerate() {
        if ch.is_uppercase() {
            if i != 0 {
                out.push('_');
            }
            for lc in ch.to_lowercase() {
                out.push(lc);
            }
        } else {
            out.push(ch);
        }
    }

    out
}

#[proc_macro_attribute]
pub fn string_enum(_args: TokenStream, input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    match string_enum::expand_string_enum(&input) {
        Ok(ts) => ts.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

#[proc_macro_attribute]
pub fn db_settings(_args: TokenStream, input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    match db_settings::expand_db_settings(&input) {
        Ok(ts) => ts.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

#[proc_macro_derive(Deref)]
pub fn derive_deref(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    match deref::expand_deref(&input) {
        Ok(ts) => ts.into(),
        Err(e) => e.to_compile_error().into(),
    }
}
