use std::{result, str::FromStr};

use prefixmap::error::DerefError;
use prefixmap::{DerefIri, IriRef, PrefixMap};
use rudof_iri::IriS;
use rudof_iri::error::IriSError;
use serde::{Deserialize, Serialize, Serializer};

use crate::ast::serde_string_or_struct::*;

use super::{annotation::Annotation, sem_act::SemAct, shape_expr::ShapeExpr, triple_expr_label::TripleExprLabel};

#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
#[serde(tag = "type")]
pub enum TripleExpr {
    EachOf {
        #[serde(default, skip_serializing_if = "Option::is_none")]
        id: Option<TripleExprLabel>,

        expressions: Vec<TripleExprWrapper>,

        #[serde(skip_serializing_if = "Option::is_none")]
        min: Option<i32>,

        #[serde(skip_serializing_if = "Option::is_none")]
        max: Option<i32>,

        #[serde(default, rename = "semActs", skip_serializing_if = "Option::is_none")]
        sem_acts: Option<Vec<SemAct>>,

        #[serde(default, skip_serializing_if = "Option::is_none")]
        annotations: Option<Vec<Annotation>>,
    },

    OneOf {
        #[serde(default, skip_serializing_if = "Option::is_none")]
        id: Option<TripleExprLabel>,

        expressions: Vec<TripleExprWrapper>,

        #[serde(skip_serializing_if = "Option::is_none")]
        min: Option<i32>,

        #[serde(skip_serializing_if = "Option::is_none")]
        max: Option<i32>,

        #[serde(default, rename = "semActs", skip_serializing_if = "Option::is_none")]
        sem_acts: Option<Vec<SemAct>>,

        #[serde(default, skip_serializing_if = "Option::is_none")]
        annotations: Option<Vec<Annotation>>,
    },
    TripleConstraint {
        #[serde(default, skip_serializing_if = "Option::is_none")]
        id: Option<TripleExprLabel>,

        #[serde(default, skip_serializing_if = "Option::is_none")]
        negated: Option<bool>,

        #[serde(default, skip_serializing_if = "Option::is_none")]
        inverse: Option<bool>,

        predicate: IriRef,

        #[serde(
            default,
            rename = "valueExpr",
            skip_serializing_if = "Option::is_none",
            serialize_with = "serialize_opt_box_string_or_struct",
            deserialize_with = "deserialize_opt_box_string_or_struct"
        )]
        value_expr: Option<Box<ShapeExpr>>,

        #[serde(skip_serializing_if = "Option::is_none")]
        min: Option<i32>,

        #[serde(skip_serializing_if = "Option::is_none")]
        max: Option<i32>,

        #[serde(default, rename = "semActs", skip_serializing_if = "Option::is_none")]
        sem_acts: Option<Vec<SemAct>>,

        #[serde(default, skip_serializing_if = "Option::is_none")]
        annotations: Option<Vec<Annotation>>,
    },

    Ref(TripleExprLabel),
}

impl TripleExpr {
    pub fn triple_constraint(
        negated: Option<bool>,
        inverse: Option<bool>,
        predicate: IriRef,
        se: Option<ShapeExpr>,
        min: Option<i32>,
        max: Option<i32>,
    ) -> TripleExpr {
        TripleExpr::TripleConstraint {
            id: None,
            negated,
            inverse,
            predicate,
            value_expr: se.map(Box::new),
            min,
            max,
            sem_acts: None,
            annotations: None,
        }
    }

    pub fn each_of(tes: Vec<TripleExpr>) -> TripleExpr {
        let mut tews = Vec::new();
        for te in tes {
            tews.push(te.into())
        }
        TripleExpr::EachOf {
            id: None,
            expressions: tews,
            min: None,
            max: None,
            sem_acts: None,
            annotations: None,
        }
    }

    pub fn one_of(tes: Vec<TripleExpr>) -> TripleExpr {
        let mut tews = Vec::new();
        for te in tes {
            tews.push(te.into())
        }
        TripleExpr::OneOf {
            id: None,
            expressions: tews,
            min: None,
            max: None,
            sem_acts: None,
            annotations: None,
        }
    }

