{-|
Module      : Toml.Schema.Generic.ToValue
Description : GHC.Generics derived table generation
Copyright   : (c) Eric Mertens, 2023
License     : ISC
Maintainer  : emertens@gmail.com

Use 'genericToTable' to derive an instance of 'Toml.ToValue.ToTable'
using the field names of a record.

Use 'genericToArray' to derive an instance of 'Toml.ToValue.ToValue'
using the positions of data in a constructor.

-}
module Toml.Schema.Generic.ToValue (

    -- * Records to Tables
    GToTable(..),
    genericToTable,

    -- * Product types to Arrays
    GToArray(..),
    genericToArray,
    ) where

import Data.Text (Text)
import Data.Text qualified as Text
import GHC.Generics
import Toml.Semantics
import Toml.Schema.ToValue (ToValue(..), table)

-- | Use a record's field names to generate a 'Table'
genericToTable :: (Generic a, GToTable (Rep a)) => a -> Table
genericToTable x = table (gToTable (from x) [])
{-# INLINE genericToTable #-}

-- | Use a record's field names to generate a 'Table'
genericToArray :: (Generic a, GToArray (Rep a)) => a -> Value
genericToArray a = List (gToArray (from a) [])
{-# INLINE genericToArray #-}

-- | Supports conversion of product types with field selector names
-- to TOML values.
class GToTable f where
    gToTable :: f a -> [(Text, Value)] -> [(Text, Value)]

-- | Ignores type constructor names
instance GToTable f => GToTable (D1 c f) where
    gToTable (M1 x) = gToTable x
    {-# INLINE gToTable #-}

-- | Ignores value constructor names
instance GToTable f => GToTable (C1 c f) where
    gToTable (M1 x) = gToTable x
    {-# INLINE gToTable #-}

instance (GToTable f, GToTable g) => GToTable (f :*: g) where
    gToTable (x :*: y) = gToTable x <> gToTable y
    {-# INLINE gToTable #-}

-- | Omits the key from the table on nothing, includes it on just
instance {-# OVERLAPS #-} (Selector s, ToValue a) => GToTable (S1 s (K1 i (Maybe a))) where
    gToTable (M1 (K1 Nothing)) = id
    gToTable s@(M1 (K1 (Just x))) = ((Text.pack (selName s), toValue x):)
    {-# INLINE gToTable #-}

-- | Uses record selector name as table key
instance (Selector s, ToValue a) => GToTable (S1 s (K1 i a)) where
    gToTable s@(M1 (K1 x)) = ((Text.pack (selName s), toValue x):)
    {-# INLINE gToTable #-}

-- | Emits empty table
instance GToTable U1 where
    gToTable _ = id
    {-# INLINE gToTable #-}

instance GToTable V1 where
    gToTable v = case v of {}
    {-# INLINE gToTable #-}

-- | Convert product types to arrays positionally.
class GToArray f where
    gToArray :: f a -> [Value] -> [Value]

-- | Ignore metadata
instance GToArray f => GToArray (M1 i c f) where
    gToArray (M1 x) = gToArray x
    {-# INLINE gToArray #-}

-- | Convert left and then right
instance (GToArray f, GToArray g) => GToArray (f :*: g) where
    gToArray (x :*: y) = gToArray x . gToArray y
    {-# INLINE gToArray #-}

-- | Convert fields using 'ToValue' instances
instance ToValue a => GToArray (K1 i a) where
    gToArray (K1 x) = (toValue x :)
    {-# INLINE gToArray #-}
