-- | @switch@ input handler
module Unison.Codebase.Editor.HandleInput.ProjectSwitch
  ( projectSwitch,
  )
where

import Data.These (These (..))
import U.Codebase.Sqlite.Project (Project (..))
import U.Codebase.Sqlite.Queries qualified as Queries
import Unison.Cli.Monad (Cli)
import Unison.Cli.Monad qualified as Cli
import Unison.Cli.MonadUtils qualified as Cli
import Unison.Cli.ProjectUtils qualified as ProjectUtils
import Unison.Codebase.Editor.Output qualified as Output
import Unison.Prelude
import Unison.Project (ProjectAndBranch (..), ProjectAndBranchNames (..), ProjectBranchName, ProjectName)
import Witch (unsafeFrom)

-- | Switch to an existing project or project branch, with a flexible syntax that does not require prefixing branch
-- names with forward slashes (though doing so makes the command unambiguous).
--
-- When the argument is ambiguous, when outside of a project, "switch foo" means switch to project "foo", not branch
-- "foo" (since there is no current project). And when inside a project, "switch foo" means one of:
--
--   1. Switch to project "foo", since there isn't a branch "foo" in the current project
--   2. Switch to branch "foo", since there isn't a project "foo"
--   3. Complain, because there's both a project "foo" and a branch "foo" in the current project
projectSwitch :: ProjectAndBranchNames -> Cli ()
projectSwitch :: ProjectAndBranchNames -> Cli ()
projectSwitch ProjectAndBranchNames
projectNames = do
  case ProjectAndBranchNames
projectNames of
    ProjectAndBranchNames'Ambiguous ProjectName
projectName ProjectBranchName
branchName -> do
      ProjectAndBranch Project
currentProject ProjectBranch
_currentBranch <- Cli (ProjectAndBranch Project ProjectBranch)
Cli.getCurrentProjectAndBranch
      (Bool
projectExists, Bool
branchExists) <-
        Transaction (Bool, Bool) -> Cli (Bool, Bool)
forall a. Transaction a -> Cli a
Cli.runTransaction do
          (,)
            (Bool -> Bool -> (Bool, Bool))
-> Transaction Bool -> Transaction (Bool -> (Bool, Bool))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ProjectName -> Transaction Bool
Queries.projectExistsByName ProjectName
projectName
            Transaction (Bool -> (Bool, Bool))
-> Transaction Bool -> Transaction (Bool, Bool)
forall a b. Transaction (a -> b) -> Transaction a -> Transaction b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> ProjectId -> ProjectBranchName -> Transaction Bool
Queries.projectBranchExistsByName Project
currentProject.projectId ProjectBranchName
branchName
      case (Bool
projectExists, Bool
branchExists) of
        (Bool
False, Bool
False) -> Output -> Cli ()
Cli.respond (ProjectName -> ProjectBranchName -> Output
Output.LocalProjectNorProjectBranchExist ProjectName
projectName ProjectBranchName
branchName)
        (Bool
False, Bool
True) -> These ProjectName ProjectBranchName -> Cli ()
switchToProjectAndBranchByTheseNames (ProjectName
-> ProjectBranchName -> These ProjectName ProjectBranchName
forall a b. a -> b -> These a b
These Project
currentProject.name ProjectBranchName
branchName)
        (Bool
True, Bool
False) -> These ProjectName ProjectBranchName -> Cli ()
switchToProjectAndBranchByTheseNames (ProjectName -> These ProjectName ProjectBranchName
forall a b. a -> These a b
This ProjectName
projectName)
        (Bool
True, Bool
True) ->
          NumberedOutput -> Cli ()
Cli.respondNumbered (NumberedOutput -> Cli ()) -> NumberedOutput -> Cli ()
forall a b. (a -> b) -> a -> b
$
            ProjectName
-> ProjectAndBranch ProjectName ProjectBranchName -> NumberedOutput
Output.AmbiguousSwitch
              ProjectName
projectName
              (ProjectName
-> ProjectBranchName
-> ProjectAndBranch ProjectName ProjectBranchName
forall a b. a -> b -> ProjectAndBranch a b
ProjectAndBranch Project
currentProject.name ProjectBranchName
branchName)
    ProjectAndBranchNames'Unambiguous These ProjectName ProjectBranchName
projectAndBranchNames0 ->
      These ProjectName ProjectBranchName -> Cli ()
switchToProjectAndBranchByTheseNames These ProjectName ProjectBranchName
projectAndBranchNames0

switchToProjectAndBranchByTheseNames :: These ProjectName ProjectBranchName -> Cli ()
switchToProjectAndBranchByTheseNames :: These ProjectName ProjectBranchName -> Cli ()
switchToProjectAndBranchByTheseNames These ProjectName ProjectBranchName
projectAndBranchNames0 = do
  ProjectBranch
branch <- case These ProjectName ProjectBranchName
projectAndBranchNames0 of
    This ProjectName
projectName ->
      ((forall void. Output -> Transaction void)
 -> Transaction ProjectBranch)
-> Cli ProjectBranch
forall a.
((forall void. Output -> Transaction void) -> Transaction a)
-> Cli a
Cli.runTransactionWithRollback \forall void. Output -> Transaction void
rollback -> do
        Project
