// Copyright 2018-2026 the Deno authors. MIT license.

mod r#enum;
mod r#struct;

use proc_macro2::TokenStream;
use quote::ToTokens;
use quote::quote;
use quote::quote_spanned;
use syn::Data;
use syn::DeriveInput;
use syn::Error;
use syn::parse2;
use syn::spanned::Spanned;

pub fn from_v8(item: TokenStream) -> Result<TokenStream, Error> {
  let input = parse2::<DeriveInput>(item)?;
  let span = input.span();
  let ident = input.ident;
  let ident_string = ident.to_string();

  let out = match input.data {
    Data::Struct(data) => {
      create_impl(ident, r#struct::get_body(ident_string, span, data)?)
    }
    Data::Enum(data) => {
      create_impl(ident, r#enum::get_body(ident_string, input.attrs, data)?)
    }
    Data::Union(_) => return Err(Error::new(span, "Unions are not supported")),
  };

  Ok(out)
}

fn convert_or_serde(
  serde: bool,
  span: proc_macro2::Span,
  value: TokenStream,
) -> TokenStream {
  if serde {
    quote_spanned! { span =>
      ::deno_core::serde_v8::from_v8(
        __scope,
        #value,
      ).map_err(::deno_error::JsErrorBox::from_err)?
    }
  } else {
    quote_spanned! { span =>
      ::deno_core::convert::FromV8::from_v8(
        __scope,
        #value,
      ).map_err(::deno_error::JsErrorBox::from_err)?
    }
  }
}

mod kw {
  syn::custom_keyword!(default);
  syn::custom_keyword!(required);
}

fn create_impl(ident: impl ToTokens, body: TokenStream) -> TokenStream {
  quote! {
    impl<'a> ::deno_core::convert::FromV8<'a> for #ident {
      type Error = ::deno_error::JsErrorBox;

      fn from_v8<'i>(
        __scope: &mut ::deno_core::v8::PinScope<'a, 'i>,
        __value: ::deno_core::v8::Local<'a, ::deno_core::v8::Value>,
      ) -> Result<Self, Self::Error>
      {
        #body
      }
    }
  }
}

#[cfg(test)]
mod tests {
  use std::path::PathBuf;

  use proc_macro2::Ident;
  use syn::Attribute;
  use syn::Item;
  use syn::Token;
  use syn::punctuated::Punctuated;

  use super::*;

  fn derives_from_v8<'a>(
    attrs: impl IntoIterator<Item = &'a Attribute>,
  ) -> bool {
    attrs.into_iter().any(|attr| {
      attr.path().is_ident("derive") && {
        let list = attr.meta.require_list().unwrap();
        let idents = list
          .parse_args_with(Punctuated::<Ident, Token![,]>::parse_terminated)
          .unwrap();
        idents.iter().any(|ident| ident == "FromV8")
      }
    })
  }

  fn expand_from_v8(item: impl ToTokens) -> TokenStream {
    from_v8(item.to_token_stream()).expect("Failed to generate FromV8")
  }

  #[testing_macros::fixture("conversion/from_v8/test_cases/*.rs")]
  fn test_proc_macro_sync(input: PathBuf) {
    crate::infra::run_macro_expansion_test(input, |file| {
      file.items.into_iter().filter_map(|item| match item {
        Item::Struct(struct_item) if derives_from_v8(&struct_item.attrs) => {
          Some(expand_from_v8(struct_item))
        }
        Item::Enum(enum_item) if derives_from_v8(&enum_item.attrs) => {
          Some(expand_from_v8(enum_item))
        }
        _ => None,
      })
    })
  }
}
