{-# LANGUAGE DerivingStrategies         #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Data.Record.Internal.GHC.Fresh (
    MonadFresh(..)
  , runFreshHsc
  ) where

import Control.Monad.Reader

import Data.Record.Internal.GHC.Shim

class Monad m => MonadFresh m where
  -- | Construct a fresh name for use in term level expressions
  --
  -- NOTES:
  --
  -- * These names should be used for module exports.
  -- * These names should be used for exactly /one/ binder.
  -- * The resulting name has the same 'NameSpace' as the argument.
  freshName :: LRdrName -> m LRdrName
  freshName = freshName' True

  -- variant which doesn't rename the variable.
  -- The 'False' variant can be used in types.
  freshName' :: Bool -> LRdrName -> m LRdrName

newtype Fresh a = WrapFresh { unwrapFresh :: ReaderT NameCacheIO IO a }
  deriving newtype (Functor, Applicative, Monad)

instance MonadFresh Fresh where
  freshName' pfx (L l name) = WrapFresh $ ReaderT $ \nc -> do
      newUniq <- takeUniqFromNameCacheIO nc
      return $ L l $ Exact $
        mkInternalName newUniq (newOccName (rdrNameOcc name)) l
    where
      -- Even when we generate fresh names, ghc can still complain about name
      -- shadowing, because this check only considers the 'OccName', not the
      -- unique. We therefore prefix the name with an underscore to avoid the
      -- warning.
      newOccName :: OccName -> OccName
      newOccName n = mkOccName (occNameSpace n) $ addPrefix $ occNameString n

      addPrefix :: String -> String
      addPrefix = if pfx then ("_" ++) else id

runFresh :: Fresh a -> NameCacheIO -> IO a
runFresh = runReaderT . unwrapFresh

runFreshHsc :: Fresh a -> Hsc a
runFreshHsc fa = do
    env <- getHscEnv
    liftIO $ runFresh fa (hscNameCacheIO env)