use std::cell::RefCell;
use std::fmt;

use bumpalo::Bump;
use liquid::model::{
    ArrayView, DisplayCow, KStringCow, ObjectRender, ObjectSource, State, Value as LiquidValue,
};
use liquid::{ObjectView, ValueView};

use crate::fields_ids_map::metadata::{FieldIdMapWithMetadata, Metadata};
use crate::{GlobalFieldsIdsMap, PatternMatch};

#[derive(Debug, Clone, Copy)]
pub struct FieldValue<'a, D: ObjectView> {
    name: &'a str,
    document: &'a D,
    metadata: Metadata,
}

impl<D: ObjectView> ValueView for FieldValue<'_, D> {
    fn as_debug(&self) -> &dyn std::fmt::Debug {
        self
    }

    fn render(&self) -> liquid::model::DisplayCow<'_> {
        DisplayCow::Owned(Box::new(ObjectRender::new(self)))
    }

    fn source(&self) -> liquid::model::DisplayCow<'_> {
        DisplayCow::Owned(Box::new(ObjectSource::new(self)))
    }

    fn type_name(&self) -> &'static str {
        "object"
    }

    fn query_state(&self, state: liquid::model::State) -> bool {
        match state {
            State::Truthy => true,
            State::DefaultValue | State::Empty | State::Blank => self.is_empty(),
        }
    }

    fn to_kstr(&self) -> liquid::model::KStringCow<'_> {
        let s = ObjectRender::new(self).to_string();
        KStringCow::from_string(s)
    }

    fn to_value(&self) -> LiquidValue {
        LiquidValue::Object(
            self.iter().map(|(k, v)| (k.to_string().into(), v.to_value())).collect(),
        )
    }

    fn as_object(&self) -> Option<&dyn ObjectView> {
        Some(self)
    }
}

impl<'a, D: ObjectView> FieldValue<'a, D> {
    pub fn name(&self) -> &&'a str {
        &self.name
    }

    pub fn value(&self) -> &dyn ValueView {
        self.document.get(self.name).unwrap_or(&LiquidValue::Nil)
    }

    pub fn is_searchable(&self) -> &bool {
        match self.metadata.is_searchable() {
            PatternMatch::Match => &true,
            _ => &false,
        }
    }

    pub fn is_empty(&self) -> bool {
        self.size() == 0
    }
}

impl<D: ObjectView> ObjectView for FieldValue<'_, D> {
    fn as_value(&self) -> &dyn ValueView {
        self
    }

    fn size(&self) -> i64 {
        2
    }

    fn keys<'k>(&'k self) -> Box<dyn Iterator<Item = KStringCow<'k>> + 'k> {
        Box::new(["name", "value", "is_searchable"].iter().map(|&x| KStringCow::from_static(x)))
    }

    fn values<'k>(&'k self) -> Box<dyn Iterator<Item = &'k dyn ValueView> + 'k> {
        Box::new(
            std::iter::once(self.name() as &dyn ValueView)
                .chain(std::iter::once(self.value()))
                .chain(std::iter::once(self.is_searchable() as &dyn ValueView)),
        )
    }

    fn iter<'k>(&'k self) -> Box<dyn Iterator<Item = (KStringCow<'k>, &'k dyn ValueView)> + 'k> {
        Box::new(self.keys().zip(self.values()))
    }

    fn contains_key(&self, index: &str) -> bool {
        index == "name" || index == "value" || index == "is_searchable"
    }

    fn get<'s>(&'s self, index: &str) -> Option<&'s dyn ValueView> {
        match index {
            "name" => Some(self.name()),
            "value" => Some(self.value()),
            "is_searchable" => Some(self.is_searchable()),
            _ => None,
        }
    }
}

#[derive(Debug, Clone)]
pub struct OwnedFields<'a, D: ObjectView>(Vec<FieldValue<'a, D>>);

#[derive(Debug)]
pub struct BorrowedFields<'a, 'doc, 'map, D: ObjectView> {
    document: &'a D,
    field_id_map: &'a RefCell<GlobalFieldsIdsMap<'map>>,
    doc_alloc: &'doc Bump,
}

impl<'a, D: ObjectView> OwnedFields<'a, D> {
    pub fn new(document: &'a D, field_id_map: &'a FieldIdMapWithMetadata) -> Self {
        Self(
            std::iter::repeat(document)
                .zip(field_id_map.iter())
                .map(|(document, (_fid, name, metadata))| FieldValue { document, name, metadata })
                .collect(),
        )
    }
}

impl<'a, 'doc, 'map, D: ObjectView> BorrowedFields<'a, 'doc, 'map, D> {
    pub fn new(
        document: &'a D,
        field_id_map: &'a RefCell<GlobalFieldsIdsMap<'map>>,
        doc_alloc: &'doc Bump,
    ) -> Self {
        Self { document, field_id_map, doc_alloc }
    }
}

