@prefix : <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#> .

:ComparableShape
    a sh:NodeShape ;
    rdfs:label "comparable shape" ;
    rdfs:comment "Validates ordering membership, rank values, precedence and comparison consistency, immediate adjacency, strict-order acyclicity, and same-rank agreement for comparable resources." ;
    sh:targetClass :Comparable ;
    sh:property [
        sh:path :inOrdering ;
        sh:class :Ordering ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :rank ;
        sh:datatype xsd:integer ;
        sh:maxCount 1 ;
    ] ;
    sh:sparql [
        sh:message "Ordering relation edges must stay inside one shared inOrdering." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?value WHERE {
                $this (:precedes|:follows|:greaterThan|:lessThan|:immediatelyPrecedes|:immediatelyFollows|:sameRankAs) ?value .
                FILTER NOT EXISTS {
                    $this :inOrdering ?ordering .
                    ?value :inOrdering ?ordering .
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Strict ordering relations must not relate a resource to itself." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/ordering/>
            SELECT $this WHERE {
                $this (:precedes|:follows|:greaterThan|:lessThan|:immediatelyPrecedes|:immediatelyFollows) $this .
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Strict ordering edges must be acyclic within one inOrdering." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/ordering/>
            SELECT $this WHERE {
                $this (:precedes|^:follows|:lessThan|^:greaterThan|:immediatelyPrecedes|^:immediatelyFollows)+ $this .
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Strict ordering edges must not contradict sameRankAs assertions." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?value WHERE {
                $this (:precedes|^:follows|:lessThan|^:greaterThan|:immediatelyPrecedes|^:immediatelyFollows)+ ?value .
                $this (:sameRankAs|^:sameRankAs)+ ?value .
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "An immediate precedence edge must not skip a known intermediate ordering position, including a position represented by same-rank resources." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?value ?middle WHERE {
                $this (:immediatelyPrecedes|^:immediatelyFollows) ?value .
                $this (
                    (:sameRankAs|^:sameRankAs)* /
                    (:precedes|^:follows|:lessThan|^:greaterThan|:immediatelyPrecedes|^:immediatelyFollows) /
                    (:sameRankAs|^:sameRankAs)*
                )+ ?middle .
                ?middle (
                    (:sameRankAs|^:sameRankAs)* /
                    (:precedes|^:follows|:lessThan|^:greaterThan|:immediatelyPrecedes|^:immediatelyFollows) /
                    (:sameRankAs|^:sameRankAs)*
                )+ ?value .
                FILTER(?middle != $this && ?middle != ?value)
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Comparable resources connected by sameRankAs must have the same asserted rank value." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?value ?rank ?valueRank WHERE {
                $this :rank ?rank .
                $this (:sameRankAs|^:sameRankAs)+ ?value .
                ?value :rank ?valueRank .
                FILTER(?rank != ?valueRank)
            }
        """ ;
    ] .

:TotalOrderingShape
    a sh:NodeShape ;
    rdfs:label "total ordering shape" ;
    rdfs:comment "Validates that every pair of resources in a total ordering is comparable by broad or specialized strict precedence, by a same-rank relation, or by ranks in an ordering that states a rank direction." ;
    sh:targetClass :TotalOrdering ;
    sh:sparql [
        sh:message "Every pair of resources in a TotalOrdering must be comparable by strict precedence or sameRankAs, including comparisons inherited through same-rank representatives, or by rank when the ordering states a rank direction." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?left ?right WHERE {
                ?left :inOrdering $this .
                ?right :inOrdering $this .
                FILTER(?left != ?right)
                FILTER NOT EXISTS {
                    {
                        ?left (:sameRankAs|^:sameRankAs)* ?leftRepresentative .
                        ?right (:sameRankAs|^:sameRankAs)* ?rightRepresentative .
                        ?leftRepresentative (:precedes|^:follows|:lessThan|^:greaterThan|:immediatelyPrecedes|^:immediatelyFollows)+ ?rightRepresentative .
                    }
                    UNION
                    {
                        ?right (:sameRankAs|^:sameRankAs)* ?rightRepresentative .
                        ?left (:sameRankAs|^:sameRankAs)* ?leftRepresentative .
                        ?rightRepresentative (:precedes|^:follows|:lessThan|^:greaterThan|:immediatelyPrecedes|^:immediatelyFollows)+ ?leftRepresentative .
                    }
                    UNION
                    {
                        ?left (:sameRankAs|^:sameRankAs)+ ?right .
                    }
                    UNION
                    {
                        $this :rankDirection ?direction .
                        ?left :rank ?leftRank .
                        ?right :rank ?rightRank .
                    }
                }
            }
        """ ;
    ] .

:RankDirectedOrderingShape
    a sh:NodeShape ;
    rdfs:label "rank directed ordering shape" ;
    rdfs:comment "Validates that an ordering states at most one rank direction and that asserted comparison edges agree with the ranks they order." ;
    sh:targetClass :Ordering ;
    sh:property [
        sh:path :rankDirection ;
        sh:in ( :AscendingRank :DescendingRank ) ;
        sh:nodeKind sh:IRI ;
        sh:maxCount 1 ;
    ] ;
    sh:sparql [
        sh:message "In an ascending-rank ordering, a resource asserted greater than another must not hold the lesser rank." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?greater ?lesser WHERE {
                $this :rankDirection :AscendingRank .
                ?greater :inOrdering $this ;
                         :rank ?greaterRank .
                ?lesser :inOrdering $this ;
                        :rank ?lesserRank .
                { ?greater :greaterThan ?lesser } UNION { ?lesser :lessThan ?greater }
                FILTER(?greaterRank < ?lesserRank)
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "In a descending-rank ordering, a resource asserted greater than another must not hold the greater rank." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?greater ?lesser WHERE {
                $this :rankDirection :DescendingRank .
                ?greater :inOrdering $this ;
                         :rank ?greaterRank .
                ?lesser :inOrdering $this ;
                        :rank ?lesserRank .
                { ?greater :greaterThan ?lesser } UNION { ?lesser :lessThan ?greater }
                FILTER(?greaterRank > ?lesserRank)
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Resources stated to share a rank must not hold different numeric ranks in a rank-directed ordering." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/ordering/>
            SELECT $this ?left ?right WHERE {
                $this :rankDirection ?direction .
                ?left :inOrdering $this ;
                      :rank ?leftRank .
                ?right :inOrdering $this ;
                       :rank ?rightRank .
                ?left (:sameRankAs|^:sameRankAs)+ ?right .
                FILTER(?leftRank != ?rightRank)
            }
        """ ;
    ] .
