-- |
--
-- 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.Completion
  ( -- * A Note About Completion
    -- $use

    -- * Completion Function
    CompletionFunc,
    Completion (..),

    -- * Completion Helpers
    CompLoc (..),
    completionFromList,

    -- * Setting the Active Completion Function
    pushCompletionFunction,
    popCompletionFunction,
  )
where

import Byline.Internal.Completion
import Byline.Internal.Eval (MonadByline (..))
import qualified Byline.Internal.Prim as Prim
import Data.Char (isSpace)
import qualified Data.Text as Text

-- | Add a 'CompletionFunc' to the stack.
--
-- @since 1.0.0.0
pushCompletionFunction :: MonadByline m => CompletionFunc IO -> m ()
pushCompletionFunction = Prim.pushCompFunc >>> liftByline

-- | Remove the top completion function from the stack.
--
-- @since 1.0.0.0
popCompletionFunction :: MonadByline m => m ()
popCompletionFunction = liftByline Prim.popCompFunc

-- | Type to describe where completions are allowed.
--
-- @since 1.1.0.0
data CompLoc
  = -- | Only complete the first word of input.
    CompHead
  | -- | Complete any word except the first.
    CompTail
  | -- | Perform completion anywhere in the input.
    CompAny

-- | Generate a completion function that uses the given list as the
-- completion candidates.
--
-- @since 1.1.0.0
completionFromList ::
  forall m.
  Applicative m =>
  -- | Where to allow completion.
  CompLoc ->
  -- | List of completion candidates.
  [Text] ->
  -- | The generated completion function.
  CompletionFunc m
completionFromList loc ts (left, right) =
  case loc of
    CompHead ->
      if Text.null left || Text.all (isSpace >>> not) left
        then go (left, right)
        else pure (mempty, mempty)
    CompTail ->
      if Text.any isSpace left
        then completeLastWord (left, right)
        else pure (mempty, mempty)
    CompAny ->
      completeLastWord (left, right)
  where
    go :: CompletionFunc m
    go (left, _) =
      if Text.null left
        then pure ("", completions ts)
        else pure ("", completions (filter (Text.isPrefixOf left) ts))
    completeLastWord :: CompletionFunc m
    completeLastWord (left, right) =
      let word = Text.takeWhileEnd (isSpace >>> not) left
          prefix = Text.dropEnd (Text.length word) left
       in go (word, right) <&> first (const prefix)
    completions :: [Text] -> [Completion]
    completions = map (\t -> Completion t t True)

-- $use
--
-- Haskeline makes it very difficult (if not impossible) to implement
-- a completion function that runs in an arbitrary monad.  More
-- information can be found in the documentation for 'CompletionFunc'.