constructPlan :: forall env. HasEnvConfig env
              => LoadedSnapshot
              -> BaseConfigOpts
              -> [LocalPackage]
              -> Set PackageName -- ^ additional packages that must be built
              -> [DumpPackage () () ()] -- ^ locally registered
              -> (PackageLocationIndex FilePath -> Map FlagName Bool -> [Text] -> IO Package) -- ^ load upstream package
              -> SourceMap
              -> InstalledMap
              -> Bool
              -> RIO env Plan
constructPlan ls0 baseConfigOpts0 locals extraToBuild0 localDumpPkgs loadPackage0 sourceMap installedMap initialBuildSteps = do
    logDebug "Constructing the build plan"
    u <- askUnliftIO

    econfig <- view envConfigL
    let onWanted = void . addDep False . packageName . lpPackage
    let inner = do
            mapM_ onWanted $ filter lpWanted locals
            mapM_ (addDep False) $ Set.toList extraToBuild0
    lp <- getLocalPackages
    let ctx = mkCtx econfig (unliftIO u . getPackageVersions) lp
    ((), m, W efinals installExes dirtyReason deps warnings parents) <-
        liftIO $ runRWST inner ctx M.empty
    mapM_ logWarn (warnings [])
    let toEither (_, Left e)  = Left e
        toEither (k, Right v) = Right (k, v)
        (errlibs, adrs) = partitionEithers $ map toEither $ M.toList m
        (errfinals, finals) = partitionEithers $ map toEither $ M.toList efinals
        errs = errlibs ++ errfinals
    if null errs
        then do
            let toTask (_, ADRFound _ _) = Nothing
                toTask (name, ADRToInstall task) = Just (name, task)
                tasks = M.fromList $ mapMaybe toTask adrs
                takeSubset =
                    case boptsCLIBuildSubset $ bcoBuildOptsCLI baseConfigOpts0 of
                        BSAll -> id
                        BSOnlySnapshot -> stripLocals
                        BSOnlyDependencies -> stripNonDeps deps
            return $ takeSubset Plan
                { planTasks = tasks
                , planFinals = M.fromList finals
                , planUnregisterLocal = mkUnregisterLocal tasks dirtyReason localDumpPkgs sourceMap initialBuildSteps
                , planInstallExes =
                    if boptsInstallExes (bcoBuildOpts baseConfigOpts0) ||
                       boptsInstallCompilerTool (bcoBuildOpts baseConfigOpts0)
                        then installExes
                        else Map.empty
                }
        else do
            planDebug $ show errs
            stackYaml <- view stackYamlL
            prettyErrorNoIndent $ pprintExceptions errs stackYaml parents (wanted ctx)
            throwM $ ConstructPlanFailed "Plan construction failed."
  where
    mkCtx econfig getVersions0 lp = Ctx
        { ls = ls0
        , baseConfigOpts = baseConfigOpts0
        , loadPackage = loadPackage0
        , combinedMap = combineMap sourceMap installedMap
        , toolToPackages = \name ->
          maybe Map.empty (Map.fromSet (const Cabal.anyVersion)) $
          Map.lookup name toolMap
        , ctxEnvConfig = econfig
        , callStack = []
        , extraToBuild = extraToBuild0
        , getVersions = getVersions0
        , wanted = wantedLocalPackages locals <> extraToBuild0
        , localNames = Set.fromList $ map (packageName . lpPackage) locals
        }
      where
        toolMap = getToolMap ls0 lp

-- | State to be maintained during the calculation of local packages
-- to unregister.
constructPlan :: forall env. HasEnvConfig env
              => LoadedSnapshot
              -> BaseConfigOpts
              -> [LocalPackage]
              -> Set PackageName -- ^ additional packages that must be built
              -> [DumpPackage () () ()] -- ^ locally registered
              -> (PackageLocationIndex FilePath -> Map FlagName Bool -> [Text] -> IO Package) -- ^ load upstream package
              -> SourceMap
              -> InstalledMap
              -> Bool
              -> RIO env Plan
