use super::node_constraint::NodeConstraint;
use crate::ast::cond_kind::CondKind;
use crate::ir::annotation::Annotation;
use crate::ir::external_resolver::ExternalShapeResolverRegistry;
use crate::ir::map_action_extension::MapActionExtension;
use crate::ir::map_state::MapState;
use crate::ir::object_value::ObjectValue;
use crate::ir::schema_ir::SchemaIR;
use crate::ir::sem_act::SemAct;
use crate::ir::semantic_action_context::SemanticActionContext;
use crate::ir::semantic_actions_registry::SemanticActionsRegistry;
use crate::ir::shape::Shape;
use crate::ir::shape_expr::ShapeExpr;
use crate::ir::shape_label::ShapeLabel;
use crate::ir::test_action_extension::TestActionExtension;
use crate::ir::value_set::ValueSet;
use crate::ir::value_set_value::ValueSetValue;
use crate::{CResult, Cond, Expr, Node, Pred, ResolveMethod, ShExFormat, ShExParser, ir};
use crate::{SchemaIRError, ShapeLabelIdx, ast, ast::Schema as SchemaAST};
use crate::{ShapeExprLabel, ast::iri_exclusion::IriExclusion};
use core::panic;
use prefixmap::{IriRef, PrefixMap};
use rbe::{Cardinality, SingleCond};
use rbe::{Component, MatchCond, Max, Min, RbeStruct, RbeTable};
use rudof_iri::IriS;
use rudof_iri::error::IriSError;
use rudof_rdf::rdf_core::term::literal::NumericLiteral;
use std::collections::HashMap;
use std::sync::Arc;
use tracing::{info, trace};

#[derive(Debug, Default)]
/// AST2IR compile a Schema in AST (JSON) to IR (Intermediate Representation).
pub struct AST2IR {
    shape_decls_counter: usize,
    resolve_method: ResolveMethod,
    semantic_actions_registry: Arc<SemanticActionsRegistry>,
    triple_expr_labels: HashMap<ast::TripleExprLabel, ast::TripleExpr>,
}

impl AST2IR {
    pub fn new(resolve_method: &ResolveMethod, map_state: MapState) -> Self {
        let semantic_actions_registry = SemanticActionsRegistry::new().with(vec![
            Box::new(TestActionExtension::new()),
            Box::new(MapActionExtension::new(map_state)),
        ]);
        Self {
            resolve_method: resolve_method.clone(),
            shape_decls_counter: 0,
            semantic_actions_registry: Arc::new(semantic_actions_registry),
            triple_expr_labels: HashMap::new(),
        }
    }

    /// Build an `AST2IR` from an existing registry handle.
    ///
    /// This is the preferred constructor when the registry (and its `MapActionExtension` Arc) has
    /// already been set up by the caller, so that all conditions compiled here share the same
    /// `Arc<Mutex<MapState>>` as the registry stored in the `SchemaIR`.
    pub fn with_registry(resolve_method: &ResolveMethod, registry: Arc<SemanticActionsRegistry>) -> Self {
        Self {
            resolve_method: resolve_method.clone(),
            shape_decls_counter: 0,
            semantic_actions_registry: registry,
            triple_expr_labels: HashMap::new(),
        }
    }

    pub fn compile(
        &mut self,
        schema_ast: &SchemaAST,
        source_iri: &IriS,
        base: &Option<IriS>,
        compiled_schema: &mut SchemaIR,
        external_resolvers: &ExternalShapeResolverRegistry,
    ) -> CResult<()> {
        // Apply the external-shape resolver registry to the root schema before any other
        // processing. Imported schemas are rewritten as they are resolved inside
        // `collect_imports_labels`. Cloning is necessary because `rewrite_ast` takes ownership;
        // it is also cheap relative to the rest of compilation.
        let rewritten_root = external_resolvers.rewrite_ast(schema_ast.clone());
        let schema_ast = &rewritten_root;

        // Collect labels of all imported schemas first, so they are available when compiling the root schema's shape expressions. This also serves as a cycle check for imports.
        let mut visited = Vec::new();

        let mut imported_asts: Vec<(SchemaAST, IriS)> = Vec::new();
        /*trace!(
            "Compiling schema from {source_iri}. Base: {}",
            base.as_ref().map(|b| b.as_str()).unwrap_or("None")
        );*/
        // trace!("Schema AST: {schema_ast:#?}");
        // Phase 1: register shape labels for every schema in the import tree (bottom-up).
        // Labels of all imported schemas must be known before compiling any shape expressions,
        // since references may cross schema boundaries.
        self.collect_imports_labels(
            schema_ast,
            source_iri,
            compiled_schema,
            &mut visited,
            base,
            &mut imported_asts,
            external_resolvers,
        )?;

        // Root schema prefixmap wins (imported schemas' prefix maps are scoped to their own file).
        compiled_schema.set_prefixmap(schema_ast.prefixmap());

        compiled_schema.set_semantic_actions_registry(self.semantic_actions_registry.clone());

        // Collect start actions
        if let Some(start_actions) = schema_ast.start_actions() {
            let mut compiled_start_acts = Vec::new();
            for sa in start_actions {
                let compiled = self.cnv_sem_action(&sa, &compiled_schema.prefixmap())?;
                compiled_start_acts.push(compiled);
            }
            compiled_schema.set_start_actions(compiled_start_acts);
        }
        // Root labels (may include `start`).

        // trace!("Collecting shape labels for root schema {source_iri}");
        let local_shapes = self.collect_shape_labels(schema_ast, compiled_schema, source_iri, base)?;
        // trace!("Collected shape labels for root schema {source_iri}: {local_shapes} shapes");
        self.triple_expr_labels.clear();
        for (ast, _) in imported_asts.iter() {
            self.collect_triple_expr_labels_schema(ast)?;
        }
        self.collect_triple_expr_labels_schema(schema_ast)?;

        // Phase 2: compile shape expressions in the order they were collected
        // (imports bottom-up, then root).
        for (ast, src) in imported_asts.iter() {
            self.collect_shape_exprs(ast, compiled_schema, src, &schema_ast.base())?;
        }
        self.collect_shape_exprs(schema_ast, compiled_schema, source_iri, &schema_ast.base())?;
        compiled_schema.set_local_shapes_counter(local_shapes);
        compiled_schema.set_imported_schemas(visited);
        compiled_schema.increment_total_shapes(local_shapes);
        compiled_schema.build_dependency_graph();
        compiled_schema.build_inheritance_graph();
        Ok(())
    }

