use crate::error::{CodeRegion, HasSpan};
use indexmap::IndexSet;
use quote::ToTokens;
use serde::Serialize;

#[derive(Debug, Clone, Serialize)]
struct CodeInfo {
    code: String,
    region: CodeRegion,
}

impl From<&dyn ToTokens> for CodeInfo {
    fn from(value: &dyn ToTokens) -> Self {
        let region = value.span().region();
        let code = value.to_token_stream().to_string();
        Self { code, region }
    }
}

#[derive(Debug, Default, Serialize)]
pub struct CodeStore {
    source_file: SourceFile,
    code: Vec<CodeInfo>,
    #[serde(skip)]
    finder: IndexSet<*const ()>,
}

impl CodeStore {
    pub fn push(&mut self, code: &dyn ToTokens) -> usize {
        let (res, was_inserted) = self
            .finder
            .insert_full((code as *const dyn ToTokens).cast());
        if was_inserted {
            assert_eq!(self.code.len(), res);
            self.code.push(code.into());
        }
        res
    }
}

#[derive(Debug, Serialize)]
struct ProfiledCode {
    code_idx: usize,
    before_size_id: usize,
    after_size_id: usize,
}

#[derive(Debug, Serialize)]
struct SourceFile(String);

fn get_source_file() -> Option<String> {
    extern crate proc_macro;
    if !proc_macro::is_available() {
        return None;
    }

    let source_file_path = proc_macro::Span::call_site().local_file()?;

    Some(std::fs::read_to_string(&source_file_path).expect("Unable to read the source file."))
}

impl Default for SourceFile {
    fn default() -> Self {
        let source_file = get_source_file().unwrap_or_default();
        Self(source_file)
    }
}

#[derive(Debug, Default, Serialize)]
pub struct Profile {
    #[serde(skip)]
    sizes: IndexSet<usize>,
    profilees: Vec<ProfiledCode>,
}

impl Profile {
    pub fn push(&mut self, code_idx: usize, before_size: usize, after_size: usize) {
        let before_size_id = self.sizes.insert_full(before_size).0;
        let after_size_id = self.sizes.insert_full(after_size).0;
        self.profilees.push(ProfiledCode {
            code_idx,
            before_size_id,
            after_size_id,
        });
    }
    pub fn sizes(&self) -> Vec<usize> {
        self.sizes.iter().cloned().collect()
    }
}
