{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}

{- |
Module      : Hindsight.Store.Parsing
Description : Event parsing utilities for event stores
Copyright   : (c) 2024
License     : BSD3
Maintainer  : maintainer@example.com
Stability   : experimental

This module provides core event parsing utilities used by all event store backends
to convert stored events into properly typed event envelopes. These utilities handle
version-aware parsing and envelope creation.

These utilities are shared across Memory, Filesystem, and PostgreSQL backends
for consistent event parsing and envelope construction.
-}
module Hindsight.Store.Parsing (
    -- * Event Processing Utilities
    parseEventPayload,
    parseStoredEventToEnvelope,
    createEventEnvelope,
)
where

import Data.Aeson qualified as Aeson
import Data.Aeson.Types (parseEither)
import Data.Map qualified as Map
import Data.Proxy (Proxy (..))
import Data.Time (UTCTime)
import Hindsight.Events (CurrentPayloadType, Event, parseMapFromProxy)
import Hindsight.Store (CorrelationId, Cursor, EventEnvelope (..), EventId, StreamId, StreamVersion)

{- | Parse event payload from JSON using the event's parse map with proper version handling

This function implements correct version-aware parsing:
1. Uses the stored event version to select the appropriate parser
2. The parser automatically migrates to the latest version via MigrateVersion
3. Returns the current version payload type
-}
parseEventPayload ::
    forall event.
    (Event event) =>
    -- | Proxy for the event type
    Proxy event ->
    -- | JSON payload from storage
    Aeson.Value ->
    -- | Event payload version
    Integer ->
    -- | Parsed and upgraded payload, or Nothing if parsing fails
    Maybe (CurrentPayloadType event)
parseEventPayload :: forall (event :: Symbol).
Event event =>
Proxy event -> Value -> Integer -> Maybe (CurrentPayloadType event)
parseEventPayload Proxy event
proxy Value
payloadJson Integer
eventPayloadVersion = do
    let parserMap :: Map Int (Value -> Parser (CurrentPayloadType event))
parserMap = Proxy event -> Map Int (Value -> Parser (CurrentPayloadType event))
forall (event :: Symbol).
Event event =>
Proxy event -> Map Int (Value -> Parser (CurrentPayloadType event))
parseMapFromProxy Proxy event
proxy
    -- Use the actual stored event payload version to select the correct parser
    -- The parser will automatically upgrade to the latest version
    case Int
-> Map
     Int
     (Value -> Parser (FinalVersionType (FromList (Versions event))))
-> Maybe
     (Value -> Parser (FinalVersionType (FromList (Versions event))))
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Integer -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Integer
eventPayloadVersion) Map
  Int
  (Value -> Parser (FinalVersionType (FromList (Versions event))))
Map Int (Value -> Parser (CurrentPayloadType event))
parserMap of
        Just Value -> Parser (FinalVersionType (FromList (Versions event)))
parser -> case (Value -> Parser (FinalVersionType (FromList (Versions event))))
-> Value
-> Either String (FinalVersionType (FromList (Versions event)))
forall a b. (a -> Parser b) -> a -> Either String b
parseEither Value -> Parser (FinalVersionType (FromList (Versions event)))
parser Value
payloadJson of
            Right FinalVersionType (FromList (Versions event))
payload -> FinalVersionType (FromList (Versions event))
-> Maybe (FinalVersionType (FromList (Versions event)))
forall a. a -> Maybe a
Just FinalVersionType (FromList (Versions event))
payload
            Left String
_ -> Maybe (FinalVersionType (FromList (Versions event)))
Maybe (CurrentPayloadType event)
forall a. Maybe a
Nothing
        Maybe
  (Value -> Parser (FinalVersionType (FromList (Versions event))))
Nothing -> Maybe (FinalVersionType (FromList (Versions event)))
Maybe (CurrentPayloadType event)
forall a. Maybe a
Nothing

{- | Create event envelope from parsed payload and metadata

This unified function creates consistent event envelopes across
both projection systems.
-}
createEventEnvelope ::
    forall event backend.
    -- | Unique event identifier
    EventId ->
    -- | Stream this event belongs to
    StreamId ->
    -- | Global position in event store
    Cursor backend ->
    -- | Local version within the stream
    StreamVersion ->
    -- | Optional correlation identifier
    Maybe CorrelationId ->
    -- | Event creation timestamp
    UTCTime ->
    -- | Parsed event payload at latest version
    CurrentPayloadType event ->
    -- | Complete event envelope with metadata
    EventEnvelope event backend
createEventEnvelope :: forall (event :: Symbol) backend.
EventId
-> StreamId
-> Cursor backend
-> StreamVersion
-> Maybe CorrelationId
-> UTCTime
-> CurrentPayloadType event
-> EventEnvelope event backend
createEventEnvelope EventId
eventId StreamId
streamId Cursor backend
cursor StreamVersion
streamVer Maybe CorrelationId
corrId UTCTime
timestamp CurrentPayloadType event
payload =
    EventWithMetadata
        { eventId :: EventId
eventId = EventId
eventId
        , streamId :: StreamId
streamId = StreamId
streamId
        , position :: Cursor backend
position = Cursor backend
cursor
        , streamVersion :: StreamVersion
streamVersion = StreamVersion
streamVer
        , correlationId :: Maybe CorrelationId
correlationId = Maybe CorrelationId
corrId
        , createdAt :: UTCTime
createdAt = UTCTime
timestamp
        , payload :: CurrentPayloadType event
payload = CurrentPayloadType event
payload
        }

{- | Parse stored event data into an event envelope

This function handles the database → envelope conversion with proper version handling:
1. Takes raw event data from storage (JSON + version + metadata)
2. Uses the stored version to parse at the correct version
3. Automatically upgrades to the latest version
4. Creates a properly typed event envelope
-}
parseStoredEventToEnvelope ::
    forall event backend.
    (Event event) =>
    -- | Proxy for the event type
    Proxy event ->
    -- | Unique event identifier
    EventId ->
    -- | Stream this event belongs to
    StreamId ->
    -- | Global position in event store
    Cursor backend ->
    -- | Local version within the stream
    StreamVersion ->
    -- | Optional correlation identifier
    Maybe CorrelationId ->
    -- | Event creation timestamp
    UTCTime ->
    -- | Raw JSON payload from storage
    Aeson.Value ->
    -- | Event payload version (from eventVersion field)
    Integer ->
    -- | Parsed envelope, or Nothing if parsing fails
    Maybe (EventEnvelope event backend)
parseStoredEventToEnvelope :: forall (event :: Symbol) backend.
Event event =>
Proxy event
-> EventId
-> StreamId
-> Cursor backend
-> StreamVersion
-> Maybe CorrelationId
-> UTCTime
-> Value
-> Integer
-> Maybe (EventEnvelope event backend)
parseStoredEventToEnvelope Proxy event
proxy EventId
eventId StreamId
streamId Cursor backend
cursor StreamVersion
streamVer Maybe CorrelationId
corrId UTCTime
timestamp Value
payloadJson Integer
eventPayloadVersion = do
    -- Parse payload using the stored event payload version (with automatic upgrade)
    payload <- Proxy event -> Value -> Integer -> Maybe (CurrentPayloadType event)
forall (event :: Symbol).
Event event =>
Proxy event -> Value -> Integer -> Maybe (CurrentPayloadType event)
parseEventPayload Proxy event
proxy Value
payloadJson Integer
eventPayloadVersion
    pure $ createEventEnvelope eventId streamId cursor streamVer corrId timestamp payload