-- |
--  Module      : Cfg.Deriving.Assert
--  Copyright   : © Jonathan Lorimer, 2023
--  License     : MIT
--  Maintainer  : jonathanlorimer@pm.me
--  Stability   : stable
--
-- @since 0.0.2.0
--
-- This module provides type level assertions so that we can constrain the
-- instances user's can create, and give them good error messages
module Cfg.Deriving.Assert where

import Data.Kind (Type)
import GHC.Base (Constraint)
import GHC.Generics
import GHC.TypeError (ErrorMessage (..), TypeError)

-- | A type level helper for creating custom error messages based on a predicate
--
-- @since 0.0.2.0
class Assert (pred :: Bool) (msg :: ErrorMessage)

instance Assert 'True msg

instance (TypeError msg ~ '()) => Assert 'False msg

-- | A type level predicate that helps us identify top level product types
--
-- @since 0.0.2.0
type family IsTopLevelRecord f where
  IsTopLevelRecord V1 = 'False
  IsTopLevelRecord U1 = 'False
  IsTopLevelRecord (K1 i c) = 'False
  IsTopLevelRecord (M1 D c f) = IsTopLevelRecord f
  IsTopLevelRecord (M1 C c f) = IsTopLevelRecord f
  IsTopLevelRecord (M1 S c f) = 'True
  IsTopLevelRecord (f :*: g) = IsTopLevelRecord f
  IsTopLevelRecord (f :+: g) = 'False

-- | A custom error message for non-top-level records
--
-- @since 0.0.2.0
type AssertTopLevelRecord (constraint :: Type -> Constraint) a =
  Assert
    (IsTopLevelRecord (Rep a))
    ( 'Text "🚫 Cannot derive "
        ':<>: 'ShowType constraint
        ':<>: 'Text " instance for "
        ':<>: 'ShowType a
        ':$$: ( 'Text "💡 "
                  ':<>: 'ShowType constraint
                  ':<>: 'Text " must be derived on a top level record type with named fields."
              )
    )