-- | Multi-pass decoding of denormalized query results.
--
-- When a Cypher query uses @OPTIONAL MATCH@, the result set is denormalized:
-- parent fields are repeated for each matched child row. 'ResultSet' bundles
-- field names with records so you can decode the same rows with different
-- decoders in multiple passes.
--
-- __Preferred alternative:__ Use Cypher @COLLECT()@ or pattern comprehensions
-- to aggregate children server-side, avoiding denormalization entirely:
--
-- @
-- MATCH (p:Parent)
-- OPTIONAL MATCH (p)-[:HAS]->(c:Child)
-- RETURN p, COLLECT(c) AS children
-- @
--
-- When that isn't practical (e.g. multiple independent optional paths),
-- use 'groupByField' as an escape hatch:
--
-- @
-- rs <- queryResult conn "MATCH (p:Parent) OPTIONAL MATCH (p)-[:HAS]->(c:Child) RETURN p.id AS pid, c"
-- groups <- either throwIO pure $ groupByField (field \"pid\" (nullable int64)) rs
-- -- groups :: Vector (Int64, ResultSet)
-- -- Each sub-ResultSet contains the rows for that parent.
-- @
module Database.Bolty.ResultSet
  ( ResultSet(..)
  , decodeResultSet
  , decodeHead
  , groupByField
  ) where

import           Data.Kind   (Type)
import qualified Data.Text   as T
import qualified Data.Vector as V

import           Database.Bolty.Decode           (DecodeError(..), RowDecoder, decodeRow, decodeRows)
import           Database.Bolty.Record           (Record)


-- | A query result: field (column) names paired with the result records.
-- Supports multi-pass decoding — decode the same rows with different
-- 'RowDecoder's without re-running the query.
type ResultSet :: Type
data ResultSet = ResultSet
  { ResultSet -> Vector Text
fields  :: !(V.Vector T.Text)
  , ResultSet -> Vector Record
records :: !(V.Vector Record)
  } deriving stock (Int -> ResultSet -> ShowS
[ResultSet] -> ShowS
ResultSet -> String
(Int -> ResultSet -> ShowS)
-> (ResultSet -> String)
-> ([ResultSet] -> ShowS)
-> Show ResultSet
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ResultSet -> ShowS
showsPrec :: Int -> ResultSet -> ShowS
$cshow :: ResultSet -> String
show :: ResultSet -> String
$cshowList :: [ResultSet] -> ShowS
showList :: [ResultSet] -> ShowS
Show, ResultSet -> ResultSet -> Bool
(ResultSet -> ResultSet -> Bool)
-> (ResultSet -> ResultSet -> Bool) -> Eq ResultSet
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ResultSet -> ResultSet -> Bool
== :: ResultSet -> ResultSet -> Bool
$c/= :: ResultSet -> ResultSet -> Bool
/= :: ResultSet -> ResultSet -> Bool
Eq)


-- | Decode every record in a 'ResultSet' using a 'RowDecoder'.
-- Fails on the first 'DecodeError'.
decodeResultSet :: RowDecoder a -> ResultSet -> Either DecodeError (V.Vector a)
decodeResultSet :: forall a.
RowDecoder a -> ResultSet -> Either DecodeError (Vector a)
decodeResultSet RowDecoder a
decoder (ResultSet Vector Text
fs Vector Record
rs) = RowDecoder a
-> Vector Text -> Vector Record -> Either DecodeError (Vector a)
forall a.
RowDecoder a
-> Vector Text -> Vector Record -> Either DecodeError (Vector a)
decodeRows RowDecoder a
decoder Vector Text
fs Vector Record
rs


-- | Decode the first record of a 'ResultSet', or fail with 'EmptyResultSet'.
decodeHead :: RowDecoder a -> ResultSet -> Either DecodeError a
decodeHead :: forall a. RowDecoder a -> ResultSet -> Either DecodeError a
decodeHead RowDecoder a
decoder (ResultSet Vector Text
fs Vector Record
rs) = case Vector Record -> Maybe (Record, Vector Record)
forall a. Vector a -> Maybe (a, Vector a)
V.uncons Vector Record
rs of
  Maybe (Record, Vector Record)
Nothing       -> DecodeError -> Either DecodeError a
forall a b. a -> Either a b
Left DecodeError
EmptyResultSet
  Just (Record
rec, Vector Record
_) -> RowDecoder a -> Vector Text -> Record -> Either DecodeError a
forall a.
RowDecoder a -> Vector Text -> Record -> Either DecodeError a
decodeRow RowDecoder a
decoder Vector Text
fs Record
rec


-- | Group consecutive records by a decoded key field.
--
-- Records whose key decodes to 'Nothing' (e.g. NULL from an @OPTIONAL MATCH@
-- with no match) are skipped. Grouping is consecutive, not global: if the same
-- key appears in non-adjacent runs, they become separate groups.
--
-- Each sub-'ResultSet' shares the parent's field names.
--
-- __Prefer Cypher @COLLECT()@__ when possible — this function is an escape
-- hatch for queries where server-side aggregation is impractical.
groupByField
  :: Eq k
  => RowDecoder (Maybe k)
  -> ResultSet
  -> Either DecodeError (V.Vector (k, ResultSet))
groupByField :: forall k.
Eq k =>
RowDecoder (Maybe k)
-> ResultSet -> Either DecodeError (Vector (k, ResultSet))
groupByField RowDecoder (Maybe k)
keyDecoder (ResultSet Vector Text
fs Vector Record
rs) =
    [(k, [Record])] -> Either DecodeError (Vector (k, ResultSet))
buildGroups ([(k, [Record])] -> Either DecodeError (Vector (k, ResultSet)))
-> Either DecodeError [(k, [Record])]
-> Either DecodeError (Vector (k, ResultSet))
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< ([(k, [Record])] -> Record -> Either DecodeError [(k, [Record])])
-> [(k, [Record])]
-> Vector Record
-> Either DecodeError [(k, [Record])]
forall (m :: * -> *) a b.
Monad m =>
(a -> b -> m a) -> a -> Vector b -> m a
V.foldM' [(k, [Record])] -> Record -> Either DecodeError [(k, [Record])]
accumulate [] Vector Record
rs
  where
    accumulate :: [(k, [Record])] -> Record -> Either DecodeError [(k, [Record])]
accumulate [(k, [Record])]
acc Record
rec = do
      mk <- RowDecoder (Maybe k)
-> Vector Text -> Record -> Either DecodeError (Maybe k)
forall a.
RowDecoder a -> Vector Text -> Record -> Either DecodeError a
decodeRow RowDecoder (Maybe k)
keyDecoder Vector Text
fs Record
rec
      pure $ case mk of
        Maybe k
Nothing -> [(k, [Record])]
acc  -- skip NULL keys
        Just k
k  -> case [(k, [Record])]
acc of
          (k
curK, [Record]
curRecs) : [(k, [Record])]
rest
            | k
curK k -> k -> Bool
forall a. Eq a => a -> a -> Bool
== k
k -> (k
curK, Record
rec Record -> [Record] -> [Record]
forall a. a -> [a] -> [a]
: [Record]
curRecs) (k, [Record]) -> [(k, [Record])] -> [(k, [Record])]
forall a. a -> [a] -> [a]
: [(k, [Record])]
rest
          [(k, [Record])]
_             -> (k
k, [Record
rec]) (k, [Record]) -> [(k, [Record])] -> [(k, [Record])]
forall a. a -> [a] -> [a]
: [(k, [Record])]
acc

    buildGroups :: [(k, [Record])] -> Either DecodeError (Vector (k, ResultSet))
buildGroups [(k, [Record])]
groups =
      Vector (k, ResultSet) -> Either DecodeError (Vector (k, ResultSet))
forall a b. b -> Either a b
Right (Vector (k, ResultSet)
 -> Either DecodeError (Vector (k, ResultSet)))
-> Vector (k, ResultSet)
-> Either DecodeError (Vector (k, ResultSet))
forall a b. (a -> b) -> a -> b
$ [(k, ResultSet)] -> Vector (k, ResultSet)
forall a. [a] -> Vector a
V.fromList
        [ (k
k, Vector Text -> Vector Record -> ResultSet
ResultSet Vector Text
fs ([Record] -> Vector Record
forall a. [a] -> Vector a
V.fromList ([Record] -> [Record]
forall a. [a] -> [a]
reverse [Record]
recs)))
        | (k
k, [Record]
recs) <- [(k, [Record])] -> [(k, [Record])]
forall a. [a] -> [a]
reverse [(k, [Record])]
groups
        ]