use serde_yaml::Value;

use crate::model::Key;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OperationKind {
    Find,
    Count,
    Update,
    Delete,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Operation {
    Find(FindOp),
    Count(CountOp),
    Update(UpdateOp),
    Delete(DeleteOp),
}

#[derive(Debug, Clone, Default, PartialEq)]
pub struct FindOp {
    pub filter: Option<Filter>,
    pub project: Option<Projection>,
    pub sort: Option<Sort>,
    pub limit: Option<Limit>,
}

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

    pub fn filter(mut self, filter: Filter) -> Self {
        self.filter = Some(filter);
        self
    }

    pub fn project(mut self, project: Projection) -> Self {
        self.project = Some(project);
        self
    }

    pub fn sort(mut self, sort: Sort) -> Self {
        self.sort = Some(sort);
        self
    }

    pub fn limit(mut self, limit: u64) -> Self {
        self.limit = Some(Limit(limit));
        self
    }
}

#[derive(Debug, Clone, Default, PartialEq)]
pub struct CountOp {
    pub filter: Option<Filter>,
    pub sort: Option<Sort>,
    pub limit: Option<Limit>,
}

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

    pub fn filter(mut self, filter: Filter) -> Self {
        self.filter = Some(filter);
        self
    }

    pub fn sort(mut self, sort: Sort) -> Self {
        self.sort = Some(sort);
        self
    }

    pub fn limit(mut self, limit: u64) -> Self {
        self.limit = Some(Limit(limit));
        self
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct UpdateOp {
    pub filter: Filter,
    pub sort: Option<Sort>,
    pub limit: Option<Limit>,
    pub update: Update,
}

impl UpdateOp {
    pub fn new(filter: Filter, update: Update) -> Self {
        Self {
            filter,
            sort: None,
            limit: None,
            update,
        }
    }

    pub fn sort(mut self, sort: Sort) -> Self {
        self.sort = Some(sort);
        self
    }

    pub fn limit(mut self, limit: u64) -> Self {
        self.limit = Some(Limit(limit));
        self
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct DeleteOp {
    pub filter: Filter,
    pub sort: Option<Sort>,
    pub limit: Option<Limit>,
}

impl DeleteOp {
    pub fn new(filter: Filter) -> Self {
        Self {
            filter,
            sort: None,
            limit: None,
        }
    }

    pub fn sort(mut self, sort: Sort) -> Self {
        self.sort = Some(sort);
        self
    }

    pub fn limit(mut self, limit: u64) -> Self {
        self.limit = Some(Limit(limit));
        self
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum Filter {
    And(Vec<Filter>),
    Or(Vec<Filter>),
    Nor(Vec<Filter>),
    Field { path: FieldPath, op: FieldOp },
    Key(KeyOp),
    Includes(Box<InclusionAnchor>),
    IncludedBy(Box<InclusionAnchor>),
    References(Box<ReferenceAnchor>),
    ReferencedBy(Box<ReferenceAnchor>),
}

#[derive(Debug, Clone, PartialEq)]
pub enum KeyOp {
    Eq(Key),
    Ne(Key),
    In(Vec<Key>),
    Nin(Vec<Key>),
}

impl KeyOp {
    pub fn eq(key: impl Into<String>) -> Self {
        KeyOp::Eq(Key::name(&key.into()))
    }
    pub fn ne(key: impl Into<String>) -> Self {
        KeyOp::Ne(Key::name(&key.into()))
    }
    pub fn in_(keys: &[&str]) -> Self {
        KeyOp::In(keys.iter().map(|s| Key::name(s)).collect())
    }
    pub fn nin(keys: &[&str]) -> Self {
        KeyOp::Nin(keys.iter().map(|s| Key::name(s)).collect())
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct InclusionAnchor {
    pub match_filter: Filter,
    pub min_depth: u32,
    pub max_depth: u32,
}

impl InclusionAnchor {
    pub fn new(key: impl Into<String>, min_depth: u32, max_depth: u32) -> Self {
        InclusionAnchor {
            match_filter: Filter::Key(KeyOp::Eq(Key::name(&key.into()))),
            min_depth,
            max_depth,
        }
    }
    pub fn with_max(key: impl Into<String>, max_depth: u32) -> Self {
        Self::new(key, 1, max_depth)
    }
    pub fn with_match(match_filter: Filter, min_depth: u32, max_depth: u32) -> Self {
        InclusionAnchor {
            match_filter,
            min_depth,
            max_depth,
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct ReferenceAnchor {
    pub match_filter: Filter,
    pub min_distance: u32,
    pub max_distance: u32,
}

impl ReferenceAnchor {
    pub fn new(key: impl Into<String>, min_distance: u32, max_distance: u32) -> Self {
        ReferenceAnchor {
            match_filter: Filter::Key(KeyOp::Eq(Key::name(&key.into()))),
            min_distance,
            max_distance,
        }
    }
    pub fn with_max(key: impl Into<String>, max_distance: u32) -> Self {
        Self::new(key, 1, max_distance)
    }
    pub fn with_match(match_filter: Filter, min_distance: u32, max_distance: u32) -> Self {
        ReferenceAnchor {
            match_filter,
            min_distance,
            max_distance,
        }
    }
}

impl Filter {
    pub fn all() -> Self {
        Filter::And(Vec::new())
    }

    pub fn and(filters: Vec<Filter>) -> Self {
        Filter::And(filters)
    }

    pub fn or(filters: Vec<Filter>) -> Self {
        Filter::Or(filters)
    }

    pub fn eq(path: &str, v: impl Into<Value>) -> Self {
        Self::field(path, FieldOp::Eq(v.into()))
    }

    pub fn ne(path: &str, v: impl Into<Value>) -> Self {
        Self::field(path, FieldOp::Ne(v.into()))
    }

    pub fn gt(path: &str, v: impl Into<Value>) -> Self {
        Self::field(path, FieldOp::Gt(v.into()))
    }

    pub fn gte(path: &str, v: impl Into<Value>) -> Self {
        Self::field(path, FieldOp::Gte(v.into()))
    }

    pub fn lt(path: &str, v: impl Into<Value>) -> Self {
        Self::field(path, FieldOp::Lt(v.into()))
    }

    pub fn lte(path: &str, v: impl Into<Value>) -> Self {
        Self::field(path, FieldOp::Lte(v.into()))
    }

    pub fn exists(path: &str, present: bool) -> Self {
        Self::field(path, FieldOp::Exists(present))
    }

    pub fn key(op: KeyOp) -> Self {
        Filter::Key(op)
    }

    fn field(path: &str, op: FieldOp) -> Self {
        Filter::Field {
            path: FieldPath::from_dotted(path),
            op,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FieldPath(pub Vec<String>);

impl FieldPath {
    pub fn segments(&self) -> &[String] {
        &self.0
    }

    pub fn from_dotted(s: &str) -> Self {
        FieldPath(s.split('.').map(|seg| seg.to_string()).collect())
    }

    pub fn leaf(&self) -> Option<&str> {
        self.0.last().map(|s| s.as_str())
    }

    pub fn starts_with(&self, other: &FieldPath) -> bool {
        if other.0.len() > self.0.len() {
            return false;
        }
        self.0.iter().zip(other.0.iter()).all(|(a, b)| a == b)
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum FieldOp {
    Eq(Value),
    Ne(Value),
    Gt(Value),
    Gte(Value),
    Lt(Value),
    Lte(Value),
    In(Vec<Value>),
    Nin(Vec<Value>),
    Exists(bool),
    Type(Vec<YamlType>),
    All(Vec<Value>),
    Size(u64),
    Not(Box<FieldOp>),
    And(Vec<FieldOp>),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum YamlType {
    String,
    Number,
    Boolean,
    Null,
    Array,
    Object,
    Date,
    Datetime,
}

impl std::fmt::Display for YamlType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            YamlType::String => write!(f, "string"),
            YamlType::Number => write!(f, "number"),
            YamlType::Boolean => write!(f, "boolean"),
            YamlType::Null => write!(f, "null"),
            YamlType::Array => write!(f, "array"),
            YamlType::Object => write!(f, "object"),
            YamlType::Date => write!(f, "date"),
            YamlType::Datetime => write!(f, "datetime"),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProjectionMode {
    Replace,
    Extend,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PseudoField {
    Key,
    Title,
    TitleSlug,
    Content,
    Frontmatter,
    IncludedBy,
    Includes,
    ReferencedBy,
    References,
}

impl PseudoField {
    pub fn from_selector(s: &str) -> Option<Self> {
        match s {
            "$key" => Some(PseudoField::Key),
            "$title" => Some(PseudoField::Title),
            "$titleSlug" => Some(PseudoField::TitleSlug),
            "$content" => Some(PseudoField::Content),
            "$frontmatter" => Some(PseudoField::Frontmatter),
            "$includedBy" => Some(PseudoField::IncludedBy),
            "$includes" => Some(PseudoField::Includes),
            "$referencedBy" => Some(PseudoField::ReferencedBy),
            "$references" => Some(PseudoField::References),
            _ => None,
        }
    }

    pub fn default_output_name(&self) -> &'static str {
        match self {
            PseudoField::Key => "key",
            PseudoField::Title => "title",
            PseudoField::TitleSlug => "titleSlug",
            PseudoField::Content => "content",
            PseudoField::Frontmatter => "frontmatter",
            PseudoField::IncludedBy => "includedBy",
            PseudoField::Includes => "includes",
            PseudoField::ReferencedBy => "referencedBy",
            PseudoField::References => "references",
        }
    }

    pub fn is_content_or_edge(&self) -> bool {
        matches!(
            self,
            PseudoField::Content
                | PseudoField::IncludedBy
                | PseudoField::Includes
                | PseudoField::ReferencedBy
                | PseudoField::References
        )
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum ProjectionSource {
    Frontmatter(FieldPath),
    Pseudo(PseudoField),
}

#[derive(Debug, Clone, PartialEq)]
pub struct ProjectionField {
    pub output: String,
    pub source: ProjectionSource,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Projection {
    pub fields: Vec<ProjectionField>,
    pub mode: ProjectionMode,
}

impl Projection {
    pub fn replace(fields: Vec<ProjectionField>) -> Self {
        Projection {
            fields,
            mode: ProjectionMode::Replace,
        }
    }

    pub fn extend(fields: Vec<ProjectionField>) -> Self {
        Projection {
            fields,
            mode: ProjectionMode::Extend,
        }
    }

    pub fn fields(fields: &[&str]) -> Self {
        Projection {
            fields: fields
                .iter()
                .map(|name| ProjectionField {
                    output: (*name).to_string(),
                    source: ProjectionSource::Frontmatter(FieldPath::from_dotted(name)),
                })
                .collect(),
            mode: ProjectionMode::Replace,
        }
    }

    pub fn default_for_find() -> Self {
        let entries = [
            ("key", PseudoField::Key),
            ("title", PseudoField::Title),
            ("references", PseudoField::References),
            ("includes", PseudoField::Includes),
            ("referencedBy", PseudoField::ReferencedBy),
            ("includedBy", PseudoField::IncludedBy),
        ];
        Projection {
            fields: entries
                .iter()
                .map(|(name, p)| ProjectionField {
                    output: (*name).to_string(),
                    source: ProjectionSource::Pseudo(*p),
                })
                .collect(),
            mode: ProjectionMode::Replace,
        }
    }

    pub fn has_content_or_edge_source(&self) -> bool {
        self.fields.iter().any(|f| match &f.source {
            ProjectionSource::Pseudo(p) => p.is_content_or_edge(),
            _ => false,
        })
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct Sort {
    pub key: FieldPath,
    pub dir: SortDir,
}

impl Sort {
    pub fn asc(path: &str) -> Self {
        Sort {
            key: FieldPath::from_dotted(path),
            dir: SortDir::Asc,
        }
    }

    pub fn desc(path: &str) -> Self {
        Sort {
            key: FieldPath::from_dotted(path),
            dir: SortDir::Desc,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SortDir {
    Asc,
    Desc,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Limit(pub u64);

impl Limit {
    pub fn is_unbounded(self) -> bool {
        self.0 == 0
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct Update {
    pub operators: Vec<UpdateOperator>,
}

impl Update {
    pub fn new(operators: Vec<UpdateOperator>) -> Self {
        Update { operators }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum UpdateOperator {
    Set { path: FieldPath, value: Value },
    Unset { path: FieldPath },
}

impl UpdateOperator {
    pub fn set(path: &str, value: impl Into<Value>) -> Self {
        UpdateOperator::Set {
            path: FieldPath::from_dotted(path),
            value: value.into(),
        }
    }

    pub fn unset(path: &str) -> Self {
        UpdateOperator::Unset {
            path: FieldPath::from_dotted(path),
        }
    }
}
