use crate::ast;
use alloc::{boxed::Box, ffi::CString};
use core::{error::Error as CoreError, ffi::c_char, ptr, slice};

pub mod visitor;

#[repr(C)]
pub struct ParseResult {
    program: *mut Program,
    error: Error,
}

#[repr(C)]
pub struct Error {
    code: ErrorCode,
    details: *const c_char,
}

#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ErrorCode {
    Ok,
    InvalidIDL,
    ParseError,
    NullPtr,
}

/// # Safety
///
/// See the safety documentation of [`slice::from_raw_parts`].
#[unsafe(no_mangle)]
pub unsafe extern "C" fn parse_idl(idl_ptr: *const u8, idl_len: u32) -> *mut ParseResult {
    let idl_slice = unsafe { slice::from_raw_parts(idl_ptr, idl_len as usize) };
    let idl_str = match str::from_utf8(idl_slice) {
        Ok(s) => s,
        Err(e) => return create_parse_error(ErrorCode::ParseError, e, "validate IDL string"),
    };

    let program = match ast::parse_idl(idl_str) {
        Ok(p) => p,
        Err(e) => return create_parse_error(ErrorCode::ParseError, e, "parse IDL"),
    };

    let result = ParseResult {
        error: Error {
            code: ErrorCode::Ok,
            details: ptr::null(),
        },
        program: Box::into_raw(Box::new(program)),
    };

    Box::into_raw(Box::new(result))
}

fn create_parse_error(
    code: ErrorCode,
    e: impl CoreError,
    context: &'static str,
) -> *mut ParseResult {
    let details = CString::new(format!("{context}: {e}")).unwrap();
    let result = ParseResult {
        error: Error {
            code,
            details: details.into_raw(),
        },
        program: ptr::null_mut(),
    };
    Box::into_raw(Box::new(result))
}

/// # Safety
///
/// Pointer must be obtained from [`parse_idl`].
#[unsafe(no_mangle)]
pub unsafe extern "C" fn free_parse_result(result: *mut ParseResult) {
    if result.is_null() {
        return;
    }
    unsafe {
        let result = Box::from_raw(result);
        if result.error.code != ErrorCode::Ok {
            let details = CString::from_raw(result.error.details as _);
            drop(details);
        }
    }
}

pub type Program = ast::Program;

#[repr(C)]
pub struct Ctor {
    raw_ptr: Ptr,
}

#[repr(C)]
pub struct CtorFunc {
    raw_ptr: Ptr,
    name_ptr: *const u8,
    name_len: u32,
    docs_ptr: *const u8,
    docs_len: u32,
}

#[repr(C)]
pub struct Service {
    raw_ptr: Ptr,
    name_ptr: *const u8,
    name_len: u32,
}

#[repr(C)]
pub struct ServiceFunc {
    raw_ptr: Ptr,
    name_ptr: *const u8,
    name_len: u32,
    is_query: bool,
    docs_ptr: *const u8,
    docs_len: u32,
}

pub type ServiceEvent = EnumVariant;

#[repr(C)]
pub struct FuncParam {
    raw_ptr: Ptr,
    name_ptr: *const u8,
    name_len: u32,
}

#[repr(C)]
pub struct Type {
    raw_ptr: Ptr,
    name_ptr: *const u8,
    name_len: u32,
    docs_ptr: *const u8,
    docs_len: u32,
}

#[repr(C)]
pub struct TypeDecl {
    raw_ptr: Ptr,
}

pub type PrimitiveType = ast::PrimitiveType;

#[repr(C)]
pub struct StructDef {
    raw_ptr: Ptr,
}

#[repr(C)]
pub struct StructField {
    raw_ptr: Ptr,
    name_ptr: *const u8,
    name_len: u32,
    docs_ptr: *const u8,
    docs_len: u32,
}

#[repr(C)]
pub struct EnumDef {
    raw_ptr: Ptr,
}

#[repr(C)]
pub struct EnumVariant {
    raw_ptr: Ptr,
    name_ptr: *const u8,
    name_len: u32,
    docs_ptr: *const u8,
    docs_len: u32,
}

#[repr(transparent)]
pub struct Ptr(*const ());

impl<T> From<&T> for Ptr {
    fn from(t: &T) -> Self {
        Self(t as *const T as *const ())
    }
}

impl<T> AsRef<T> for Ptr {
    fn as_ref(&self) -> &T {
        unsafe { (self.0 as *const T).as_ref() }.unwrap()
    }
}
