@prefix : <https://ontology.inferal.com/modules/versioning/> .
@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#> .

:VersionIdentifierShape
    a sh:NodeShape ;
    rdfs:label "version identifier shape" ;
    rdfs:comment "Validates version text, ordering membership, comparison edges, and comparison acyclicity for version identifiers." ;
    sh:targetClass :VersionIdentifier ;
    sh:property [
        sh:path :versionText ;
        sh:datatype xsd:string ;
        sh:minLength 1 ;
        sh:pattern "^\\S+$" ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :inVersionOrdering ;
        sh:class :VersionOrdering ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:sparql [
        sh:message "Version comparison edges must stay inside one inVersionOrdering and must not compare a version to itself." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/versioning/>
            SELECT $this ?value WHERE {
                $this (:greaterThanVersion|:lessThanVersion) ?value .
                FILTER(
                    $this = ?value ||
                    NOT EXISTS {
                        $this :inVersionOrdering ?ordering .
                        ?value :inVersionOrdering ?ordering .
                    }
                )
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Version comparison edges must be acyclic within one inVersionOrdering." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/versioning/>
            SELECT $this WHERE {
                $this (:greaterThanVersion|^:lessThanVersion)+ $this .
            }
        """ ;
    ] .

:ComponentVersionIdentifierShape
    a sh:NodeShape ;
    rdfs:label "component version identifier shape" ;
    rdfs:comment "Validates component version major, minor, patch, optional suffix, and uniqueness within a version ordering." ;
    sh:targetClass :ComponentVersionIdentifier ;
    sh:property [
        sh:path :versionMajor ;
        sh:datatype xsd:integer ;
        sh:minInclusive 0 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :versionMinor ;
        sh:datatype xsd:integer ;
        sh:minInclusive 0 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :versionPatch ;
        sh:datatype xsd:integer ;
        sh:minInclusive 0 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :versionSuffix ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
    ] ;
    sh:sparql [
        sh:message "Component version identifiers must be unique by inVersionOrdering, versionMajor, versionMinor, and versionPatch." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/versioning/>
            SELECT $this ?other WHERE {
                $this :inVersionOrdering ?ordering ;
                      :versionMajor ?major ;
                      :versionMinor ?minor ;
                      :versionPatch ?patch .
                ?other a :ComponentVersionIdentifier ;
                       :inVersionOrdering ?ordering ;
                       :versionMajor ?major ;
                       :versionMinor ?minor ;
                       :versionPatch ?patch .
                FILTER(?other != $this)
            }
        """ ;
    ] .

:VersionRangeShape
    a sh:NodeShape ;
    rdfs:label "version range shape" ;
    rdfs:comment "Validates version range bounds, inclusivity flags, shared ordering, bound order, and non-empty equal-bound ranges." ;
    sh:targetClass :VersionRange ;
    sh:property [
        sh:path :lowerBound ;
        sh:class :VersionIdentifier ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :upperBound ;
        sh:class :VersionIdentifier ;
        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:sparql [
        sh:message "A lowerInclusive flag must be present exactly when lowerBound is present." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/versioning/>
            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/versioning/>
            SELECT $this WHERE {
                OPTIONAL { $this :upperBound ?upper . }
                OPTIONAL { $this :upperInclusive ?upperInclusive . }
                FILTER(
                    (BOUND(?upper) && !BOUND(?upperInclusive)) ||
                    (!BOUND(?upper) && BOUND(?upperInclusive))
                )
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "VersionRange lower and upper bounds must share one inVersionOrdering when both bounds are present." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/versioning/>
            SELECT $this ?lower ?upper WHERE {
                $this :lowerBound ?lower ;
                      :upperBound ?upper .
                FILTER NOT EXISTS {
                    ?lower :inVersionOrdering ?ordering .
                    ?upper :inVersionOrdering ?ordering .
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "VersionRange lowerBound must not be greater than upperBound." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/versioning/>
            SELECT $this ?lower ?upper WHERE {
                $this :lowerBound ?lower ;
                      :upperBound ?upper .
                ?lower :greaterThanVersion ?upper .
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A VersionRange with identical lower and upper bounds must include both bounds; otherwise the interval is empty." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/versioning/>
            SELECT $this ?bound WHERE {
                $this :lowerBound ?bound ;
                      :upperBound ?bound ;
                      :lowerInclusive ?lowerInclusive ;
                      :upperInclusive ?upperInclusive .
                FILTER(?lowerInclusive != true || ?upperInclusive != true)
            }
        """ ;
    ] .