    pub fn with_id(mut self, id: Option<TripleExprLabel>) -> Self {
        self = match self {
            TripleExpr::EachOf {
                id: _,
                expressions,
                min,
                max,
                sem_acts,
                annotations,
            } => TripleExpr::EachOf {
                id,
                expressions,
                min,
                max,
                sem_acts,
                annotations,
            },
            TripleExpr::OneOf {
                id: _,
                expressions,
                min,
                max,
                sem_acts,
                annotations,
            } => TripleExpr::OneOf {
                id,
                expressions,
                min,
                max,
                sem_acts,
                annotations,
            },
            TripleExpr::TripleConstraint {
                id: _,
                negated,
                inverse,
                predicate,
                value_expr,
                min,
                max,
                sem_acts,
                annotations,
            } => TripleExpr::TripleConstraint {
                id,
                negated,
                inverse,
                predicate,
                value_expr,
                min,
                max,
                sem_acts,
                annotations,
            },
            TripleExpr::Ref(lbl) => {
                panic!("Can't update id to TripleExprRef({lbl:?}")
            },
        };
        self
    }

    pub fn with_min(mut self, new_min: Option<i32>) -> Self {
        self = match self {
            TripleExpr::EachOf {
                id,
                expressions,
                min: _,
                max,
                sem_acts,
                annotations,
            } => TripleExpr::EachOf {
                id,
                expressions,
                min: new_min,
                max,
                sem_acts,
                annotations,
            },
            TripleExpr::OneOf {
                id,
                expressions,
                min: _,
                max,
                sem_acts,
                annotations,
            } => TripleExpr::OneOf {
                id,
                expressions,
                min: new_min,
                max,
                sem_acts,
                annotations,
            },
            TripleExpr::TripleConstraint {
                id,
                negated,
                inverse,
                predicate,
                value_expr,
                min: _,
                max,
                sem_acts,
                annotations,
            } => TripleExpr::TripleConstraint {
                id,
                negated,
                inverse,
                predicate,
                value_expr,
                min: new_min,
                max,
                sem_acts,
                annotations,
            },
            TripleExpr::Ref(lbl) => {
                panic!("Can't update min to TripleExprRef({lbl:?}")
            },
        };
        self
    }

    pub fn with_max(mut self, new_max: Option<i32>) -> Self {
        self = match self {
            TripleExpr::EachOf {
                id,
                expressions,
                min,
                max: _,
                sem_acts,
                annotations,
            } => TripleExpr::EachOf {
                id,
                expressions,
                min,
                max: new_max,
                sem_acts,
                annotations,
            },
            TripleExpr::OneOf {
                id,
                expressions,
                min,
                max: _,
                sem_acts,
                annotations,
            } => TripleExpr::OneOf {
                id,
                expressions,
                min,
                max: new_max,
                sem_acts,
                annotations,
            },
            TripleExpr::TripleConstraint {
                id,
                negated,
                inverse,
                predicate,
                value_expr,
                min,
                max: _,
                sem_acts,
                annotations,
            } => TripleExpr::TripleConstraint {
                id,
                negated,
                inverse,
                predicate,
                value_expr,
                min,
                max: new_max,
                sem_acts,
                annotations,
            },
            TripleExpr::Ref(lbl) => {
                panic!("Can't update max to TripleExprRef({lbl:?}")
            },
        };
        self
    }

    pub fn with_sem_acts(mut self, new_sem_acts: Option<Vec<SemAct>>) -> Self {
        self = match self {
            TripleExpr::EachOf {
                id,
                expressions,
                min,
                max,
                sem_acts: _,
                annotations,
            } => TripleExpr::EachOf {
                id,
                expressions,
                min,
                max,
                sem_acts: new_sem_acts,
                annotations,
            },
            TripleExpr::OneOf {
                id,
                expressions,
                min,
                max,
                sem_acts: _,
                annotations,
            } => TripleExpr::OneOf {
                id,
                expressions,
                min,
                max,
                sem_acts: new_sem_acts,
                annotations,
            },
            TripleExpr::TripleConstraint {
                id,
                negated,
                inverse,
                predicate,
                value_expr,
                min,
                max,
                sem_acts: _,
                annotations,
            } => TripleExpr::TripleConstraint {
                id,
                negated,
                inverse,
                predicate,
                value_expr,
                min,
                max,
                sem_acts: new_sem_acts,
                annotations,
            },
            TripleExpr::Ref(lbl) => {
                panic!("Can't update sem_acts to TripleExprRef({lbl:?}")
            },
        };
        self
    }

    pub fn with_annotations(mut self, new_annotations: Option<Vec<Annotation>>) -> Self {
        self = match self {
            TripleExpr::EachOf {
                id,
                expressions,
                min,
                max,
                sem_acts,
                annotations: _,
            } => TripleExpr::EachOf {
                id,
                expressions,
                min,
                max,
                sem_acts,
                annotations: new_annotations,
            },
            TripleExpr::OneOf {
                id,
                expressions,
                min,
                max,
                sem_acts,
                annotations: new_annotations,
            } => TripleExpr::OneOf {
                id,
                expressions,
                min,
                max,
                sem_acts,
                annotations: new_annotations,
            },
            TripleExpr::TripleConstraint {
                id,
                negated,
                inverse,
                predicate,
                value_expr,
                min,
                max,
                sem_acts,
                annotations: _,
            } => TripleExpr::TripleConstraint {
                id,
                negated,
                inverse,
                predicate,
                value_expr,
                min,
                max,
                sem_acts,
                annotations: new_annotations,
            },
            TripleExpr::Ref(lbl) => {
                panic!("Can't update annotations to TripleExprRef({lbl:?}")
            },
        };
        self
    }

