content: {-# LANGUAGE ExplicitForAll      #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE ViewPatterns        #-}
  
  -- | Utilities to remove unused qualified imports
  
  module Importify.Resolution.Qualified
         ( removeUnusedQualifiedImports
         ) where
  
  import           Universum
  
  import           Data.List                          (partition)
  
  import           Language.Haskell.Exts              (ImportDecl (..), ModuleHead (..),
                                                       ModuleName (..), QName (..))
  import           Language.Haskell.Names             (NameInfo (GlobalSymbol), Scoped)
  import           Language.Haskell.Names.SyntaxUtils (dropAnn)
  
  import           Extended.Data.Bool                 ((==>))
  import           Importify.Syntax                   (getImportModuleName, isInsideExport,
                                                       scopedNameInfo, scopedNameInfo)
  
  -- | Remove unused @qualified as@ imports, i.e. in one of the next form:
  -- @
  --   import qualified Data.List
  --   import qualified Data.List as L
  --   import           Data.List as L
  -- @
  -- This function ignores imports with explicit import lists because it
  -- is running after stage where symbols from explicit list removed.
  removeUnusedQualifiedImports :: [ImportDecl l]
                               -> Maybe (ModuleHead l)
                               -> [Scoped l]
                               -> [ModuleName ()]  -- ^ Unused @import A as B@
                               -> [ImportDecl l]
  removeUnusedQualifiedImports imports moduleHead annotations unusedImplicits =
      let (possiblyUnused, others) = partition (possiblyUnusedImport unusedImplicits) imports
  
          isImportNeeded name      = isInsideExport moduleHead  name
                                  || isInsideModule annotations name
  
          byModuleName             = maybe True isImportNeeded
                                   . fmap dropAnn
                                   . qualifiedName
  
          neededQualified          = filter byModuleName possiblyUnused
      in neededQualified ++ others
  
  possiblyUnusedImport :: [ModuleName ()] -> ImportDecl l -> Bool
  possiblyUnusedImport unusedImplicits decl = isNothing (importSpecs decl)
                                           && isNotImplicitUnused
    where
      isNotImplicitUnused = (isJust (importAs decl) && not (importQualified decl))
                        ==> getImportModuleName decl `elem` unusedImplicits
  
  -- | For given import collect qualified name.
  -- Qualified names gathered using next scheme:
  -- @
  --   import           A      ⇒ Nothing
  --   import qualified B      ⇒ Just B
  --   import qualified C as X ⇒ Just X
  --   import           D as Y ⇒ Just Y
  -- @
  -- Used later to determine whether empty @qualified@ import needed or not.
  qualifiedName :: ImportDecl l -> Maybe (ModuleName l)
  qualifiedName ImportDecl{ importAs = as@(Just _)     } = as
  qualifiedName ImportDecl{ importQualified = True, .. } = Just importModule
  qualifiedName _                                        = Nothing
  
  isInsideModule :: forall l. [Scoped l] -> ModuleName () -> Bool
  isInsideModule annotations moduleName = any isNameUsed annotations
    where
      isNameUsed :: Scoped l -> Bool
      isNameUsed (scopedNameInfo -> nameInfo) = case nameInfo of
          GlobalSymbol _ (Qual _ usedName _) -> moduleName == usedName
          _                                  -> False
  