    fn collect_imports_labels(
        &mut self,
        schema_ast: &SchemaAST,
        source_iri: &IriS,
        compiled_schema: &mut SchemaIR,
        visited_sources: &mut Vec<IriS>,
        base: &Option<IriS>,
        imported_asts: &mut Vec<(SchemaAST, IriS)>,
        external_resolvers: &ExternalShapeResolverRegistry,
    ) -> CResult<()> {
        let imports = schema_ast
            .imports_resolved(base)
            .map_err(|e| Box::new(SchemaIRError::ImportIriError { error: e.to_string() }))?;
        for import_iri in imports {
            trace!("Import IRI resolved to {import_iri}");
            if !visited_sources.contains(&import_iri) {
                visited_sources.push(import_iri.clone());
                // For imported schemas, the base for dereferencing is the source IRI of the importer.
                let imported_schema = self.resolve(&import_iri, Some(source_iri))?;
                // Apply the external-resolver registry so EXTERNAL decls inside imported schemas are
                // substituted before their labels are registered.
                let imported_schema = external_resolvers.rewrite_ast(imported_schema);

                // TODO: Maybe append imported semantic actions to the importer's registry instead of rejecting the schema if it declares start actions?
                // This would allow more modularity and reuse of semantic actions across schemas,
                // at the cost of making it less clear where a given start action is declared.
                /* I removed the following line because I didn't find in the spec any explicit prohibition of start actions in imported schemas, and it was causing problems for some of my test cases. If this turns out to be a problem, we can always add it back in later.
                if imported_schema.start_actions().is_some() {
                    return Err(Box::new(SchemaIRError::ImportIriError {
                        error: format!("Imported schema {import_iri} declares startActs (disallowed by ShEx spec)"),
                    }));
                }*/

                // Nested imports must resolve relative to the imported schema's URL (RFC 3986).
                let nested_base = Some(import_iri.clone());
                self.collect_imports_labels(
                    &imported_schema,
                    &import_iri,
                    compiled_schema,
                    visited_sources,
                    &nested_base,
                    imported_asts,
                    external_resolvers,
                )?;
                // Register labels of this imported schema. Per spec, the imported schema's
                // `start` is ignored — only shape declarations contribute.
                self.collect_imported_shape_labels(&imported_schema, compiled_schema, &import_iri)?;
                imported_asts.push((imported_schema, import_iri));
            }
        }
        Ok(())
    }

    fn collect_imported_shape_labels(
        &mut self,
        schema_ast: &SchemaAST,
        compiled_schema: &mut SchemaIR,
        source_iri: &IriS,
    ) -> CResult<usize> {
        let mut shape_labels_counter = 0;
        if let Some(shape_decls) = &schema_ast.shapes() {
            for shape_decl in shape_decls {
                let label = self.shape_expr_label_to_shape_label(&shape_decl.id)?;
                let idx = compiled_schema.add_shape(label, ShapeExpr::Empty, source_iri);
                if shape_decl.is_abstract {
                    compiled_schema.add_abstract_shape(idx);
                }
                self.shape_decls_counter += 1;
                shape_labels_counter += 1;
            }
        }
        Ok(shape_labels_counter)
    }

    fn resolve(&self, iri: &IriS, base: Option<&IriS>) -> CResult<SchemaAST> {
        let new_schema = match &self.resolve_method {
            ResolveMethod::RotatingFormats(formats) => find_schema_rotating_formats(iri, formats.clone(), base),
            ResolveMethod::ByGuessingExtension => todo!(),
            ResolveMethod::ByContentNegotiation => todo!(),
        }?;
        Ok(new_schema)
    }

    pub fn collect_shape_labels(
        &mut self,
        schema_ast: &SchemaAST,
        compiled_schema: &mut SchemaIR,
        source_iri: &IriS,
        base: &Option<IriS>,
    ) -> CResult<usize> {
        let mut shape_labels_counter = 0;
        match &schema_ast.shapes() {
            None => {},
            Some(shape_decls) => {
                for shape_decl in shape_decls {
                    let label = self.shape_expr_label_to_shape_label(&shape_decl.id)?;
                    // trace!("Collecting shape label for {label}");
                    let idx = compiled_schema.add_shape(label, ShapeExpr::Empty, source_iri);
                    if shape_decl.is_abstract {
                        compiled_schema.add_abstract_shape(idx);
                    }
                    self.shape_decls_counter += 1;
                    shape_labels_counter += 1;
                }
            },
        }
        if let Some(shape_expr_start) = &schema_ast.start() {
            let start_label = ShapeLabel::Start;
            let idx = compiled_schema.add_shape(start_label.clone(), ShapeExpr::Empty, source_iri);
            let start_compiled = self.compile_shape_expr(shape_expr_start, &idx, compiled_schema, source_iri, base)?;
            compiled_schema.replace_shape(&idx, start_compiled);
            self.shape_decls_counter += 1;
            shape_labels_counter += 1;
        }
        Ok(shape_labels_counter)
    }

    pub fn collect_shape_exprs(
        &mut self,
        schema_ast: &SchemaAST,
        compiled_schema: &mut SchemaIR,
        source_iri: &IriS,
        base: &Option<IriS>,
    ) -> CResult<()> {
        match &schema_ast.shapes() {
            None => Ok(()),
            Some(sds) => {
                for sd in sds {
                    let idx = self.get_shape_label_idx(&sd.id, compiled_schema)?;
                    let se = self.compile_shape_decl(sd, &idx, compiled_schema, source_iri, base)?;
                    compiled_schema.replace_shape(&idx, se)
                }
                Ok(())
            },
        }
    }

    fn shape_expr_label_to_shape_label(&self, id: &ShapeExprLabel) -> CResult<ShapeLabel> {
        // trace!("Converting shape expression label {id} to shape label");
        match id {
            ShapeExprLabel::IriRef { value } => {
                let shape_label = iri_ref_2_shape_label(value)?;
                Ok(shape_label)
            },
            ShapeExprLabel::BNode { value } => Ok(ShapeLabel::BNode(value.clone())),
            ShapeExprLabel::Start => Ok(ShapeLabel::Start),
        }
    }

    fn get_shape_label_idx(&self, id: &ShapeExprLabel, compiled_schema: &mut SchemaIR) -> CResult<ShapeLabelIdx> {
        let label = self.shape_expr_label_to_shape_label(id)?;
        compiled_schema.get_shape_label_idx(&label)
    }

    fn compile_shape_decl(
        &self,
        sd: &ast::ShapeDecl,
        idx: &ShapeLabelIdx,
        compiled_schema: &mut SchemaIR,
        source_iri: &IriS,
        base: &Option<IriS>,
    ) -> CResult<ShapeExpr> {
        let se = self.compile_shape_expr(&sd.shape_expr, idx, compiled_schema, source_iri, base)?;
        Ok(se)
    }

    fn ref2idx(&self, sref: &ast::ShapeExprLabel, compiled_schema: &mut SchemaIR) -> CResult<ShapeLabelIdx> {
        let idx = self.get_shape_label_idx(sref, compiled_schema)?;
        Ok(idx)
    }

