{-# LANGUAGE CPP #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE LambdaCase #-}


module Data.Aeson.TypeScript.Transform (
  transformTypeFamilies
  ) where

import Control.Monad.Writer
import Data.Aeson.TypeScript.Lookup
import Data.Aeson.TypeScript.Types
import qualified Data.List as L
import Data.Typeable
import Language.Haskell.TH hiding (stringE)
import qualified Language.Haskell.TH.Lib as TH

#if !MIN_VERSION_base(4,11,0)
import Data.Monoid
#endif


-- | Search the given type for type families. For each one found, emit a declaration for a new
-- corresponding concrete type and a TypeScript instance for it which emits a lookup type.
-- Then, replace all occurrences of the given type family with the concrete type in the return value.
-- Thus the type becomes "de-family-ified".
transformTypeFamilies :: ExtraTypeScriptOptions -> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies :: ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies eo :: ExtraTypeScriptOptions
eo@(ExtraTypeScriptOptions {[Name]
Maybe String
keyType :: ExtraTypeScriptOptions -> Maybe String
typeFamiliesToMapToTypeScript :: ExtraTypeScriptOptions -> [Name]
keyType :: Maybe String
typeFamiliesToMapToTypeScript :: [Name]
..}) (AppT (ConT Name
name) Type
typ)
  | Name
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`L.elem` [Name]
typeFamiliesToMapToTypeScript = forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (Name -> Q Info
reify Name
name) forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
      FamilyI (ClosedTypeFamilyD (TypeFamilyHead Name
typeFamilyName [TyVarBndr ()]
_ FamilyResultSig
_ Maybe InjectivityAnn
_) [TySynEqn]
eqns) [Dec]
_ -> Name -> [TySynEqn] -> WriterT [ExtraDeclOrGenericInfo] Q Type
handle Name
typeFamilyName [TySynEqn]
eqns

#if MIN_VERSION_template_haskell(2,15,0)
      FamilyI (OpenTypeFamilyD (TypeFamilyHead Name
typeFamilyName [TyVarBndr ()]
_ FamilyResultSig
_ Maybe InjectivityAnn
_)) [Dec]
decs -> Name -> [TySynEqn] -> WriterT [ExtraDeclOrGenericInfo] Q Type
handle Name
typeFamilyName [TySynEqn
eqn | TySynInstD TySynEqn
eqn <- [Dec]
decs]
#else
      FamilyI (OpenTypeFamilyD (TypeFamilyHead typeFamilyName _ _ _)) decs -> handle typeFamilyName [eqn | TySynInstD _name eqn <- decs]
#endif

      Info
_ -> Type -> Type -> Type
AppT (Name -> Type
ConT Name
name) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies ExtraTypeScriptOptions
eo Type
typ
  | Bool
otherwise = Type -> Type -> Type
AppT (Name -> Type
ConT Name
name) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies ExtraTypeScriptOptions
eo Type
typ
        where
          handle :: Name -> [TySynEqn] -> WriterT [ExtraDeclOrGenericInfo] Q Type
          handle :: Name -> [TySynEqn] -> WriterT [ExtraDeclOrGenericInfo] Q Type
handle Name
typeFamilyName [TySynEqn]
eqns = do
            Name
name' <- forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *). Quote m => String -> m Name
newName (Name -> String
nameBase Name
typeFamilyName forall a. Semigroup a => a -> a -> a
<> String
"'")

            Name
f <- forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *). Quote m => String -> m Name
newName String
"f"
#if MIN_VERSION_template_haskell(2,17,0)
            let inst1 :: Dec
inst1 = Cxt
-> Name
-> [TyVarBndr ()]
-> Maybe Type
-> [Con]
-> [DerivClause]
-> Dec
DataD [] Name
name' [forall flag. Name -> flag -> TyVarBndr flag
PlainTV Name
f ()] forall a. Maybe a
Nothing [] []
#else
            let inst1 = DataD [] name' [PlainTV f] Nothing [] []