project <-
          ProjectName -> Transaction (Maybe Project)
Queries.loadProjectByName ProjectName
projectName Transaction (Maybe Project)
-> (Transaction (Maybe Project) -> Transaction Project)
-> Transaction Project
forall a b. a -> (a -> b) -> b
& Transaction Project
-> Transaction (Maybe Project) -> Transaction Project
forall (m :: * -> *) a. Monad m => m a -> m (Maybe a) -> m a
onNothingM do
            Output -> Transaction Project
forall void. Output -> Transaction void
rollback (ProjectName -> Output
Output.LocalProjectDoesntExist ProjectName
projectName)
        ProjectId -> Transaction (Maybe ProjectBranchId)
Queries.loadMostRecentBranch (Project
project Project -> Getting ProjectId Project ProjectId -> ProjectId
forall s a. s -> Getting a s a -> a
^. Getting ProjectId Project ProjectId
#projectId) Transaction (Maybe ProjectBranchId)
-> (Maybe ProjectBranchId -> Transaction ProjectBranch)
-> Transaction ProjectBranch
forall a b. Transaction a -> (a -> Transaction b) -> Transaction b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
          Maybe ProjectBranchId
Nothing -> do
            let branchName :: ProjectBranchName
branchName = forall source target.
(HasCallStack, TryFrom source target, Show source, Typeable source,
 Typeable target) =>
source -> target
unsafeFrom @Text Text
"main"
            ProjectBranch
branch <-
              ProjectId -> ProjectBranchName -> Transaction (Maybe ProjectBranch)
Queries.loadProjectBranchByName Project
project.projectId ProjectBranchName
branchName Transaction (Maybe ProjectBranch)
-> (Transaction (Maybe ProjectBranch) -> Transaction ProjectBranch)
-> Transaction ProjectBranch
forall a b. a -> (a -> b) -> b
& Transaction ProjectBranch
-> Transaction (Maybe ProjectBranch) -> Transaction ProjectBranch
forall (m :: * -> *) a. Monad m => m a -> m (Maybe a) -> m a
onNothingM do
                Output -> Transaction ProjectBranch
forall void. Output -> Transaction void
rollback (ProjectAndBranch ProjectName ProjectBranchName -> Output
Output.LocalProjectBranchDoesntExist (ProjectName
-> ProjectBranchName
-> ProjectAndBranch ProjectName ProjectBranchName
forall a b. a -> b -> ProjectAndBranch a b
ProjectAndBranch ProjectName
projectName ProjectBranchName
branchName))
            pure ProjectBranch
branch
          Just ProjectBranchId
branchId -> ProjectId -> ProjectBranchId -> Transaction ProjectBranch
Queries.expectProjectBranch Project
project.projectId ProjectBranchId
branchId
    These ProjectName ProjectBranchName
_ -> do
      projectAndBranchNames :: ProjectAndBranch ProjectName ProjectBranchName
projectAndBranchNames@(ProjectAndBranch ProjectName
projectName ProjectBranchName
branchName) <- These ProjectName ProjectBranchName
-> Cli (ProjectAndBranch ProjectName ProjectBranchName)
ProjectUtils.hydrateNames These ProjectName ProjectBranchName
projectAndBranchNames0
      ((forall void. Output -> Transaction void)
 -> Transaction ProjectBranch)
-> Cli ProjectBranch
forall a.
((forall void. Output -> Transaction void) -> Transaction a)
-> Cli a
Cli.runTransactionWithRollback \forall void. Output -> Transaction void
rollback -> do
        ProjectBranch
branch <-
          ProjectName
-> ProjectBranchName -> Transaction (Maybe ProjectBranch)
Queries.loadProjectBranchByNames ProjectName
projectName ProjectBranchName
branchName Transaction (Maybe ProjectBranch)
-> (Transaction (Maybe ProjectBranch) -> Transaction ProjectBranch)
-> Transaction ProjectBranch
forall a b. a -> (a -> b) -> b
& Transaction ProjectBranch
-> Transaction (Maybe ProjectBranch) -> Transaction ProjectBranch
forall (m :: * -> *) a. Monad m => m a -> m (Maybe a) -> m a
onNothingM do
            Output -> Transaction ProjectBranch
forall void. Output -> Transaction void
rollback (ProjectAndBranch ProjectName ProjectBranchName -> Output
Output.LocalProjectBranchDoesntExist ProjectAndBranch ProjectName ProjectBranchName
projectAndBranchNames)
        pure ProjectBranch
branch
  ProjectAndBranch ProjectId ProjectBranchId -> Cli ()
Cli.switchProject (ProjectId
-> ProjectBranchId -> ProjectAndBranch ProjectId ProjectBranchId
forall a b. a -> b -> ProjectAndBranch a b
ProjectAndBranch (ProjectBranch
branch ProjectBranch
-> Getting ProjectId ProjectBranch ProjectId -> ProjectId
forall s a. s -> Getting a s a -> a
^. Getting ProjectId ProjectBranch ProjectId
#projectId) (ProjectBranch
branch ProjectBranch
-> Getting ProjectBranchId ProjectBranch ProjectBranchId
-> ProjectBranchId
forall s a. s -> Getting a s a -> a
^. Getting ProjectBranchId ProjectBranch ProjectBranchId
#branchId))