xquery version "1.0";
(:
    ART-DECOR® STANDARD COPYRIGHT AND LICENSE NOTE
    Copyright © ART-DECOR Expert Group and ART-DECOR Open Tools GmbH
    see https://docs.art-decor.org/copyright and https://docs.art-decor.org/licenses

    This file is part of the ART-DECOR® tools suite.
:)

(:
   Xquery for retrieving concept for editing
   Input:
   - concept/@id
   - concept/@effectiveDate
   - language code
   - optional breakLock=true
   Returns
   If a lock is present and breakLock=false:    the existing lock element
   It a lock is present and breakLock=true:     the concept with the new lock element
   If no lock is present:                       the concept with the new lock element
:)
import module namespace get     = "http://art-decor.org/ns/art-decor-settings" at "art-decor-settings.xqm";
import module namespace art     = "http://art-decor.org/ns/art" at "art-decor.xqm";
import module namespace decor   = "http://art-decor.org/ns/decor" at "../api/api-decor.xqm";

declare namespace request       = "http://exist-db.org/xquery/request";
declare namespace xmldb         = "http://exist-db.org/xquery/xmldb";
declare namespace datetime      = "http://exist-db.org/xquery/datetime";

declare %private function local:expandedInheritContainsRelationship($in as element(), $referred as element()?, $original as element()?, $language as xs:string?) as element() {
    element{name($in)}
    {
        $in/@type, $in/@ref, $in/@effectiveDate, $in/@flexibility,
        $referred/ancestor::decor/project/@prefix,
        attribute datasetId {$referred/ancestor-or-self::dataset/@id},
        attribute datasetEffectiveDate {$referred/ancestor-or-self::dataset/@effectiveDate},
        attribute datasetStatusCode {$referred/ancestor-or-self::dataset/@statusCode},
        attribute iType {$original/@type}, 
        attribute iStatusCode {$referred/@statusCode}, 
        attribute iEffectiveDate {$referred/@effectiveDate},
        if ($referred/@expirationDate) then attribute iExpirationDate {$referred/@expirationDate} else (),
        if ($referred/@versionLabel) then attribute iVersionLabel {$referred/@versionLabel} else (),
        attribute iddisplay {art:getNameForOID($in/@ref, $language, $referred/ancestor::decor)},
        if ($referred[@id = $original/@id][@effectiveDate = $original/@effectiveDate]) then () else (
            attribute originalId {$original/@id}, 
            attribute originalEffectiveDate {$original/@effectiveDate}, 
            attribute originalStatusCode {$original/@statusCode}, 
            if ($original[@expirationDate]) then attribute originalExpirationDate {$original/@expirationDate} else (),
            if ($original[@versionLabel]) then attribute originalVersionLabel {$original/@versionLabel} else (),
            attribute originalPrefix {$original/ancestor::decor/project/@prefix}
        )
        ,
        let $localInherit           := 
            if ($in[self::inherit]) then ($referred/ancestor::decor/project/@prefix=$in/ancestor::decor/project/@prefix) else 
            if ($in[self::contains]) then ($referred/ancestor::decor/project/@prefix=$in/ancestor::decor/project/@prefix) else (false())
        return
            attribute localInherit {$localInherit}
        ,
        if (name($in) = 'relationship') then (
            let $n      := $original/name[@language=$language]
            
            return if ($n) then $n else <name language="{$language}">{$original/name[1]/node()}</name>
        )
        else ()
    }
};

