{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module OpenAPI.Generate.Reference
  ( ReferenceMap,
    ComponentReference (..),
    buildReferenceMap,
    getSchemaReference,
    getResponseReference,
    getParameterReference,
    getExampleReference,
    getRequestBodyReference,
    getHeaderReference,
    getSecuritySchemeReference,
  )
where
import Control.Monad
import qualified Data.Bifunctor as BF
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe
import Data.Text (Text)
import GHC.Generics
import qualified OpenAPI.Generate.Types as OAT
import qualified OpenAPI.Generate.Types.Schema as OAS
data ComponentReference
  = SchemaReference OAS.SchemaObject
  | ResponseReference OAT.ResponseObject
  | ParameterReference OAT.ParameterObject
  | ExampleReference OAT.ExampleObject
  | RequestBodyReference OAT.RequestBodyObject
  | HeaderReference OAT.HeaderObject
  | SecuritySchemeReference OAT.SecuritySchemeObject
  deriving (Show, Eq, Generic)
type ReferenceMap = Map.Map Text ComponentReference
buildReferenceMap :: OAT.OpenApiSpecification -> ReferenceMap
buildReferenceMap =
  Map.fromList
    . ( \o ->
          buildReferencesForComponentType "schemas" SchemaReference (OAT.schemas o)
            <> buildReferencesForComponentType "responses" ResponseReference (OAT.responses (o :: OAT.ComponentsObject))
            <> buildReferencesForComponentType "parameters" ParameterReference (OAT.parameters (o :: OAT.ComponentsObject))
            <> buildReferencesForComponentType "examples" ExampleReference (OAT.examples (o :: OAT.ComponentsObject))
            <> buildReferencesForComponentType "requestBodies" RequestBodyReference (OAT.requestBodies o)
            <> buildReferencesForComponentType "headers" HeaderReference (OAT.headers (o :: OAT.ComponentsObject))
            <> buildReferencesForComponentType "securitySchemes" SecuritySchemeReference (OAT.securitySchemes o)
      )
    . OAT.components
buildReferencesForComponentType ::
  Text ->
  (a -> ComponentReference) ->
  Map.Map Text (OAT.Referencable a) ->
  [(Text, ComponentReference)]
buildReferencesForComponentType componentName constructor =
  fmap (BF.first (("#/components/" <> componentName <> "/") <>))
    . Maybe.mapMaybe (convertReferencableToReference constructor)
    . Map.toList
convertReferencableToReference ::
  (a -> ComponentReference) ->
  (Text, OAT.Referencable a) ->
  Maybe (Text, ComponentReference)
convertReferencableToReference constructor (name', OAT.Concrete object) = Just (name', constructor object)
convertReferencableToReference _ (_, OAT.Reference _) = Nothing
getReference :: Text -> ReferenceMap -> Maybe ComponentReference
getReference = Map.lookup
createReferenceLookup :: (ComponentReference -> Maybe a) -> Text -> ReferenceMap -> Maybe a
createReferenceLookup conversionFn key = getReference key >=> conversionFn
getSchemaReference :: Text -> ReferenceMap -> Maybe OAS.SchemaObject
getSchemaReference = createReferenceLookup $ \case
  SchemaReference r -> Just r
  _ -> Nothing
getResponseReference :: Text -> ReferenceMap -> Maybe OAT.ResponseObject
getResponseReference = createReferenceLookup $ \case
  ResponseReference r -> Just r
  _ -> Nothing
getParameterReference :: Text -> ReferenceMap -> Maybe OAT.ParameterObject
getParameterReference = createReferenceLookup $ \case
  ParameterReference r -> Just r
  _ -> Nothing
getExampleReference :: Text -> ReferenceMap -> Maybe OAT.ExampleObject
getExampleReference = createReferenceLookup $ \case
  ExampleReference r -> Just r
  _ -> Nothing
getRequestBodyReference :: Text -> ReferenceMap -> Maybe OAT.RequestBodyObject
getRequestBodyReference = createReferenceLookup $ \case
  RequestBodyReference r -> Just r
  _ -> Nothing
getHeaderReference :: Text -> ReferenceMap -> Maybe OAT.HeaderObject
getHeaderReference = createReferenceLookup $ \case
  HeaderReference r -> Just r
  _ -> Nothing
getSecuritySchemeReference :: Text -> ReferenceMap -> Maybe OAT.SecuritySchemeObject
getSecuritySchemeReference = createReferenceLookup $ \case
  SecuritySchemeReference r -> Just r
  _ -> Nothing