{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE TemplateHaskell #-}

module Hasura.RQL.Types.Source.Column
  ( SourceColumnInfo (..),
    sciName,
    sciType,
    sciNullable,
    sciDescription,
    sciInsertable,
    sciUpdatable,
    sciValueGenerated,
    ColumnValueGenerationStrategy (..),
  )
where

--------------------------------------------------------------------------------

import Autodocodec
import Autodocodec.OpenAPI ()
import Control.DeepSeq (NFData)
import Control.Lens.TH (makeLenses)
import Data.Aeson (FromJSON, ToJSON)
import Data.HashMap.Strict qualified as HashMap
import Data.Hashable (Hashable)
import Data.OpenApi (ToSchema)
import Data.Text (Text)
import GHC.Generics (Generic)
import Hasura.RQL.Types.Backend (Backend (..))
import Hasura.RQL.Types.Column (RawColumnType (..))
import Prelude

--------------------------------------------------------------------------------

data SourceColumnInfo b = SourceColumnInfo
  { _sciName :: Column b,
    _sciType :: RawColumnType b,
    _sciNullable :: Bool,
    _sciDescription :: Maybe Text,
    _sciInsertable :: Bool,
    _sciUpdatable :: Bool,
    _sciValueGenerated :: Maybe ColumnValueGenerationStrategy
  }
  deriving stock (Generic)
  deriving anyclass (Hashable)
  deriving (FromJSON, ToJSON, ToSchema) via Autodocodec (SourceColumnInfo b)

deriving instance (Backend b) => Eq (SourceColumnInfo b)

deriving instance (Backend b) => Ord (SourceColumnInfo b)

deriving instance (Backend b) => Show (SourceColumnInfo b)

instance (Backend b) => HasCodec (SourceColumnInfo b) where
  codec =
    object "ColumnInfo" $
      SourceColumnInfo
        <$> requiredField "name" "Column name" .= _sciName
        <*> requiredField "type" "Column type" .= _sciType
        <*> requiredField "nullable" "Is column nullable" .= _sciNullable
        <*> optionalFieldOrNull "description" "Column description" .= _sciDescription
        <*> optionalFieldWithDefault "insertable" False "Whether or not the column can be inserted into" .= _sciInsertable
        <*> optionalFieldWithDefault "updatable" False "Whether or not the column can be updated" .= _sciUpdatable
        <*> optionalFieldOrNull "value_generated" "Whether or not and how the value of the column can be generated by the database" .= _sciValueGenerated

data ColumnValueGenerationStrategy
  = AutoIncrement
  | UniqueIdentifier
  | DefaultValue
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData, Hashable)
  deriving (FromJSON, ToJSON, ToSchema) via Autodocodec ColumnValueGenerationStrategy

-- | We're encoding the different strategies as tagged objects rather than just strings because
-- it is anticipated that additional information may need to be added to each strategy in future.
-- For example, the actual default value (a literal), or what type of unique identifier is being
-- used (eg. a UUID). By using a tagged object, we ensure the addition of such information is not
-- a breaking change to the shape of the JSON.
instance HasCodec ColumnValueGenerationStrategy where
  codec =
    named "ColumnValueGenerationStrategy" $ object "ColumnValueGenerationStrategy" $ discriminatedUnionCodec "type" enc dec
    where
      autoIncrementCodec = pure ()
      uniqueIdentifierCodec = pure ()
      defaultCodec = pure ()
      enc = \case
        AutoIncrement -> ("auto_increment", mapToEncoder () autoIncrementCodec)
        UniqueIdentifier -> ("unique_identifier", mapToEncoder () uniqueIdentifierCodec)
        DefaultValue -> ("default_value", mapToEncoder () defaultCodec)
      dec =
        HashMap.fromList
          [ ("auto_increment", ("AutoIncrementGenerationStrategy", mapToDecoder (const AutoIncrement) autoIncrementCodec)),
            ("unique_identifier", ("UniqueIdentifierGenerationStrategy", mapToDecoder (const UniqueIdentifier) uniqueIdentifierCodec)),
            ("default_value", ("DefaultValueGenerationStrategy", mapToDecoder (const DefaultValue) uniqueIdentifierCodec))
          ]

$(makeLenses ''SourceColumnInfo)
