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.
  -}
  
  {-# OPTIONS_GHC -Wno-orphans #-}
  {-# LANGUAGE TypeApplications #-}
  
  module Glean.Glass.SymbolId.Python () where
  
  import Data.Text (Text, splitOn)
  import Data.List (nub)
  import Data.Maybe ( maybeToList )
  
  import qualified Glean
  import Glean.Angle as Angle
  import qualified Glean.Haxl.Repos as Glean
  import qualified Glean.Schema.Python.Types as Py
  import Glean.Util.ToAngle
  
  import Glean.Glass.SymbolId.Class
  import Glean.Glass.Types (Name(..))
  import Glean.Glass.Utils ( fetchData )
  
  instance Symbol Py.Name where
    toSymbol k = do
      v <- Glean.keyOf k
      return [v]
  
  instance Symbol Py.Declaration where
    toSymbol d = do
      locations <- maybeToList <$> fetchData (declarationLocation $ toAngle d)
      let loc = case nub $ map (head . splitOn "/") locations of
                [location] -> location
                _ -> "."
      sym <- case d of
        Py.Declaration_cls x -> toSymbolPredicate x
        Py.Declaration_func x -> toSymbolPredicate x
        Py.Declaration_module x -> toSymbolPredicate x
        Py.Declaration_variable x -> toSymbolPredicate x
        Py.Declaration_imp x -> toSymbolPredicate x
        Py.Declaration_EMPTY -> return []
      return $ loc:sym
  
  instance Symbol Py.ImportStatement_key where
    toSymbol (Py.ImportStatement_key _ asName) = toSymbol asName
  
  instance Symbol Py.VariableDeclaration_key where
    toSymbol (Py.VariableDeclaration_key n) = toSymbol n
  
  instance Symbol Py.FunctionDeclaration_key where
    toSymbol (Py.FunctionDeclaration_key n) = toSymbol n
  
  instance Symbol Py.ClassDeclaration_key where
    toSymbol (Py.ClassDeclaration_key n _) = toSymbol n
  
  instance Symbol Py.Module_key where
    toSymbol (Py.Module_key n) = toSymbol n
  
  declarationLocation :: Angle Py.Declaration -> Angle Text
  declarationLocation decl = var $ \file ->
    file `where_` [
      wild .= predicate @Py.DeclarationLocation (
        rec $
          field @"declaration" decl $
          field @"file" (sig file)
        end)
    ]
  
  instance ToQName Py.Declaration where
    toQName (Py.Declaration_cls x) = Glean.keyOf x >>= toQName
    toQName (Py.Declaration_func x) = Glean.keyOf x >>= toQName
    toQName (Py.Declaration_module x) = Glean.keyOf x >>= toQName
    toQName (Py.Declaration_variable x) = Glean.keyOf x >>= toQName
    toQName (Py.Declaration_imp x) = Glean.keyOf x >>= toQName
    toQName Py.Declaration_EMPTY = return $ Left "unknown Declaration"
  
  instance ToQName Py.ImportStatement_key where
    toQName (Py.ImportStatement_key _ asName) = toQName asName
  
  instance ToQName Py.VariableDeclaration_key where
    toQName (Py.VariableDeclaration_key x) = toQName x
  
  instance ToQName Py.FunctionDeclaration_key where
    toQName (Py.FunctionDeclaration_key x) = toQName x
  
  instance ToQName Py.ClassDeclaration_key where
    toQName (Py.ClassDeclaration_key x _) = toQName x
  
  instance ToQName Py.Module_key where
    toQName (Py.Module_key x) = toQName x
  
  instance ToQName Py.Name where
    toQName name = do
      let nameId = Glean.getId name
      (mr :: Maybe (Py.Name, Py.Name)) <- fetchData $ qNameQuery nameId
      case mr of
        Nothing -> Left . ("No qualified names found for " <>) <$>
          Glean.keyOf name
        Just (local, container) ->
          fmap Right $ (,) <$> pyNameToName local <*> pyNameToName container
  
  pyNameToName :: Py.Name -> Glean.RepoHaxl u w Name
  pyNameToName name = Name <$> Glean.keyOf name
  
  qNameQuery :: Glean.IdOf Py.Name -> Angle (Py.Name, Py.Name)
  qNameQuery nameId = vars $
    \ (lname :: Angle Py.Name)
      (pname :: Angle Py.Name)
      (psname :: Angle Py.SName)
      (maybe_psname :: Angle (Maybe Py.SName)) ->
      tuple (lname, pname) `where_` [
          wild .= predicate @Py.NameToSName (factId nameId
            .-> predicate @Py.SName (rec $
                field @"local_name" (asPredicate lname) $
                field @"parent" maybe_psname
              end))
        , wild .=
            or_ [ just psname .= maybe_psname,
                  wild .= (predicate @Py.SNameToName $ psname .-> pname) ]
                [ nothing .= maybe_psname, pname .= predicate @Py.Name "" ]
        ]
  