    pub fn add_annotation(&mut self, annotation: Annotation) {
        match self {
            Self::EachOf { annotations, .. } => {
                if let Some(anns) = annotations {
                    anns.push(annotation)
                } else {
                    *annotations = Some(vec![annotation])
                }
            },
            Self::TripleConstraint { annotations, .. } => {
                if let Some(anns) = annotations {
                    anns.push(annotation)
                } else {
                    *annotations = Some(vec![annotation])
                }
            },
            Self::OneOf { annotations, .. } => {
                if let Some(anns) = annotations {
                    anns.push(annotation)
                } else {
                    *annotations = Some(vec![annotation])
                }
            },
            _ => todo!(),
        }
    }
}

impl DerefIri for TripleExpr {
    fn deref_iri(self, base: Option<&IriS>, prefixmap: Option<&PrefixMap>) -> Result<Self, DerefError> {
        match self {
            TripleExpr::EachOf {
                id,
                expressions,
                min,
                max,
                sem_acts,
                annotations,
            } => {
                let id = id.deref_iri(base, prefixmap)?;
                let annotations = annotations.deref_iri(base, prefixmap)?;
                let sem_acts = sem_acts.deref_iri(base, prefixmap)?;
                let expressions = expressions.deref_iri(base, prefixmap)?;
                Ok(TripleExpr::EachOf {
                    id,
                    expressions,
                    min,
                    max,
                    sem_acts,
                    annotations,
                })
            },
            TripleExpr::OneOf {
                id,
                expressions,
                min,
                max,
                sem_acts,
                annotations,
            } => {
                let id = id.deref_iri(base, prefixmap)?;
                let annotations = annotations.deref_iri(base, prefixmap)?;
                let sem_acts = sem_acts.deref_iri(base, prefixmap)?;
                let expressions = expressions.deref_iri(base, prefixmap)?;
                Ok(TripleExpr::OneOf {
                    id,
                    expressions,
                    min,
                    max,
                    sem_acts,
                    annotations,
                })
            },
            TripleExpr::TripleConstraint {
                id,
                negated,
                inverse,
                predicate,
                value_expr,
                min,
                max,
                sem_acts,
                annotations,
            } => {
                let id = id.deref_iri(base, prefixmap)?;
                let annotations = annotations.deref_iri(base, prefixmap)?;
                let sem_acts = sem_acts.deref_iri(base, prefixmap)?;
                let predicate = predicate.deref_iri(base, prefixmap)?;
                let value_expr = value_expr.deref_iri(base, prefixmap)?;
                Ok(TripleExpr::TripleConstraint {
                    id,
                    negated,
                    inverse,
                    predicate,
                    value_expr,
                    min,
                    max,
                    sem_acts,
                    annotations,
                })
            },
            TripleExpr::Ref(label) => {
                let label = label.deref_iri(base, prefixmap)?;
                Ok(TripleExpr::Ref(label))
            },
        }
    }
}

impl FromStr for TripleExpr {
    type Err = IriSError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let iri_ref = IriRef::try_from(s)?;
        Ok(TripleExpr::Ref(TripleExprLabel::IriRef { value: iri_ref }))
    }
}

impl SerializeStringOrStruct for TripleExpr {
    fn serialize_string_or_struct<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match &self {
            TripleExpr::Ref(r) => r.serialize(serializer),
            _ => self.serialize(serializer),
        }
    }
}

#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
#[serde(transparent)]
pub struct TripleExprWrapper {
    #[serde(
        serialize_with = "serialize_string_or_struct",
        deserialize_with = "deserialize_string_or_struct"
    )]
    pub te: TripleExpr,
}

impl TripleExprWrapper {}

impl DerefIri for TripleExprWrapper {
    fn deref_iri(self, base: Option<&IriS>, prefixmap: Option<&PrefixMap>) -> Result<Self, DerefError> {
        let te = self.te.deref_iri(base, prefixmap)?;
        Ok(TripleExprWrapper { te })
    }
}

impl From<TripleExpr> for TripleExprWrapper {
    fn from(value: TripleExpr) -> Self {
        Self { te: value }
    }
}
