@prefix : <https://ontology.inferal.com/modules/range/> .
@prefix ord: <https://ontology.inferal.com/modules/ordering/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

:RangeShape
    a sh:NodeShape ;
    rdfs:label "range shape" ;
    rdfs:comment "Validates range bounds, inclusivity flags, bound ordering, non-empty equal-bound ranges, and included values against the declared bounds." ;
    sh:targetClass :Range ;
    sh:property [
        sh:path :lowerBound ;
        sh:class ord:Comparable ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :upperBound ;
        sh:class ord:Comparable ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :lowerInclusive ;
        sh:datatype xsd:boolean ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :upperInclusive ;
        sh:datatype xsd:boolean ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :includesValue ;
        sh:class ord:Comparable ;
    ] ;
    sh:sparql [
        sh:message "A lowerInclusive flag must be present exactly when lowerBound is present." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/range/>
            SELECT $this WHERE {
                OPTIONAL { $this :lowerBound ?lower . }
                OPTIONAL { $this :lowerInclusive ?lowerInclusive . }
                FILTER(
                    (BOUND(?lower) && !BOUND(?lowerInclusive)) ||
                    (!BOUND(?lower) && BOUND(?lowerInclusive))
                )
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "An upperInclusive flag must be present exactly when upperBound is present." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/range/>
            SELECT $this WHERE {
                OPTIONAL { $this :upperBound ?upper . }
                OPTIONAL { $this :upperInclusive ?upperInclusive . }
                FILTER(
                    (BOUND(?upper) && !BOUND(?upperInclusive)) ||
                    (!BOUND(?upper) && BOUND(?upperInclusive))
                )
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Range lower and upper bounds must share one inOrdering when both bounds are present." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/range/>
            PREFIX ord: <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?lower ?upper WHERE {
                $this :lowerBound ?lower ;
                      :upperBound ?upper .
                FILTER NOT EXISTS {
                    ?lower ord:inOrdering ?ordering .
                    ?upper ord:inOrdering ?ordering .
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Range lowerBound must not be greater than upperBound through strict ordering paths." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/range/>
            PREFIX ord: <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?lower ?upper WHERE {
                $this :lowerBound ?lower ;
                      :upperBound ?upper .
                ?lower (ord:sameRankAs|^ord:sameRankAs)* ?lowerRepresentative .
                ?upper (ord:sameRankAs|^ord:sameRankAs)* ?upperRepresentative .
                ?lowerRepresentative (ord:follows|^ord:precedes|ord:greaterThan|^ord:lessThan|ord:immediatelyFollows|^ord:immediatelyPrecedes)+ ?upperRepresentative .
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Range lowerBound must not be greater than upperBound by rank in a rank-directed ordering." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/range/>
            PREFIX ord: <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?lower ?upper WHERE {
                $this :lowerBound ?lower ;
                      :upperBound ?upper .
                ?lower ord:inOrdering ?ordering ;
                       ord:rank ?lowerRank .
                ?upper ord:inOrdering ?ordering ;
                       ord:rank ?upperRank .
                ?ordering ord:rankDirection ?direction .
                FILTER(
                    (?direction = ord:AscendingRank && ?lowerRank > ?upperRank) ||
                    (?direction = ord:DescendingRank && ?lowerRank < ?upperRank)
                )
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A Range with identical or same-rank lower and upper bounds must include both bounds; otherwise the interval is empty." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/range/>
            PREFIX ord: <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?lower ?upper WHERE {
                $this :lowerBound ?lower ;
                      :upperBound ?upper ;
                      :lowerInclusive ?lowerInclusive ;
                      :upperInclusive ?upperInclusive .
                FILTER(
                    ?lower = ?upper ||
                    EXISTS { ?lower (ord:sameRankAs|^ord:sameRankAs)+ ?upper }
                )
                FILTER(?lowerInclusive != true || ?upperInclusive != true)
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "An included value must share an ordering with each present range bound." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/range/>
            PREFIX ord: <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?value ?bound WHERE {
                $this :includesValue ?value .
                {
                    $this :lowerBound ?bound .
                }
                UNION
                {
                    $this :upperBound ?bound .
                }
                FILTER NOT EXISTS {
                    ?value ord:inOrdering ?ordering .
                    ?bound ord:inOrdering ?ordering .
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "An included value must satisfy the lower-bound side of the range, counting sameRankAs ties as equal to the bound." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/range/>
            PREFIX ord: <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?value ?lower WHERE {
                $this
                    :includesValue ?value ;
                    :lowerBound ?lower ;
                    :lowerInclusive ?lowerInclusive .
                FILTER(
                    !(
                        ?lowerInclusive = true &&
                        (
                            ?value = ?lower ||
                            EXISTS { ?value (ord:sameRankAs|^ord:sameRankAs)+ ?lower . }
                        )
                    ) &&
                    NOT EXISTS {
                        ?value (ord:sameRankAs|^ord:sameRankAs)* ?valueRepresentative .
                        ?lower (ord:sameRankAs|^ord:sameRankAs)* ?lowerRepresentative .
                        ?valueRepresentative (ord:follows|^ord:precedes|ord:greaterThan|^ord:lessThan|ord:immediatelyFollows|^ord:immediatelyPrecedes)+ ?lowerRepresentative .
                    }
                )
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "An included value must satisfy the upper-bound side of the range, counting sameRankAs ties as equal to the bound." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/range/>
            PREFIX ord: <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?value ?upper WHERE {
                $this
                    :includesValue ?value ;
                    :upperBound ?upper ;
                    :upperInclusive ?upperInclusive .
                FILTER(
                    !(
                        ?upperInclusive = true &&
                        (
                            ?value = ?upper ||
                            EXISTS { ?value (ord:sameRankAs|^ord:sameRankAs)+ ?upper . }
                        )
                    ) &&
                    NOT EXISTS {
                        ?value (ord:sameRankAs|^ord:sameRankAs)* ?valueRepresentative .
                        ?upper (ord:sameRankAs|^ord:sameRankAs)* ?upperRepresentative .
                        ?upperRepresentative (ord:follows|^ord:precedes|ord:greaterThan|^ord:lessThan|ord:immediatelyFollows|^ord:immediatelyPrecedes)+ ?valueRepresentative .
                    }
                )
            }
        """ ;
    ] .