    fn compile_shape_expr(
        &self,
        se: &ast::ShapeExpr,
        idx: &ShapeLabelIdx,
        compiled_schema: &mut SchemaIR,
        source_iri: &IriS,
        base: &Option<IriS>,
    ) -> CResult<ShapeExpr> {
        // trace!("Compiling shape expression {se:?} with index {idx} in schema {source_iri}");
        let result: ShapeExpr = match se {
            ast::ShapeExpr::Ref(se_ref) => {
                // let label = self.shape_expr_label_to_shape_label(se_ref)?;
                // trace!("Compiling ShapeRef with label {label}");
                let new_idx = self.ref2idx(se_ref, compiled_schema)?;
                let se: ShapeExpr = ShapeExpr::Ref { idx: new_idx };
                Ok::<ShapeExpr, SchemaIRError>(se)
            },
            ast::ShapeExpr::ShapeOr { shape_exprs: ses } => {
                let mut cnv = Vec::new();
                for sew in ses {
                    let internal_idx = compiled_schema.new_index(source_iri);
                    let se = self.compile_shape_expr(&sew.se, &internal_idx, compiled_schema, source_iri, base)?;
                    compiled_schema.replace_shape(&internal_idx, se.clone());
                    cnv.push(internal_idx);
                }
                let result = ShapeExpr::ShapeOr { exprs: cnv };
                compiled_schema.replace_shape(idx, result.clone());
                Ok(result)
            },
            ast::ShapeExpr::ShapeAnd { shape_exprs: ses } => {
                let mut cnv = Vec::new();
                for sew in ses {
                    let internal_idx = compiled_schema.new_index(source_iri);
                    let se = self.compile_shape_expr(&sew.se, &internal_idx, compiled_schema, source_iri, base)?;
                    compiled_schema.replace_shape(&internal_idx, se.clone());
                    cnv.push(internal_idx);
                }
                let result = ShapeExpr::ShapeAnd { exprs: cnv };
                compiled_schema.replace_shape(idx, result.clone());
                Ok(result)
            },
            ast::ShapeExpr::ShapeNot { shape_expr: sew } => {
                let internal_idx = compiled_schema.new_index(source_iri);
                let se = self.compile_shape_expr(&sew.se, &internal_idx, compiled_schema, source_iri, base)?;
                compiled_schema.replace_shape(&internal_idx, se.clone());
                let not_se = ShapeExpr::ShapeNot { expr: internal_idx };
                compiled_schema.replace_shape(idx, not_se.clone());
                Ok(not_se)
            },
            ast::ShapeExpr::Shape(shape) => {
                let new_extra = self.cnv_extra(&shape.extra, &compiled_schema.prefixmap())?;
                let rbe_table: Expr = match &shape.expression {
                    None => RbeTable::new(),
                    Some(tew) => {
                        let mut table: Expr = RbeTable::new();
                        let rbe = self.triple_expr2rbe(&tew.te, compiled_schema, &mut table, source_iri, base)?;
                        table.with_rbe(rbe);
                        table
                    },
                };
                let preds = self.get_preds_shape(shape)?;
                let extends = shape
                    .extends()
                    .iter()
                    .map(|s| self.ref2idx(s, compiled_schema))
                    .collect::<CResult<Vec<_>>>()?;

                let sem_acts = self.cnv_sem_actions(&shape.sem_acts, &compiled_schema.prefixmap())?;

                let shape = Shape::new(
                    Self::cnv_closed(&shape.closed),
                    new_extra,
                    rbe_table,
                    sem_acts,
                    Self::cnv_annotations(&shape.annotations),
                    preds,
                    extends,
                );
                Ok(ShapeExpr::Shape(Box::new(shape)))
            },
            ast::ShapeExpr::NodeConstraint(nc) => {
                let (cond, display) = Self::cnv_node_constraint(
                    self,
                    &nc.node_kind(),
                    &nc.datatype(),
                    &nc.xs_facet(),
                    &nc.values(),
                    &compiled_schema.prefixmap(),
                    base,
                )?;
                let node_constraint = NodeConstraint::new(nc.clone(), cond, display);
                Ok(ShapeExpr::NodeConstraint(Box::new(node_constraint)))
            },
            ast::ShapeExpr::External => Ok(ShapeExpr::External {}),
        }?;
        // trace!("Result of compilation: {result}");
        Ok(result)
    }

    fn cnv_node_constraint(
        &self,
        nk: &Option<ast::NodeKind>,
        dt: &Option<IriRef>,
        xs_facet: &Option<Vec<ast::XsFacet>>,
        values: &Option<Vec<ast::ValueSetValue>>,
        prefixmap: &PrefixMap,
        base: &Option<IriS>,
    ) -> CResult<(Cond, String)> {
        let maybe_value_set = match values {
            Some(vs) => {
                let value_set = create_value_set(vs, prefixmap)?;
                Some(value_set)
            },
            None => None,
        };
        node_constraint2match_cond(nk, dt, xs_facet, &maybe_value_set, prefixmap, base)
    }

    fn cnv_closed(closed: &Option<bool>) -> bool {
        match closed {
            None => false,
            Some(closed) => *closed,
        }
    }

    fn cnv_extra(&self, extra: &Option<Vec<IriRef>>, prefixmap: &PrefixMap) -> CResult<Vec<Pred>> {
        if let Some(extra) = extra {
            let mut vs = Vec::new();
            for iri in extra {
                let nm = cnv_iri_ref(iri, prefixmap)?;
                vs.push(Pred::new(nm, true));
            }
            Ok(vs)
        } else {
            Ok(Vec::new())
        }
    }

    fn cnv_annotations(annotations: &Option<Vec<ast::Annotation>>) -> Vec<Annotation> {
        if let Some(_anns) = annotations {
            // TODO
            Vec::new()
        } else {
            Vec::new()
        }
    }

    fn triple_expr2rbe(
        &self,
        triple_expr: &ast::TripleExpr,
        compiled_schema: &mut SchemaIR,
        current_table: &mut RbeTable<Pred, Node, ShapeLabelIdx, SemanticActionContext, CondKind>,
        source_iri: &IriS,
        base: &Option<IriS>,
    ) -> CResult<RbeStruct<Component>> {
        let mut visited_refs = Vec::new();
        self.triple_expr2rbe_with_refs(
            triple_expr,
            compiled_schema,
            current_table,
            source_iri,
            &mut visited_refs,
            base,
        )
    }

    fn triple_expr2rbe_with_refs(
        &self,
        triple_expr: &ast::TripleExpr,
        compiled_schema: &mut SchemaIR,
        current_table: &mut RbeTable<Pred, Node, ShapeLabelIdx, SemanticActionContext, CondKind>,
        source_iri: &IriS,
        visited_refs: &mut Vec<ast::TripleExprLabel>,
        base: &Option<IriS>,
    ) -> CResult<RbeStruct<Component>> {
        match triple_expr {
            ast::TripleExpr::EachOf {
                id: _,
                expressions,
                min,
                max,
                sem_acts,
                annotations: _,
            } => {
                let mut cs = Vec::new();
                for e in expressions {
                    let c = self.triple_expr2rbe_with_refs(
                        &e.te,
                        compiled_schema,
                        current_table,
                        source_iri,
                        visited_refs,
                        base,
                    )?;
                    cs.push(c)
                }
                let card = self.cnv_min_max(min, max)?;
                if sem_acts.is_some() {
                    info!("Semantic actions in EachOf ignored: {sem_acts:?}");
                    let actions = self.cnv_sem_actions(sem_acts, &compiled_schema.prefixmap())?;
                    let _conds = self.sem_acts_to_conds(actions)?;
                    // cs.extend(actions);
                    // TODO: how to combine semantic actions with the triple expressions in EachOf?
                };
                Ok(Self::mk_card_group(RbeStruct::and(cs), card))
            },
            ast::TripleExpr::OneOf {
                id: _,
                expressions,
                min,
                max,
                sem_acts,
                annotations: _,
            } => {
                let mut cs = Vec::new();
                for e in expressions {
                    let c = self.triple_expr2rbe_with_refs(
                        &e.te,
                        compiled_schema,
                        current_table,
                        source_iri,
                        visited_refs,
                        base,
                    )?;
                    cs.push(c)
                }
                let card = self.cnv_min_max(min, max)?;
                if sem_acts.is_some() {
                    info!("Semantic actions in OneOf ignored: {sem_acts:?}");
                }
                Ok(Self::mk_card_group(RbeStruct::or(cs), card))
            },
            ast::TripleExpr::TripleConstraint {
                id: _,
                negated: _,
                inverse,
                predicate,
                value_expr,
                min,
                max,
                sem_acts,
                annotations: _,
            } => {
                let min = self.cnv_min(min)?;
                let max = self.cnv_max(max)?;
                let iri = Self::cnv_predicate(predicate, inverse)?;
                let actions = self.cnv_sem_actions(sem_acts, &compiled_schema.prefixmap())?;
                let (cond, _display) =
                    self.value_expr2match_cond(value_expr, actions, compiled_schema, source_iri, base)?;
                let component = current_table.add_component(iri, &cond);
                Ok(RbeStruct::symbol(component, min.value, max))
            },
            ast::TripleExpr::Ref(r) => {
                if visited_refs.contains(r) {
                    return Err(Box::new(SchemaIRError::Internal {
                        msg: format!("Cyclic TripleExprRef detected while compiling: {r}"),
                    }));
                }
                let referenced = self.triple_expr_labels.get(r).cloned().ok_or_else(|| {
                    Box::new(SchemaIRError::Internal {
                        msg: format!("TripleExprRef not found in schema imports closure: {r}"),
                    })
                })?;
                visited_refs.push(r.clone());
                let rbe = self.triple_expr2rbe_with_refs(
                    &referenced,
                    compiled_schema,
                    current_table,
                    source_iri,
                    visited_refs,
                    base,
                )?;
                visited_refs.pop();
                Ok(rbe)
            },
        }
    }

