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.Shell.Index
    ( indexCmd
    , pickHash
    , load
    , create
    ) where
  
  import Options.Applicative as OptParse
  import Control.Exception
  import Control.Monad
  import Control.Monad.Catch
  import Control.Monad.IO.Class
  import Control.Monad.Trans.Control
  import Data.Aeson as Aeson
  import qualified Data.Aeson.Types as Aeson
  import qualified Data.ByteString as B
  import Data.Default
  import Data.Text (Text)
  import qualified Data.Text as Text
  import Foreign.CPP.Dynamic
  import System.Directory
  import System.FilePath
  import System.IO.Temp
  import TextShow
  
  import qualified Glean
  import Glean.Shell.Types
  import Glean.Util.Some
  import Glean.Write
  
  import Glean.Indexer
  import Glean.Indexer.List
  
  indexCmd :: String -> Eval ()
  indexCmd str = do
    let parser = (,)
          <$> cmdLineParser
          <*> (strArgument (metavar "ROOT"))
  
        result = execParserPure defaultPrefs (info parser fullDesc)
          (words str {- TODO: quoting -})
  
    (runIndexer, root) <- case result of
      OptParse.Success a -> return a
      OptParse.Failure f -> throwM $ ErrorCall $ fst $ renderFailure f ":index"
      OptParse.CompletionInvoked{} -> throwM $ ErrorCall "CompletionInvoked"
  
    projectRoot <- liftIO $ getCurrentDirectory
    withSystemTempDirectory' "glean-shell" $ \tmp -> do
      let name = Text.pack (takeBaseName (dropTrailingPathSeparator root))
      hash <- pickHash name
      let repo = Glean.Repo name hash
      withBackend $ \backend -> do
        let exists = throwIO (ErrorCall (show repo <> ": already exists"))
        liftIO $ Glean.fillDatabase backend repo Nothing exists $
          runIndexer (Some backend) repo
            IndexerParams {
              indexerRoot = root,
              indexerProjectRoot = projectRoot,
              indexerOutput = tmp,
              indexerGroup = ""
            }
      setRepo repo
  
  withSystemTempDirectory' :: String -> (FilePath -> Eval a) -> Eval a
  withSystemTempDirectory' str action = Eval $ do
    a <- liftWith $ \run -> withSystemTempDirectory str (run . unEval . action)
    restoreT $ return a
  
  pickHash :: Text -> Eval Text
  pickHash name = withBackend $ \be -> do
    r <- liftIO $ Glean.listDatabases be def
    let
      hashes =
        [ repo_hash
        | Glean.Database{..} <- Glean.listDatabasesResult_databases r
        , let Glean.Repo{..} = database_repo
        , repo_name == name
        ]
    return $ head $ filter (`notElem` hashes) $ map showt [0::Int ..]
  
  -- | Load a set of JSON files as a DB
  load :: Glean.Repo -> [FilePath] -> Eval ()
  load repo files = withBackend $ \be ->  liftIO $ do
    let onExisting  = throwIO $ ErrorCall "database already exists"
    -- load all files first
    parsedFiles <- forM files $ \file -> do
        r <- Foreign.CPP.Dynamic.parseJSON =<< B.readFile file
        val <- either (throwIO  . ErrorCall . Text.unpack) return r
        case Aeson.parse parseJsonFactBatches val of
          Error str -> throwIO $ ErrorCall str
          Aeson.Success x -> return x
    -- then, send them to the server
    Glean.fillDatabase be repo Nothing onExisting $
      forM_ parsedFiles $ \(batches, schema_id) -> do
        let opts = schemaIdToOpts schema_id
        Glean.sendJsonBatch be repo batches opts
  
  create :: Glean.Repo -> Eval ()
  create repo = withBackend $ \be ->  liftIO $ do
    exists <- Glean.create be repo Nothing
    when exists $ throwIO $ ErrorCall "database already exists"
  