content: {-# LANGUAGE AllowAmbiguousTypes #-}
  {-# LANGUAGE DefaultSignatures #-}
  {-# LANGUAGE DeriveGeneric #-}
  {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE FlexibleInstances #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE StandaloneDeriving #-}
  {-# LANGUAGE TypeApplications #-}
  {-# LANGUAGE TypeFamilyDependencies #-}
  {-# LANGUAGE TypeOperators #-}
  {-# LANGUAGE UndecidableInstances #-}
  
  module Cardano.Db.Statement.Types where
  
  import Data.Char (isUpper, toLower)
  import Data.List (stripPrefix)
  import qualified Data.List.NonEmpty as NE
  import Data.Proxy
  import Data.Text (Text)
  import qualified Data.Text as Text
  import Data.Typeable (Typeable, tyConName, typeRep, typeRepTyCon)
  import GHC.Generics
  
  -- | DbInfo provides automatic derivation of table and column names from Haskell types.
  -- Table names are derived from the type name converted to snake_case.
  -- Column names are derived from record field names, where each field must follow
  -- this convention:
  --   * Start with the type name (first letter lowercased)
  --   * Continue with an uppercase letter
  --   * E.g., for type 'TxMetadata', use field names like 'txMetadataId', 'txMetadataKey'
  --
  -- Example:
  --
  -- @
  -- data TxMetadata = TxMetadata
  --   { txMetadataId    :: !Int
  --   , txMetadataKey   :: !Int
  --   , txMetadataJson  :: !(Maybe Text)
  --   } deriving (Show, Generic, Typeable)
  --
  -- instance DbInfo TxMetadata
  --   uniqueFields _ = ["key", "json"]
  --
  -- -- Table name: "tx_metadata"
  -- -- Column names: ["id", "key", "json"]
  -- -- Unique fields: ["key", "json"]
  -- @
  class Typeable a => DbInfo a where
    tableName :: Proxy a -> Text
    default tableName :: Proxy a -> Text
    tableName = Text.pack . camelToSnake . tyConName . typeRepTyCon . typeRep
  
    columnNames :: Proxy a -> NE.NonEmpty Text
    default columnNames :: (Generic a, GRecordFieldNames (Rep a)) => Proxy a -> NE.NonEmpty Text
    columnNames p =
      let typeName = tyConName $ typeRepTyCon $ typeRep p
          -- Safe use of undefined: acts as type witness, never evaluated
          fieldNames = gRecordFieldNames (from (undefined :: a))
       in case fieldNames of
            [] -> error "No fields found"
            ns -> NE.fromList $ map (fieldToColumnWithType typeName) ns
  
    -- | Column names that are generated by the database
    -- it is only here temporarily to allow for backwards compatibility
    -- but will be removed in the future.
    generatedFields :: Proxy a -> [Text]
    default generatedFields :: Proxy a -> [Text]
    generatedFields _ = []
  
    -- | Validate that all generated fields are present in the column names.
    validateGeneratedFields :: Proxy a -> Either String ()
    validateGeneratedFields p =
      let allCols = NE.toList $ columnNames p
          genFields = generatedFields p
          invalidFields = filter (`notElem` allCols) genFields
       in if null invalidFields
            then Right ()
            else Left $ "Generated fields not found in columns for " <> show (typeRep p) <> ": " <> show invalidFields
  
    -- | Validates that the unique constraints are valid columns in the table.
    -- If there are no unique constraints, this function will return successfully with [].
    validateUniqueConstraints :: Proxy a -> Either String [Text.Text]
    validateUniqueConstraints p =
      let colNames = NE.toList $ columnNames p
          constraints = uniqueFields p
          invalidConstraints = filter (`notElem` colNames) constraints
       in if null invalidConstraints
            then Right constraints
            else Left $ "Invalid unique constraint columns: " ++ show invalidConstraints
  
    -- | Column names that can be of the type jsonb.
    jsonbFields :: Proxy a -> [Text]
    default jsonbFields :: Proxy a -> [Text]
    jsonbFields _ = []
  
    -- | Column names that have an enum type.
    enumFields :: Proxy a -> [(Text, Text)] -- (column_name, enum_type)
    default enumFields :: Proxy a -> [(Text, Text)]
    enumFields _ = []
  
    uniqueFields ::
      Proxy a ->
      -- | Lists of column names that form unique constraints
      [Text]
    default uniqueFields :: Proxy a -> [Text]
    uniqueFields _ = []
  
    -- \| Column names and their pg_array type. Used for UNNEST statements.
    unnestParamTypes :: Proxy a -> [(Text, Text)] -- (column_name, pg_array_type)
    default unnestParamTypes :: Proxy a -> [(Text, Text)]
    unnestParamTypes _ = []
  
  -- | Convert a field name to a column name
  fieldToColumnWithType :: String -> String -> Text
  fieldToColumnWithType typeName field = Text.pack $
    camelToSnake $
      case stripPrefix (uncamelize typeName) field of
        Just remaining -> case remaining of
          (c : _) | isUpper c -> remaining
          _otherwise ->
            error $
              "Field name '"
                ++ field
                ++ "' does not match pattern '"
                ++ uncamelize typeName
                ++ "X...'"
        Nothing ->
          error $
            "Field name '"
              ++ field
              ++ "' does not start with type prefix '"
              ++ uncamelize typeName
              ++ "'"
  
  -- | Convert a string to snake case
  uncamelize :: String -> String
  uncamelize [] = []
  uncamelize (x : xs) = toLower x : xs
  
  -- | Convert a camel case string to snake case
  camelToSnake :: String -> String
  camelToSnake [] = []
  camelToSnake (x : xs) = toLower x : go xs
    where
      go [] = []
      go (c : cs)
        | isUpper c = '_' : toLower c : go cs
        | otherwise = c : go cs
  
  -- | Type class for generic representation of record field names
  class GRecordFieldNames f where
    gRecordFieldNames :: f p -> [String]
  
  instance GRecordFieldNames U1 where
    gRecordFieldNames _ = []
  
  instance (GRecordFieldNames a, GRecordFieldNames b) => GRecordFieldNames (a :*: b) where
    -- Safe use of undefined: type witnesses for generic recursion, never evaluated
    gRecordFieldNames _ = gRecordFieldNames (undefined :: a p) ++ gRecordFieldNames (undefined :: b p)
  
  instance GRecordFieldNames a => GRecordFieldNames (M1 D c a) where
    -- Safe use of undefined: type witness for generic metadata, never evaluated
    gRecordFieldNames _ = gRecordFieldNames (undefined :: a p)
  
  instance GRecordFieldNames a => GRecordFieldNames (M1 C c a) where
    -- Safe use of undefined: type witness for generic metadata, never evaluated
    gRecordFieldNames _ = gRecordFieldNames (undefined :: a p)
  
  instance Selector c => GRecordFieldNames (M1 S c (K1 i a)) where
    gRecordFieldNames m = [selName m]
  
  instance GRecordFieldNames (K1 i c) where
    gRecordFieldNames _ = []
  
  -- | Validate a column name against the list of columns in the table.
  validateColumn :: forall a. DbInfo a => Text -> Text
  validateColumn colName =
    let cols = "id" : NE.toList (columnNames (Proxy @a))
     in if colName `elem` cols
          then colName
          else
            error $
              "Column "
                <> Text.unpack colName
                <> " not found in table "
                <> Text.unpack (tableName (Proxy @a))
  
  --------------------------------------------------------------------------------
  -- Entity
  --------------------------------------------------------------------------------
  data Entity record = Entity
    { entityKey :: Key record
    , entityVal :: record
    }
  
  -- Type family for keys
  type family Key a = k | k -> a
  
  -- Add standalone deriving instances
  deriving instance Generic (Entity record)
  deriving instance (Eq (Key record), Eq record) => Eq (Entity record)
  deriving instance (Ord (Key record), Ord record) => Ord (Entity record)
  deriving instance (Show (Key record), Show record) => Show (Entity record)
  deriving instance (Read (Key record), Read record) => Read (Entity record)
  