use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, parse::{Parse, ParseStream}, Token, Type, Ident};
use std::path::Path;
use std::fs::OpenOptions;
use std::io::{Read, Write, Seek, SeekFrom};
use fs2::FileExt;

const MACRO_CRATE_DIR: &str = env!("CARGO_MANIFEST_DIR");

fn next_slot(key: &str) -> u64 {
    let state_path = Path::new(MACRO_CRATE_DIR).join(".iris_slots.json");

    let mut file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(&state_path)
        .unwrap();

    file.lock_exclusive().unwrap();

    let mut content = String::new();
    file.read_to_string(&mut content).unwrap();

    let mut map: std::collections::HashMap<String, u64> = if content.is_empty() {
        std::collections::HashMap::new()
    } else {
        serde_json::from_str(&content).unwrap_or_default()
    };

    if let Some(&slot) = map.get(key) {
        file.unlock().unwrap();
        return slot;
    }

    let next = map.get("__next").copied().unwrap_or(0);
    map.insert(key.to_string(), next);
    map.insert("__next".to_string(), next + 1);

    let new_content = serde_json::to_string_pretty(&map).unwrap();
    file.seek(SeekFrom::Start(0)).unwrap();
    file.set_len(0).unwrap();
    file.write_all(new_content.as_bytes()).unwrap();
    file.unlock().unwrap();

    next
}

struct RegisterCapability {
    cap_name: Ident,
    trait_bound: Type,
    returns: Type,
}

impl Parse for RegisterCapability {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let _: Ident = input.parse()?;
        let _: Token![=] = input.parse()?;
        let cap_name: Ident = input.parse()?;
        let _: Token![,] = input.parse()?;

        let _: Ident = input.parse()?;
        let _: Token![=] = input.parse()?;
        let trait_bound: Type = input.parse()?;
        let _: Token![,] = input.parse()?;

        let _: Ident = input.parse()?;
        let _: Token![=] = input.parse()?;
        let returns: Type = input.parse()?;

        let _ = input.parse::<Token![,]>();

        Ok(Self { cap_name, trait_bound, returns })
    }
}

#[proc_macro]
pub fn register_capability(input: TokenStream) -> TokenStream {
    let RegisterCapability { cap_name, trait_bound, returns } =
        parse_macro_input!(input as RegisterCapability);

    let pkg_name = std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "unknown".to_string());
    let key = format!("{}::{}", pkg_name, cap_name);
    let slot = next_slot(&key);

    let output = quote! {
        pub struct #cap_name;
        impl iris::Capability for #cap_name {
            type Handle = #returns;
            const SLOT: u64 = #slot;
        }

        impl<'a, T: #trait_bound + 'static> iris::HasCap<#cap_name, #slot>
            for iris::Probe<'a, T, #slot>
        {
            fn probe(&self, caps: &mut iris::CapabilityMap) {
                caps.insert_cast::<#cap_name>(
                    |any| {
                        let val = any.downcast_ref::<T>().unwrap();
                        val as &#returns
                    },
                    |any| {
                        let val = any.downcast_mut::<T>().unwrap();
                        val as &mut #returns
                    },
                );
            }
        }
    };

    output.into()
}
