use alloc::{boxed::Box, string::String, vec::Vec};

use crate::runtime::{Constraint, LiteralValue, PrimitiveType};
use crate::{CompareOp, Diagnostic, DiagnosticKind, Result, Span};

#[derive(Debug, Clone, PartialEq)]
pub enum HostValue {
    String(String),
    Int(i64),
    Float(f64),
    Bool(bool),
    Array(Vec<HostValue>),
    Object(Vec<HostField>),
    Abstract {
        constraints: Vec<Constraint>,
        default: Option<Box<HostValue>>,
    },
}

#[derive(Debug, Clone, PartialEq)]
pub struct HostField {
    pub name: String,
    pub value: HostValue,
}

impl HostValue {
    pub fn string(value: impl Into<String>) -> Self {
        Self::String(value.into())
    }

    pub fn int(value: i64) -> Self {
        Self::Int(value)
    }

    pub fn float(value: f64) -> Self {
        Self::Float(value)
    }

    pub fn bool(value: bool) -> Self {
        Self::Bool(value)
    }

    pub fn array<I>(items: I) -> Self
    where
        I: IntoIterator<Item = HostValue>,
    {
        Self::Array(items.into_iter().collect())
    }

    pub fn object<I, N>(fields: I) -> Self
    where
        I: IntoIterator<Item = (N, HostValue)>,
        N: Into<String>,
    {
        Self::Object(
            fields
                .into_iter()
                .map(|(name, value)| HostField {
                    name: name.into(),
                    value,
                })
                .collect(),
        )
    }

    pub fn object_from_paths<I, N>(fields: I) -> Self
    where
        I: IntoIterator<Item = (N, HostValue)>,
        N: Into<String>,
    {
        let mut root = Vec::new();
        for (path, value) in fields {
            insert_path(&mut root, &path.into(), value);
        }
        Self::Object(root)
    }

    pub fn string_type() -> Self {
        Self::abstract_with_constraint(Constraint::Type(PrimitiveType::String))
    }

    pub fn int_type() -> Self {
        Self::abstract_with_constraint(Constraint::Type(PrimitiveType::Int))
    }

    pub fn float_type() -> Self {
        Self::abstract_with_constraint(Constraint::Type(PrimitiveType::Float))
    }

    pub fn bool_type() -> Self {
        Self::abstract_with_constraint(Constraint::Type(PrimitiveType::Bool))
    }

    pub fn array_type() -> Self {
        Self::abstract_with_constraint(Constraint::Type(PrimitiveType::Array))
    }

    pub fn builtin_predicate(name: impl Into<String>) -> Self {
        Self::abstract_with_constraint(Constraint::BuiltinPredicate(name.into()))
    }

    pub fn abstract_with_constraint(constraint: Constraint) -> Self {
        Self::Abstract {
            constraints: alloc::vec![constraint],
            default: None,
        }
    }

    pub fn with_constraint(mut self, constraint: Constraint) -> Self {
        match &mut self {
            Self::Abstract { constraints, .. } => constraints.push(constraint),
            _ => {
                self = Self::Abstract {
                    constraints: alloc::vec![constraint],
                    default: Some(Box::new(self)),
                };
            }
        }
        self
    }

    pub fn gt(self, value: i64) -> Self {
        self.with_constraint(Constraint::Compare(CompareOp::Gt, LiteralValue::Int(value)))
    }

    pub fn gte(self, value: i64) -> Self {
        self.with_constraint(Constraint::Compare(
            CompareOp::Gte,
            LiteralValue::Int(value),
        ))
    }

    pub fn lt(self, value: i64) -> Self {
        self.with_constraint(Constraint::Compare(CompareOp::Lt, LiteralValue::Int(value)))
    }

    pub fn lte(self, value: i64) -> Self {
        self.with_constraint(Constraint::Compare(
            CompareOp::Lte,
            LiteralValue::Int(value),
        ))
    }

    pub fn default(self, value: HostValue) -> Result<Self> {
        match self {
            Self::Abstract {
                constraints,
                default: None,
            } => Ok(Self::Abstract {
                constraints,
                default: Some(Box::new(value)),
            }),
            Self::Abstract { .. } => Err(Diagnostic::new(
                DiagnosticKind::DefaultConflict,
                Span::default(),
                "host value already has a default",
            )),
            concrete => Ok(Self::Abstract {
                constraints: Vec::new(),
                default: Some(Box::new(concrete)),
            }),
        }
    }

    pub fn default_string(self, value: impl Into<String>) -> Result<Self> {
        self.default(Self::string(value))
    }

    pub fn default_int(self, value: i64) -> Result<Self> {
        self.default(Self::int(value))
    }

    pub fn default_float(self, value: f64) -> Result<Self> {
        self.default(Self::float(value))
    }

    pub fn default_bool(self, value: bool) -> Result<Self> {
        self.default(Self::bool(value))
    }
}

impl HostField {
    pub fn new(name: impl Into<String>, value: HostValue) -> Self {
        Self {
            name: name.into(),
            value,
        }
    }
}

fn insert_path(fields: &mut Vec<HostField>, path: &str, value: HostValue) {
    let mut parts = path.splitn(2, '.');
    let Some(head) = parts.next().filter(|part| !part.is_empty()) else {
        return;
    };
    if let Some(tail) = parts.next() {
        if let Some(field) = fields.iter_mut().find(|field| field.name == head) {
            if let HostValue::Object(children) = &mut field.value {
                insert_path(children, tail, value);
            } else {
                let mut children = Vec::new();
                insert_path(&mut children, tail, value);
                field.value = HostValue::Object(children);
            }
        } else {
            let mut children = Vec::new();
            insert_path(&mut children, tail, value);
            fields.push(HostField::new(head, HostValue::Object(children)));
        }
    } else if let Some(field) = fields.iter_mut().find(|field| field.name == head) {
        field.value = value;
    } else {
        fields.push(HostField::new(head, value));
    }
}