impl<D: ObjectView> ArrayView for OwnedFields<'_, D> {
    fn as_value(&self) -> &dyn ValueView {
        self.0.as_value()
    }

    fn size(&self) -> i64 {
        self.0.len() as i64
    }

    fn values<'k>(&'k self) -> Box<dyn Iterator<Item = &'k dyn ValueView> + 'k> {
        self.0.values()
    }

    fn contains_key(&self, index: i64) -> bool {
        self.0.contains_key(index)
    }

    fn get(&self, index: i64) -> Option<&dyn ValueView> {
        ArrayView::get(&self.0, index)
    }
}

impl<D: ObjectView> ArrayView for BorrowedFields<'_, '_, '_, D> {
    fn as_value(&self) -> &dyn ValueView {
        self
    }

    fn size(&self) -> i64 {
        self.document.size()
    }

    fn values<'k>(&'k self) -> Box<dyn Iterator<Item = &'k dyn ValueView> + 'k> {
        Box::new(self.document.keys().map(|k| {
            let mut field_id_map = self.field_id_map.borrow_mut();
            let (_, metadata) = field_id_map.id_with_metadata_or_insert(&k).unwrap();
            let fv = self.doc_alloc.alloc(FieldValue {
                name: self.doc_alloc.alloc_str(&k),
                document: self.document,
                metadata,
            });
            fv as _
        }))
    }

    fn contains_key(&self, index: i64) -> bool {
        let index = if index >= 0 { index } else { self.size() + index };
        index >= 0 && index < self.size()
    }

    fn get(&self, index: i64) -> Option<&dyn ValueView> {
        let index = if index >= 0 { index } else { self.size() + index };
        let index: usize = index.try_into().ok()?;
        let key = self.document.keys().nth(index)?;
        let mut field_id_map = self.field_id_map.borrow_mut();
        let (_, metadata) = field_id_map.id_with_metadata_or_insert(&key)?;
        let fv = self.doc_alloc.alloc(FieldValue {
            name: self.doc_alloc.alloc_str(&key),
            document: self.document,
            metadata,
        });
        Some(fv as _)
    }
}

impl<D: ObjectView> ValueView for BorrowedFields<'_, '_, '_, D> {
    fn as_debug(&self) -> &dyn std::fmt::Debug {
        self
    }

    fn render(&self) -> liquid::model::DisplayCow<'_> {
        DisplayCow::Owned(Box::new(ArrayRender { s: self }))
    }

    fn source(&self) -> liquid::model::DisplayCow<'_> {
        DisplayCow::Owned(Box::new(ArraySource { s: self }))
    }

    fn type_name(&self) -> &'static str {
        "array"
    }

    fn query_state(&self, state: liquid::model::State) -> bool {
        match state {
            State::Truthy => true,
            State::DefaultValue | State::Empty | State::Blank => self.document.size() == 0,
        }
    }

    fn to_kstr(&self) -> liquid::model::KStringCow<'_> {
        let s = ArrayRender { s: self }.to_string();
        KStringCow::from_string(s)
    }

    fn to_value(&self) -> LiquidValue {
        LiquidValue::Array(self.values().map(|v| v.to_value()).collect())
    }

    fn as_array(&self) -> Option<&dyn ArrayView> {
        Some(self)
    }

    fn is_array(&self) -> bool {
        true
    }
}

impl<D: ObjectView> ValueView for OwnedFields<'_, D> {
    fn as_debug(&self) -> &dyn std::fmt::Debug {
        self
    }

    fn render(&self) -> liquid::model::DisplayCow<'_> {
        self.0.render()
    }

    fn source(&self) -> liquid::model::DisplayCow<'_> {
        self.0.source()
    }

    fn type_name(&self) -> &'static str {
        self.0.type_name()
    }

    fn query_state(&self, state: liquid::model::State) -> bool {
        self.0.query_state(state)
    }

    fn to_kstr(&self) -> liquid::model::KStringCow<'_> {
        self.0.to_kstr()
    }

    fn to_value(&self) -> LiquidValue {
        self.0.to_value()
    }

    fn as_array(&self) -> Option<&dyn ArrayView> {
        Some(self)
    }
}

struct ArraySource<'a, 'doc, 'map, D: ObjectView> {
    s: &'a BorrowedFields<'a, 'doc, 'map, D>,
}

impl<D: ObjectView> fmt::Display for ArraySource<'_, '_, '_, D> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[")?;
        for item in self.s.values() {
            write!(f, "{}, ", item.render())?;
        }
        write!(f, "]")?;
        Ok(())
    }
}

struct ArrayRender<'a, 'doc, 'map, D: ObjectView> {
    s: &'a BorrowedFields<'a, 'doc, 'map, D>,
}

impl<D: ObjectView> fmt::Display for ArrayRender<'_, '_, '_, D> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for item in self.s.values() {
            write!(f, "{}", item.render())?;
        }
        Ok(())
    }
}