    fn cnv_sem_actions(&self, sem_acts: &Option<Vec<ast::SemAct>>, prefixmap: &PrefixMap) -> CResult<Vec<SemAct>> {
        if let Some(actions) = sem_acts {
            actions.iter().map(|a| self.cnv_sem_action(a, prefixmap)).collect()
        } else {
            Ok(Vec::new())
        }
    }

    fn cnv_sem_action(&self, sem_act: &ast::SemAct, prefixmap: &PrefixMap) -> CResult<SemAct> {
        let iri_s = cnv_iri_ref(&sem_act.name(), prefixmap)?;
        Ok(SemAct::new(iri_s, sem_act.code()))
    }

    fn cnv_predicate(predicate: &IriRef, inverse: &Option<bool>) -> CResult<Pred> {
        let is_direct = match inverse {
            None => true,
            Some(inverse) => !inverse,
        };
        match predicate {
            IriRef::Iri(iri) => Ok(Pred::new(iri.clone(), is_direct)),
            IriRef::Prefixed { prefix, local } => Err(Box::new(SchemaIRError::Internal {
                msg: format!("Cannot convert prefixed {prefix}:{local} to predicate without context"),
            })),
        }
    }

    fn cnv_min_max(&self, min: &Option<i32>, max: &Option<i32>) -> CResult<Cardinality> {
        let min = self.cnv_min(min)?;
        let max = self.cnv_max(max)?;
        Ok(Cardinality::from(min, max))
    }

    fn mk_card_group(rbe: RbeStruct<Component>, card: Cardinality) -> RbeStruct<Component> {
        match &card {
            c if c.is_1_1() => rbe,
            c if c.is_star() => RbeStruct::star(rbe),
            c if c.is_plus() => RbeStruct::plus(rbe),
            _c => RbeStruct::repeat(rbe, card.min.value, card.max.clone()),
        }
    }

    fn cnv_min(&self, min: &Option<i32>) -> CResult<Min> {
        match min {
            Some(min) if *min < 0 => Err(Box::new(SchemaIRError::MinLessZero { min: *min })),
            Some(min) => Ok(Min::from(*min)),
            None => Ok(Min::from(1)),
        }
    }

    fn cnv_max(&self, max: &Option<i32>) -> CResult<Max> {
        match *max {
            Some(-1) => Ok(Max::Unbounded),
            Some(max) if max < -1 => Err(Box::new(SchemaIRError::MaxIncorrect { max })),
            Some(max) => Ok(Max::from(max)),
            None => Ok(Max::from(1)),
        }
    }

    fn value_expr2match_cond(
        &self,
        value_expr: &Option<Box<ast::ShapeExpr>>,
        actions: Vec<SemAct>,
        compiled_schema: &mut SchemaIR,
        source_iri: &IriS,
        base: &Option<IriS>,
    ) -> CResult<(Cond, String)> {
        let mut sem_actions_conds = self.sem_acts_to_conds(actions)?;
        let (cond, display) = if let Some(se) = value_expr.as_deref() {
            match se {
                ast::ShapeExpr::NodeConstraint(nc) => self.cnv_node_constraint(
                    &nc.node_kind(),
                    &nc.datatype(),
                    &nc.xs_facet(),
                    &nc.values(),
                    &compiled_schema.prefixmap(),
                    base,
                ),

                ast::ShapeExpr::Ref(sref) => {
                    let idx = self.ref2idx(sref, compiled_schema)?;
                    Ok((mk_cond_ref(idx), format!("ShapeRef {sref}")))
                },
                ast::ShapeExpr::Shape { .. } => {
                    // TODO: avoid recompiling the same shape expression?
                    // I think this code should be reviewed....
                    let idx = compiled_schema.new_index(source_iri);
                    let se = self.compile_shape_expr(se, &idx, compiled_schema, source_iri, base)?;
                    compiled_schema.replace_shape(&idx, se.clone());
                    // trace!("Returning SHAPE cond with idx {idx}");
                    Ok((mk_cond_ref(idx), format!("Shape {idx}")))
                },
                ast::ShapeExpr::ShapeAnd { shape_exprs } => {
                    let mut ands = Vec::new();
                    for shape_expr in shape_exprs {
                        let idx_se = compiled_schema.new_index(source_iri);
                        let se = self.compile_shape_expr(&shape_expr.se, &idx_se, compiled_schema, source_iri, base)?;
                        compiled_schema.replace_shape(&idx_se, se.clone());
                        ands.push(idx_se);
                    }
                    let and_se = ShapeExpr::ShapeAnd { exprs: ands.clone() };
                    let idx_and = compiled_schema.new_index(source_iri);
                    compiled_schema.replace_shape(&idx_and, and_se);
                    let display = format!(
                        "AND({})",
                        ands.iter().map(|i| i.to_string()).collect::<Vec<_>>().join(", ")
                    );
                    Ok((mk_cond_ref(idx_and), display))
                },
                ast::ShapeExpr::ShapeOr { shape_exprs } => {
                    let mut ors = Vec::new();
                    for se in shape_exprs {
                        let idx_se = compiled_schema.new_index(source_iri);
                        let se = self.compile_shape_expr(&se.se, &idx_se, compiled_schema, source_iri, base)?;
                        compiled_schema.replace_shape(&idx_se, se.clone());
                        ors.push(idx_se);
                    }
                    let or_se = ShapeExpr::ShapeOr { exprs: ors.clone() };
                    let display = format!(
                        "OR({})",
                        ors.iter().map(|i| i.to_string()).collect::<Vec<_>>().join(", ")
                    );
                    let idx_or = compiled_schema.new_index(source_iri);
                    compiled_schema.replace_shape(&idx_or, or_se);
                    Ok((mk_cond_ref(idx_or), display))
                },
                ast::ShapeExpr::ShapeNot { shape_expr } => {
                    let idx_shape_expr = compiled_schema.new_index(source_iri);
                    trace!(
                        "value_expr2matchcond: Compiling ShapeNot with {shape_expr:?}, idx_shape_expr {idx_shape_expr}"
                    );
                    let se =
                        self.compile_shape_expr(&shape_expr.se, &idx_shape_expr, compiled_schema, source_iri, base)?;
                    compiled_schema.replace_shape(&idx_shape_expr, se.clone());
                    let display = format!("NOT {idx_shape_expr}");
                    let not_se = ShapeExpr::ShapeNot { expr: idx_shape_expr };
                    let idx_not = compiled_schema.new_index(source_iri);
                    compiled_schema.replace_shape(&idx_not, not_se);
                    trace!("Returning NOT cond with idx {idx_not}");
                    Ok((mk_cond_ref(idx_not), display))
                },
                ast::ShapeExpr::External => {
                    let idx_ext = compiled_schema.new_index(source_iri);
                    compiled_schema.replace_shape(&idx_ext, ShapeExpr::External {});
                    let display = format!("EXTERNAL {idx_ext}");
                    Ok((mk_cond_ref(idx_ext), display))
                },
            }
        } else {
            Ok((
                MatchCond::single(SingleCond::new().with_name(".").with_kind(CondKind::Any)),
                ".".to_string(),
            ))
        }?;
        if !sem_actions_conds.is_empty() {
            sem_actions_conds.push(cond);
            Ok((
                MatchCond::and(sem_actions_conds),
                format!("value expression {} + semantic actions", display),
            ))
        } else {
            Ok((cond, display))
        }
    }

