content: {-# LANGUAGE CPP #-}
  {-# LANGUAGE TemplateHaskell #-}
  {-# LANGUAGE ExistentialQuantification #-}
  {-# LANGUAGE FlexibleInstances #-}
  {-# LANGUAGE PolyKinds #-}
  
  module Data.Aeson.TypeScript.TypeManipulation (
    searchForConstraints
    , hasFreeTypeVariable
    , unifyGenericVariable
    ) where
  
  import Control.Monad.Writer
  import Data.Aeson.TypeScript.Types
  import qualified Data.List as L
  import Language.Haskell.TH
  
  #if !MIN_VERSION_base(4,11,0)
  import Data.Monoid
  #endif
  
  
  searchForConstraints :: ExtraTypeScriptOptions -> Type -> Name -> WriterT [GenericInfo] Q ()
  searchForConstraints eo@(ExtraTypeScriptOptions {..}) (AppT (ConT name) typ) var
    | typ == VarT var && (name `L.elem` typeFamiliesToMapToTypeScript) = lift (reify name) >>= \case
        FamilyI (ClosedTypeFamilyD (TypeFamilyHead typeFamilyName _ _ _) _) _ -> do
          tell [GenericInfo var (TypeFamilyKey typeFamilyName)]
          searchForConstraints eo typ var
        FamilyI (OpenTypeFamilyD (TypeFamilyHead typeFamilyName _ _ _)) _ -> do
          tell [GenericInfo var (TypeFamilyKey typeFamilyName)]
          searchForConstraints eo typ var
        _ -> searchForConstraints eo typ var
    | otherwise = searchForConstraints eo typ var
  searchForConstraints eo (AppT typ1 typ2) var = searchForConstraints eo typ1 var >> searchForConstraints eo typ2 var
  searchForConstraints eo (SigT typ _) var = searchForConstraints eo typ var
  searchForConstraints eo (InfixT typ1 _ typ2) var = searchForConstraints eo typ1 var >> searchForConstraints eo typ2 var
  searchForConstraints eo (UInfixT typ1 _ typ2) var = searchForConstraints eo typ1 var >> searchForConstraints eo typ2 var
  searchForConstraints eo (ParensT typ) var = searchForConstraints eo typ var
  #if MIN_VERSION_template_haskell(2,15,0)
  searchForConstraints eo (AppKindT typ _) var = searchForConstraints eo typ var
  searchForConstraints eo (ImplicitParamT _ typ) var = searchForConstraints eo typ var
  #endif
  searchForConstraints _ _ _ = return ()
  
  hasFreeTypeVariable :: Type -> Bool
  hasFreeTypeVariable (VarT _) = True
  hasFreeTypeVariable (AppT typ1 typ2) = hasFreeTypeVariable typ1 || hasFreeTypeVariable typ2
  hasFreeTypeVariable (SigT typ _) = hasFreeTypeVariable typ
  hasFreeTypeVariable (InfixT typ1 _ typ2) = hasFreeTypeVariable typ1 || hasFreeTypeVariable typ2
  hasFreeTypeVariable (UInfixT typ1 _ typ2) = hasFreeTypeVariable typ1 || hasFreeTypeVariable typ2
  hasFreeTypeVariable (ParensT typ) = hasFreeTypeVariable typ
  #if MIN_VERSION_template_haskell(2,15,0)
  hasFreeTypeVariable (AppKindT typ _) = hasFreeTypeVariable typ
  hasFreeTypeVariable (ImplicitParamT _ typ) = hasFreeTypeVariable typ
  #endif
  hasFreeTypeVariable _ = False
  
  unifyGenericVariable :: [GenericInfo] -> String
  unifyGenericVariable genericInfos = case [nameBase name | GenericInfo _ (TypeFamilyKey name) <- genericInfos] of
    [] -> ""
    names -> " extends keyof " <> (L.intercalate " & " names)
  