declare %private function local:getConceptForEdit($concept as element(), $lock as element(), $language as xs:string, $deInherit as xs:boolean, $generateConceptListIds as xs:boolean, $deRefId as xs:string?) as element() {
let $projectPrefix          := $concept/ancestor::decor/project/@prefix
let $projectLanguages       := $concept/ancestor::decor/project/name/@language
let $editMode               := if ($concept/@statusCode=('new','draft','pending')) then 'edit' else if ($concept/@statusCode='final') then 'move' else ()
let $deRefId                := if ($concept[inherit | contains]) then () else ($deRefId)
(:usually you inherit from the original, but if you inherit from something that inherits, then these two will differ
    originalConcept has the type and the associations. The rest comes from the inheritConcept
:)
let $inheritConcept         := if ($concept[inherit]) then art:getConcept($concept/inherit/@ref, $concept/inherit/@effectiveDate) else ()
let $containConcept         := if ($concept[contains]) then art:getConcept($concept/contains/@ref, $concept/contains/@flexibility) else ()
let $originalConcept        := art:getOriginalForConcept($concept)
let $localInherit           := 
    if ($concept/inherit) then ($inheritConcept/ancestor::decor/project/@prefix=$projectPrefix) else 
    if ($concept/contains) then ($containConcept/ancestor::decor/project/@prefix=$projectPrefix) else (false())
let $copyFromConcept        := if ($concept[inherit | contains]) then $originalConcept else $concept
let $inheritedAssociations  := if ($originalConcept or $deRefId) then art:getConceptAssociations($concept) else ()

let $calculatedType         := 
    if ($originalConcept[valueDomain][empty(concept)]) then 'item' else
    if ($originalConcept[concept][empty(valueDomain)]) then 'group' else ()

return
    <concept navkey="{util:uuid()}" statusCode="{$concept/@statusCode}" effectiveDate="{$concept/@effectiveDate}"
             expirationDate="{$concept/@expirationDate}" officialReleaseDate="{$concept/@officialReleaseDate}"
             versionLabel="{$concept/@versionLabel}" canonicalUri="{$concept/@canonicalUri}">
    {
        $concept/@id | $concept/@ref | $concept/@flexibility,
        attribute type {($originalConcept/@type, $calculatedType)[1]}
        ,
        if ($concept[@id]) then (attribute iddisplay {art:getNameForOID($concept/@id, $language, $concept/ancestor::decor)}) else ()
    }
    {
        (:
            When an item/group is new, we need a pseudo move so upon save we move to either the location it
            already is, or where the user moved it after creation with potential other new items trailing it.
        :)
        if ($concept/@statusCode='new') then (
            <move/>
        ) else ()
    }
    <edit mode="{$editMode}" deinherit="{$deInherit}"/>
    {
        $lock
    }
    {
        if ($deInherit) then
            for $e in $inheritConcept/inherit | $inheritConcept/contains | $containConcept/inherit | $containConcept/contains
            let $targetConcept := art:getConcept($e/@ref, $e/@effectiveDate | $e/@flexibility)
            return
                local:expandedInheritContainsRelationship($e, $targetConcept, $originalConcept, $targetConcept/ancestor::decor/project/@defaultLanguage)
        else (
            if ($concept[inherit]) then
                local:expandedInheritContainsRelationship($concept/inherit, $inheritConcept, $originalConcept, $inheritConcept/ancestor::decor/project/@defaultLanguage)
            else (),
            if ($concept[contains]) then
                local:expandedInheritContainsRelationship($concept/contains, $containConcept, $originalConcept, $containConcept/ancestor::decor/project/@defaultLanguage)
            else ()
        )
    }
    {
        for $lang in distinct-values(($projectLanguages, $language, $copyFromConcept/name/@language))
        return 
            if ($copyFromConcept/name[@language=$lang]) 
            then ($copyFromConcept/name[@language=$lang]) 
            else if ($concept[inherit | contains] and not($deInherit)) then () else (<name language="{$lang}"/>)
        ,
        for $lang in distinct-values(($projectLanguages, $language, $copyFromConcept/synonym/@language))
        return 
            if ($copyFromConcept/synonym[@language=$lang]) 
            then ($copyFromConcept/synonym[@language=$lang]) 
            else if ($concept[inherit | contains] and not($deInherit)) then () else (<synonym language="{$lang}"/>)
        ,
        for $lang in distinct-values(($projectLanguages, $language, $copyFromConcept/desc/@language))
        return 
            if ($copyFromConcept/desc[@language=$lang]) 
            then (art:serializeNode($copyFromConcept/desc[@language=$lang])) 
            else if ($concept[inherit | contains] and not($deInherit)) then () else (<desc language="{$lang}"/>)
        ,
        for $lang in distinct-values(($projectLanguages, $language, $copyFromConcept/source/@language))
        return 
            if ($copyFromConcept/source[@language=$lang]) 
            then (art:serializeNode($copyFromConcept/source[@language=$lang])) 
            else if ($concept[inherit | contains] and not($deInherit)) then () else (<source language="{$lang}"/>)
        ,
        for $lang in distinct-values(($projectLanguages, $language, $copyFromConcept/rationale/@language))
        return 
            if ($copyFromConcept/rationale[@language=$lang]) 
            then (art:serializeNode($copyFromConcept/rationale[@language=$lang])) 
            else if ($concept[inherit | contains] and not($deInherit)) then () else (<rationale language="{$lang}"/>)
        ,
        for $lang in distinct-values(($projectLanguages, $language, $copyFromConcept/comment/@language))
        return 
            if ($concept/comment[@language=$lang]) 
            then (
                for $node in $concept/comment[@language=$lang]
                return art:serializeNode($node)
            )
            else (<comment language="{$lang}"/>)
        ,
        if ($concept[inherit | contains]) then
            for $node in $originalConcept/comment
            let $serializedNode := art:serializeNode($node)
            return
                if ($deInherit) then
                    $serializedNode
                else (
                    <inheritedComment>{$serializedNode/@*, $serializedNode/node()}</inheritedComment>
                )
        else ()
        ,
        (:new since 2015-04-21:)
        if ((not($concept[inherit | contains]) or $deInherit) and not($copyFromConcept/property)) then
            <property name=""/>
        else (
            for $property in $copyFromConcept/property
            return
                <property name="{$property/@name}">{data(art:serializeNode($property))}</property>
        )
    }
    {
        (:new since 2015-04-21:)
        if ($concept[inherit | contains] and $deInherit) then (
            let $referredConcept    := $inheritConcept | $containConcept

            return
            <relationship type="SPEC" ref="{$concept/inherit/@ref | $concept/contains/@ref}" flexibility="{$concept/inherit/@effectiveDate | $concept/contains/@flexibility}">
            {
                $referredConcept/ancestor::decor/project/@prefix,
                attribute datasetId {$referredConcept/ancestor::dataset/@id},
                attribute datasetEffectiveDate {$referredConcept/ancestor::dataset/@effectiveDate},
                attribute datasetStatusCode {$referredConcept/ancestor::dataset/@statusCode},
                attribute iType {$originalConcept/@type}, 
                attribute iStatusCode {$referredConcept/@statusCode}, 
                if ($referredConcept/@expirationDate) then attribute iExpirationDate {$referredConcept/@expirationDate} else (),
                if ($referredConcept/@versionLabel) then attribute iVersionLabel {$referredConcept/@versionLabel} else (),
                attribute iddisplay {art:getNameForOID($concept/inherit/@ref | $concept/contains/@ref, $language, $referredConcept/ancestor::decor)},
                attribute localInherit {$localInherit},
                for $lang in distinct-values(($projectLanguages, $language, $originalConcept/name/@language))
                return 
                    if ($originalConcept/name[@language=$lang]) 
                    then ($originalConcept/name[@language=$lang]) 
                    else (<name language="{$lang}">{$originalConcept/name[1]/node()}</name>)
            }
            </relationship>
        ) else ()
        ,
        if ($concept/relationship) then (
            for $relationship in $copyFromConcept/relationship
            let $referredConcept            := art:getConcept($relationship/@ref, $relationship/@flexibility)
            let $originalReferredConcept    := art:getOriginalForConcept($referredConcept)[1]
            return
                <relationship type="{$relationship/@type}" ref="{$relationship/@ref}" flexibility="{$relationship/@flexibility}">
                {
                    $referredConcept/ancestor::decor/project/@prefix,
                    attribute datasetId {$referredConcept/ancestor::dataset/@id},
                    attribute datasetEffectiveDate {$referredConcept/ancestor::dataset/@effectiveDate},
                    attribute datasetStatusCode {$referredConcept/ancestor::dataset/@statusCode},
                    attribute iType {$originalReferredConcept/@type}, 
                    attribute iStatusCode {$referredConcept/@statusCode}, 
                    if ($referredConcept[@expirationDate]) then attribute iExpirationDate {$referredConcept/@expirationDate} else (),
                    if ($referredConcept[@versionLabel]) then attribute iVersionLabel {$referredConcept/@versionLabel} else (),
                    attribute iddisplay {art:getNameForOID($relationship/@ref, $language, $referredConcept/ancestor::decor)},
                    attribute localInherit {$referredConcept/ancestor::decor/project/@prefix = $projectPrefix},
                    for $lang in distinct-values(($projectLanguages, $language, $originalReferredConcept/name/@language))
                    return 
                        if ($originalReferredConcept/name[@language=$lang]) 
                        then ($originalReferredConcept/name[@language=$lang]) 
                        else (<name language="{$lang}">{$originalReferredConcept/name[1]/node()}</name>)
                }
                </relationship>
        ) else ()
    }
    {
        for $lang in distinct-values(($projectLanguages, $language, $copyFromConcept/operationalization/@language))
        return 
            if ($copyFromConcept/operationalization[@language=$lang]) 
            then (art:serializeNode($copyFromConcept/operationalization[@language=$lang])) 
            else if ($concept[inherit | contains] and not($deInherit)) then () else (<operationalization language="{$lang}"/>)
    }
    {
        if ($concept[inherit | contains] and $deInherit) then
            (: create identifier/terminology association for concept if present for inherited concept:)
            for $association in $inheritedAssociations[@conceptId = $originalConcept/@id]
            return
                element {name($association)}
                {
                    attribute conceptId {$concept/@id},
                    attribute conceptFlexibility {$concept/@effectiveDate},
                    attribute effectiveDate {format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01]')},
                    $association/(@*[string-length()>0] except (@conceptId | @conceptFlexibility | @effectiveDate))
                }
        else ()
    }
    {
        if ((not($concept[inherit | contains]) or $deInherit) and $copyFromConcept[@type='item'][not(valueDomain)]) then
            <valueDomain type="count">
                <property unit="" minInclude="" maxInclude="" fractionDigits="" timeStampPrecision="YMD" default="" fixed="" minLength="" maxLength=""/>
                <conceptList id="{concat($concept/@id,'.',replace($concept/@effectiveDate,'[^\d]',''),'.0')}"/>
                <example type="neutral" caption=""/>
            </valueDomain>
        else ()
        ,
        for $valueDomain at $vdpos in $copyFromConcept[@type='item']/valueDomain
        return
        <valueDomain type="{$valueDomain/@type}">
        {
            if ((not($concept[inherit | contains]) or $deInherit) and not($valueDomain/property[@*[string-length()>0]])) then
                <property unit="" minInclude="" maxInclude="" fractionDigits="" timeStampPrecision="" default="" fixed="" minLength="" maxLength=""/>
            else (
                for $property in $valueDomain/property[@*[string-length()>0]]
                return
                <property unit="{$property/@unit}" minInclude="{$property/@minInclude}" maxInclude="{$property/@maxInclude}" fractionDigits="{$property/@fractionDigits}" timeStampPrecision="{$property/@timeStampPrecision}" default="{$property/@default}" fixed="{$property/@fixed}" minLength="{$property/@minLength}" maxLength="{$property/@maxLength}">
                {
                    $property/*
                }
                </property>
            )
        }
        {
            (: valueDomain/conceptList and valueDomain/conceptList/concept id logic:
                - parent::concept/@id . parent::concept/@effectiveDate is base
                    [the effectiveDate makes versioned concepts possible. without this timestamp, the 
                        conceptList ids from different concept versions would be the same]
                - conceptList/@id and conceptList/concept/@id are max new item
                - for historic reasons first conceptList/@id is '0' (instead of '1')
            :)
            (:
                Please note that the same numbering logic is applied when adding new conceptList/concepts in the dataset form!
            :)
            let $allConceptListConcepts := ($copyFromConcept/valueDomain/conceptList[@id] | $copyFromConcept/valueDomain/conceptList[@id]/concept[@id])
            return
            if (not($valueDomain/conceptList)) then (
                (: This valueDomain does not have a conceptList (yet), maybe due to its type, add a pseudo conceptList :)
                let $newclid                := 
                    if ($allConceptListConcepts[@id])
                    then concat($concept/@id,'.',string(max($allConceptListConcepts/number(tokenize(@id,'\.')[last()]))+1))
                    else concat($concept/@id,'.',replace($concept/@effectiveDate,'[^\d]',''),'.0')
                return
                <conceptList id="{$newclid}"/>
            )
            else (
                (: 
                    This valueDomain has conceptList[@id] or conceptList[@ref]. 
                    - conceptList[@id]
                        - Normal case is to copy @id and concepts
                        - Deinherit case generates new ids
                    - conceptList[@ref]
                        - Normal case is to copy @ref and concepts from original
                        - Deinherit case generates new ids
                :)
                for $conceptList at $clpos in $valueDomain/conceptList
                let $newclid                := 
                    if ($allConceptListConcepts[@id])
                    then concat($concept/@id,'.',string(max($allConceptListConcepts/number(tokenize(@id,'\.')[last()]))+1))
                    else concat($concept/@id,'.',replace($concept/@effectiveDate,'[^\d]',''),'.0')
                
                (: could be @ref :)
                let $originalConceptList    := art:getOriginalConceptList($conceptList)
                return (
                    <conceptList>
                    {
                        if ($conceptList[@ref=$deRefId] or ($deInherit and $generateConceptListIds)) then (
                            attribute id {$newclid},
                            attribute oldid {$conceptList/(@id|@ref)},
                            (: create terminology association for concept if present for inherited concept:)
                            for $terminologyAssociation in $inheritedAssociations[@conceptId=$originalConceptList/@id]
                            return
                                <terminologyAssociation conceptId="{$newclid}" effectiveDate="{format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01]')}">
                                {
                                    $terminologyAssociation/(@*[string-length()>0] except (@conceptId | @effectiveDate))
                                }
                                </terminologyAssociation>
                        ) 
                        else if ($deInherit) then (
                            attribute ref {$conceptList/(@id|@ref)}
                        )
                        else ($conceptList/(@id|@ref))
                    }
                    {
                        for $conceptListConcept at $clcpos in $originalConceptList/concept
                        let $newclcid := 
                            if (count($copyFromConcept/valueDomain/conceptList)=1) 
                            then concat($concept/@id,'.',$clcpos)
                            else concat($newclid,'.',$clcpos)
                        
                        return (
                            if ($conceptList[@ref=$deRefId] or ($deInherit and $generateConceptListIds)) then
                                (: create terminology association for concept if present for inherited concept:)
                                for $terminologyAssociation in $inheritedAssociations[@conceptId=$conceptListConcept/@id]
                                return
                                    <terminologyAssociation conceptId="{$newclcid}" effectiveDate="{format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01]')}">
                                    {
                                        $terminologyAssociation/(@*[string-length()>0] except (@conceptId | @effectiveDate))
                                    }
                                    </terminologyAssociation>
                            else ()
                            ,
                            <concept>
                            {
                                if ($conceptList[@ref=$deRefId] or ($deInherit and $generateConceptListIds)) then (
                                    attribute id {$newclcid}
                                ) else ($conceptListConcept/(@id|@ref))
                                ,
                                if ($valueDomain[@type = 'ordinal']) then 
                                    attribute ordinal {$conceptListConcept/@ordinal}
                                else (),
                                attribute exception {$conceptListConcept/@exception='true'},
                                attribute level {$conceptListConcept/@level},
                                attribute type {$conceptListConcept/@type}
                            }
                            {
                                for $lang in distinct-values(($projectLanguages, $language, $conceptListConcept/name/@language))
                                return 
                                    if ($conceptListConcept/name[@language=$lang]) 
                                    then ($conceptListConcept/name[@language=$lang]) 
                                    else (<name language="{$lang}"/>)
                                ,
                                for $lang in distinct-values(($projectLanguages, $language, $conceptListConcept/synonym/@language))
                                return 
                                    if ($conceptListConcept/synonym[@language=$lang]) 
                                    then ($conceptListConcept/synonym[@language=$lang]) 
                                    else (<synonym language="{$lang}"/>)
                                ,
                                for $lang in distinct-values(($projectLanguages, $language, $copyFromConcept/desc/@language))
                                return 
                                    if ($conceptListConcept/desc[@language=$lang]) 
                                    then (art:serializeNode($conceptListConcept/desc[@language=$lang])) 
                                    else (<desc language="{$lang}"/>)
                            }
                            </concept>
                        )
                    }
                    </conceptList>
                )
            )
        }
        {
            if ((not($concept[inherit | contains]) or $deInherit) and not($valueDomain/example)) then
                <example type="neutral" caption=""/>
            else (
                for $example in $valueDomain/example
                return
                <example>
                {
                    if ($example/@type[string-length()>0]) then $example/@type else (attribute type {'neutral'}),
                    attribute caption {$example/@caption},
                    $example/node()
                }
                </example>
            )
            ,
            $valueDomain/(* except (property|conceptList|example))
        }
        </valueDomain>
        ,
        $concept/history
    }
    </concept>
};

(: variables for request parameters:)
let $id                         := if (request:exists()) then request:get-parameter('id',())[not(.='')]                 else ()
let $effectiveDate              := if (request:exists()) then request:get-parameter('effectiveDate',())[not(.='')]      else ()
let $breakLock                  := if (request:exists()) then xs:boolean(request:get-parameter('breakLock','false'))    else (false())
let $language                   := if (request:exists()) then request:get-parameter('language',())[not(.='')]           else ()
(:  when this variable is true, we copy properties from the concept that this concept 
    used to inherit from so they become copied properties from the this concept:)
let $deInherit                  := if (request:exists()) then xs:boolean(request:get-parameter('deInherit','false'))    else (false())
let $generateConceptListIds     := if (request:exists()) then xs:boolean(request:get-parameter('generateIds','true'))   else (true())

(:  when this variable is valued, we are instructed to generate new ids for a certain conceptList[@ref]
    this variable can only be used if the concept itself does not inherit
:)
let $deRefId                    := if (request:exists()) then request:get-parameter('deRefId',())[not(.='')]            else ()

let $lock                       := decor:setLock($id, $effectiveDate, $breakLock)
let $response           :=
    if ($lock[name() = 'true']) then (
        let $concept            := art:getConcept($id, $effectiveDate)
        let $language           := if ($language) then $language else $concept/ancestor::decor/project/@defaultLanguage/string()
        let $newLock            := $lock/node()
        return
            local:getConceptForEdit($concept, $newLock, $language, $deInherit, $generateConceptListIds, $deRefId)
    )
    else (
        <concept>{$lock/node()}</concept>
    )

return
    $response