constructPlan ls0 baseConfigOpts0 locals extraToBuild0 localDumpPkgs loadPackage0 sourceMap installedMap initialBuildSteps = do
    logDebug "Constructing the build plan"
    u <- askUnliftIO

    econfig <- view envConfigL
    let onWanted = void . addDep False . packageName . lpPackage
    let inner = do
            mapM_ onWanted $ filter lpWanted locals
            mapM_ (addDep False) $ Set.toList extraToBuild0
    lp <- getLocalPackages
    let ctx = mkCtx econfig (unliftIO u . getPackageVersions) lp
    ((), m, W efinals installExes dirtyReason deps warnings parents) <-
        liftIO $ runRWST inner ctx M.empty
    mapM_ logWarn (warnings [])
    let toEither (_, Left e)  = Left e
        toEither (k, Right v) = Right (k, v)
        (errlibs, adrs) = partitionEithers $ map toEither $ M.toList m
        (errfinals, finals) = partitionEithers $ map toEither $ M.toList efinals
        errs = errlibs ++ errfinals
    if null errs
        then do
            let toTask (_, ADRFound _ _) = Nothing
                toTask (name, ADRToInstall task) = Just (name, task)
                tasks = M.fromList $ mapMaybe toTask adrs
                takeSubset =
                    case boptsCLIBuildSubset $ bcoBuildOptsCLI baseConfigOpts0 of
                        BSAll -> id
                        BSOnlySnapshot -> stripLocals
                        BSOnlyDependencies -> stripNonDeps deps
            return $ takeSubset Plan
                { planTasks = tasks
                , planFinals = M.fromList finals
                , planUnregisterLocal = mkUnregisterLocal tasks dirtyReason localDumpPkgs sourceMap initialBuildSteps
                , planInstallExes =
                    if boptsInstallExes (bcoBuildOpts baseConfigOpts0) ||
                       boptsInstallCompilerTool (bcoBuildOpts baseConfigOpts0)
                        then installExes
                        else Map.empty
                }
        else do
            planDebug $ show errs
            stackYaml <- view stackYamlL
            prettyErrorNoIndent $ pprintExceptions errs stackYaml parents (wanted ctx)
            throwM $ ConstructPlanFailed "Plan construction failed."
  where
    mkCtx econfig getVersions0 lp = Ctx
        { ls = ls0
        , baseConfigOpts = baseConfigOpts0
        , loadPackage = loadPackage0
        , combinedMap = combineMap sourceMap installedMap
        , toolToPackages = \name ->
          maybe Map.empty (Map.fromSet (const Cabal.anyVersion)) $
          Map.lookup name toolMap
        , ctxEnvConfig = econfig
        , callStack = []
        , extraToBuild = extraToBuild0
        , getVersions = getVersions0
        , wanted = wantedLocalPackages locals <> extraToBuild0
        , localNames = Set.fromList $ map (packageName . lpPackage) locals
        }
      where
        toolMap = getToolMap ls0 lp

-- | State to be maintained during the calculation of local packages
-- to unregister.
constructPlan ls0 baseConfigOpts0 locals extraToBuild0 localDumpPkgs loadPackage0 sourceMap installedMap initialBuildSteps = do
    logDebug "Constructing the build plan"
    u <- askUnliftIO

    econfig <- view envConfigL
    let onWanted = void . addDep False . packageName . lpPackage
    let inner = do
            mapM_ onWanted $ filter lpWanted locals
            mapM_ (addDep False) $ Set.toList extraToBuild0
    lp <- getLocalPackages
    let ctx = mkCtx econfig (unliftIO u . getPackageVersions) lp
    ((), m, W efinals installExes dirtyReason deps warnings parents) <-
        liftIO $ runRWST inner ctx M.empty
    mapM_ logWarn (warnings [])
    let toEither (_, Left e)  = Left e
        toEither (k, Right v) = Right (k, v)
        (errlibs, adrs) = partitionEithers $ map toEither $ M.toList m
        (errfinals, finals) = partitionEithers $ map toEither $ M.toList efinals
        errs = errlibs ++ errfinals
    if null errs
        then do
            let toTask (_, ADRFound _ _) = Nothing
                toTask (name, ADRToInstall task) = Just (name, task)
                tasks = M.fromList $ mapMaybe toTask adrs
                takeSubset =
                    case boptsCLIBuildSubset $ bcoBuildOptsCLI baseConfigOpts0 of
                        BSAll -> id
                        BSOnlySnapshot -> stripLocals
                        BSOnlyDependencies -> stripNonDeps deps
            return $ takeSubset Plan
                { planTasks = tasks
                , planFinals = M.fromList finals
                , planUnregisterLocal = mkUnregisterLocal tasks dirtyReason localDumpPkgs sourceMap initialBuildSteps
                , planInstallExes =
                    if boptsInstallExes (bcoBuildOpts baseConfigOpts0) ||
                       boptsInstallCompilerTool (bcoBuildOpts baseConfigOpts0)
                        then installExes
                        else Map.empty
                }
        else do
            planDebug $ show errs
            stackYaml <- view stackYamlL
            prettyErrorNoIndent $ pprintExceptions errs stackYaml parents (wanted ctx)
            throwM $ ConstructPlanFailed "Plan construction failed."
  where
    mkCtx econfig getVersions0 lp = Ctx
        { ls = ls0
        , baseConfigOpts = baseConfigOpts0
        , loadPackage = loadPackage0
        , combinedMap = combineMap sourceMap installedMap
        , toolToPackages = \name ->
          maybe Map.empty (Map.fromSet (const Cabal.anyVersion)) $
          Map.lookup name toolMap
        , ctxEnvConfig = econfig
        , callStack = []
        , extraToBuild = extraToBuild0
        , getVersions = getVersions0
        , wanted = wantedLocalPackages locals <> extraToBuild0
        , localNames = Set.fromList $ map (packageName . lpPackage) locals
        }
      where
        toolMap = getToolMap ls0 lp

-- | State to be maintained during the calculation of local packages
-- to unregister.