    fn get_preds_shape(&self, shape: &ast::Shape) -> CResult<Vec<Pred>> {
        let mut visited_refs = Vec::new();
        match shape.triple_expr() {
            None => Ok(Vec::new()),
            Some(te) => self.get_preds_triple_expr(&te, &mut visited_refs),
        }
    }

    fn get_preds_triple_expr(
        &self,
        te: &ast::TripleExpr,
        visited_refs: &mut Vec<ast::TripleExprLabel>,
    ) -> CResult<Vec<Pred>> {
        match te {
            ast::TripleExpr::EachOf { expressions, .. } => expressions
                .iter()
                .map(|te| self.get_preds_triple_expr(&te.te, visited_refs))
                .collect::<CResult<Vec<Vec<Pred>>>>()
                .map(|vss| vss.into_iter().flatten().collect()),
            ast::TripleExpr::OneOf { expressions, .. } => expressions
                .iter()
                .map(|te| self.get_preds_triple_expr(&te.te, visited_refs))
                .collect::<CResult<Vec<Vec<Pred>>>>()
                .map(|vss| vss.into_iter().flatten().collect()),
            ast::TripleExpr::TripleConstraint { predicate, inverse, .. } => {
                let pred = iri_ref2iri_s(predicate);
                let is_direct = if let Some(inverse) = inverse { !inverse } else { true };
                Ok(vec![Pred::new(pred, is_direct)])
            },
            ast::TripleExpr::Ref(r) => {
                if visited_refs.contains(r) {
                    // Ignore already visited references while collecting predicates to avoid loops.
                    return Ok(Vec::new());
                }
                let referenced = self.triple_expr_labels.get(r).cloned().ok_or_else(|| {
                    Box::new(SchemaIRError::Internal {
                        msg: format!("TripleExprRef not found while collecting predicates: {r}"),
                    })
                })?;
                visited_refs.push(r.clone());
                let preds = self.get_preds_triple_expr(&referenced, visited_refs)?;
                visited_refs.pop();
                Ok(preds)
            },
        }
    }

    fn collect_triple_expr_labels_schema(&mut self, schema_ast: &SchemaAST) -> CResult<()> {
        if let Some(start) = schema_ast.start() {
            self.collect_triple_expr_labels_shape_expr(&start)?;
        }
        if let Some(shape_decls) = schema_ast.shapes() {
            for shape_decl in shape_decls {
                self.collect_triple_expr_labels_shape_expr(&shape_decl.shape_expr)?;
            }
        }
        Ok(())
    }

    fn collect_triple_expr_labels_shape_expr(&mut self, shape_expr: &ast::ShapeExpr) -> CResult<()> {
        match shape_expr {
            ast::ShapeExpr::ShapeOr { shape_exprs } | ast::ShapeExpr::ShapeAnd { shape_exprs } => {
                for sew in shape_exprs {
                    self.collect_triple_expr_labels_shape_expr(&sew.se)?;
                }
                Ok(())
            },
            ast::ShapeExpr::ShapeNot { shape_expr } => self.collect_triple_expr_labels_shape_expr(&shape_expr.se),
            ast::ShapeExpr::Shape(shape) => {
                if let Some(expr) = shape.triple_expr() {
                    self.collect_triple_expr_labels_triple_expr(&expr)?;
                }
                Ok(())
            },
            ast::ShapeExpr::NodeConstraint(_) | ast::ShapeExpr::External | ast::ShapeExpr::Ref(_) => Ok(()),
        }
    }

    fn collect_triple_expr_labels_triple_expr(&mut self, triple_expr: &ast::TripleExpr) -> CResult<()> {
        match triple_expr {
            ast::TripleExpr::EachOf { id, expressions, .. } | ast::TripleExpr::OneOf { id, expressions, .. } => {
                self.register_triple_expr_label(id, triple_expr)?;
                for tew in expressions {
                    self.collect_triple_expr_labels_triple_expr(&tew.te)?;
                }
                Ok(())
            },
            ast::TripleExpr::TripleConstraint { id, .. } => self.register_triple_expr_label(id, triple_expr),
            ast::TripleExpr::Ref(_) => Ok(()),
        }
    }

    fn register_triple_expr_label(
        &mut self,
        maybe_label: &Option<ast::TripleExprLabel>,
        triple_expr: &ast::TripleExpr,
    ) -> CResult<()> {
        if let Some(label) = maybe_label {
            if self.triple_expr_labels.contains_key(label) {
                return Err(Box::new(SchemaIRError::DuplicatedTripleExprLabel {
                    label: Box::new(label.clone()),
                }));
            }
            self.triple_expr_labels.insert(label.clone(), triple_expr.clone());
        }
        Ok(())
    }

    fn sem_acts_to_conds(&self, actions: Vec<SemAct>) -> CResult<Vec<Cond>> {
        actions
            .iter()
            .map(|a| {
                self.semantic_actions_registry
                    .find_code(a.name())
                    .map_err(|e| Box::new(SchemaIRError::SemanticActionError { error: e.to_string() }))?;
                Ok(mk_cond_sem_act(a.name().clone(), a.code().cloned()))
            })
            .collect::<Result<Vec<_>, Box<SchemaIRError>>>()
    }
}

fn iri_ref2iri_s(iri_ref: &IriRef) -> IriS {
    match iri_ref {
        IriRef::Iri(iri) => iri.clone(),
        IriRef::Prefixed { prefix, local } => {
            panic!("Compiling schema...found prefixed iri: {prefix}:{local}")
        },
    }
}

