use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};

use crate::fast_hash_map::FastHashMap;
use crate::frame::FrameFlags;
use crate::resource_table::ResourceIndex;
use crate::serialization_helpers::SerializableSingleValueColumn;
use crate::thread_string_table::ThreadInternalStringIndex;

#[derive(Debug, Clone, Default)]
pub struct FuncTable {
    names: Vec<ThreadInternalStringIndex>,
    resources: Vec<Option<ResourceIndex>>,
    flags: Vec<FrameFlags>,
    func_name_and_resource_and_flags_to_func_index:
        FastHashMap<(ThreadInternalStringIndex, Option<ResourceIndex>, FrameFlags), usize>,
    contains_js_function: bool,
}

impl FuncTable {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn index_for_func(
        &mut self,
        name: ThreadInternalStringIndex,
        resource: Option<ResourceIndex>,
        flags: FrameFlags,
    ) -> FuncIndex {
        let func_index = *self
            .func_name_and_resource_and_flags_to_func_index
            .entry((name, resource, flags))
            .or_insert_with(|| {
                let func_index = self.names.len();
                self.names.push(name);
                self.resources.push(resource);
                self.flags.push(flags);
                func_index
            });
        if flags.intersects(FrameFlags::IS_JS | FrameFlags::IS_RELEVANT_FOR_JS) {
            self.contains_js_function = true;
        }
        FuncIndex(func_index as u32)
    }

    pub fn contains_js_function(&self) -> bool {
        self.contains_js_function
    }
}

#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct FuncIndex(u32);

impl Serialize for FuncIndex {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_u32(self.0)
    }
}

impl Serialize for FuncTable {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let len = self.names.len();
        let mut map = serializer.serialize_map(None)?;
        map.serialize_entry("length", &len)?;
        map.serialize_entry("name", &self.names)?;
        map.serialize_entry(
            "isJS",
            &SerializableFlagColumn(&self.flags, FrameFlags::IS_JS),
        )?;
        map.serialize_entry(
            "relevantForJS",
            &SerializableFlagColumn(&self.flags, FrameFlags::IS_RELEVANT_FOR_JS),
        )?;
        map.serialize_entry(
            "resource",
            &SerializableFuncTableResourceColumn(&self.resources),
        )?;
        map.serialize_entry("fileName", &SerializableSingleValueColumn((), len))?;
        map.serialize_entry("lineNumber", &SerializableSingleValueColumn((), len))?;
        map.serialize_entry("columnNumber", &SerializableSingleValueColumn((), len))?;
        map.end()
    }
}

struct SerializableFuncTableResourceColumn<'a>(&'a [Option<ResourceIndex>]);

impl Serialize for SerializableFuncTableResourceColumn<'_> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
        for resource in self.0 {
            match resource {
                Some(resource) => seq.serialize_element(&resource)?,
                None => seq.serialize_element(&-1)?,
            }
        }
        seq.end()
    }
}

pub struct SerializableFlagColumn<'a>(&'a [FrameFlags], FrameFlags);

impl Serialize for SerializableFlagColumn<'_> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
        for item_flags in self.0 {
            seq.serialize_element(&item_flags.contains(self.1))?;
        }
        seq.end()
    }
}
