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

:RepositoryShape
    a sh:NodeShape ;
    rdfs:label "Git repository shape" ;
    rdfs:comment "Validates a repository's object format, optional default store, objects, reference names, object identifiers, and inherited store resolution." ;
    sh:targetClass :Repository ;
    sh:property [ sh:path :objectFormat ; sh:in ( "sha1" "sha256" ) ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path :defaultObjectStore ; sh:class :ObjectStore ; sh:maxCount 1 ] ;
    sh:property [ sh:path :hasObject ; sh:class :GitObject ] ;
    sh:property [ sh:path :hasReference ; sh:class :Reference ] ;
    sh:sparql [
        sh:message "Every repository object must have exactly one native Git object kind and must not be a GitlinkCommit." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/git/>
            SELECT $this ?object WHERE {
                $this :hasObject ?object .
                {
                    FILTER NOT EXISTS {
                        ?object a ?kind .
                        FILTER(?kind IN (:Blob, :Tree, :Commit, :Tag))
                    }
                }
                UNION {
                    ?object a :GitlinkCommit .
                }
                UNION {
                    ?object a ?leftKind, ?rightKind .
                    FILTER(
                        ?leftKind != ?rightKind &&
                        ?leftKind IN (:Blob, :Tree, :Commit, :Tag) &&
                        ?rightKind IN (:Blob, :Tree, :Commit, :Tag)
                    )
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Object IDs must be unique within a repository and match its object format." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/git/>
            SELECT $this ?object ?id WHERE {
                $this :objectFormat ?format .
                {
                    $this :hasObject ?object .
                }
                UNION {
                    $this :hasObject/:hasTreeEntry ?entry .
                    ?entry :entryMode "160000" ; :entryObject ?object .
                }
                ?object :objectId ?id .
                FILTER(
                    (?format = "sha1" && !REGEX(?id, "^[0-9a-f]{40}$")) ||
                    (?format = "sha256" && !REGEX(?id, "^[0-9a-f]{64}$")) ||
                    EXISTS {
                        {
                            $this :hasObject ?other .
                        }
                        UNION {
                            $this :hasObject/:hasTreeEntry ?otherEntry .
                            ?otherEntry :entryMode "160000" ; :entryObject ?other .
                        }
                        ?other :objectId ?id .
                        FILTER(?other != ?object)
                    }
                )
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Every repository object must have an explicit payloadStore or inherit defaultObjectStore." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/git/>
            SELECT $this ?object WHERE {
                $this :hasObject ?object .
                FILTER NOT EXISTS { ?object :payloadStore ?store }
                FILTER NOT EXISTS { $this :defaultObjectStore ?store }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Reference names must be unique within a repository." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/git/>
            SELECT $this ?reference ?name WHERE {
                $this :hasReference ?reference .
                ?reference :referenceName ?name .
                $this :hasReference ?other .
                ?other :referenceName ?name .
                FILTER(?reference != ?other)
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Every object selected by described topology or a direct reference must belong to the same repository graph." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/git/>
            SELECT $this ?owner ?target WHERE {
                {
                    $this :hasObject ?owner .
                    ?owner :hasTreeEntry ?entry .
                    ?entry :entryObject ?target ; :entryMode ?mode .
                    FILTER(?mode != "160000")
                }
                UNION {
                    $this :hasObject ?owner .
                    ?owner :commitTree ?target .
                }
                UNION {
                    $this :hasObject ?owner .
                    ?owner :hasParent/:parentCommit ?target .
                }
                UNION {
                    $this :hasObject ?owner .
                    ?owner :tagTarget ?target .
                }
                UNION {
                    $this :hasReference/:pointsToObject ?target .
                }
                FILTER NOT EXISTS { $this :hasObject ?target }
            }
        """ ;
    ] .

:ObjectStoreShape
    a sh:NodeShape ;
    rdfs:label "Git object store shape" ;
    rdfs:comment "Validates that an object store has at least one access IRI." ;
    sh:targetClass :ObjectStore ;
    sh:property [ sh:path :accessIRI ; sh:datatype xsd:anyURI ; sh:minCount 1 ] .

:GitObjectShape
    a sh:NodeShape ;
    rdfs:label "Git object shape" ;
    rdfs:comment "Validates the identifier and optional explicit store of every Git object." ;
    sh:targetClass :GitObject ;
    sh:property [ sh:path :objectId ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path :payloadStore ; sh:class :ObjectStore ; sh:maxCount 1 ] .

:BlobShape
    a sh:NodeShape ;
    rdfs:label "Git blob shape" ;
    rdfs:comment "Provides class coverage for hash-addressed blobs; payload availability is validated through object-store resolution." ;
    sh:targetClass :Blob .

:GitlinkCommitShape
    a sh:NodeShape ;
    rdfs:label "gitlink commit identity shape" ;
    rdfs:comment "Provides class coverage for an external commit identity selected by a 160000 tree entry without requiring unavailable commit topology." ;
    sh:targetClass :GitlinkCommit .

:TreeShape
    a sh:NodeShape ;
    rdfs:label "Git tree shape" ;
    rdfs:comment "Validates tree-entry membership and unique contiguous entry indices." ;
    sh:targetClass :Tree ;
    sh:property [ sh:path :hasTreeEntry ; sh:class :TreeEntry ] ;
    sh:sparql [
        sh:message "Tree entry indices must be unique and contiguous from zero." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/git/>
            SELECT $this WHERE {
                {
                    $this :hasTreeEntry ?left, ?right .
                    ?left :entryIndex ?index .
                    ?right :entryIndex ?index .
                    FILTER(?left != ?right)
                }
                UNION
                {
                    $this :hasTreeEntry ?entry .
                    ?entry :entryIndex ?index .
                    FILTER(?index > 0)
                    FILTER NOT EXISTS {
                        $this :hasTreeEntry ?previous .
                        ?previous :entryIndex ?previousIndex .
                        FILTER(?previousIndex = ?index - 1)
                    }
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A tree cannot contain two entry occurrences with the same exact name bytes." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/git/>
            SELECT $this ?left ?right ?leftName ?rightName WHERE {
                $this :hasTreeEntry ?left, ?right .
                ?left :entryNameBytes ?leftName .
                ?right :entryNameBytes ?rightName .
                FILTER(?left != ?right && ?leftName = ?rightName)
            }
        """ ;
    ] .

:TreeEntryShape
    a sh:NodeShape ;
    rdfs:label "Git tree entry shape" ;
    rdfs:comment "Validates exact name bytes, mode, target, and occurrence index for a tree entry." ;
    sh:targetClass :TreeEntry ;
    sh:property [ sh:path [ sh:inversePath :hasTreeEntry ] ; sh:class :Tree ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path :entryIndex ; sh:datatype xsd:nonNegativeInteger ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path :entryNameBytes ; sh:datatype xsd:base64Binary ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path :entryMode ; sh:datatype xsd:string ; sh:pattern "^(100644|100755|120000|160000|40000)$" ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path :entryObject ; sh:class :GitObject ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:sparql [
        sh:message "Tree entry mode must agree with the selected object kind: 40000 tree, 160000 commit or gitlink commit identity, and file modes blob." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/git/>
            SELECT $this ?mode ?object WHERE {
                $this :entryMode ?mode ; :entryObject ?object .
                FILTER(
                    (?mode = "40000" && NOT EXISTS { ?object a :Tree }) ||
                    (?mode = "160000" && NOT EXISTS { ?object a :Commit } && NOT EXISTS { ?object a :GitlinkCommit }) ||
                    (?mode IN ("100644", "100755", "120000") && NOT EXISTS { ?object a :Blob })
                )
            }
        """ ;
    ] .

:CommitShape
    a sh:NodeShape ;
    rdfs:label "Git commit shape" ;
    rdfs:comment "Validates the root tree and unique contiguous parent indices of a commit." ;
    sh:targetClass :Commit ;
    sh:property [ sh:path :commitTree ; sh:class :Tree ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path :hasParent ; sh:class :CommitParent ] ;
    sh:sparql [
        sh:message "Commit parent indices must be unique and contiguous from zero." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/git/>
            SELECT $this WHERE {
                {
                    $this :hasParent ?left, ?right .
                    ?left :parentIndex ?index .
                    ?right :parentIndex ?index .
                    FILTER(?left != ?right)
                }
                UNION
                {
                    $this :hasParent ?parent .
                    ?parent :parentIndex ?index .
                    FILTER(?index > 0)
                    FILTER NOT EXISTS {
                        $this :hasParent ?previous .
                        ?previous :parentIndex ?previousIndex .
                        FILTER(?previousIndex = ?index - 1)
                    }
                }
            }
        """ ;
    ] .

:CommitParentShape
    a sh:NodeShape ;
    rdfs:label "Git commit parent occurrence shape" ;
    rdfs:comment "Validates an ordered commit-parent occurrence." ;
    sh:targetClass :CommitParent ;
    sh:property [ sh:path [ sh:inversePath :hasParent ] ; sh:class :Commit ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path :parentIndex ; sh:datatype xsd:nonNegativeInteger ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path :parentCommit ; sh:class :Commit ; sh:minCount 1 ; sh:maxCount 1 ] .

:TagShape
    a sh:NodeShape ;
    rdfs:label "annotated Git tag shape" ;
    rdfs:comment "Validates the target of an annotated Git tag object." ;
    sh:targetClass :Tag ;
    sh:property [ sh:path :tagTarget ; sh:class :GitObject ; sh:minCount 1 ; sh:maxCount 1 ] .

:ReferenceShape
    a sh:NodeShape ;
    rdfs:label "Git reference shape" ;
    rdfs:comment "Validates a complete Git reference name." ;
    sh:targetClass :Reference ;
    sh:property [ sh:path :referenceName ; sh:datatype xsd:string ; sh:minLength 1 ; sh:minCount 1 ; sh:maxCount 1 ] .

:DirectReferenceShape
    a sh:NodeShape ;
    rdfs:label "direct Git reference shape" ;
    rdfs:comment "Validates that a direct reference selects exactly one described Git object." ;
    sh:targetClass :DirectReference ;
    sh:property [ sh:path :pointsToObject ; sh:class :GitObject ; sh:minCount 1 ; sh:maxCount 1 ] .

:SymbolicReferenceShape
    a sh:NodeShape ;
    rdfs:label "symbolic Git reference shape" ;
    rdfs:comment "Validates that a symbolic reference records exactly one target reference name." ;
    sh:targetClass :SymbolicReference ;
    sh:property [ sh:path :symbolicTargetName ; sh:datatype xsd:string ; sh:minLength 1 ; sh:minCount 1 ; sh:maxCount 1 ] .
