// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

use {
  crate::error::{Error, ErrorAccumulator},
  proc_macro2::TokenStream,
  quote::{ToTokens, spanned::Spanned as quote_spanned},
  syn::{Item, Type, spanned::Spanned},
};

// The AST is very simple for this macro, as you can _only_ attach it to
// a function.
#[derive(Debug, Clone)]
pub struct Ast {
  pub name: String,
  pub fields: Vec<Field>,
}

#[derive(Debug, Clone)]
pub struct Field {
  pub(crate) name: String,

  pub(crate) ty: FieldType,

  // the field is wrapped in an outer Vec<T>
  // Vec can only contain NodeId (which is implicit)
  pub(crate) is_vec: bool,

  // the field is wrapped in an outer Option<T>
  pub(crate) is_optional: bool,

  // the field is wrapped in Field<T> indicating it is embedded on the struct
  pub(crate) is_field: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum FieldType {
  Unknown,
  #[allow(dead_code)]
  Error(String),

  // Literal path to a type
  Path(String),
  // The type is a literal Enum
  Enum(String),
  // Only a single Node type is valid
  SingleNode(String),
  // Multiple Node types are valid
  MultipleNode(Vec<String>),

  // An existing literal Enum, that maps to a Node
  EnumNodeId(String),
  // Use the literal provided type
  Literal(&'static str),
  // The type is a string
  String,
  // laburnum::Span - for storing span references
  Span,
  // laburnum::Ident - for storing identifier hashes
  Ident,
  // laburnum::Spanned<laburnum::Ident> - (Ident, Span) tuple
  SpannedIdent,
}

// Parsing a field
//
// In the CST
// -> Option<FieldType> = { is_option: true, ty: FieldType }
// -> FieldType = { is_option: false, ty: FieldType }
//
// In the AST, a field is either
//
// - NodeId<T>, which means there's only one type of node that can go there
// - NodeId<A, B, C>, which means there's an enum of nodes that can go there
// - Vec<T>, which means the underlying type is a Vec<NodeId>
//
// Fields wrapped in Option<T> don't need to be `Some` for the AST to be valid.
// But all fields are implemented as `Option<T>` in the AST.

pub fn parse(ts: TokenStream) -> Result<Ast, syn::Error> {
  let item = ts;
  let span = item.__span();

  match syn::parse2::<Item>(item) {
    | Ok(Item::Struct(item_stct)) => {
      let name = item_stct.ident.clone().to_string().trim().to_string();

      let fields = {
        match item_stct.fields {
          | syn::Fields::Named(fields_named) => {
            parse_fields_with_accumulation(&fields_named.named)?
          },
          | _ => unimplemented!(),
        }
      };

      Ok(Ast { name, fields })
    },
    | Ok(_) => {
      let error_details = Error::ParseItemNotStruct(span).get();
      let mut syn_error =
        syn::Error::new(error_details.span, &error_details.message);

      // Add help and hints if available
      if let Some(help) = &error_details.help {
        let help_error =
          syn::Error::new(error_details.span, format!("help: {help}"));
        syn_error.combine(help_error);
      }
      if let Some(hints) = &error_details.hints {
        let hints_error =
          syn::Error::new(error_details.span, format!("hint: {hints}"));
        syn_error.combine(hints_error);
      }

      Err(syn_error)
    },
    | Err(parse_error) => {
      let error_details = Error::Parser(span).get();
      let mut combined_error =
        syn::Error::new(error_details.span, &error_details.message);
      combined_error.combine(parse_error);
      Err(combined_error)
    },
  }
}

/// Parse all fields and accumulate any errors found
fn parse_fields_with_accumulation(
  fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
) -> Result<Vec<Field>, syn::Error> {
  let mut accumulator = ErrorAccumulator::new();
  let mut parsed_fields = Vec::new();

  for field in fields {
    match parse_field(field) {
      | Ok(parsed_field) => {
        parsed_fields.push(parsed_field);
      },
      | Err(error) => {
        accumulator.add_error(error);
      },
    }
  }

  accumulator.into_result(parsed_fields)
}

fn parse_field(f: &syn::Field) -> Result<Field, Error> {
  let name = match &f.ident {
    | Some(ident) => ident.to_string(),
    | None => {
      return Err(Error::MissingFieldIdentifier(f.span()));
    },
  };

  match &f.ty {
    | Type::Path(type_path) => {
      let mut segments = type_path.path.segments.clone().into_iter();
      let Some(seg) = segments.next() else {
        return Err(Error::EmptyTypePath(type_path.span()));
      };

      let fld = Field {
        name,
        ty: FieldType::Unknown,
        is_vec: false,
        is_optional: false,
        is_field: false,
      };

      parse_path_segment(type_path, &seg, fld)
    },
    | _ => Err(Error::UnsupportedFieldType(
      f.ty.span(),
      format!("{:?}", f.ty),
    )),
  }
}

// TODO: all these errors could be helpful (gold-1up)

fn format_path(ts: &TokenStream) -> String {
  ts.to_string().replace(' ', "")
}

fn parse_path_segment(
  type_path: &syn::TypePath,
  seg: &syn::PathSegment,
  f: Field,
) -> Result<Field, Error> {
  let s = seg.ident.clone().to_string();

  // TODO: rename these to match the validation requirement (gold-5v3)
  // ie, OneOf, OneOrMore, ZeroOrMore, ZeroOrOne
  match s.as_str() {
    | "Option" => parse_field_option(type_path, seg, f),
    | "NodeId" => parse_field_nodeid(type_path, seg, f),
    | "EnumNodeId" => parse_field_enum_nodeid(type_path, seg, f),
    | "Vec" => parse_field_vec(type_path, seg, f),
    | "Field" => parse_field_field(type_path, seg, f),
    | "Enum" => parse_field_enum(type_path, seg, f),
    | "Spanned" => parse_field_spanned(type_path, seg, f),
    | "crate" => Ok(Field {
      ty: FieldType::Path(format_path(&type_path.to_token_stream())),
      ..f
    }),

    // Terminals
    | "String" => parse_field_string(type_path, seg, f),
    | "Span" => Ok(Field {
      ty: FieldType::Span,
      ..f
    }),
    | "Ident" => Ok(Field {
      ty: FieldType::Ident,
      ..f
    }),
    | "bool" => parse_field_literal(type_path, seg, f, "bool"),
    | "usize" => parse_field_literal(type_path, seg, f, "usize"),
    | "u8" => parse_field_literal(type_path, seg, f, "u8"),
    | "u16" => parse_field_literal(type_path, seg, f, "u16"),
    | "u32" => parse_field_literal(type_path, seg, f, "u32"),
    | "u64" => parse_field_literal(type_path, seg, f, "u64"),
    | "u128" => parse_field_literal(type_path, seg, f, "u128"),
    | "isize" => parse_field_literal(type_path, seg, f, "isize"),
    | "i8" => parse_field_literal(type_path, seg, f, "i8"),
    | "i16" => parse_field_literal(type_path, seg, f, "i16"),
    | "i32" => parse_field_literal(type_path, seg, f, "i32"),
    | "i64" => parse_field_literal(type_path, seg, f, "i64"),
    | "i128" => parse_field_literal(type_path, seg, f, "i128"),
    | "f32" => parse_field_literal(type_path, seg, f, "f32"),
    | "f64" => parse_field_literal(type_path, seg, f, "f64"),
    | _ => {
      let type_name: &'static str =
        Box::leak(seg.ident.to_string().into_boxed_str());
      parse_field_literal(type_path, seg, f, type_name)
    },
  }
}

fn parse_field_nodeid(
  _type_path: &syn::TypePath,
  seg: &syn::PathSegment,
  f: Field,
) -> Result<Field, Error> {
  if seg.ident.to_string().as_str() != "NodeId" {
    return Err(Error::InvalidFieldTypeMessage(
      seg.__span(),
      "Expected NodeId to have type arguments".to_string(),
    ));
  }

  let field_ty = match &seg.arguments {
    | syn::PathArguments::AngleBracketed(args) => {
      let args_len = args.args.len();

      match args_len {
        | 0 => {
          return Err(Error::InvalidFieldTypeMessage(
            seg.__span(),
            "Expected NodeId to have type arguments".to_string(),
          ));
        },
        | 1 => {
          match args.args.first().unwrap() {
            | syn::GenericArgument::Type(ty) => {
              FieldType::SingleNode(format_path(&ty.to_token_stream()))
            },
            | _ => {
              return Err(Error::InvalidFieldTypeMessage(
                seg.__span(),
                "Expected NodeId to have type arguments".to_string(),
              ));
            },
          }
        },
        | _ => {
          let mut tys = Vec::with_capacity(args_len);

          for arg in args.args.iter() {
            match arg {
              | syn::GenericArgument::Type(ty) => {
                tys.push(format_path(&ty.to_token_stream()));
              },
              | _ => {
                return Err(Error::InvalidFieldTypeMessage(
                  seg.__span(),
                  "Expected NodeId to have type arguments".to_string(),
                ));
              },
            }
          }

          FieldType::MultipleNode(tys)
        },
      }
    },
    | _ => {
      return Err(Error::InvalidFieldTypeMessage(
        seg.__span(),
        "Expected NodeId to have at least one type argument".to_string(),
      ));
    },
  };

  Ok(Field { ty: field_ty, ..f })
}

fn parse_field_enum_nodeid(
  _type_path: &syn::TypePath,
  seg: &syn::PathSegment,
  f: Field,
) -> Result<Field, Error> {
  if seg.ident.to_string().as_str() != "EnumNodeId" {
    return Err(Error::InvalidFieldTypeMessage(
      seg.__span(),
      "Expected NodeId to have type arguments".to_string(),
    ));
  }

  let field_ty = match &seg.arguments {
    | syn::PathArguments::AngleBracketed(args) => match args.args.len() {
      | 0 => {
        return Err(Error::InvalidFieldTypeMessage(
          seg.__span(),
          "Expected EnumNodeId to have type argument".to_string(),
        ));
      },
      | 1 => match args.args.first().unwrap() {
        | syn::GenericArgument::Type(ty) => {
          FieldType::EnumNodeId(format_path(&ty.to_token_stream()))
        },
        | _ => {
          return Err(Error::InvalidFieldTypeMessage(
            seg.__span(),
            "Expected EnumNodeId to have type arguments".to_string(),
          ));
        },
      },
      | _ => {
        return Err(Error::InvalidFieldTypeMessage(
          seg.__span(),
          "Expected EnumNodeId to have only one type argument".to_string(),
        ));
      },
    },
    | _ => {
      return Err(Error::InvalidFieldTypeMessage(
        seg.__span(),
        "Expected EnumNodeId to have one type argument".to_string(),
      ));
    },
  };

  Ok(Field { ty: field_ty, ..f })
}

fn parse_field_field(
  _type_path: &syn::TypePath,
  seg: &syn::PathSegment,
  f: Field,
) -> Result<Field, Error> {
  if seg.ident.to_string().as_str() != "Field" {
    return Err(Error::InvalidFieldTypeMessage(
      seg.__span(),
      "Expected Field to have type arguments".to_string(),
    ));
  }

  let field = match &seg.arguments {
    | syn::PathArguments::AngleBracketed(args) => match args.args.len() {
      | 0 => {
        return Err(Error::InvalidFieldTypeMessage(
          seg.__span(),
          "Expected Field to have type arguments".to_string(),
        ));
      },
      | 1 => match args.args.first().unwrap() {
        | syn::GenericArgument::Type(ty) => match ty {
          | Type::Path(p) => {
            let mut segments = p.path.segments.clone().into_iter();
            let seg = segments
              .next()
              .expect("Expected Type::Path to have at least one segment");
            parse_path_segment(p, &seg, f)?
          },
          | _ => {
            return Err(Error::InvalidFieldTypeMessage(
              seg.__span(),
              "Invalid argument type for 'Field'".to_string(),
            ));
          },
        },
        | _ => {
          return Err(Error::InvalidFieldTypeMessage(
            seg.__span(),
            "Expected Field to have type arguments".to_string(),
          ));
        },
      },
      | _ => {
        return Err(Error::InvalidFieldTypeMessage(
          seg.__span(),
          "Expected Field to have only on type argument".to_string(),
        ));
      },
    },
    | _ => {
      return Err(Error::InvalidFieldTypeMessage(
        seg.__span(),
        "Expected Field to have at least one type argument".to_string(),
      ));
    },
  };

  Ok(Field {
    is_field: true,
    ..field
  })
}

fn parse_field_enum(
  _type_path: &syn::TypePath,
  seg: &syn::PathSegment,
  f: Field,
) -> Result<Field, Error> {
  if seg.ident.to_string().as_str() != "Enum" {
    return Err(Error::InvalidFieldTypeMessage(
      seg.__span(),
      "Expected Field to have type arguments".to_string(),
    ));
  }

  let field_ty = match &seg.arguments {
    | syn::PathArguments::AngleBracketed(args) => match args.args.len() {
      | 0 => {
        return Err(Error::InvalidFieldTypeMessage(
          seg.__span(),
          "Expected NodeId to have type arguments".to_string(),
        ));
      },
      | 1 => match args.args.first().unwrap() {
        | syn::GenericArgument::Type(ty) => {
          FieldType::Enum(format_path(&ty.to_token_stream()))
        },
        | _ => {
          return Err(Error::InvalidFieldTypeMessage(
            seg.__span(),
            "Expected NodeId to have type arguments".to_string(),
          ));
        },
      },
      | _ => {
        return Err(Error::InvalidFieldTypeMessage(
          seg.__span(),
          "Expected Enum to have only one type arguments".to_string(),
        ));
      },
    },
    | _ => {
      return Err(Error::InvalidFieldTypeMessage(
        seg.__span(),
        "Expected NodeId to have at least one type argument".to_string(),
      ));
    },
  };

  Ok(Field { ty: field_ty, ..f })
}

fn parse_field_vec(
  _type_path: &syn::TypePath,
  seg: &syn::PathSegment,
  f: Field,
) -> Result<Field, Error> {
  if seg.ident.to_string().as_str() != "Vec" {
    return Err(Error::InvalidFieldTypeMessage(
      seg.__span(),
      "Expected Vec".to_string(),
    ));
  }

  let field = match &seg.arguments {
    | syn::PathArguments::AngleBracketed(args) => match args.args.len() {
      | 0 => {
        return Err(Error::InvalidFieldTypeMessage(
          seg.__span(),
          "Vec must have at least one type argument".to_string(),
        ));
      },
      | 1 => match args.args.first().unwrap() {
        | syn::GenericArgument::Type(ty) => match ty {
          | Type::Path(p) => {
            let mut segments = p.path.segments.clone().into_iter();
            let seg = segments
              .next()
              .expect("Expected Type::Path to have at least one segment");

            parse_path_segment(p, &seg, f)?
          },
          | _ => {
            return Err(Error::InvalidFieldTypeMessage(
              seg.__span(),
              "Invalid argument type for 'Vec'".to_string(),
            ));
          },
        },
        | _ => {
          return Err(Error::InvalidFieldTypeMessage(
            seg.__span(),
            "Expected Vec to have type arguments".to_string(),
          ));
        },
      },
      | _ => Field {
        ty: FieldType::MultipleNode(
          args
            .args
            .iter()
            .map(|a| format_path(&a.to_token_stream()))
            .collect(),
        ),
        ..f
      },
    },
    | _ => {
      return Err(Error::InvalidFieldTypeMessage(
        seg.__span(),
        "Vec must have at least one type argument".to_string(),
      ));
    },
  };

  Ok(Field {
    is_vec: true,
    ..field
  })
}

fn parse_field_option(
  type_path: &syn::TypePath,
  seg: &syn::PathSegment,
  f: Field,
) -> Result<Field, Error> {
  if seg.ident.to_string().as_str() != "Option" {
    return Err(Error::InvalidFieldTypeMessage(
      seg.__span(),
      "Invalid field type: expected 'Option'".to_string(),
    ));
  }

  let field = match &seg.arguments {
    | syn::PathArguments::AngleBracketed(args) => match args.args.len() {
      | 0 => {
        return Err(Error::InvalidFieldTypeMessage(
          seg.__span(),
          "No arguments provided for 'Option' type".to_string(),
        ));
      },
      | 1 => match args.args.first().unwrap() {
        | syn::GenericArgument::Type(ty) => match ty {
          | Type::Path(p) => {
            let mut segments = p.path.segments.clone().into_iter();
            let seg = segments
              .next()
              .expect("Expected Type::Path to have at least one segment");
            parse_path_segment(type_path, &seg, f)?
          },
          | _ => {
            return Err(Error::InvalidFieldTypeMessage(
              seg.__span(),
              "Invalid argument type for 'Option'".to_string(),
            ));
          },
        },
        | _ => {
          return Err(Error::InvalidFieldTypeMessage(
            seg.__span(),
            "Invalid generic argument for 'Option'".to_string(),
          ));
        },
      },
      | _ => {
        return Err(Error::InvalidFieldTypeMessage(
          seg.__span(),
          "Invalid number of arguments for 'Option'".to_string(),
        ));
      },
    },
    | _ => {
      return Err(Error::InvalidFieldTypeMessage(
        seg.__span(),
        "Invalid path arguments: expected 'AngleBracketed'".to_string(),
      ));
    },
  };

  Ok(Field {
    is_optional: true,
    ..field
  })
}

fn parse_field_string(
  _type_path: &syn::TypePath,
  seg: &syn::PathSegment,
  f: Field,
) -> Result<Field, Error> {
  if seg.ident.to_string().as_str() != "String" {
    return Err(Error::InvalidFieldTypeMessage(
      seg.__span(),
      "Invalid field type: expected 'String'".to_string(),
    ));
  }

  if !seg.arguments.is_empty() {
    return Err(Error::InvalidFieldTypeMessage(
      seg.__span(),
      "String does not take any arguments".to_string(),
    ));
  }

  Ok(Field {
    ty: FieldType::String,
    ..f
  })
}

fn parse_field_literal(
  _type_path: &syn::TypePath,
  seg: &syn::PathSegment,
  f: Field,
  lit_ty: &'static str,
) -> Result<Field, Error> {
  if !seg.arguments.is_empty() {
    return Err(Error::InvalidFieldTypeMessage(
      seg.__span(),
      "literal types do not take any arguments".to_string(),
    ));
  }

  Ok(Field {
    ty: FieldType::Literal(lit_ty),
    ..f
  })
}

fn parse_field_spanned(
  _type_path: &syn::TypePath,
  seg: &syn::PathSegment,
  f: Field,
) -> Result<Field, Error> {
  if seg.ident.to_string().as_str() != "Spanned" {
    return Err(Error::InvalidFieldTypeMessage(
      seg.__span(),
      "Expected Spanned".to_string(),
    ));
  }

  let field_ty = match &seg.arguments {
    | syn::PathArguments::AngleBracketed(args) => match args.args.len() {
      | 0 => {
        return Err(Error::InvalidFieldTypeMessage(
          seg.__span(),
          "Expected Spanned to have type argument".to_string(),
        ));
      },
      | 1 => match args.args.first().unwrap() {
        | syn::GenericArgument::Type(syn::Type::Path(p)) => {
          let inner_seg = p.path.segments.first();
          if let Some(inner_seg) = inner_seg {
            if inner_seg.ident.to_string().as_str() == "Ident" {
              FieldType::SpannedIdent
            } else {
              return Err(Error::InvalidFieldTypeMessage(
                inner_seg.__span(),
                format!(
                  "Expected Spanned<Ident>, found Spanned<{}>",
                  inner_seg.ident
                ),
              ));
            }
          } else {
            return Err(Error::InvalidFieldTypeMessage(
              args.__span(),
              "Expected Spanned<Ident>".to_string(),
            ));
          }
        },
        | _ => {
          return Err(Error::InvalidFieldTypeMessage(
            seg.__span(),
            "Expected Spanned<Ident>".to_string(),
          ));
        },
      },
      | _ => {
        return Err(Error::InvalidFieldTypeMessage(
          seg.__span(),
          "Spanned only takes one type argument".to_string(),
        ));
      },
    },
    | _ => {
      return Err(Error::InvalidFieldTypeMessage(
        seg.__span(),
        "Expected Spanned to have type argument".to_string(),
      ));
    },
  };

  Ok(Field { ty: field_ty, ..f })
}
