content: {-
    Copyright (c) Meta Platforms, Inc. and affiliates.
    All rights reserved.
  
    This source code is licensed under the BSD-style license found in the
    LICENSE file in the root directory of this source tree.
  -}
  
  module Glean.Glass.Neighborhood ( searchNeighborhood) where
  
  import Data.Ord ( comparing )
  import Data.Text ( Text )
  import qualified Data.Map.Strict as Map
  
  import Util.List ( uniq, uniqBy )
  
  import Glean.Haxl.Repos as Glean ( withRepo, RepoHaxl, ReposHaxl )
  
  import qualified Glean.Schema.Code.Types as Code
  
  import Glean.Glass.Utils ( fst4 )
  import Glean.Glass.Describe
      ( mkSymbolDescription, mkBriefSymbolDescription )
  import Glean.Glass.Repos ( Language(Language_Hack), ScmRevisions )
  import Glean.Glass.Types
      ( SymbolId(SymbolId),
        RelatedNeighborhoodRequest(..),
        SymbolDescription,
        RelatedNeighborhoodResult(..),
        InheritedSymbols(..),
        RelatedSymbols(RelatedSymbols),
        RelationDirection(RelationDirection_Parent,
                          RelationDirection_Child),
        RelationType(RelationType_Extends, RelationType_Contains,
                     RelationType_RequireExtends, RelationType_RequireImplements,
                     RelationType_RequireClass),
        RepoName,
        SymbolBasicDescription,
        SymbolDescription(..) )
  import Glean.Glass.Search as Search
      ( CodeEntityLocation(CodeEntityLocation, entityName, entityRange,
                           entityFile, entity),
        SearchEntity(..) )
  import Glean.Glass.SearchRelated (InheritedContainer, LocatedEntity)
  import qualified Glean.Glass.SearchRelated as Search
  import Glean.Glass.Search.Class ( ResultLocation )
  
  type SearchRelatedQuery u w = Code.Entity -> RepoName
        -> RepoHaxl u w [Search.RelatedLocatedEntities]
  type SearchRelatedList u w = Code.Entity -> RepoName
        -> RepoHaxl u w [Search.LocatedEntity]
  
  -- being careful to keep things with clear data dependencies
  searchNeighborhood
    :: Int
    -> RelatedNeighborhoodRequest
    -> SymbolId
    -> RepoName
    -> ScmRevisions -> Language -> SearchEntity (ResultLocation Code.Entity)
    -> ReposHaxl u w RelatedNeighborhoodResult
  searchNeighborhood limit
      RelatedNeighborhoodRequest{..} sym repo scmRevs lang baseEntity =
    withRepo (entityRepo baseEntity) $ do
      let entity = fst4 $ decl baseEntity
      a <- childrenContains1Level
            (fromIntegral relatedNeighborhoodRequest_children_limit) entity repo
      b <- childrenExtends1Level
            (fromIntegral relatedNeighborhoodRequest_inherited_limit) entity repo
      c <- parentContainsNLevel
            (fromIntegral relatedNeighborhoodRequest_parent_depth) entity repo
      d <- parentExtends1Level limit entity repo
      e <- parentRequire RelationType_RequireExtends limit entity repo
      f <- parentRequire RelationType_RequireImplements limit entity repo
      g <- parentRequire RelationType_RequireClass limit entity repo
  
      (parents, members, inheritedSyms) <- inheritedSymbols lang limit
        (fromIntegral relatedNeighborhoodRequest_inherited_limit)
        (if relatedNeighborhoodRequest_hide_uninteresting
          then Search.HideUninteresting
          else Search.ShowAll)
        entity repo
      -- syms visible to the client, we need their full details
      let !syms = uniqBy (comparing snd) $ fromSearchEntity sym baseEntity :
              a ++ flattenEdges c ++ members
                -- full descriptions of final methods
      descriptions <- Map.fromAscList <$> mapM (mkDescribe repo scmRevs) syms
      -- brief descriptions for inherited things
      basics <- Map.fromAscList <$> mapM (mkBriefDescribe repo scmRevs)
        (uniqBy (comparing snd) $ (b ++ d ++ e ++ f ++ g) ++ parents)
  
      return $ RelatedNeighborhoodResult {
          relatedNeighborhoodResult_childrenContained = map snd a,
          relatedNeighborhoodResult_childrenExtended = map snd b,
          relatedNeighborhoodResult_containsParents = symbolIdPairs c,
          relatedNeighborhoodResult_parentsExtended = map snd d,
          relatedNeighborhoodResult_inheritedSymbols = inheritedSyms,
          relatedNeighborhoodResult_symbolDetails = descriptions,
          relatedNeighborhoodResult_symbolBasicDetails = basics,
          relatedNeighborhoodResult_requireExtends = map snd e,
          relatedNeighborhoodResult_requireImplements = map snd f,
          relatedNeighborhoodResult_requireClass = map snd g
        }
  
  -- building map of sym id -> descriptions, by first occurence
  mkDescribe
    :: RepoName
    -> ScmRevisions
    -> Search.LocatedEntity
    -> Glean.RepoHaxl u w (Text, SymbolDescription)
  mkDescribe repo scmRevs e@(_,SymbolId rawSymId) =
    (rawSymId,) <$> describe repo scmRevs e
  
  describe
     :: RepoName
    -> ScmRevisions
    -> Search.LocatedEntity
    -> Glean.RepoHaxl u w SymbolDescription
  describe repo scmRevs ((entity, entityFile, entityRange, entityName), symId)
    = mkSymbolDescription symId scmRevs repo CodeEntityLocation{..} Nothing
  
  -- for children by inheritance, we only need name, kind, parent name, sym id
  -- and signature. In particular, we don't e.g. need comments or annotations
  -- or type xrefs
  mkBriefDescribe
    :: RepoName
    -> ScmRevisions
    -> Search.LocatedEntity
    -> Glean.RepoHaxl u w (Text, SymbolBasicDescription)
  mkBriefDescribe repo scmRevs e@(_,SymbolId rawSymId) =
    (rawSymId,) <$> briefDescribe repo scmRevs e
  
  briefDescribe
    :: RepoName
    -> ScmRevisions
    -> Search.LocatedEntity
    -> Glean.RepoHaxl u w SymbolBasicDescription
  briefDescribe repo scmRevs
      ((entity, entityFile, entityRange, entityName), symId)
    = mkBriefSymbolDescription symId scmRevs repo CodeEntityLocation{..}
      Nothing
  
  childrenContains1Level :: Int -> SearchRelatedList u w
  childrenContains1Level limit baseEntity repo = map Search.childRL <$>
    Search.searchRelatedEntities
      limit
      Search.ShowAll
      Search.NotRecursive
      RelationDirection_Child
      RelationType_Contains
      baseEntity
      repo
  
  -- children by `extends`. typically a list. this includes method overrides
  -- note that some methods have a lot of overrides (50k+) so be careful with
  -- the limit values
  childrenExtends1Level :: Int -> SearchRelatedList u w
  childrenExtends1Level limit baseEntity repo = map Search.childRL <$>
    Search.searchRelatedEntities
      limit
      Search.ShowAll
      Search.NotRecursive
      RelationDirection_Child
      RelationType_Extends
      baseEntity
      repo
  
  -- Direct inheritance parents
  parentExtends1Level :: Int -> SearchRelatedList u w
  parentExtends1Level limit baseEntity repo = map Search.parentRL <$>
    Search.searchRelatedEntities
      limit
      Search.ShowAll
      Search.NotRecursive
      RelationDirection_Parent
      RelationType_Extends
      baseEntity
      repo
  
  -- N levels of container hierarchy
  parentContainsNLevel :: Int -> SearchRelatedQuery u w
  parentContainsNLevel limit baseEntity repo = Search.searchRelatedEntities
    limit
    Search.ShowAll
    Search.Recursive
    RelationDirection_Parent
    RelationType_Contains
    baseEntity
    repo
  
  --  Returns (recursively) parent entities, inherited members
  --  and inherited symbols of a container entity
  --
  --  If not directly available in DB computes an over approximation
  --  of inherited methods computed by unioning the contained children
  --  of inheritedLimit levels of extended parents
  inheritedSymbols
    :: Language -> Int -> Int -> Search.SearchStyle -> Code.Entity -> RepoName
    -> RepoHaxl u w ([LocatedEntity], [LocatedEntity], [InheritedSymbols])
  
  -- Optimized implementation for Hack: all members inherited into scope by parent
  inheritedSymbols Language_Hack
    memberLimit _inheritedLimit style entity repo = do
      inherited <- Search.searchInheritedEntities style memberLimit entity repo
      let parents = map fst inherited
          members = concatMap snd inherited
          inheritedSyms = map inheritedSymbolIdSets inherited
      return (parents, members, inheritedSyms)
  
  -- Fall back implementation: enumerate inherited parents, and find their members
  inheritedSymbols _ memberLimit inheritedLimit style entity repo = do
      topoEdges <- Search.searchRelatedEntities  -- "extend relationship"
        inheritedLimit
        style
        Search.Recursive
        RelationDirection_Parent
        RelationType_Extends
        entity
        repo
      let parents = uniq (map Search.parentRL topoEdges)
      inherited <- mapM (childrenOf memberLimit repo) parents
      let inheritedSyms = map inheritedSymbolIdSets inherited
      let members = concatMap snd inherited
      return (parents, members, inheritedSyms)
  
  -- Fetch the "required" parent entities
  -- This only exists for Hack entity so we can avoid
  -- useless queries for other languages
  parentRequire :: RelationType -> Int -> SearchRelatedList u w
  parentRequire relationType limit baseEntity repo =
    case baseEntity of
      Code.Entity_hack _ ->
          map Search.parentRL <$>
            Search.searchRelatedEntities
              limit
              Search.ShowAll
              Search.NotRecursive
              RelationDirection_Parent
              relationType
              baseEntity
              repo
      _ -> return []
  
  childrenOf
    :: Int -> RepoName -> Search.LocatedEntity
    -> Glean.RepoHaxl u w (Search.LocatedEntity, [Search.LocatedEntity])
  childrenOf limit repo parent = (parent,) <$>
    childrenContains1Level limit (toEntity parent) repo
  
  toEntity :: Search.LocatedEntity -> Code.Entity
  toEntity ((entity, _file, _rangespan, _name), _symId) = entity
  
  symbolIdPairs :: [Search.RelatedLocatedEntities] -> [RelatedSymbols]
  symbolIdPairs = map (\Search.RelatedLocatedEntities{..} ->
    RelatedSymbols (snd parentRL) (snd childRL) Nothing)
  
  inheritedSymbolIdSets :: InheritedContainer -> InheritedSymbols
  inheritedSymbolIdSets (parent, children) = InheritedSymbols {
      inheritedSymbols_base = snd parent,
      inheritedSymbols_provides = map snd children
    }
  
  fromSearchEntity
    :: SymbolId
    -> SearchEntity (ResultLocation Code.Entity)
    -> Search.LocatedEntity
  fromSearchEntity symId SearchEntity{..} = (decl, symId)
  
  flattenEdges :: [Search.RelatedLocatedEntities] -> [Search.LocatedEntity]
  flattenEdges pairs = concat
    [ [ e1, e2 ] | Search.RelatedLocatedEntities e1 e2 <- pairs ]
  