{-# OPTIONS_HADDOCK hide #-}

-- |
--
-- Copyright:
--   This file is part of the package byline. It is subject to the
--   license terms in the LICENSE file found in the top-level
--   directory of this distribution and at:
--
--     https://github.com/pjones/byline
--
--   No part of this package, including this file, may be copied,
--   modified, propagated, or distributed except according to the
--   terms contained in the LICENSE file.
--
-- License: BSD-2-Clause
module Byline.Internal.Completion
  ( CompletionFunc,
    Completion (..),
    runCompletionFunction,
    runCompletionFunctions,
  )
where

import qualified Data.Text as Text
import qualified System.Console.Haskeline.Completion as Haskeline

-- | A completion function modeled after the one used in Haskeline.
--
-- /Warning:/ If you're familiar with the Haskeline version of the
-- @CompletionFunc@ type please be sure to read this description
-- carefully since the two behave differently.
--
-- The completion function is called when the user presses the tab
-- key.  The current input line is split into two parts based on where
-- the cursor is positioned.  Text to the left of the cursor will be
-- the first value in the tuple and text to the right of the cursor
-- will be the second value.
--
-- The text returned from the completion function is the text from the
-- left of the cursor which wasn't used in the completion.  It should
-- also produce a list of possible 'Completion' values.
--
-- In Haskeline, some of these text values are reversed.  This is
-- /not/ the case in Byline.
--
-- /A note about @IO@:/
--
-- Due to Haskeline, the completion function is forced to return an
-- @IO@ value.  It would be better if it could return a value in the
-- base monad instead but it doesn't look like that's possible.
-- Patches welcome.
--
-- @since 1.0.0.0
type CompletionFunc m = (Text, Text) -> m (Text, [Completion])

-- | A type representing a completion match to the user's input.
--
-- @since 1.0.0.0
data Completion = Completion
  { -- | Text to insert to the right of the cursor.
    replacement :: Text,
    -- | Text to display when listing all completions.
    display :: Text,
    -- | Whether to follow the completed word with a
    --  terminating space or close existing quotes.
    isFinished :: Bool
  }
  deriving (Eq, Ord, Show)

-- | Convert a Byline completion result into a Haskeline completion result.
--
-- @since 1.0.0.0
convertCompletion :: Completion -> Haskeline.Completion
convertCompletion (Completion r d i) =
  Haskeline.Completion
    { Haskeline.replacement = toString r,
      Haskeline.display = toString d,
      Haskeline.isFinished = i
    }

-- | Adapt a completion function so it works with Haskeline.
--
-- @since 1.0.0.0
runCompletionFunction ::
  Monad m =>
  CompletionFunc m ->
  Haskeline.CompletionFunc m
runCompletionFunction comp (left, right) = do
  (output, completions) <-
    comp
      ( Text.reverse $ toText left,
        toText right
      )
  pure
    ( toString $ Text.reverse output,
      map convertCompletion completions
    )

-- | Run a list of completion functions, returning the results of the
-- first function that produced any.
--
-- @since 1.0.0.0
runCompletionFunctions ::
  forall m.
  Monad m =>
  [CompletionFunc m] ->
  Haskeline.CompletionFunc m
runCompletionFunctions fs input =
  foldlM go (mempty, mempty) fs
  where
    go ::
      (String, [Haskeline.Completion]) ->
      CompletionFunc m ->
      m (String, [Haskeline.Completion])
    go prev f = case prev of
      (_, []) -> runCompletionFunction f input
      _ -> pure prev