fn node_constraint2match_cond(
    node_kind: &Option<ast::NodeKind>,
    datatype: &Option<IriRef>,
    xs_facet: &Option<Vec<ast::XsFacet>>,
    values: &Option<ValueSet>,
    prefixmap: &PrefixMap,
    base: &Option<IriS>,
) -> CResult<(Cond, String)> {
    let c1: Option<(Cond, String)> = node_kind.as_ref().map(node_kind2match_cond);
    let c2 = datatype
        .as_ref()
        .map(|dt| datatype2match_cond(dt, prefixmap))
        .transpose()?;
    let c3 = xs_facet.as_ref().map(|xsd| xs_facets2match_cond(xsd, base));
    let c4 = values.as_ref().map(|vs| valueset2match_cond(vs.clone(), prefixmap));
    let os = vec![c1, c2, c3, c4];
    Ok(options2match_cond(os))
}

fn node_kind2match_cond(nodekind: &ast::NodeKind) -> (Cond, String) {
    (mk_cond_nodekind(nodekind.clone()), format!("nodekind({nodekind})"))
}

fn datatype2match_cond(datatype: &IriRef, prefixmap: &PrefixMap) -> CResult<(Cond, String)> {
    let iri = cnv_iri_ref(datatype, prefixmap)?;
    let cond = mk_cond_datatype(&iri, prefixmap);
    let str = cond.to_string();
    Ok((cond, str))
}

fn xs_facets2match_cond(xs_facets: &Vec<ast::XsFacet>, base: &Option<IriS>) -> (Cond, String) {
    let mut conds = Vec::new();
    for xs_facet in xs_facets {
        conds.push(xs_facet2match_cond(xs_facet, base))
    }
    (MatchCond::And(conds), format!("xs_facets({xs_facets:?})"))
}

fn xs_facet2match_cond(xs_facet: &ast::XsFacet, base: &Option<IriS>) -> Cond {
    match xs_facet {
        ast::XsFacet::StringFacet(sf) => string_facet_to_match_cond(sf, base),
        ast::XsFacet::NumericFacet(nf) => numeric_facet_to_match_cond(nf),
    }
}

fn string_facet_to_match_cond(sf: &ast::StringFacet, base: &Option<IriS>) -> Cond {
    match sf {
        ast::StringFacet::Length(len) => mk_cond_length(*len),
        ast::StringFacet::MinLength(len) => mk_cond_min_length(*len),
        ast::StringFacet::MaxLength(len) => mk_cond_max_length(*len),
        ast::StringFacet::Pattern(pat) => mk_cond_pattern(pat.regex(), pat.flags(), base),
    }
}

fn numeric_facet_to_match_cond(nf: &ast::NumericFacet) -> Cond {
    match nf {
        ast::NumericFacet::MinInclusive(min) => mk_cond_min_inclusive(min.clone()),
        ast::NumericFacet::MinExclusive(min) => mk_cond_min_exclusive(min.clone()),
        ast::NumericFacet::MaxInclusive(max) => mk_cond_max_inclusive(max.clone()),
        ast::NumericFacet::MaxExclusive(max) => mk_cond_max_exclusive(max.clone()),
        ast::NumericFacet::TotalDigits(td) => mk_cond_total_digits(*td),
        ast::NumericFacet::FractionDigits(fd) => mk_cond_fraction_digits(*fd),
    }
}

fn valueset2match_cond(vs: ValueSet, prefixmap: &PrefixMap) -> (Cond, String) {
    (
        mk_cond_value_set(vs.clone(), prefixmap),
        format!(
            "[{}]",
            vs.values()
                .iter()
                .map(|v| v.show_qualified(prefixmap))
                .collect::<Vec<_>>()
                .join(", ")
        ),
    )
}

fn options2match_cond<T: IntoIterator<Item = Option<(Cond, String)>>>(os: T) -> (Cond, String) {
    let vec: Vec<(Cond, String)> = os.into_iter().flatten().collect();
    match &vec[..] {
        [] => (MatchCond::empty(), ".".to_string()),
        [(c, s)] => (c.clone(), s.clone()),
        _ => (
            MatchCond::And(vec.iter().map(|(c, _)| c.clone()).collect()),
            vec.iter().map(|(_, s)| s.clone()).collect::<Vec<_>>().join(" AND "),
        ),
    }
}

fn mk_cond_ref(idx: ShapeLabelIdx) -> Cond {
    MatchCond::ref_(idx)
}

fn mk_cond_sem_act(name: IriS, code: Option<String>) -> Cond {
    let display = name.as_str().to_string();
    MatchCond::single(
        SingleCond::new()
            .with_name(display.as_str())
            .with_kind(CondKind::SemAct { name, code }),
    )
}

fn mk_cond_datatype(datatype: &IriS, prefixmap: &PrefixMap) -> Cond {
    let qualified = prefixmap.qualify(datatype).to_string();
    MatchCond::single(
        SingleCond::new()
            .with_name(qualified.as_str())
            .with_kind(CondKind::Datatype {
                iri: datatype.clone(),
                qualified,
            }),
    )
}

fn mk_cond_length(len: usize) -> Cond {
    MatchCond::single(
        SingleCond::new()
            .with_name(format!("length({len})").as_str())
            .with_kind(CondKind::Length(len)),
    )
}

fn mk_cond_min_inclusive(min: NumericLiteral) -> Cond {
    let name = format!("minInclusive({min})");
    MatchCond::single(
        SingleCond::new()
            .with_name(name.as_str())
            .with_kind(CondKind::MinInclusive(min)),
    )
}

fn mk_cond_min_exclusive(min: NumericLiteral) -> Cond {
    let name = format!("minExclusive({min})");
    MatchCond::single(
        SingleCond::new()
            .with_name(name.as_str())
            .with_kind(CondKind::MinExclusive(min)),
    )
}

fn mk_cond_total_digits(total: usize) -> Cond {
    MatchCond::single(
        SingleCond::new()
            .with_name(format!("totalDigits({total})").as_str())
            .with_kind(CondKind::TotalDigits(total)),
    )
}

fn mk_cond_fraction_digits(total: usize) -> Cond {
    MatchCond::single(
        SingleCond::new()
            .with_name(format!("fractionDigits({total})").as_str())
            .with_kind(CondKind::FractionDigits(total)),
    )
}

fn mk_cond_max_exclusive(max: NumericLiteral) -> Cond {
    let name = format!("maxExclusive({max})");
    MatchCond::single(
        SingleCond::new()
            .with_name(name.as_str())
            .with_kind(CondKind::MaxExclusive(max)),
    )
}

fn mk_cond_max_inclusive(max: NumericLiteral) -> Cond {
    let name = format!("maxInclusive({max})");
    MatchCond::single(
        SingleCond::new()
            .with_name(name.as_str())
            .with_kind(CondKind::MaxInclusive(max)),
    )
}

fn mk_cond_min_length(len: usize) -> Cond {
    MatchCond::single(
        SingleCond::new()
            .with_name(format!("minLength({len})").as_str())
            .with_kind(CondKind::MinLength(len)),
    )
}

fn mk_cond_max_length(len: usize) -> Cond {
    MatchCond::single(
        SingleCond::new()
            .with_name(format!("maxLength({len})").as_str())
            .with_kind(CondKind::MaxLength(len)),
    )
}

