module Dhall.LSP.Backend.Linting
  ( Suggestion(..)
  , suggest
  , Dhall.lint
  )
where

import Dhall.Parser (Src)
import Dhall.Core ( Expr(..), Binding(..), MultiLet(..), Import
                  , subExpressions, multiLet, wrapInLets)
import qualified Dhall.Lint as Dhall

import Dhall.LSP.Backend.Diagnostics

import qualified Data.List.NonEmpty as NonEmpty
import Data.Maybe (maybeToList)
import Data.Monoid ((<>))
import Data.Text (Text)
import Control.Lens (universeOf)

data Suggestion = Suggestion {
    range :: Range,
    suggestion :: Text
    }

-- Diagnose nested let-blocks.
--
-- Pattern matching on a 'Let' wrapped in a 'Note' prevents us from repeating
-- the search beginning at different @let@s in the same let-block – only
-- the outermost 'Let' of a let-block is wrapped in a 'Note'.
diagLetInLet :: Expr Src a -> Maybe Suggestion
diagLetInLet (Note _ (Let b e)) = case multiLet b e of
    MultiLet _ (Note src (Let {})) ->
      Just (Suggestion (rangeFromDhall src) "Superfluous 'in' before nested let binding")
    _ -> Nothing
diagLetInLet _ = Nothing

-- Given a let-block compute all unused variables in the block.
unusedBindings :: Eq a => MultiLet s a -> [Text]
unusedBindings (MultiLet bindings d) =
  let go bs@(Binding { variable = var} : _) | Just _ <- Dhall.removeUnusedBindings (wrapInLets bs d) = [var]
      go _ = []
  in foldMap go (NonEmpty.tails bindings)

-- Diagnose unused let bindings.
diagUnusedBindings :: Eq a => Expr Src a -> [Suggestion]
diagUnusedBindings (Note src (Let b e)) = map
  (\var ->
    Suggestion (rangeFromDhall src) ("Unused let binding '" <> var <> "'"))
  (unusedBindings (multiLet b e))
diagUnusedBindings _ = []

-- | Given an dhall expression suggest all the possible improvements that would
--   be made by the linter.
suggest :: Expr Src Import -> [Suggestion]
suggest expr = concat [ maybeToList (diagLetInLet e) ++ diagUnusedBindings e
                      | e <- universeOf subExpressions expr ]