module Unison.Codebase.Editor.HandleInput.EditDependents
  ( handleEditDependents,
  )
where

import Control.Monad.Reader (ask)
import Data.Set qualified as Set
import U.Codebase.Sqlite.Operations qualified as Operations
import Unison.Cli.Monad (Cli)
import Unison.Cli.Monad qualified as Cli
import Unison.Cli.MonadUtils qualified as Cli
import Unison.Cli.NameResolutionUtils (resolveHQName)
import Unison.Codebase.Branch qualified as Branch
import Unison.Codebase.Branch.Names qualified as Branch
import Unison.Codebase.Editor.HandleInput.EditNamespace (getNamesForEdit)
import Unison.Codebase.Editor.HandleInput.ShowDefinition (showDefinitions)
import Unison.Codebase.Editor.Input (OutputLocation (..), RelativeToFold (..))
import Unison.Codebase.Editor.Output qualified as Output
import Unison.ConstructorReference qualified as ConstructorReference
import Unison.HashQualified qualified as HQ
import Unison.Name (Name)
import Unison.Names (Names (..))
import Unison.Prelude
import Unison.PrettyPrintEnv.Names qualified as PPE
import Unison.PrettyPrintEnvDecl qualified as PPED
import Unison.Reference (TermReference, TypeReference)
import Unison.Reference qualified as Reference
import Unison.Referent qualified as Referent
import Unison.Util.Defns (Defns (..), DefnsF)
import Unison.Util.Defns qualified as Defns
import Unison.Util.Relation qualified as Relation

handleEditDependents :: HQ.HashQualified Name -> Cli ()
handleEditDependents :: HashQualified Name -> Cli ()
handleEditDependents HashQualified Name
name = do
  -- Get all of the referents and type references this name refers to
  refs0 <- HashQualified Name -> Cli (DefnsF Set Referent TermReference)
resolveHQName HashQualified Name
name

  -- Since we don't track constructor dependents precisely, convert to just the term and type references
  let refs :: DefnsF Set TermReference TypeReference
      refs =
        let f :: Referent -> DefnsF Set TermReference TermReference
f = \case
              Referent.Con ConstructorReference
ref ConstructorType
_ -> Set TermReference -> DefnsF Set TermReference TermReference
forall terms types. Monoid terms => types -> Defns terms types
Defns.fromTypes (TermReference -> Set TermReference
forall a. a -> Set a
Set.singleton (ConstructorReference
ref ConstructorReference
-> Getting TermReference ConstructorReference TermReference
-> TermReference
forall s a. s -> Getting a s a -> a
^. Getting TermReference ConstructorReference TermReference
forall r s (f :: * -> *).
Functor f =>
(r -> f s)
-> GConstructorReference r -> f (GConstructorReference s)
ConstructorReference.reference_))
              Referent.Ref TermReference
ref -> Set TermReference -> DefnsF Set TermReference TermReference
forall types terms. Monoid types => terms -> Defns terms types
Defns.fromTerms (TermReference -> Set TermReference
forall a. a -> Set a
Set.singleton TermReference
ref)
         in Set TermReference
-> Set TermReference -> DefnsF Set TermReference TermReference
forall terms types. terms -> types -> Defns terms types
Defns Set TermReference
forall a. Set a
Set.empty DefnsF Set Referent TermReference
refs0.types DefnsF Set TermReference TermReference
-> DefnsF Set TermReference TermReference
-> DefnsF Set TermReference TermReference
forall a. Semigroup a => a -> a -> a
<> (Referent -> DefnsF Set TermReference TermReference)
-> Set Referent -> DefnsF Set TermReference TermReference
forall m a. Monoid m => (a -> m) -> Set a -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap Referent -> DefnsF Set TermReference TermReference
f DefnsF Set Referent TermReference
refs0.terms

  (ppe, types, terms) <-
    Cli.withRespondRegion \Output -> Cli ()
respondRegion -> do
      Output -> Cli ()
respondRegion (Pretty ColorText -> Output
Output.Literal Pretty ColorText
"Loading branch...")

      -- Load the current project namespace and throw away the libdeps
      branch <- Cli (Branch0 IO)
Cli.getCurrentBranch0
      let ppe =
            let names :: Names
names = Branch0 IO -> Names
forall (m :: * -> *). Branch0 m -> Names
Branch.toNames Branch0 IO
branch
             in Namer -> Suffixifier -> PrettyPrintEnvDecl
PPED.makePPED (Int -> Names -> Namer
PPE.hqNamer Int
10 Names
names) (Names -> Suffixifier
PPE.suffixifyByHashName Names
names)

      -- Throw away the libdeps
      let branchWithoutLibdeps = Branch0 IO -> Branch0 IO
forall (m :: * -> *). Branch0 m -> Branch0 m
Branch.deleteLibdeps Branch0 IO
branch

      -- Identify the local dependents of the input name
      respondRegion (Output.Literal "Identifying dependents...")
      dependents <-
        Cli.runTransaction do
          Operations.transitiveDependentsWithinScope
            (Branch.deepDefnsIds branchWithoutLibdeps)
            refs

      let refsAndDependents =
            Defns
              { terms :: Set Referent
terms =
                  [Set Referent] -> Set Referent
forall (f :: * -> *) a. (Foldable f, Ord a) => f (Set a) -> Set a
Set.unions
                    [ (TermReference -> Referent) -> Set TermReference -> Set Referent
forall a b. (a -> b) -> Set a -> Set b
Set.mapMonotonic TermReference -> Referent
Referent.fromTermReference DefnsF Set TermReference TermReference
refs.terms,
                      (TermReferenceId -> Referent)
-> Set TermReferenceId -> Set Referent
forall a b. (a -> b) -> Set a -> Set b
Set.mapMonotonic TermReferenceId -> Referent
Referent.fromTermReferenceId DefnsF Set TermReferenceId TermReferenceId
dependents.terms
                    ],
                types :: Set TermReference
types =
                  [Set TermReference] -> Set TermReference
forall (f :: * -> *) a. (Foldable f, Ord a) => f (Set a) -> Set a
Set.unions
                    [ DefnsF Set TermReference TermReference
refs.types,
                      (TermReferenceId -> TermReference)
-> Set TermReferenceId -> Set TermReference
forall a b. (a -> b) -> Set a -> Set b
Set.mapMonotonic TermReferenceId -> TermReference
Reference.fromId DefnsF Set TermReferenceId TermReferenceId
dependents.types
                    ]
              }

      respondRegion (Output.Literal "Loading dependents...")
      env <- ask
      (types, terms) <-
        Cli.runTransaction
          ( getNamesForEdit
              env.codebase
              ppe
              Names
                { terms =
                    branchWithoutLibdeps
                      & Branch.deepTerms
                      & Relation.restrictDom refsAndDependents.terms
                      & Relation.swap,
                  types =
                    branchWithoutLibdeps
                      & Branch.deepTypes
                      & Relation.restrictDom refsAndDependents.types
                      & Relation.swap
                }
          )
      pure (ppe, types, terms)

  let misses = []
  showDefinitions (LatestFileLocation WithinFold) (const True) ppe terms types misses