fn mk_cond_nodekind(nodekind: ast::NodeKind) -> Cond {
    MatchCond::single(
        SingleCond::new()
            .with_name(format!("nodekind({nodekind})").as_str())
            .with_kind(CondKind::NodeKind(nodekind)),
    )
}

fn mk_cond_pattern(regex: &str, flags: Option<&str>, base: &Option<IriS>) -> Cond {
    let name = format!("/{regex}/{}", flags.unwrap_or(""));
    MatchCond::single(SingleCond::new().with_name(name.as_str()).with_kind(CondKind::Pattern {
        regex: regex.to_string(),
        flags: flags.map(|f| f.to_string()),
        base: base.clone(),
    }))
}

fn iri_ref_2_shape_label(id: &IriRef) -> CResult<ShapeLabel> {
    match id {
        IriRef::Iri(iri) => Ok(ShapeLabel::Iri(iri.clone())),
        IriRef::Prefixed { prefix, local } => Err(Box::new(SchemaIRError::IriRef2ShapeLabelError {
            prefix: prefix.clone(),
            local: local.clone(),
        })),
    }
}

fn mk_cond_value_set(value_set: ValueSet, prefixmap: &PrefixMap) -> Cond {
    MatchCond::single(
        SingleCond::new()
            .with_name(value_set.show_qualified(prefixmap).as_str())
            .with_kind(CondKind::ValueSet {
                vs: value_set,
                prefixmap: prefixmap.clone(),
            }),
    )
}

fn create_value_set(values: &Vec<ast::ValueSetValue>, prefixmap: &PrefixMap) -> CResult<ValueSet> {
    let mut vs = ValueSet::new();
    for v in values {
        let cvalue = cnv_value(v, prefixmap)?;
        vs.add_value(cvalue)
    }
    Ok(vs)
}

fn cnv_value(v: &ast::ValueSetValue, prefixmap: &PrefixMap) -> CResult<ValueSetValue> {
    match &v {
        ast::ValueSetValue::IriStem { stem, .. } => {
            let cnv_stem = cnv_iri_ref(stem, prefixmap)?;
            Ok(ValueSetValue::IriStem { stem: cnv_stem })
        },
        ast::ValueSetValue::ObjectValue(ovw) => {
            let ov = cnv_object_value(ovw, prefixmap)?;
            Ok(ValueSetValue::ObjectValue(ov))
        },
        ast::ValueSetValue::Language { language_tag, .. } => Ok(ValueSetValue::Language {
            language_tag: language_tag.clone(),
        }),
        ast::ValueSetValue::LiteralStem { stem, .. } => Ok(ValueSetValue::LiteralStem { stem: stem.to_string() }),
        ast::ValueSetValue::LiteralStemRange { stem, exclusions } => {
            let stem = cnv_string_or_wildcard(stem)?;
            let exclusions = cnv_literal_exclusions(exclusions)?;
            Ok(ValueSetValue::LiteralStemRange { stem, exclusions })
        },
        ast::ValueSetValue::IriStemRange { stem, exclusions } => {
            let stem = cnv_iriref_or_wildcard(stem, prefixmap)?;
            let exclusions = cnv_iri_exclusions(exclusions)?;
            Ok(ValueSetValue::IriStemRange { stem, exclusions })
        },
        ast::ValueSetValue::LanguageStem { stem } => {
            let stem = cnv_lang_or_wildcard(stem)?;
            Ok(ValueSetValue::LanguageStem { stem })
        },
        ast::ValueSetValue::LanguageStemRange { stem, exclusions } => {
            let stem = cnv_lang_or_wildcard(stem)?;
            let exclusions = cnv_lang_exclusions(exclusions)?;
            Ok(ValueSetValue::LanguageStemRange { stem, exclusions })
        },
    }
}

fn cnv_lang_or_wildcard(stem: &ast::LangOrWildcard) -> CResult<crate::ir::value_set_value::LangOrWildcard> {
    match stem {
        ast::LangOrWildcard::Lang(s) => Ok(crate::ir::value_set_value::LangOrWildcard::Lang(s.clone())),
        ast::LangOrWildcard::Wildcard => Ok(crate::ir::value_set_value::LangOrWildcard::Wildcard {
            type_: "Lang wildcard".to_string(),
        }),
    }
}

fn cnv_string_or_wildcard(stem: &ast::StringOrWildcard) -> CResult<crate::ir::value_set_value::StringOrWildcard> {
    match stem {
        ast::StringOrWildcard::String(s) => Ok(crate::ir::value_set_value::StringOrWildcard::String(s.to_string())),
        ast::StringOrWildcard::Wildcard => {
            Ok(crate::ir::value_set_value::StringOrWildcard::Wildcard { type_: "".to_string() })
        },
    }
}

fn cnv_iriref_or_wildcard(
    stem: &ast::IriRefOrWildcard,
    prefixmap: &PrefixMap,
) -> CResult<crate::ir::value_set_value::IriOrWildcard> {
    match stem {
        ast::IriRefOrWildcard::IriRef(iri) => {
            let cnv_iri = cnv_iri_ref(iri, prefixmap)?;
            Ok(crate::ir::value_set_value::IriOrWildcard::Iri(cnv_iri))
        },
        ast::IriRefOrWildcard::Wildcard => {
            Ok(crate::ir::value_set_value::IriOrWildcard::Wildcard { type_: "".to_string() })
        },
    }
}

/*fn cnv_exclusions(
    exclusions: &Option<Vec<ast::StringOrLiteralStemWrapper>>,
) -> CResult<Option<Vec<crate::ir::value_set_value::StringOrLiteralStem>>> {
    match exclusions {
        None => Ok(None),
        Some(exs) => {
            let mut rs = Vec::new();
            for ex in exs {
                let cnv_ex = cnv_string_or_literal_stem(ex)?;
                rs.push(cnv_ex);
            }
            Ok(Some(rs))
        }
    }
}*/

fn cnv_literal_exclusions(
    exclusions: &Option<Vec<ast::literal_exclusion::LiteralExclusion>>,
) -> CResult<Option<Vec<ir::exclusion::LiteralExclusion>>> {
    match exclusions {
        None => Ok(None),
        Some(exs) => {
            let mut rs = Vec::new();
            for ex in exs {
                let cnv_ex = cnv_literal_exclusion(ex)?;
                rs.push(cnv_ex);
            }
            Ok(Some(rs))
        },
    }
}

fn cnv_iri_exclusions(
    exclusions: &Option<Vec<ast::iri_exclusion::IriExclusion>>,
) -> CResult<Option<Vec<ir::exclusion::IriExclusion>>> {
    match exclusions {
        None => Ok(None),
        Some(exs) => {
            let mut rs = Vec::new();
            for ex in exs {
                let cnv_ex = cnv_iri_exclusion(ex)?;
                rs.push(cnv_ex);
            }
            Ok(Some(rs))
        },
    }
}

fn cnv_lang_exclusions(
    exclusions: &Option<Vec<ast::language_exclusion::LanguageExclusion>>,
) -> CResult<Option<Vec<ir::exclusion::LanguageExclusion>>> {
    match exclusions {
        None => Ok(None),
        Some(exs) => {
            let mut rs = Vec::new();
            for ex in exs {
                let cnv_ex = cnv_language_exclusion(ex)?;
                rs.push(cnv_ex);
            }
            Ok(Some(rs))
        },
    }
}

