packages feed

seihou-core-0.4.0.0: src/Seihou/Interaction/Confirm.hs

module Seihou.Interaction.Confirm
  ( confirmDefaults,
  )
where

import Control.Monad (foldM)
import Data.Map.Strict qualified as Map
import Seihou.Composition.Instance (ModuleInstance (..))
import Seihou.Core.Types
import Seihou.Effect.Console (Console, isInteractive, putText)
import Seihou.Interaction.Prompt (promptForVar)
import Seihou.Prelude

-- | Walk the resolved variable map and prompt the user to confirm or
-- override each variable whose source is 'FromDefault' or 'FromParent'.
--
-- Variables whose new value matches the original are left unchanged
-- (preserving their original source). Variables whose new value differs
-- are recorded with 'FromPrompt' as their source, so the downstream
-- save-prompted flow picks them up.
--
-- A no-op in non-interactive mode, and a no-op when no variable is
-- resolved from a default.
--
-- Operates per 'ModuleInstance': two invocations of the same module
-- are confirmed independently, so the user can approve different
-- defaults for each.
confirmDefaults ::
  (Console :> es) =>
  [(ModuleInstance, Module, FilePath)] ->
  Map ModuleInstance (Map VarName ResolvedVar) ->
  Eff es (Map ModuleInstance (Map VarName ResolvedVar))
confirmDefaults modulesInOrder resolved = do
  interactive <- isInteractive
  if not interactive || not (anyNeedsConfirm resolved)
    then pure resolved
    else do
      putText ""
      putText "Confirm default values:"
      foldM processInstance resolved modulesInOrder

anyNeedsConfirm :: Map ModuleInstance (Map VarName ResolvedVar) -> Bool
anyNeedsConfirm = any (any isDefaultOrParent) . Map.elems

isDefaultOrParent :: ResolvedVar -> Bool
isDefaultOrParent rv = case rv.source of
  FromDefault -> True
  FromParent _ -> True
  _ -> False

processInstance ::
  (Console :> es) =>
  Map ModuleInstance (Map VarName ResolvedVar) ->
  (ModuleInstance, Module, FilePath) ->
  Eff es (Map ModuleInstance (Map VarName ResolvedVar))
processInstance acc (inst, m, _dir) = do
  let modResolved = Map.findWithDefault Map.empty inst acc
  newModResolved <- foldM (processVar m acc) modResolved m.vars
  pure (Map.insert inst newModResolved acc)

processVar ::
  (Console :> es) =>
  Module ->
  Map ModuleInstance (Map VarName ResolvedVar) ->
  Map VarName ResolvedVar ->
  VarDecl ->
  Eff es (Map VarName ResolvedVar)
processVar m allResolved modResolved decl =
  case Map.lookup decl.name modResolved of
    Just rv | isDefaultOrParent rv -> do
      let prompt = findOrSynthesize m decl
          currentBindings =
            Map.map (.value) (Map.unions (Map.elems allResolved))
      result <- promptForVar prompt decl currentBindings
      case result of
        Left _err -> pure modResolved
        Right newRv
          | newRv.value == rv.value -> pure modResolved
          | otherwise -> pure (Map.insert decl.name newRv modResolved)
    _ -> pure modResolved

-- | Find the authored 'Prompt' for a variable, or build a minimal one
-- from the declaration.
findOrSynthesize :: Module -> VarDecl -> Prompt
findOrSynthesize m decl =
  case filter (\p -> p.var == decl.name) m.prompts of
    (p : _) -> p
    [] ->
      Prompt
        { var = decl.name,
          text = decl.name.unVarName,
          condition = Nothing,
          choices = Nothing
        }