use proc_macro::{
  TokenStream,
  TokenTree::{self, *},
};

use cg2::{Generator, f};

use pm2_types::EnumRepr;

use crate::{get_idents, get_repr, seek_ident};

pub fn run(attr: TokenStream, mut item: TokenStream) -> TokenStream {
  let item_tokens: Vec<TokenTree> = item.clone().into_iter().collect();
  let attr_tokens: Vec<TokenTree> = attr.clone().into_iter().collect();

  let mut item_stream = item.clone().into_iter();

  let repr = if let Some(r) = get_repr(&item_tokens) {
    match EnumRepr::try_from(r.as_str()) {
      Ok(r) => Some(r),
      Err(r) => panic!("unknown repr: {r}"),
    }
  } else {
    None
  };

  if !seek_ident(&mut item_stream, "enum") {
    panic!("missing enum keyword");
  }

  let Ident(i) = item_stream.next().expect("missing name") else {
    panic!("name not an Ident");
  };

  let name = i.to_string();

  let Group(body) = item_stream.next().expect("missing body") else {
    panic!("generic params and where clauses are not allowed");
  };

  let body_tokens: Vec<TokenTree> = body.stream().into_iter().collect();

  let variants = get_enum_variants(&body_tokens);

  if variants.is_empty() {
    panic!("must have atleast 1 variant");
  }

  let opts = get_opts(&attr_tokens);

  let mut g = Generator::new();

  // constants
  g.implement()
    .space()
    .ident(&name)
    .block(|g: &mut Generator| {
      cg2::bind!($g, declare_const);

      // REPR
      declare_const!(pub REPR: Option<&str>, |g: &mut Generator| {
        if let Some(r) = repr {
          g.out(f!("Some({:?})", r.as_str()));
        } else {
          g.out("None");
        }
      });

      // NUM_VARIANTS
      declare_const!(pub NUM_VARIANTS: usize = variants.len());

      // FIRST_VARIANT
      // LAST_VARIANT
      declare_const!(pub FIRST_VARIANT: Self = f!("Self::{}", variants.first().unwrap()));
      declare_const!(pub LAST_VARIANT: Self = f!("Self::{}", variants.last().unwrap()));

      // MAX_DISCRIMINANT
      if let Some(r) = repr
        && r.is_primitive()
      {
        declare_const!(pub MAX_DISCRIMINANT: r.as_str(), "Self::LAST_VARIANT as _");
      }
    });

  for o in opts {
    match o {
      Opt::DerefDiscriminant => {
        let Some(r) = repr.filter(|r| r.is_primitive()) else {
          panic!("deref_discriminant: must have a primitive repr");
        };

        g.implement()
          .space()
          .out(f!("::core::ops::Deref for {name}"))
          .block(|g: &mut Generator| {
            g.out(f!("type Target = {};", r.as_str()));

            g.out("fn deref(&self) -> &Self::Target")
              .block(|g: &mut Generator| {
                g.unsafe_block("::core::mem::transmute(self)");
              });
          });
      }
    }
  }

  item.extend::<TokenStream>(g.into_code().parse().expect("failed to parse new code"));

  item
}

#[derive(Debug)]
enum Opt {
  // previously Seal
  DerefDiscriminant,
}

fn get_opts(tt: &[TokenTree]) -> Vec<Opt> {
  let mut opts = vec![];

  for t in tt.iter() {
    if let TokenTree::Ident(i) = t
      && i.to_string() == "deref_discriminant"
    {
      opts.push(Opt::DerefDiscriminant);
      continue;
    }
  }

  opts
}

pub fn get_enum_variants(tt: &[TokenTree]) -> Vec<String> {
  get_idents(tt)
}
