mod r#const;
mod r#enum;
mod r#fn;
pub(crate) mod r#struct;
use std::{cell::RefCell, collections::HashMap, env};
use once_cell::sync::Lazy;
use syn::Type;
pub static NAPI_RS_CLI_VERSION: Lazy<semver::Version> = Lazy::new(|| {
  let version = env::var("CARGO_CFG_NAPI_RS_CLI_VERSION").unwrap_or_else(|_| "0.0.0".to_string());
  semver::Version::parse(&version).unwrap_or_else(|_| semver::Version::new(0, 0, 0))
});
pub static NAPI_RS_CLI_VERSION_WITH_SHARED_CRATES_FIX: Lazy<semver::Version> =
  Lazy::new(|| semver::Version::new(2, 15, 1));
#[derive(Default, Debug)]
pub struct TypeDef {
  pub kind: String,
  pub name: String,
  pub original_name: Option<String>,
  pub def: String,
  pub js_mod: Option<String>,
  pub js_doc: String,
}
thread_local! {
  static ALIAS: RefCell<HashMap<String, String>> = Default::default();
}
fn add_alias(name: String, alias: String) {
  ALIAS.with(|aliases| {
    aliases.borrow_mut().insert(name, alias);
  });
}
pub fn js_doc_from_comments(comments: &[String]) -> String {
  if comments.is_empty() {
    return "".to_owned();
  }
  if comments.len() == 1 {
    return format!("/**{} */\n", comments[0]);
  }
  format!(
    "/**\n{} */\n",
    comments
      .iter()
      .map(|c| format!(" *{}\n", c))
      .collect::<Vec<String>>()
      .join("")
  )
}
fn escape_json(src: &str) -> String {
  use std::fmt::Write;
  let mut escaped = String::with_capacity(src.len());
  let mut utf16_buf = [0u16; 2];
  let mut pending_backslash = false;
  for c in src.chars() {
    if pending_backslash {
      match c {
        'b' | 'f' | 'n' | 'r' | 't' | 'u' | '"' => escaped += "\\",
        _ => escaped += "\\\\",
      }
      pending_backslash = false;
    }
    match c {
      '\x08' => escaped += "\\b",
      '\x0c' => escaped += "\\f",
      '\n' => escaped += "\\n",
      '\r' => escaped += "\\r",
      '\t' => escaped += "\\t",
      '"' => escaped += "\\\"",
      '\\' => {
        pending_backslash = true;
      }
      ' ' => escaped += " ",
      c if c.is_ascii_graphic() => escaped.push(c),
      c => {
        let encoded = c.encode_utf16(&mut utf16_buf);
        for utf16 in encoded {
          write!(escaped, "\\u{:04X}", utf16).unwrap();
        }
      }
    }
  }
  if pending_backslash {
    escaped += "\\\\"
  }
  escaped
}
impl ToString for TypeDef {
  fn to_string(&self) -> String {
    let pkg_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME is not set");
    let js_mod = if let Some(js_mod) = &self.js_mod {
      format!(", \"js_mod\": \"{}\"", js_mod)
    } else {
      "".to_owned()
    };
    let original_name = if let Some(original_name) = &self.original_name {
      format!(", \"original_name\": \"{}\"", original_name)
    } else {
      "".to_owned()
    };
    let prefix = if *NAPI_RS_CLI_VERSION >= *NAPI_RS_CLI_VERSION_WITH_SHARED_CRATES_FIX {
      format!("{}:", pkg_name)
    } else {
      "".to_string()
    };
    format!(
      r#"{}{{"kind": "{}", "name": "{}", "js_doc": "{}", "def": "{}"{}{}}}"#,
      prefix,
      self.kind,
      self.name,
      escape_json(&self.js_doc),
      escape_json(&self.def),
      original_name,
      js_mod,
    )
  }
}
pub trait ToTypeDef {
  fn to_type_def(&self) -> Option<TypeDef>;
}
static KNOWN_TYPES: Lazy<HashMap<&'static str, (&'static str, bool, bool)>> = Lazy::new(|| {
  let mut map = HashMap::default();
  map.extend(crate::PRIMITIVE_TYPES.iter().cloned());
  map.extend([
    ("JsObject", ("object", false, false)),
    ("Object", ("object", false, false)),
    ("Array", ("unknown[]", false, false)),
    ("Value", ("any", false, false)),
    ("Map", ("Record<string, any>", false, false)),
    ("HashMap", ("Record<{}, {}>", false, false)),
    ("ArrayBuffer", ("ArrayBuffer", false, false)),
    ("JsArrayBuffer", ("ArrayBuffer", false, false)),
    ("Int8Array", ("Int8Array", false, false)),
    ("Uint8Array", ("Uint8Array", false, false)),
    ("Uint8ClampedArray", ("Uint8ClampedArray", false, false)),
    ("Int16Array", ("Int16Array", false, false)),
    ("Uint16Array", ("Uint16Array", false, false)),
    ("Int32Array", ("Int32Array", false, false)),
    ("Uint32Array", ("Uint32Array", false, false)),
    ("Float32Array", ("Float32Array", false, false)),
    ("Float64Array", ("Float64Array", false, false)),
    ("BigInt64Array", ("BigInt64Array", false, false)),
    ("BigUint64Array", ("BigUint64Array", false, false)),
    ("DataView", ("DataView", false, false)),
    ("DateTime", ("Date", false, false)),
    ("NaiveDateTime", ("Date", false ,false)),
    ("Date", ("Date", false, false)),
    ("JsDate", ("Date", false, false)),
    ("JsBuffer", ("Buffer", false, false)),
    ("Buffer", ("Buffer", false, false)),
    ("Vec", ("Array<{}>", false, false)),
    ("Result", ("Error | {}", false, true)),
    ("Error", ("Error", false, false)),
    ("JsError", ("Error", false, false)),
    ("JsTypeError", ("TypeError", false, false)),
    ("JsRangeError", ("RangeError", false, false)),
    ("ClassInstance", ("{}", false, false)),
    ("Function", ("({}) => {}", true, false)),
    ("FunctionRef", ("({}) => {}", true, false)),
    ("Either", ("{} | {}", false, true)),
    ("Either3", ("{} | {} | {}", false, true)),
    ("Either4", ("{} | {} | {} | {}", false, true)),
    ("Either5", ("{} | {} | {} | {} | {}", false, true)),
    ("Either6", ("{} | {} | {} | {} | {} | {}", false, true)),
    ("Either7", ("{} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either8", ("{} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either9", ("{} | {} | {} | {} | {} | {} | {} | {} | {}",false, true)),
    ("Either10", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either11", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either12", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either13", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either14", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either15", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either16", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either17", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either18", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either19", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either20", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either21", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either22", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either23", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either24", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either25", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("Either26", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
    ("external", ("object", false, false)),
    ("Promise", ("Promise<{}>", false, false)),
    ("AbortSignal", ("AbortSignal", false, false)),
    ("JsGlobal", ("typeof global", false, false)),
    ("External", ("ExternalObject<{}>", false, false)),
    ("unknown", ("unknown", false, false)),
    ("Unknown", ("unknown", false, false)),
    ("JsUnknown", ("unknown", false, false)),
    ("This", ("this", false, false)),
    ("Rc", ("{}", false, false)),
    ("Arc", ("{}", false, false)),
    ("Mutex", ("{}", false, false)),
  ]);
  map
});
fn fill_ty(template: &str, args: Vec<String>) -> String {
  let matches = template.match_indices("{}").collect::<Vec<_>>();
  if args.len() != matches.len() {
    return String::from("any");
  }
  let mut ret = String::from("");
  let mut prev = 0;
  matches.into_iter().zip(args).for_each(|((index, _), arg)| {
    ret.push_str(&template[prev..index]);
    ret.push_str(&arg);
    prev = index + 2;
  });
  ret.push_str(&template[prev..]);
  ret
}
fn is_ts_union_type(rust_ty: &str) -> bool {
  KNOWN_TYPES
    .get(rust_ty)
    .map(|&(_, _, is_union_type)| is_union_type)
    .unwrap_or(false)
}
const TSFN_RUST_TY: &str = "ThreadsafeFunction";
const FUNCTION_TY: &str = "Function";
const FUNCTION_REF_TY: &str = "FunctionRef";
fn is_generic_function_type(rust_ty: &str) -> bool {
  rust_ty == TSFN_RUST_TY || rust_ty == FUNCTION_TY || rust_ty == FUNCTION_REF_TY
}
fn is_ts_function_type_notation(ty: &Type) -> bool {
  match ty {
    Type::Path(syn::TypePath { qself: None, path }) => {
      if let Some(syn::PathSegment { ident, .. }) = path.segments.last() {
        let rust_ty = ident.to_string();
        return KNOWN_TYPES
          .get(&*rust_ty)
          .map(|&(_, is_fn, _)| is_fn)
          .unwrap_or(false);
      }
      false
    }
    _ => false,
  }
}
pub fn ty_to_ts_type(
  ty: &Type,
  is_return_ty: bool,
  is_struct_field: bool,
  convert_tuple_to_variadic: bool,
) -> (String, bool) {
  match ty {
    Type::Reference(r) => ty_to_ts_type(&r.elem, is_return_ty, is_struct_field, false),
    Type::Tuple(tuple) => {
      if tuple.elems.is_empty() {
        if convert_tuple_to_variadic {
          if is_return_ty {
            ("void".to_owned(), false)
          } else {
            ("".to_owned(), false)
          }
        } else {
          ("undefined".to_owned(), false)
        }
      } else if convert_tuple_to_variadic {
        let variadic = &tuple
          .elems
          .iter()
          .enumerate()
          .map(|(i, arg)| {
            let (ts_type, is_optional) = ty_to_ts_type(arg, false, false, false);
            r#fn::FnArg {
              arg: format!("arg{}", i),
              ts_type,
              is_optional,
            }
          })
          .collect::<r#fn::FnArgList>();
        (format!("{}", variadic), false)
      } else {
        (
          format!(
            "[{}]",
            tuple
              .elems
              .iter()
              .map(|elem| ty_to_ts_type(elem, false, false, false).0)
              .collect::<Vec<_>>()
              .join(", ")
          ),
          false,
        )
      }
    }
    Type::Path(syn::TypePath { qself: None, path }) => {
      let mut ts_ty = None;
      if let Some(syn::PathSegment { ident, arguments }) = path.segments.last() {
        let rust_ty = ident.to_string();
        let is_ts_union_type = is_ts_union_type(&rust_ty);
        let args = if let syn::PathArguments::AngleBracketed(arguments) = arguments {
          arguments
            .args
            .iter()
            .enumerate()
            .filter_map(|(index, arg)| match arg {
              syn::GenericArgument::Type(generic_ty) => Some(ty_to_ts_type(
                generic_ty,
                index == 1 && is_generic_function_type(&rust_ty),
                false,
                is_generic_function_type(&rust_ty),
              ))
              .map(|(mut ty, is_optional)| {
                if is_ts_union_type && is_ts_function_type_notation(generic_ty) {
                  ty = format!("({})", ty);
                }
                (ty, is_optional)
              }),
              _ => None,
            })
            .collect::<Vec<_>>()
        } else {
          vec![]
        };
        if rust_ty == "Result" && is_return_ty {
          ts_ty = Some(args.first().unwrap().to_owned());
        } else if rust_ty == "Option" {
          ts_ty = args.first().map(|(arg, _)| {
            (
              if is_struct_field {
                arg.to_string()
              } else if is_return_ty {
                format!("{} | null", arg)
              } else {
                format!("{} | undefined | null", arg)
              },
              true,
            )
          });
        } else if rust_ty == "AsyncTask" {
          ts_ty = r#struct::TASK_STRUCTS.with(|t| {
            let (output_type, _) = args.first().unwrap().to_owned();
            if let Some(o) = t.borrow().get(&output_type) {
              Some((format!("Promise<{}>", o), false))
            } else {
              Some(("Promise<unknown>".to_owned(), false))
            }
          });
        } else if rust_ty == "Reference" || rust_ty == "WeakReference" {
          ts_ty = r#struct::TASK_STRUCTS.with(|t| {
            if let Some(arg) = args.first() {
              let (output_type, _) = arg.to_owned();
              if let Some(o) = t.borrow().get(&output_type) {
                Some((o.to_owned(), false))
              } else {
                Some((output_type, false))
              }
            } else {
              Some((rust_ty, false))
            }
          });
        } else if let Some(&(known_ty, _, _)) = KNOWN_TYPES.get(rust_ty.as_str()) {
          if rust_ty == "()" && is_return_ty {
            ts_ty = Some(("void".to_owned(), false));
          } else if known_ty.contains("{}") {
            ts_ty = Some((
              fill_ty(known_ty, args.into_iter().map(|(arg, _)| arg).collect()),
              false,
            ));
          } else {
            ts_ty = Some((known_ty.to_owned(), false));
          }
        } else if let Some(t) = crate::typegen::r#struct::CLASS_STRUCTS
          .with(|c| c.borrow_mut().get(rust_ty.as_str()).cloned())
        {
          ts_ty = Some((t, false));
        } else if rust_ty == TSFN_RUST_TY {
          let fatal_tsfn = match args.get(1) {
            Some((arg, _)) => arg == "Fatal",
            _ => false,
          };
          let args = args.first().map(|(arg, _)| arg).unwrap();
          ts_ty = if fatal_tsfn {
            Some((format!("({}) => any", args), false))
          } else {
            Some((format!("(err: Error | null, {}) => any", args), false))
          };
        } else {
          let type_alias = ALIAS.with(|aliases| {
            aliases
              .borrow()
              .get(rust_ty.as_str())
              .map(|a| (a.to_owned(), false))
          });
          ts_ty = type_alias.or(Some((rust_ty, false)));
        }
      }
      let (ty, is_optional) = ts_ty.unwrap_or_else(|| ("any".to_owned(), false));
      (
        (convert_tuple_to_variadic && !is_return_ty)
          .then(|| format!("arg: {ty}"))
          .unwrap_or(ty),
        is_optional,
      )
    }
    Type::Group(g) => ty_to_ts_type(&g.elem, is_return_ty, is_struct_field, false),
    Type::Array(a) => {
      let (element_type, is_optional) =
        ty_to_ts_type(&a.elem, is_return_ty, is_struct_field, false);
      (format!("{}[]", element_type), is_optional)
    }
    Type::Paren(p) => {
      let (element_type, is_optional) =
        ty_to_ts_type(&p.elem, is_return_ty, is_struct_field, false);
      (element_type, is_optional)
    }
    _ => ("any".to_owned(), false),
  }
}