#endif
            forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [[Dec] -> ExtraDeclOrGenericInfo
ExtraTopLevelDecs [Dec
inst1]]

            Cxt
imageTypes <- forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift forall a b. (a -> b) -> a -> b
$ [TySynEqn] -> Q Cxt
getClosedTypeFamilyImage [TySynEqn]
eqns
            [Dec]
inst2 <- forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift forall a b. (a -> b) -> a -> b
$ [d|instance (Typeable g, TypeScript g) => TypeScript ($(conT name') g) where
                                 getTypeScriptType _ = $(TH.stringE $ nameBase name) <> "[" <> (getTypeScriptType (Proxy :: Proxy g)) <> "]"
                                 getTypeScriptDeclarations _ = [$(getClosedTypeFamilyInterfaceDecl name eqns)]
                                 getParentTypes _ = $(listE [ [|TSType (Proxy :: Proxy $(return x))|] | x <- imageTypes])
                            |]
            forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [[Dec] -> ExtraDeclOrGenericInfo
ExtraTopLevelDecs [Dec]
inst2]

            forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [Type -> ExtraDeclOrGenericInfo
ExtraParentType (Type -> Type -> Type
AppT (Name -> Type
ConT Name
name') (Name -> Type
ConT ''T))]

            Type
ret <- ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies ExtraTypeScriptOptions
eo (Type -> Type -> Type
AppT (Name -> Type
ConT Name
name') Type
typ)
            forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [Type -> ExtraDeclOrGenericInfo
ExtraConstraint (Type -> Type -> Type
AppT (Name -> Type
ConT ''TypeScript) Type
ret)]
            forall (m :: * -> *) a. Monad m => a -> m a
return Type
ret
transformTypeFamilies ExtraTypeScriptOptions
eo (AppT Type
typ1 Type
typ2) = Type -> Type -> Type
AppT forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies ExtraTypeScriptOptions
eo Type
typ1 forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies ExtraTypeScriptOptions
eo Type
typ2
transformTypeFamilies ExtraTypeScriptOptions
eo (SigT Type
typ Type
kind) = forall a b c. (a -> b -> c) -> b -> a -> c
flip Type -> Type -> Type
SigT Type
kind forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies ExtraTypeScriptOptions
eo Type
typ
transformTypeFamilies ExtraTypeScriptOptions
eo (InfixT Type
typ1 Name
n Type
typ2) = Type -> Name -> Type -> Type
InfixT forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies ExtraTypeScriptOptions
eo Type
typ1 forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> forall (f :: * -> *) a. Applicative f => a -> f a
pure Name
n forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies ExtraTypeScriptOptions
eo Type
typ2
transformTypeFamilies ExtraTypeScriptOptions
eo (UInfixT Type
typ1 Name
n Type
typ2) = Type -> Name -> Type -> Type
UInfixT forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies ExtraTypeScriptOptions
eo Type
typ1 forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> forall (f :: * -> *) a. Applicative f => a -> f a
pure Name
n forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies ExtraTypeScriptOptions
eo Type
typ2
transformTypeFamilies ExtraTypeScriptOptions
eo (ParensT Type
typ) = Type -> Type
ParensT forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies ExtraTypeScriptOptions
eo Type
typ
#if MIN_VERSION_template_haskell(2,15,0)
transformTypeFamilies ExtraTypeScriptOptions
eo (AppKindT Type
typ Type
kind) = forall a b c. (a -> b -> c) -> b -> a -> c
flip Type -> Type -> Type
AppKindT Type
kind forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies ExtraTypeScriptOptions
eo Type
typ
transformTypeFamilies ExtraTypeScriptOptions
eo (ImplicitParamT String
s Type
typ) = String -> Type -> Type
ImplicitParamT String
s forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ExtraTypeScriptOptions
-> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type
transformTypeFamilies ExtraTypeScriptOptions
eo Type
typ
#endif
transformTypeFamilies ExtraTypeScriptOptions
_ Type
typ = forall (m :: * -> *) a. Monad m => a -> m a
return Type
typ