/*
fn cnv_string_or_literal_exclusions(
    exclusions: &Option<Vec<ast::StringOrLiteralExclusion>>,
) -> CResult<Option<Vec<crate::ir::value_set_value::StringOrLiteralExclusion>>> {
    match exclusions {
        None => Ok(None),
        Some(exs) => {
            let mut rs = Vec::new();
            for ex in exs {
                let cnv_ex = cnv_string_or_literal_exclusion(ex)?;
                rs.push(cnv_ex);
            }
            Ok(Some(rs))
        }
    }
}*/

/*
fn cnv_string_or_literalstem(sl: &ast::StringOrLiteralStemWrapper) -> CResult<StringOrLiteralStem> {
    match sl.inner() {
        ast::StringOrLiteralStem::String(s) => Ok(StringOrLiteralStem::String(s.to_string())),
        ast::StringOrLiteralStem::LiteralStem { stem } => Ok(StringOrLiteralStem::LiteralStem {
            stem: stem.to_string(),
        }),
    }
}*/

fn cnv_literal_exclusion(
    le: &ast::literal_exclusion::LiteralExclusion,
) -> CResult<crate::ir::exclusion::LiteralExclusion> {
    match le {
        ast::literal_exclusion::LiteralExclusion::Literal(s) => {
            Ok(crate::ir::exclusion::LiteralExclusion::Literal(s.to_string()))
        },
        ast::literal_exclusion::LiteralExclusion::LiteralStem(s) => {
            Ok(crate::ir::exclusion::LiteralExclusion::LiteralStem(s.to_string()))
        },
    }
}

fn cnv_iri_exclusion(le: &IriExclusion) -> CResult<crate::ir::exclusion::IriExclusion> {
    match le {
        ast::iri_exclusion::IriExclusion::Iri(s) => {
            let iri_s = iri_ref2iri_s(s);
            Ok(crate::ir::exclusion::IriExclusion::Iri(iri_s))
        },
        ast::iri_exclusion::IriExclusion::IriStem(s) => {
            let iri_s = iri_ref2iri_s(s);
            Ok(crate::ir::exclusion::IriExclusion::IriStem(iri_s))
        },
    }
}

fn cnv_language_exclusion(
    le: &ast::language_exclusion::LanguageExclusion,
) -> CResult<crate::ir::exclusion::LanguageExclusion> {
    match le {
        ast::language_exclusion::LanguageExclusion::Language(s) => {
            Ok(crate::ir::exclusion::LanguageExclusion::Language(s.clone()))
        },
        ast::language_exclusion::LanguageExclusion::LanguageStem(s) => {
            Ok(crate::ir::exclusion::LanguageExclusion::LanguageStem(s.clone()))
        },
    }
}

fn cnv_object_value(ov: &ast::ObjectValue, prefixmap: &PrefixMap) -> CResult<ObjectValue> {
    match ov {
        ast::ObjectValue::IriRef(ir) => {
            let iri = cnv_iri_ref(ir, prefixmap)?;
            Ok(ObjectValue::IriRef(iri))
        },
        ast::ObjectValue::Literal(lit) => Ok(ObjectValue::ObjectLiteral(lit.clone())),
    }
}

fn cnv_iri_ref(iri: &IriRef, prefixmap: &PrefixMap) -> Result<IriS, Box<SchemaIRError>> {
    match iri {
        IriRef::Iri(iri) => Ok(iri.clone()),
        IriRef::Prefixed { prefix, local } => prefixmap.resolve_prefix_local(prefix, local).map_err(|e| {
            Box::new(SchemaIRError::CnvIriRefError {
                prefix: prefix.clone(),
                local: local.clone(),
                error: e.to_string(),
            })
        }),
    }
}

pub fn find_schema_rotating_formats(
    iri: &IriS,
    formats: Vec<ShExFormat>,
    base: Option<&IriS>,
) -> Result<SchemaAST, Box<SchemaIRError>> {
    let mut errors = Vec::new();
    for format in &formats {
        trace!("Trying format {format} for IRI {iri} with base {base:?}");
        match get_schema_from_iri(iri, format, base) {
            Err(e) => {
                errors.push((format.clone(), e));
            },
            Ok(schema) => return Ok(schema),
        }
    }
    Err(Box::new(SchemaIRError::SchemaFromIriRotatingFormats {
        iri: iri.clone(),
        errors,
    }))
}

pub fn get_schema_from_iri(
    iri: &IriS,
    format: &ShExFormat,
    base: Option<&IriS>,
) -> Result<SchemaAST, Box<SchemaIRError>> {
    let candidates = candidates(iri, base, format).map_err(|e| {
        Box::new(SchemaIRError::CandidatesError {
            iri: iri.clone(),
            error: e.to_string(),
        })
    })?;
    match format {
        ShExFormat::ShExC => {
            let content = find_content_from_iris(candidates, base)?;
            let schema = ShExParser::parse(content.as_str(), Some(iri.clone()), iri).map_err(|e| {
                Box::new(SchemaIRError::ShExCError {
                    iri: iri.clone(),
                    error: e.to_string(),
                })
            })?;
            Ok(schema)
        },
        ShExFormat::ShExJ => {
            let content = find_content_from_iris(candidates, base)?;
            let schema = SchemaAST::from_reader(content.as_bytes()).map_err(|e| {
                Box::new(SchemaIRError::ShExJError {
                    iri: iri.clone(),
                    error: e.to_string(),
                })
            })?;
            Ok(schema)
        },
        ShExFormat::RDFFormat(_) => {
            todo!()
        },
    }
}

pub fn candidates(iri: &IriS, base: Option<&IriS>, format: &ShExFormat) -> Result<Vec<IriS>, IriSError> {
    let mut candidates = vec![iri.clone()];
    let extended_iris: Result<Vec<IriS>, IriSError> = format
        .extensions()
        .into_iter()
        .map(|ext| {
            let ext_iri = format!("{}.{ext}", iri.as_str());
            IriS::from_str_base_iri(&ext_iri, base)
        })
        .collect();
    let extended = extended_iris?;
    candidates.extend(extended);
    Ok(candidates)
}

fn find_content_from_iris(iris: Vec<IriS>, base: Option<&IriS>) -> Result<String, Box<SchemaIRError>> {
    find_first_ok(iris, |iri| get_content(iri, base))
        .map_err(|errs| Box::new(SchemaIRError::FindingContentFromIrisError { errors: errs }))
        .map(|(content, _)| content)
}

fn get_content(iri: IriS, base: Option<&IriS>) -> Result<String, SchemaIRError> {
    iri.dereference(base).map_err(|e| SchemaIRError::DereferencingIri {
        iri: iri.clone(),
        error: e.to_string(),
    })
}

/// Applies function `f` to each element of `vec` in order, returning the first `Ok` result
/// along with a vector of all errors encountered before that point.
/// If all applications of `f` result in `Err`, returns a vector of all errors.
fn find_first_ok<A, B, E, F>(vec: Vec<A>, f: F) -> Result<(B, Vec<E>), Vec<E>>
where
    F: Fn(A) -> Result<B, E>,
{
    let mut errors = Vec::new();

    for item in vec {
        match f(item) {
            Ok(b) => return Ok((b, errors)),
            Err(e) => errors.push(e),
        }
    }
    // If we get here, all items resulted in errors
    Err(errors)
}
