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

mod attributes;
mod models;
mod orm;
mod utils;

/// `#[derive(ToLOrm)]`
/// generate methods for Object Relational Mapping.
///
/// attributes:
///
/// `#[lorm(pk)]`
///  Annotated field is marked as being the primary key and can only be generated at insertion time.
///  This field is also automatically considered as a `#[lorm(by)]` field.
///  - If `#[lorm(new)]` is specified, it will use its struct method to generate a new pk at insertion time
///  - If `#[lorm(is_set)]` is specified, it will invoke the callable as `(callable)(&pk_value)` to check if the pk is set. Otherwise it compares the pk value with `Default::default()`.
///  - If `#[lorm(readonly)]` is specified, it will ignore is_set `#[lorm(new)]` and `#[lorm(is_set)]` and let the database handles the field
///
/// `#[lorm(rename="name")]`
///   - at struct level to rename at table name
///   - at field level to rename at column name
///
///   by default, a table name is the struct name pluralized and converted to table case: UserDetail => user_details.
///   by default, a field name is converted to snake_case: UserDetail => user_detail.
///
/// `#[lorm(skip)]`
///  Ignore field for persistence operations. Using sqlx::FromRow, skip needs `#[lorm(skip)]` and `#[sqlx(skip)]`
///
/// `#[lorm(readonly)]`
///  readonly attribute. Cannot be updated not inserted.
///  Special cases to consider:
///   - If applied to the primary key, key generation is left to the database. No update possible as it is the primary key.
///   - If applied to create_at or updated_at field, timestamp generation is left to the database. No update possible.
///
/// `#[lorm(by)]`
///  Generates query methods for this field:
///  - `by_<field>(executor, value)` - Find single record by field value
///  - `with_<field>(executor, value)` - Find all records matching field value
///  - `where_<field>(Where, value)` - Filter in select() query builder
///  - `where_between_<field>(start, end)` - Range filter in select() query builder
///  - `order_by_<field>()` - Order results by this field (chain with `.asc()` or `.desc()`)
///  - `group_by_<field>()` - Group results by this field
///
/// `#[lorm(created_at)]`
///  Add the `#[lorm(created_at)]` annotation to mark the field as the `created_at` field.
///  - If `#[lorm(new)]` is specified, it will use its method to update the time upon insertion
///  - If `#[lorm(readonly)]` is specified, it will ignore is_set `#[lorm(new)]` and let the database handles the field
///
/// `#[lorm(updated_at)]`
///  Add the `#[lorm(updated_at)]` annotation to mark the field as the `updated_at` field.
///  - If `#[lorm(new)]` is specified, it will use its method to update the time upon insertion and update
///  - If `#[lorm(readonly)]` is specified, it will ignore is_set `#[lorm(new)]` and let the database handles the field
///
/// `#[lorm(new="module::path::class::new_custom()")]`
///  Add the `#[lorm(new="module::path::class::new_custom()")]` annotation to use a custom creation method.
///  - The function call is expected to return an instance
///  - When not provided, the type::new() method is called
///
/// `#[lorm(is_set="Uuid::is_nil")]`
///  Uses a callable path to check if the field has its default (unset) value.
///  Invoked as `(callable)(&field_value)` and must return `bool`.
///  Defaults to comparing with `Default::default()` (requires `Default + PartialEq`).
///
#[proc_macro_derive(ToLOrm,
    attributes(
        lorm,
        sqlx,
        // lorm(pk),
        // lorm(by),
        // lorm(skip),
        // lorm(readonly),
        // lorm(new="module::path::class::new_custom()"),
        // lorm(is_set="Uuid::is_nil"),
        // lorm(rename="name"),
        // lorm(created_at),
        // lorm(updated_at),
    )
)]
pub fn sql_derive_to_orm(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    match orm::expand_derive_to_orm(&input) {
        Ok(ts) => ts,
        Err(e) => e.to_compile_error().into(),
    }
}
