{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
-- | SD-JWT presentation: Creating presentations with selected disclosures.
--
-- This module provides functions for creating SD-JWT presentations on the holder side.
-- The holder selects which disclosures to include when presenting to a verifier.
module SDJWT.Internal.Presentation
  ( createPresentation
  , selectDisclosures
  , selectDisclosuresByNames
  , addKeyBinding
  ) where

import SDJWT.Internal.Types (HashAlgorithm(..), Digest(..), SDJWT(..), SDJWTPayload(..), SDJWTPresentation(..), SDJWTError(..), EncodedDisclosure(..), Disclosure(..))
import SDJWT.Internal.Disclosure (decodeDisclosure, getDisclosureClaimName, getDisclosureValue)
import SDJWT.Internal.Digest (extractDigestsFromValue, computeDigest, computeDigestText, extractDigestStringsFromSDArray, defaultHashAlgorithm)
import SDJWT.Internal.Utils (splitJSONPointer, unescapeJSONPointer, groupPathsByFirstSegment)
import SDJWT.Internal.KeyBinding (addKeyBindingToPresentation)
import SDJWT.Internal.JWT (JWKLike)
import SDJWT.Internal.Verification (parsePayloadFromJWT, extractDigestsFromPayload)
import qualified Data.Text as T
import qualified Data.Set as Set
import qualified Data.Map.Strict as Map
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.KeyMap as KeyMap
import qualified Data.Aeson.Key as Key
import qualified Data.Vector as V
import Data.Int (Int64)
import Data.Maybe (mapMaybe, fromMaybe)
import Data.List (partition, find, nubBy)
import Data.Either (partitionEithers)
import Text.Read (readMaybe)

-- | Create a presentation with selected disclosures.
--
-- This is a simple function that creates an SDJWTPresentation from an SDJWT
-- and a list of selected disclosures. The selected disclosures must be a subset
-- of the disclosures in the original SDJWT.
createPresentation
  :: SDJWT
  -> [EncodedDisclosure]  -- ^ Selected disclosures to include
  -> SDJWTPresentation
createPresentation :: SDJWT -> [EncodedDisclosure] -> SDJWTPresentation
createPresentation (SDJWT Text
jwt [EncodedDisclosure]
_) [EncodedDisclosure]
selectedDisclos =
  SDJWTPresentation
    { presentationJWT :: Text
presentationJWT = Text
jwt
    , selectedDisclosures :: [EncodedDisclosure]
selectedDisclosures = [EncodedDisclosure]
selectedDisclos
    , keyBindingJWT :: Maybe Text
keyBindingJWT = Maybe Text
forall a. Maybe a
Nothing
    }

-- | Select disclosures from an SD-JWT based on claim names.
--
-- This function:
--
-- 1. Decodes all disclosures from the SD-JWT
-- 2. Filters disclosures to include only those matching the provided claim names
-- 3. Handles recursive disclosures (Section 6.3): when selecting nested claims,
--    automatically includes parent disclosures if they are recursively disclosable
-- 4. Validates disclosure dependencies (ensures all required parent disclosures are present)
-- 5. Returns a presentation with the selected disclosures
--
-- Note: This function validates that the selected disclosures exist in the SD-JWT.
-- Supports JSON Pointer syntax for nested paths:
--
-- * Object properties: @["address\/street_address", "address\/locality"]@
-- * Array elements: @["nationalities\/0", "nationalities\/2"]@
-- * Mixed paths: @["address\/street_address", "nationalities\/1"]@
-- * Nested arrays: @["nested_array\/0\/0", "nested_array\/1\/1"]@
--
-- Paths with numeric segments (e.g., @["x\/22"]@) are resolved by checking the
-- actual claim type: if @x@ is an array, it refers to index 22; if @x@ is an
-- object, it refers to property @"22"@.
selectDisclosuresByNames
  :: SDJWT
  -> [T.Text]  -- ^ Claim names to include in presentation (supports JSON Pointer syntax for nested paths, including array indices)
  -> Either SDJWTError SDJWTPresentation
selectDisclosuresByNames :: SDJWT -> [Text] -> Either SDJWTError SDJWTPresentation
selectDisclosuresByNames sdjwt :: SDJWT
sdjwt@(SDJWT Text
issuerJWT [EncodedDisclosure]
allDisclosures) [Text]
claimNames = do
  -- Extract hash algorithm from JWT payload (RFC 9901 Section 7.2 requires this for validation)
  HashAlgorithm
hashAlg <- Text -> Either SDJWTError HashAlgorithm
extractHashAlgorithmFromJWT Text
issuerJWT
  
  -- Decode all disclosures to check their claim names and detect recursive disclosures
  [Disclosure]
decodedDisclosures <- (EncodedDisclosure -> Either SDJWTError Disclosure)
-> [EncodedDisclosure] -> Either SDJWTError [Disclosure]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM EncodedDisclosure -> Either SDJWTError Disclosure
decodeDisclosure [EncodedDisclosure]
allDisclosures
  
  -- Parse claim names to separate top-level and nested paths
  let ([Text]
topLevelNames, [[Text]]
nestedPaths) = [Text] -> ([Text], [[Text]])
partitionNestedPaths [Text]
claimNames
  
  -- Parse JWT payload
  SDJWTPayload
sdPayload <- Text -> Either SDJWTError SDJWTPayload
parsePayloadFromJWT Text
issuerJWT
  let payloadValueObj :: Value
payloadValueObj = SDJWTPayload -> Value
payloadValue SDJWTPayload
sdPayload
  
  -- Recursively collect disclosures for all paths (handles both objects and arrays at each level)
  -- Pass decoded disclosures for efficient claim name lookup
  [EncodedDisclosure]
selectedDisclos <- HashAlgorithm
-> [Text]
-> [[Text]]
-> Value
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectDisclosuresRecursively HashAlgorithm
hashAlg [Text]
topLevelNames [[Text]]
nestedPaths Value
payloadValueObj [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures
  
  -- For top-level array claims, also collect array element disclosures
  -- When selecting "foo" where foo is an array with ellipsis objects, we need to include
  -- the disclosures for those ellipsis objects
  -- When nestedPaths is empty (holder_disclosed_claims is empty), don't recursively collect nested disclosures
  let shouldRecurse :: Bool
shouldRecurse = Bool -> Bool
not ([[Text]] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [[Text]]
nestedPaths)
  [EncodedDisclosure]
arrayElementDisclos <- HashAlgorithm
-> [Text]
-> Text
-> [EncodedDisclosure]
-> [Disclosure]
-> Bool
-> Either SDJWTError [EncodedDisclosure]
collectArrayElementDisclosures HashAlgorithm
hashAlg [Text]
topLevelNames Text
issuerJWT [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures Bool
shouldRecurse
  
  -- Combine all selected disclosures and deduplicate by digest
  let allSelectedDisclosRaw :: [EncodedDisclosure]
allSelectedDisclosRaw = [EncodedDisclosure]
selectedDisclos [EncodedDisclosure] -> [EncodedDisclosure] -> [EncodedDisclosure]
forall a. [a] -> [a] -> [a]
++ [EncodedDisclosure]
arrayElementDisclos
      -- Deduplicate by computing digest for each disclosure
      allSelectedDisclos :: [EncodedDisclosure]
allSelectedDisclos = (EncodedDisclosure -> EncodedDisclosure -> Bool)
-> [EncodedDisclosure] -> [EncodedDisclosure]
forall a. (a -> a -> Bool) -> [a] -> [a]
nubBy (\EncodedDisclosure
enc1 EncodedDisclosure
enc2 -> HashAlgorithm -> EncodedDisclosure -> Digest
computeDigest HashAlgorithm
hashAlg EncodedDisclosure
enc1 Digest -> Digest -> Bool
forall a. Eq a => a -> a -> Bool
== HashAlgorithm -> EncodedDisclosure -> Digest
computeDigest HashAlgorithm
hashAlg EncodedDisclosure
enc2) [EncodedDisclosure]
allSelectedDisclosRaw
  
  -- Validate disclosure dependencies per RFC 9901 Section 7.2, step 2b:
  -- Verify that each selected Disclosure satisfies one of:
  -- a. The hash is contained in the Issuer-signed JWT claims
  -- b. The hash is contained in the claim value of another selected Disclosure
  HashAlgorithm
-> [EncodedDisclosure] -> Text -> Either SDJWTError ()
validateDisclosureDependencies HashAlgorithm
hashAlg [EncodedDisclosure]
allSelectedDisclos Text
issuerJWT
  
  -- Create presentation
  SDJWTPresentation -> Either SDJWTError SDJWTPresentation
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return (SDJWTPresentation -> Either SDJWTError SDJWTPresentation)
-> SDJWTPresentation -> Either SDJWTError SDJWTPresentation
forall a b. (a -> b) -> a -> b
$ SDJWT -> [EncodedDisclosure] -> SDJWTPresentation
createPresentation SDJWT
sdjwt [EncodedDisclosure]
allSelectedDisclos

-- | Recursively collect disclosures for paths, handling both objects and arrays at each level.
collectDisclosuresRecursively
  :: HashAlgorithm
  -> [T.Text]  -- ^ Top-level claim names
  -> [[T.Text]]  -- ^ Nested paths as segments
  -> Aeson.Value  -- ^ Current value (object or array)
  -> [EncodedDisclosure]  -- ^ All available disclosures
  -> [Disclosure]  -- ^ Decoded disclosures (for claim name lookup)
  -> Either SDJWTError [EncodedDisclosure]
collectDisclosuresRecursively :: HashAlgorithm
-> [Text]
-> [[Text]]
-> Value
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectDisclosuresRecursively HashAlgorithm
hashAlg [Text]
topLevelNames [[Text]]
nestedPaths Value
value [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures = do
  case Value
value of
    Aeson.Object Object
obj -> HashAlgorithm
-> [Text]
-> [[Text]]
-> Object
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectFromObject HashAlgorithm
hashAlg [Text]
topLevelNames [[Text]]
nestedPaths Object
obj [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures
    Aeson.Array Array
arr -> HashAlgorithm
-> [Text]
-> [[Text]]
-> Array
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectFromArray HashAlgorithm
hashAlg [Text]
topLevelNames [[Text]]
nestedPaths Array
arr [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures
    Value
_ -> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []  -- Primitive value, no disclosures

-- | Collect top-level disclosures from root _sd array.
collectTopLevelDisclosures
  :: HashAlgorithm
  -> [T.Text]  -- ^ Top-level claim names
  -> KeyMap.KeyMap Aeson.Value  -- ^ Current object
  -> [EncodedDisclosure]  -- ^ All available disclosures
  -> [Disclosure]  -- ^ Decoded disclosures (for claim name lookup)
  -> Either SDJWTError [EncodedDisclosure]
collectTopLevelDisclosures :: HashAlgorithm
-> [Text]
-> Object
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectTopLevelDisclosures HashAlgorithm
hashAlg [Text]
topLevelNames Object
obj [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures =
  if [Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
topLevelNames
    then [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
    else do
      -- Extract digests from root _sd array
      let rootDigests :: [Text]
rootDigests = Object -> [Text]
extractDigestStringsFromSDArray Object
obj
      if [Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
rootDigests
        then [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []  -- No root _sd array
        else do
          -- Find disclosures matching these digests AND matching the claim names
          let matchingDisclos :: [EncodedDisclosure]
matchingDisclos = ((EncodedDisclosure, Disclosure) -> Maybe EncodedDisclosure)
-> [(EncodedDisclosure, Disclosure)] -> [EncodedDisclosure]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\(EncodedDisclosure
encDisclosure, Disclosure
decoded) ->
                let digestText :: Text
digestText = HashAlgorithm -> EncodedDisclosure -> Text
computeDigestText HashAlgorithm
hashAlg EncodedDisclosure
encDisclosure
                in do
                  -- Check if digest is in root _sd array AND claim name matches
                  Text
claimName <- Disclosure -> Maybe Text
getDisclosureClaimName Disclosure
decoded
                  if Text
digestText Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
rootDigests Bool -> Bool -> Bool
&& Text
claimName Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
topLevelNames
                    then EncodedDisclosure -> Maybe EncodedDisclosure
forall a. a -> Maybe a
Just EncodedDisclosure
encDisclosure
                    else Maybe EncodedDisclosure
forall a. Maybe a
Nothing
                ) ([(EncodedDisclosure, Disclosure)] -> [EncodedDisclosure])
-> [(EncodedDisclosure, Disclosure)] -> [EncodedDisclosure]
forall a b. (a -> b) -> a -> b
$ [EncodedDisclosure]
-> [Disclosure] -> [(EncodedDisclosure, Disclosure)]
forall a b. [a] -> [b] -> [(a, b)]
zip [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures
          [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return [EncodedDisclosure]
matchingDisclos

-- | Find disclosure for a claim name in an _sd array.
findDisclosureForClaim
  :: HashAlgorithm
  -> T.Text  -- ^ Claim name
  -> KeyMap.KeyMap Aeson.Value  -- ^ Current object
  -> [EncodedDisclosure]  -- ^ All available disclosures
  -> [Disclosure]  -- ^ Decoded disclosures
  -> Maybe (EncodedDisclosure, Disclosure)
findDisclosureForClaim :: HashAlgorithm
-> Text
-> Object
-> [EncodedDisclosure]
-> [Disclosure]
-> Maybe (EncodedDisclosure, Disclosure)
findDisclosureForClaim HashAlgorithm
hashAlg Text
claimName Object
obj [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures =
  let sdArrayDigests :: [Text]
sdArrayDigests = Object -> [Text]
extractDigestStringsFromSDArray Object
obj
  in ((EncodedDisclosure, Disclosure) -> Bool)
-> [(EncodedDisclosure, Disclosure)]
-> Maybe (EncodedDisclosure, Disclosure)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\(EncodedDisclosure
encDisclosure, Disclosure
decoded) ->
        let digestText :: Text
digestText = HashAlgorithm -> EncodedDisclosure -> Text
computeDigestText HashAlgorithm
hashAlg EncodedDisclosure
encDisclosure
            decodedClaimName :: Maybe Text
decodedClaimName = Disclosure -> Maybe Text
getDisclosureClaimName Disclosure
decoded
        in Text
digestText Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
sdArrayDigests Bool -> Bool -> Bool
&& Maybe Text
decodedClaimName Maybe Text -> Maybe Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text -> Maybe Text
forall a. a -> Maybe a
Just Text
claimName
      ) ([(EncodedDisclosure, Disclosure)]
 -> Maybe (EncodedDisclosure, Disclosure))
-> [(EncodedDisclosure, Disclosure)]
-> Maybe (EncodedDisclosure, Disclosure)
forall a b. (a -> b) -> a -> b
$ [EncodedDisclosure]
-> [Disclosure] -> [(EncodedDisclosure, Disclosure)]
forall a b. [a] -> [b] -> [(a, b)]
zip [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures

-- | Collect nested disclosures for a single path segment.
collectNestedDisclosuresForSegment
  :: HashAlgorithm
  -> T.Text  -- ^ First segment (claim name)
  -> [[T.Text]]  -- ^ Remaining paths
  -> KeyMap.KeyMap Aeson.Value  -- ^ Current object
  -> [EncodedDisclosure]  -- ^ All available disclosures
  -> [Disclosure]  -- ^ Decoded disclosures
  -> Either SDJWTError [EncodedDisclosure]
collectNestedDisclosuresForSegment :: HashAlgorithm
-> Text
-> [[Text]]
-> Object
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectNestedDisclosuresForSegment HashAlgorithm
hashAlg Text
firstSeg [[Text]]
remainingPaths Object
obj [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures = do
  -- Find disclosure for this claim name
  let claimDisclosure :: Maybe (EncodedDisclosure, Disclosure)
claimDisclosure = HashAlgorithm
-> Text
-> Object
-> [EncodedDisclosure]
-> [Disclosure]
-> Maybe (EncodedDisclosure, Disclosure)
findDisclosureForClaim HashAlgorithm
hashAlg Text
firstSeg Object
obj [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures
  let nestedValue :: Value
nestedValue = case Maybe (EncodedDisclosure, Disclosure)
claimDisclosure of
        Just (EncodedDisclosure
_, Disclosure
decoded) -> Disclosure -> Value
getDisclosureValue Disclosure
decoded
        Maybe (EncodedDisclosure, Disclosure)
Nothing -> Value -> Maybe Value -> Value
forall a. a -> Maybe a -> a
fromMaybe Value
Aeson.Null (Maybe Value -> Value) -> Maybe Value -> Value
forall a b. (a -> b) -> a -> b
$ Key -> Object -> Maybe Value
forall v. Key -> KeyMap v -> Maybe v
KeyMap.lookup (Text -> Key
Key.fromText Text
firstSeg) Object
obj
  
  -- Filter out empty paths (this segment is the target)
  let ([[Text]]
emptyPaths, [[Text]]
nonEmptyPaths) = ([Text] -> Bool) -> [[Text]] -> ([[Text]], [[Text]])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition [Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [[Text]]
remainingPaths
  
  -- Collect disclosure for this level if it's a target
  [EncodedDisclosure]
thisLevelDisclos <- if [[Text]] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [[Text]]
emptyPaths
    then [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
    else case Maybe (EncodedDisclosure, Disclosure)
claimDisclosure of
      Just (EncodedDisclosure
encDisclosure, Disclosure
_) -> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return [EncodedDisclosure
encDisclosure]
      Maybe (EncodedDisclosure, Disclosure)
Nothing -> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []  -- Claim not in parent's _sd array and is a target - no disclosure to collect
  
  -- Recurse into nested value
  [EncodedDisclosure]
deeperDisclos <- if [[Text]] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [[Text]]
nonEmptyPaths
    then [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
    else do
      -- Collect disclosures for nested paths
      [EncodedDisclosure]
nestedDisclos2 <- HashAlgorithm
-> [Text]
-> [[Text]]
-> Value
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectDisclosuresRecursively HashAlgorithm
hashAlg [] [[Text]]
nonEmptyPaths Value
nestedValue [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures
      -- If we found nested disclosures, check if the parent itself is selectively disclosable
      -- (Section 6.3 recursive disclosure)
      [EncodedDisclosure]
parentDisclos <- if Bool -> Bool
not ([EncodedDisclosure] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [EncodedDisclosure]
nestedDisclos2) Bool -> Bool -> Bool
&& Value -> Bool
isRecursiveValue Value
nestedValue
        then case Maybe (EncodedDisclosure, Disclosure)
claimDisclosure of
          Just (EncodedDisclosure
encDisclosure, Disclosure
_) -> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return [EncodedDisclosure
encDisclosure]  -- Parent is selectively disclosable
          Maybe (EncodedDisclosure, Disclosure)
Nothing -> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []  -- Parent is not selectively disclosable (Section 6.2)
        else [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
      [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ([EncodedDisclosure]
parentDisclos [EncodedDisclosure] -> [EncodedDisclosure] -> [EncodedDisclosure]
forall a. [a] -> [a] -> [a]
++ [EncodedDisclosure]
nestedDisclos2)
  
  [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ([EncodedDisclosure]
thisLevelDisclos [EncodedDisclosure] -> [EncodedDisclosure] -> [EncodedDisclosure]
forall a. [a] -> [a] -> [a]
++ [EncodedDisclosure]
deeperDisclos)

-- | Collect disclosures from an object.
collectFromObject
  :: HashAlgorithm
  -> [T.Text]  -- ^ Top-level claim names
  -> [[T.Text]]  -- ^ Nested paths as segments
  -> KeyMap.KeyMap Aeson.Value  -- ^ Current object
  -> [EncodedDisclosure]  -- ^ All available disclosures
  -> [Disclosure]  -- ^ Decoded disclosures (for claim name lookup)
  -> Either SDJWTError [EncodedDisclosure]
collectFromObject :: HashAlgorithm
-> [Text]
-> [[Text]]
-> Object
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectFromObject HashAlgorithm
hashAlg [Text]
topLevelNames [[Text]]
nestedPaths Object
obj [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures = do
  -- Process top-level names
  -- For top-level selectively disclosable claims (including arrays), the claim is removed
  -- from payload and its digest is in the root _sd array (RFC 9901 treats top-level arrays
  -- as object properties, not array elements).
  [EncodedDisclosure]
topLevelDisclos <- HashAlgorithm
-> [Text]
-> Object
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectTopLevelDisclosures HashAlgorithm
hashAlg [Text]
topLevelNames Object
obj [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures
  
  -- Group nested paths by first segment
  let groupedByFirst :: Map Text [[Text]]
groupedByFirst = [[Text]] -> Map Text [[Text]]
groupPathsByFirstSegment [[Text]]
nestedPaths
  
  -- Process each group recursively
  [[EncodedDisclosure]]
nestedDisclos <- ((Text, [[Text]]) -> Either SDJWTError [EncodedDisclosure])
-> [(Text, [[Text]])] -> Either SDJWTError [[EncodedDisclosure]]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (\(Text
firstSeg, [[Text]]
remainingPaths) ->
      HashAlgorithm
-> Text
-> [[Text]]
-> Object
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectNestedDisclosuresForSegment HashAlgorithm
hashAlg Text
firstSeg [[Text]]
remainingPaths Object
obj [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures
    ) (Map Text [[Text]] -> [(Text, [[Text]])]
forall k a. Map k a -> [(k, a)]
Map.toList Map Text [[Text]]
groupedByFirst)
  
  [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ([EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure])
-> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a b. (a -> b) -> a -> b
$ [EncodedDisclosure]
topLevelDisclos [EncodedDisclosure] -> [EncodedDisclosure] -> [EncodedDisclosure]
forall a. [a] -> [a] -> [a]
++ [[EncodedDisclosure]] -> [EncodedDisclosure]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[EncodedDisclosure]]
nestedDisclos

-- | Collect disclosures from an array.
collectFromArray
  :: HashAlgorithm
  -> [T.Text]  -- ^ Top-level claim names (should be empty for arrays)
  -> [[T.Text]]  -- ^ Nested paths as segments
  -> V.Vector Aeson.Value  -- ^ Current array
  -> [EncodedDisclosure]  -- ^ All available disclosures
  -> [Disclosure]  -- ^ Decoded disclosures (for claim name lookup)
  -> Either SDJWTError [EncodedDisclosure]
collectFromArray :: HashAlgorithm
-> [Text]
-> [[Text]]
-> Array
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectFromArray HashAlgorithm
hashAlg [Text]
_topLevelNames [[Text]]
nestedPaths Array
arr [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures = do
  -- Parse first segment of each path to extract array index
  -- Group paths by first index
  let groupedByFirstIndex :: Map Int [[Text]]
groupedByFirstIndex = ([[Text]] -> [[Text]] -> [[Text]])
-> [(Int, [[Text]])] -> Map Int [[Text]]
forall k a. Ord k => (a -> a -> a) -> [(k, a)] -> Map k a
Map.fromListWith [[Text]] -> [[Text]] -> [[Text]]
forall a. [a] -> [a] -> [a]
(++) ([(Int, [[Text]])] -> Map Int [[Text]])
-> [(Int, [[Text]])] -> Map Int [[Text]]
forall a b. (a -> b) -> a -> b
$ ([Text] -> Maybe (Int, [[Text]])) -> [[Text]] -> [(Int, [[Text]])]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\[Text]
path -> case [Text]
path of
        [] -> Maybe (Int, [[Text]])
forall a. Maybe a
Nothing
        (Text
firstSeg:[Text]
rest) -> case String -> Maybe Int
forall a. Read a => String -> Maybe a
readMaybe (Text -> String
T.unpack Text
firstSeg) :: Maybe Int of
          Just Int
idx -> (Int, [[Text]]) -> Maybe (Int, [[Text]])
forall a. a -> Maybe a
Just (Int
idx, [[Text]
rest])
          Maybe Int
Nothing -> Maybe (Int, [[Text]])
forall a. Maybe a
Nothing  -- Not a numeric segment, skip
        ) [[Text]]
nestedPaths
  
  -- Process each group
  [[EncodedDisclosure]]
results <- ((Int, [[Text]]) -> Either SDJWTError [EncodedDisclosure])
-> [(Int, [[Text]])] -> Either SDJWTError [[EncodedDisclosure]]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (\(Int
firstIdx, [[Text]]
remainingPaths) ->
      if Int
firstIdx Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
0 Bool -> Bool -> Bool
|| Int
firstIdx Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Array -> Int
forall a. Vector a -> Int
V.length Array
arr
        then [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
        else do
          let element :: Value
element = Array
arr Array -> Int -> Value
forall a. Vector a -> Int -> a
V.! Int
firstIdx
          -- Filter out empty paths (this element is the target)
          let ([[Text]]
emptyPaths, [[Text]]
nonEmptyPaths) = ([Text] -> Bool) -> [[Text]] -> ([[Text]], [[Text]])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition [Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [[Text]]
remainingPaths
          -- Collect disclosure for this element if it's a target
          [EncodedDisclosure]
thisLevelDisclos <- if [[Text]] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [[Text]]
emptyPaths
            then [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
            else HashAlgorithm
-> Value
-> [EncodedDisclosure]
-> Either SDJWTError [EncodedDisclosure]
collectDisclosuresForArrayElement HashAlgorithm
hashAlg Value
element [EncodedDisclosure]
allDisclosures
          -- Recurse into nested value
          [EncodedDisclosure]
deeperDisclos <- if [[Text]] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [[Text]]
nonEmptyPaths
            then [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
            else do
              -- If the element is an ellipsis object, we need to get the actual value from the disclosure
              -- and include the parent element disclosure
              case Value
element of
                Aeson.Object Object
ellipsisObj ->
                  case Key -> Object -> Maybe Value
forall v. Key -> KeyMap v -> Maybe v
KeyMap.lookup (Text -> Key
Key.fromText Text
"...") Object
ellipsisObj of
                    Just (Aeson.String Text
digest) ->
                      -- Find the disclosure for this digest
                      case (EncodedDisclosure -> Bool)
-> [EncodedDisclosure] -> Maybe EncodedDisclosure
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\EncodedDisclosure
encDisclosure ->
                            HashAlgorithm -> EncodedDisclosure -> Text
computeDigestText HashAlgorithm
hashAlg EncodedDisclosure
encDisclosure Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
digest
                          ) [EncodedDisclosure]
allDisclosures of
                        Just EncodedDisclosure
encDisclosure -> do
                          -- Decode to get the actual value
                          Disclosure
decoded <- EncodedDisclosure -> Either SDJWTError Disclosure
decodeDisclosure EncodedDisclosure
encDisclosure
                          let actualValue :: Value
actualValue = Disclosure -> Value
getDisclosureValue Disclosure
decoded
                          -- Recurse into the actual value (not the ellipsis object)
                          [EncodedDisclosure]
nestedDisclos <- HashAlgorithm
-> [Text]
-> [[Text]]
-> Value
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectDisclosuresRecursively HashAlgorithm
hashAlg [] [[Text]]
nonEmptyPaths Value
actualValue [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures
                          -- Always include parent element disclosure when recursing into ellipsis object
                          [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ([EncodedDisclosure
encDisclosure] [EncodedDisclosure] -> [EncodedDisclosure] -> [EncodedDisclosure]
forall a. [a] -> [a] -> [a]
++ [EncodedDisclosure]
nestedDisclos)
                        Maybe EncodedDisclosure
Nothing -> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []  -- No disclosure found
                    Maybe Value
_ -> do
                      -- Not an ellipsis object, recurse normally
                      [EncodedDisclosure]
nestedDisclos <- HashAlgorithm
-> [Text]
-> [[Text]]
-> Value
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectDisclosuresRecursively HashAlgorithm
hashAlg [] [[Text]]
nonEmptyPaths Value
element [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures
                      [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return [EncodedDisclosure]
nestedDisclos
                Value
_ -> do
                  -- Not an ellipsis object, recurse normally
                  [EncodedDisclosure]
nestedDisclos <- HashAlgorithm
-> [Text]
-> [[Text]]
-> Value
-> [EncodedDisclosure]
-> [Disclosure]
-> Either SDJWTError [EncodedDisclosure]
collectDisclosuresRecursively HashAlgorithm
hashAlg [] [[Text]]
nonEmptyPaths Value
element [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures
                  [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return [EncodedDisclosure]
nestedDisclos
          [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ([EncodedDisclosure]
thisLevelDisclos [EncodedDisclosure] -> [EncodedDisclosure] -> [EncodedDisclosure]
forall a. [a] -> [a] -> [a]
++ [EncodedDisclosure]
deeperDisclos)
    ) (Map Int [[Text]] -> [(Int, [[Text]])]
forall k a. Map k a -> [(k, a)]
Map.toList Map Int [[Text]]
groupedByFirstIndex)
  
  [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ([EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure])
-> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a b. (a -> b) -> a -> b
$ [[EncodedDisclosure]] -> [EncodedDisclosure]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[EncodedDisclosure]]
results

-- | Collect disclosures for an array element.
collectDisclosuresForArrayElement
  :: HashAlgorithm
  -> Aeson.Value  -- ^ Array element value
  -> [EncodedDisclosure]  -- ^ All available disclosures
  -> Either SDJWTError [EncodedDisclosure]
collectDisclosuresForArrayElement :: HashAlgorithm
-> Value
-> [EncodedDisclosure]
-> Either SDJWTError [EncodedDisclosure]
collectDisclosuresForArrayElement HashAlgorithm
hashAlg Value
value [EncodedDisclosure]
allDisclosures = do
  case Value
value of
    Aeson.Object Object
ellipsisObj -> do
      -- Extract digest from ellipsis object
      case Key -> Object -> Maybe Value
forall v. Key -> KeyMap v -> Maybe v
KeyMap.lookup (Text -> Key
Key.fromText Text
"...") Object
ellipsisObj of
        Just (Aeson.String Text
digestText) -> do
          -- Find disclosure matching this digest
          let matchingDisclos :: [EncodedDisclosure]
matchingDisclos = (EncodedDisclosure -> Maybe EncodedDisclosure)
-> [EncodedDisclosure] -> [EncodedDisclosure]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\EncodedDisclosure
encDisclosure ->
                let digestText2 :: Text
digestText2 = HashAlgorithm -> EncodedDisclosure -> Text
computeDigestText HashAlgorithm
hashAlg EncodedDisclosure
encDisclosure
                in if Text
digestText2 Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
digestText
                  then EncodedDisclosure -> Maybe EncodedDisclosure
forall a. a -> Maybe a
Just EncodedDisclosure
encDisclosure
                  else Maybe EncodedDisclosure
forall a. Maybe a
Nothing
                ) [EncodedDisclosure]
allDisclosures
          [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return [EncodedDisclosure]
matchingDisclos
        Maybe Value
_ -> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []  -- No ellipsis object, no disclosures
    Value
_ -> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []  -- Not an ellipsis object, no disclosures

-- | Check if a value is a recursive disclosure (contains _sd array).
isRecursiveValue :: Aeson.Value -> Bool
isRecursiveValue :: Value -> Bool
isRecursiveValue Value
value = case Value
value of
  Aeson.Object Object
obj ->
    case Key -> Object -> Maybe Value
forall v. Key -> KeyMap v -> Maybe v
KeyMap.lookup (Text -> Key
Key.fromText Text
"_sd") Object
obj of
      Just (Aeson.Array Array
_) -> Bool
True
      Maybe Value
_ -> Bool
False
  Value
_ -> Bool
False

-- | Select disclosures from an SD-JWT (more flexible version).
--
-- This function allows selecting disclosures directly by providing the disclosure
-- objects themselves. Useful when you already know which disclosures to include.
selectDisclosures
  :: SDJWT
  -> [EncodedDisclosure]  -- ^ Disclosures to include
  -> Either SDJWTError SDJWTPresentation
selectDisclosures :: SDJWT -> [EncodedDisclosure] -> Either SDJWTError SDJWTPresentation
selectDisclosures sdjwt :: SDJWT
sdjwt@(SDJWT Text
_ [EncodedDisclosure]
allDisclosures) [EncodedDisclosure]
selectedDisclos = do
  -- Validate that all selected disclosures are in the original SD-JWT
  let allDisclosuresSet :: Set Text
allDisclosuresSet = [Text] -> Set Text
forall a. Ord a => [a] -> Set a
Set.fromList ((EncodedDisclosure -> Text) -> [EncodedDisclosure] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map EncodedDisclosure -> Text
unEncodedDisclosure [EncodedDisclosure]
allDisclosures)
  let selectedSet :: Set Text
selectedSet = [Text] -> Set Text
forall a. Ord a => [a] -> Set a
Set.fromList ((EncodedDisclosure -> Text) -> [EncodedDisclosure] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map EncodedDisclosure -> Text
unEncodedDisclosure [EncodedDisclosure]
selectedDisclos)
  
  -- Check if all selected disclosures are in the original set
  if Set Text
selectedSet Set Text -> Set Text -> Bool
forall a. Ord a => Set a -> Set a -> Bool
`Set.isSubsetOf` Set Text
allDisclosuresSet
    then SDJWTPresentation -> Either SDJWTError SDJWTPresentation
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return (SDJWTPresentation -> Either SDJWTError SDJWTPresentation)
-> SDJWTPresentation -> Either SDJWTError SDJWTPresentation
forall a b. (a -> b) -> a -> b
$ SDJWT -> [EncodedDisclosure] -> SDJWTPresentation
createPresentation SDJWT
sdjwt [EncodedDisclosure]
selectedDisclos
    else SDJWTError -> Either SDJWTError SDJWTPresentation
forall a b. a -> Either a b
Left (SDJWTError -> Either SDJWTError SDJWTPresentation)
-> SDJWTError -> Either SDJWTError SDJWTPresentation
forall a b. (a -> b) -> a -> b
$ Text -> SDJWTError
InvalidDisclosureFormat Text
"Selected disclosures must be a subset of original disclosures"

-- | Add key binding to a presentation.
--
-- Creates a Key Binding JWT and adds it to the presentation, converting it
-- to SD-JWT+KB format. The KB-JWT proves that the holder possesses a specific key.
--
-- Returns the presentation with key binding added, or an error if KB-JWT creation fails.
addKeyBinding
  :: JWKLike jwk => HashAlgorithm  -- ^ Hash algorithm to use for sd_hash computation
  -> jwk  -- ^ Holder private key (Text or jose JWK object)
  -> T.Text  -- ^ Audience claim (verifier identifier)
  -> T.Text  -- ^ Nonce provided by verifier
  -> Int64   -- ^ Issued at timestamp (Unix epoch seconds)
  -> SDJWTPresentation  -- ^ The SD-JWT presentation to add key binding to
  -> Aeson.Object  -- ^ Optional additional claims (e.g., exp, nbf). Default: empty object
  -> IO (Either SDJWTError SDJWTPresentation)
addKeyBinding :: forall jwk.
JWKLike jwk =>
HashAlgorithm
-> jwk
-> Text
-> Text
-> Int64
-> SDJWTPresentation
-> Object
-> IO (Either SDJWTError SDJWTPresentation)
addKeyBinding HashAlgorithm
hashAlg jwk
holderKey Text
audience Text
nonce Int64
issuedAt SDJWTPresentation
presentation Object
optionalClaims = 
  HashAlgorithm
-> jwk
-> Text
-> Text
-> Int64
-> SDJWTPresentation
-> Object
-> IO (Either SDJWTError SDJWTPresentation)
forall jwk.
JWKLike jwk =>
HashAlgorithm
-> jwk
-> Text
-> Text
-> Int64
-> SDJWTPresentation
-> Object
-> IO (Either SDJWTError SDJWTPresentation)
addKeyBindingToPresentation HashAlgorithm
hashAlg jwk
holderKey Text
audience Text
nonce Int64
issuedAt SDJWTPresentation
presentation Object
optionalClaims

-- | Partition claim names into top-level and nested paths (using JSON Pointer syntax).
--
-- Supports JSON Pointer escaping (RFC 6901):
--
-- - "~1" represents a literal forward slash "/"
-- - "~0" represents a literal tilde "~"
--
-- Note: The path "x/22" is ambiguous - it could refer to:
--   - Array element at index 22 if "x" is an array
--   - Object property "22" if "x" is an object
-- The actual type is determined when processing (see 'selectDisclosuresByNames').
--
-- Returns: (top-level claims, nested paths as list of segments)
partitionNestedPaths :: [T.Text] -> ([T.Text], [[T.Text]])
partitionNestedPaths :: [Text] -> ([Text], [[Text]])
partitionNestedPaths [Text]
claimNames =
  let ([Text]
topLevel, [Text]
nested) = (Text -> Bool) -> [Text] -> ([Text], [Text])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition (Bool -> Bool
not (Bool -> Bool) -> (Text -> Bool) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text -> Bool
T.isInfixOf Text
"/") [Text]
claimNames
      nestedPaths :: [[Text]]
nestedPaths = (Text -> Maybe [Text]) -> [Text] -> [[Text]]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Text -> Maybe [Text]
parseJSONPointerPath [Text]
nested
      -- Unescape top-level claim names (they may contain ~0 or ~1)
      unescapedTopLevel :: [Text]
unescapedTopLevel = (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
unescapeJSONPointer [Text]
topLevel
  in ([Text]
unescapedTopLevel, [[Text]]
nestedPaths)
  where
    -- Parse a JSON Pointer path, handling escaping
    -- Returns Nothing if invalid, Just [segments] if valid nested path
    -- Supports arbitrary depth: ["a"], ["a", "b"], ["a", "b", "c"], etc.
    parseJSONPointerPath :: T.Text -> Maybe [T.Text]
    parseJSONPointerPath :: Text -> Maybe [Text]
parseJSONPointerPath Text
path = do
      -- Split by "/" but handle escaped slashes
      let segments :: [Text]
segments = Text -> [Text]
splitJSONPointer Text
path
      case [Text]
segments of
        [] -> Maybe [Text]
forall a. Maybe a
Nothing  -- Empty path is invalid
        [Text
_] -> Maybe [Text]
forall a. Maybe a
Nothing  -- Single segment is top-level, not nested
        [Text]
_ -> [Text] -> Maybe [Text]
forall a. a -> Maybe a
Just ((Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
unescapeJSONPointer [Text]
segments)  -- Two or more segments = nested path

-- | Extract hash algorithm from JWT payload.
--
-- Helper function to extract _sd_alg from JWT payload, defaulting to SHA-256.
extractHashAlgorithmFromJWT :: T.Text -> Either SDJWTError HashAlgorithm
extractHashAlgorithmFromJWT :: Text -> Either SDJWTError HashAlgorithm
extractHashAlgorithmFromJWT Text
jwt =
  (SDJWTPayload -> HashAlgorithm)
-> Either SDJWTError SDJWTPayload
-> Either SDJWTError HashAlgorithm
forall a b. (a -> b) -> Either SDJWTError a -> Either SDJWTError b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (HashAlgorithm -> Maybe HashAlgorithm -> HashAlgorithm
forall a. a -> Maybe a -> a
fromMaybe HashAlgorithm
defaultHashAlgorithm (Maybe HashAlgorithm -> HashAlgorithm)
-> (SDJWTPayload -> Maybe HashAlgorithm)
-> SDJWTPayload
-> HashAlgorithm
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SDJWTPayload -> Maybe HashAlgorithm
sdAlg) (Text -> Either SDJWTError SDJWTPayload
parsePayloadFromJWT Text
jwt)

-- | Validate disclosure dependencies per RFC 9901 Section 7.2, step 2.
--
-- Verifies that each selected Disclosure satisfies one of:
-- a. The hash of the Disclosure is contained in the Issuer-signed JWT claims
-- b. The hash of the Disclosure is contained in the claim value of another selected Disclosure
--
-- This implements the Holder's validation requirement before presenting to Verifier.
validateDisclosureDependencies
  :: HashAlgorithm
  -> [EncodedDisclosure]
  -> T.Text  -- ^ Issuer-signed JWT
  -> Either SDJWTError ()
validateDisclosureDependencies :: HashAlgorithm
-> [EncodedDisclosure] -> Text -> Either SDJWTError ()
validateDisclosureDependencies HashAlgorithm
hashAlg [EncodedDisclosure]
selectedDisclos Text
issuerJWT = do
  -- Extract digests from issuer-signed JWT payload (condition a)
  Set Text
issuerDigests <- Text -> Either SDJWTError (Set Text)
extractDigestsFromJWTPayload Text
issuerJWT
  
  -- Compute digests for all selected disclosures (as Text for comparison)
  let selectedDigests :: Set Text
selectedDigests = [Text] -> Set Text
forall a. Ord a => [a] -> Set a
Set.fromList ([Text] -> Set Text) -> [Text] -> Set Text
forall a b. (a -> b) -> a -> b
$ (EncodedDisclosure -> Text) -> [EncodedDisclosure] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (HashAlgorithm -> EncodedDisclosure -> Text
computeDigestText HashAlgorithm
hashAlg) [EncodedDisclosure]
selectedDisclos
  
  -- Build set of all valid digests (from JWT payload + recursive disclosures)
  -- This is used to verify condition (a): disclosure digest is in issuer-signed JWT
  let allValidDigests :: Set Text
allValidDigests = Set Text -> Set Text -> Set Text
forall a. Ord a => Set a -> Set a -> Set a
Set.union Set Text
issuerDigests Set Text
selectedDigests
  
  -- RFC 9901 Section 7.2, step 2: Verify each selected Disclosure satisfies one of:
  -- a. The hash is contained in the Issuer-signed JWT claims
  -- b. The hash is contained in the claim value of another selected Disclosure
  
  -- First, verify condition (a): each selected disclosure's digest must be in issuer JWT or another disclosure
  (EncodedDisclosure -> Either SDJWTError ())
-> [EncodedDisclosure] -> Either SDJWTError ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\EncodedDisclosure
encDisclosure -> do
    let disclosureDigestText :: Text
disclosureDigestText = HashAlgorithm -> EncodedDisclosure -> Text
computeDigestText HashAlgorithm
hashAlg EncodedDisclosure
encDisclosure
    if Text
disclosureDigestText Text -> Set Text -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.member` Set Text
allValidDigests
      then () -> Either SDJWTError ()
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- Condition (a) or (b) satisfied ✓
      else SDJWTError -> Either SDJWTError ()
forall a b. a -> Either a b
Left (SDJWTError -> Either SDJWTError ())
-> SDJWTError -> Either SDJWTError ()
forall a b. (a -> b) -> a -> b
$ Text -> SDJWTError
MissingDisclosure (Text -> SDJWTError) -> Text -> SDJWTError
forall a b. (a -> b) -> a -> b
$ Text
"Disclosure digest not found in issuer-signed JWT or other selected disclosures: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
disclosureDigestText
    ) [EncodedDisclosure]
selectedDisclos
  
  -- Second, verify condition (b) for recursive disclosures: 
  -- If a recursive disclosure is selected, child digests that are selected must be valid.
  -- Note: Child digests that are NOT selected are simply not disclosed, which is valid.
  [Disclosure]
decodedSelected <- (EncodedDisclosure -> Either SDJWTError Disclosure)
-> [EncodedDisclosure] -> Either SDJWTError [Disclosure]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM EncodedDisclosure -> Either SDJWTError Disclosure
decodeDisclosure [EncodedDisclosure]
selectedDisclos
  
  -- Check each selected disclosure for recursive structure (condition b)
  (Disclosure -> Either SDJWTError ())
-> [Disclosure] -> Either SDJWTError ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\Disclosure
disclosure -> do
    case Disclosure -> Value
getDisclosureValue Disclosure
disclosure of
      Aeson.Object Object
obj -> do
        -- This is a recursive disclosure - extract child digests
        let childDigests :: [Text]
childDigests = Object -> [Text]
extractDigestStringsFromSDArray Object
obj
        if [Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
childDigests
          then () -> Either SDJWTError ()
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- Not a recursive disclosure
          else do
            -- RFC 9901 Section 7.2, step 2b: For each child digest that IS selected,
            -- verify it's valid (in issuer JWT or another selected disclosure).
            -- Child digests that are NOT selected are simply not disclosed, which is fine.
            (Text -> Either SDJWTError ()) -> [Text] -> Either SDJWTError ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\Text
childDigestText -> do
              if Text
childDigestText Text -> Set Text -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.member` Set Text
selectedDigests
                then () -> Either SDJWTError ()
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- Child digest is selected and matches a selected disclosure ✓
                else if Text
childDigestText Text -> Set Text -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.member` Set Text
issuerDigests
                  then () -> Either SDJWTError ()
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- Child digest is in issuer JWT (valid but not selected) ✓
                  else () -> Either SDJWTError ()
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- Child digest is not selected (holder chose not to disclose it) ✓
              ) [Text]
childDigests
      Value
_ -> () -> Either SDJWTError ()
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- Not an object disclosure
    ) [Disclosure]
decodedSelected

-- | Extract digests from JWT payload (_sd arrays and array ellipsis objects).
--
-- Helper function to extract all digests from the issuer-signed JWT payload.
extractDigestsFromJWTPayload :: T.Text -> Either SDJWTError (Set.Set T.Text)
extractDigestsFromJWTPayload :: Text -> Either SDJWTError (Set Text)
extractDigestsFromJWTPayload Text
jwt =
  Text -> Either SDJWTError SDJWTPayload
parsePayloadFromJWT Text
jwt Either SDJWTError SDJWTPayload
-> (SDJWTPayload -> Either SDJWTError (Set Text))
-> Either SDJWTError (Set Text)
forall a b.
Either SDJWTError a
-> (a -> Either SDJWTError b) -> Either SDJWTError b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \SDJWTPayload
sdPayload ->
    ([Digest] -> Set Text)
-> Either SDJWTError [Digest] -> Either SDJWTError (Set Text)
forall a b. (a -> b) -> Either SDJWTError a -> Either SDJWTError b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ([Text] -> Set Text
forall a. Ord a => [a] -> Set a
Set.fromList ([Text] -> Set Text)
-> ([Digest] -> [Text]) -> [Digest] -> Set Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Digest -> Text) -> [Digest] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Digest -> Text
unDigest) (SDJWTPayload -> Either SDJWTError [Digest]
extractDigestsFromPayload SDJWTPayload
sdPayload)

-- | Collect array element disclosures for selected array claims.
--
-- When an array claim is selected, we need to include array element disclosures
-- that are referenced by digests in that array. For nested arrays, we recursively
-- process array disclosure values to find nested array element disclosures.
collectArrayElementDisclosures
  :: HashAlgorithm
  -> [T.Text]  -- ^ Selected top-level claim names (may include array claims)
  -> T.Text  -- ^ Issuer-signed JWT
  -> [EncodedDisclosure]  -- ^ All available disclosures
  -> [Disclosure]  -- ^ Decoded disclosures (for claim name lookup)
  -> Bool  -- ^ Whether to recursively collect nested array element disclosures
  -> Either SDJWTError [EncodedDisclosure]
collectArrayElementDisclosures :: HashAlgorithm
-> [Text]
-> Text
-> [EncodedDisclosure]
-> [Disclosure]
-> Bool
-> Either SDJWTError [EncodedDisclosure]
collectArrayElementDisclosures HashAlgorithm
hashAlg [Text]
claimNames Text
issuerJWT [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures Bool
shouldRecurse = do
  -- Early return if no claim names (no disclosures should be selected)
  if [Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
claimNames
    then [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
    else do
      -- Parse JWT payload
      SDJWTPayload
sdPayload <- Text -> Either SDJWTError SDJWTPayload
parsePayloadFromJWT Text
issuerJWT
      let payloadValueObj :: Value
payloadValueObj = SDJWTPayload -> Value
payloadValue SDJWTPayload
sdPayload
      
      -- Extract digests from selected array claims
      -- Check both payload (for Section 6.2 structured disclosure) and disclosure values (for top-level selective disclosure)
      case Value
payloadValueObj of
        Aeson.Object Object
obj -> do
          -- For each selected claim name, check if it's an array in payload or in disclosure value
          [(Text, [Digest])]
arrayDigests <- (Text -> Either SDJWTError (Text, [Digest]))
-> [Text] -> Either SDJWTError [(Text, [Digest])]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (\Text
claimName -> do
            -- First check payload
            [Digest]
payloadDigests <- case Key -> Object -> Maybe Value
forall v. Key -> KeyMap v -> Maybe v
KeyMap.lookup (Text -> Key
Key.fromText Text
claimName) Object
obj of
              Just (Aeson.Array Array
arr) -> do
                -- Extract digests from ellipsis objects in this array
                [Digest]
digests <- Value -> Either SDJWTError [Digest]
extractDigestsFromValue (Array -> Value
Aeson.Array Array
arr)
                [Digest] -> Either SDJWTError [Digest]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return [Digest]
digests
              Maybe Value
_ -> [Digest] -> Either SDJWTError [Digest]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
            -- Also check if this claim is selectively disclosable (in root _sd array)
            let rootDigests :: [Text]
rootDigests = Object -> [Text]
extractDigestStringsFromSDArray Object
obj
            [Digest]
disclosureDigests <- if [Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
rootDigests
              then [Digest] -> Either SDJWTError [Digest]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
              else do
                -- Find disclosure for this claim name
                case ((EncodedDisclosure, Disclosure) -> Bool)
-> [(EncodedDisclosure, Disclosure)]
-> Maybe (EncodedDisclosure, Disclosure)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\(EncodedDisclosure
encDisclosure, Disclosure
decoded) ->
                      let digestText :: Text
digestText = HashAlgorithm -> EncodedDisclosure -> Text
computeDigestText HashAlgorithm
hashAlg EncodedDisclosure
encDisclosure
                          claimNameFromDisclosure :: Maybe Text
claimNameFromDisclosure = Disclosure -> Maybe Text
getDisclosureClaimName Disclosure
decoded
                      in Text
digestText Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
rootDigests Bool -> Bool -> Bool
&& Maybe Text
claimNameFromDisclosure Maybe Text -> Maybe Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text -> Maybe Text
forall a. a -> Maybe a
Just Text
claimName
                      ) ([(EncodedDisclosure, Disclosure)]
 -> Maybe (EncodedDisclosure, Disclosure))
-> [(EncodedDisclosure, Disclosure)]
-> Maybe (EncodedDisclosure, Disclosure)
forall a b. (a -> b) -> a -> b
$ [EncodedDisclosure]
-> [Disclosure] -> [(EncodedDisclosure, Disclosure)]
forall a b. [a] -> [b] -> [(a, b)]
zip [EncodedDisclosure]
allDisclosures [Disclosure]
decodedDisclosures of
                  Just (EncodedDisclosure
_, Disclosure
decoded) -> do
                    -- Get the disclosure value and check if it's an array with ellipsis objects
                    let value :: Value
value = Disclosure -> Value
getDisclosureValue Disclosure
decoded
                    case Value
value of
                      Aeson.Array Array
arr -> Value -> Either SDJWTError [Digest]
extractDigestsFromValue (Array -> Value
Aeson.Array Array
arr)
                      Value
_ -> [Digest] -> Either SDJWTError [Digest]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
                  Maybe (EncodedDisclosure, Disclosure)
Nothing -> [Digest] -> Either SDJWTError [Digest]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
            (Text, [Digest]) -> Either SDJWTError (Text, [Digest])
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return (Text
claimName, [Digest]
payloadDigests [Digest] -> [Digest] -> [Digest]
forall a. [a] -> [a] -> [a]
++ [Digest]
disclosureDigests)
            ) [Text]
claimNames
          
          -- Find array element disclosures matching digests from selected arrays
          -- When shouldRecurse is False (holder_disclosed_claims is empty), don't include element disclosures
          -- They will be processed as empty arrays instead
          let selectedArrayElementDisclos :: [EncodedDisclosure]
selectedArrayElementDisclos = if Bool
shouldRecurse
                then (EncodedDisclosure -> Maybe EncodedDisclosure)
-> [EncodedDisclosure] -> [EncodedDisclosure]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\EncodedDisclosure
encDisclosure ->
                      let digestText :: Text
digestText = HashAlgorithm -> EncodedDisclosure -> Text
computeDigestText HashAlgorithm
hashAlg EncodedDisclosure
encDisclosure
                      in if ((Text, [Digest]) -> Bool) -> [(Text, [Digest])] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\(Text
_, [Digest]
digests) -> (Digest -> Bool) -> [Digest] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ((Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
digestText) (Text -> Bool) -> (Digest -> Text) -> Digest -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Digest -> Text
unDigest) [Digest]
digests) [(Text, [Digest])]
arrayDigests
                        then EncodedDisclosure -> Maybe EncodedDisclosure
forall a. a -> Maybe a
Just EncodedDisclosure
encDisclosure
                        else Maybe EncodedDisclosure
forall a. Maybe a
Nothing
                      ) [EncodedDisclosure]
allDisclosures
                else []  -- Don't include element disclosures when shouldRecurse is False
          
          -- Recursively collect nested array element disclosures
          -- For each selected array element disclosure, check if its value is an array
          -- and extract digests from it. This needs to be recursive to handle multiple levels.
          let collectNestedRecursive :: [EncodedDisclosure] -> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
              collectNestedRecursive :: [EncodedDisclosure]
-> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
collectNestedRecursive [EncodedDisclosure]
currentDisclos [EncodedDisclosure]
alreadyCollected = do
                -- For each current disclosure, check if its value is an array
                [[EncodedDisclosure]]
nestedDisclos <- (EncodedDisclosure -> Either SDJWTError [EncodedDisclosure])
-> [EncodedDisclosure] -> Either SDJWTError [[EncodedDisclosure]]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (\EncodedDisclosure
encDisclosure -> do
                    Disclosure
decoded <- EncodedDisclosure -> Either SDJWTError Disclosure
decodeDisclosure EncodedDisclosure
encDisclosure
                    let value :: Value
value = Disclosure -> Value
getDisclosureValue Disclosure
decoded
                    case Value
value of
                      Aeson.Array Array
nestedArr -> do
                        -- Extract digests from nested array
                        [Digest]
nestedDigests <- Value -> Either SDJWTError [Digest]
extractDigestsFromValue (Array -> Value
Aeson.Array Array
nestedArr)
                        -- Find disclosures matching these digests that we haven't already collected
                        let matchingDisclos :: [EncodedDisclosure]
matchingDisclos = (EncodedDisclosure -> Maybe EncodedDisclosure)
-> [EncodedDisclosure] -> [EncodedDisclosure]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\EncodedDisclosure
encDisclosure2 ->
                              let digestText2 :: Text
digestText2 = HashAlgorithm -> EncodedDisclosure -> Text
computeDigestText HashAlgorithm
hashAlg EncodedDisclosure
encDisclosure2
                              in if (Digest -> Bool) -> [Digest] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ((Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
digestText2) (Text -> Bool) -> (Digest -> Text) -> Digest -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Digest -> Text
unDigest) [Digest]
nestedDigests Bool -> Bool -> Bool
&&
                                    Bool -> Bool
not (EncodedDisclosure
encDisclosure2 EncodedDisclosure -> [EncodedDisclosure] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [EncodedDisclosure]
alreadyCollected) Bool -> Bool -> Bool
&&
                                    Bool -> Bool
not (EncodedDisclosure
encDisclosure2 EncodedDisclosure -> [EncodedDisclosure] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [EncodedDisclosure]
currentDisclos)
                                then EncodedDisclosure -> Maybe EncodedDisclosure
forall a. a -> Maybe a
Just EncodedDisclosure
encDisclosure2
                                else Maybe EncodedDisclosure
forall a. Maybe a
Nothing
                              ) [EncodedDisclosure]
allDisclosures
                        [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return [EncodedDisclosure]
matchingDisclos
                      Value
_ -> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
                    ) [EncodedDisclosure]
currentDisclos
                
                let newDisclos :: [EncodedDisclosure]
newDisclos = [[EncodedDisclosure]] -> [EncodedDisclosure]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[EncodedDisclosure]]
nestedDisclos
                if [EncodedDisclosure] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [EncodedDisclosure]
newDisclos
                  then [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []  -- No more nested disclosures to collect
                  else do
                    -- Recursively collect from the newly found disclosures
                    [EncodedDisclosure]
deeperDisclos <- [EncodedDisclosure]
-> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
collectNestedRecursive [EncodedDisclosure]
newDisclos ([EncodedDisclosure]
alreadyCollected [EncodedDisclosure] -> [EncodedDisclosure] -> [EncodedDisclosure]
forall a. [a] -> [a] -> [a]
++ [EncodedDisclosure]
currentDisclos [EncodedDisclosure] -> [EncodedDisclosure] -> [EncodedDisclosure]
forall a. [a] -> [a] -> [a]
++ [EncodedDisclosure]
newDisclos)
                    [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ([EncodedDisclosure]
newDisclos [EncodedDisclosure] -> [EncodedDisclosure] -> [EncodedDisclosure]
forall a. [a] -> [a] -> [a]
++ [EncodedDisclosure]
deeperDisclos)
          
          [EncodedDisclosure]
nestedDisclos <- if Bool
shouldRecurse
            then [EncodedDisclosure]
-> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
collectNestedRecursive [EncodedDisclosure]
selectedArrayElementDisclos [EncodedDisclosure]
selectedArrayElementDisclos
            else [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []
          
          -- Combine all array element disclosures (including nested ones if shouldRecurse is True)
          [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ([EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure])
-> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a b. (a -> b) -> a -> b
$ [EncodedDisclosure]
selectedArrayElementDisclos [EncodedDisclosure] -> [EncodedDisclosure] -> [EncodedDisclosure]
forall a. [a] -> [a] -> [a]
++ [EncodedDisclosure]
nestedDisclos
        Value
_ -> [EncodedDisclosure] -> Either SDJWTError [EncodedDisclosure]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return []  -- Payload is not an object, no arrays to process