{-# LANGUAGE NamedFieldPuns #-}

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

import Control.Lens                  (universeOf)
import Data.Maybe                    (maybeToList)
import Data.Text                     (Text)
import Dhall.Core
    ( Binding (..)
    , Expr (..)
    , Import
    , MultiLet (..)
    )
import Dhall.LSP.Backend.Diagnostics
import Dhall.Parser                  (Src)

import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Maybe         as Maybe
import qualified Dhall.Core         as Core
import qualified Dhall.Lint         as Lint

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 Core.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, Maybe s) ]
unusedBindings (MultiLet bindings d) =
  let go bs@(Binding { variable = var, value } : _)
          | Just _ <- Lint.removeUnusedBindings (Core.wrapInLets bs d) =
              [ (var, maybeSrc) ]
        where
          maybeSrc = case value of
              Note src _ -> Just src
              _          -> Nothing
      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 adapt (unusedBindings (Core.multiLet b e))
  where
    adapt (var, maybeSrc) =
        Suggestion (rangeFromDhall finalSrc) ("Unused let binding '" <> var <> "'")
      where
        finalSrc = Maybe.fromMaybe src maybeSrc
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 Core.subExpressions expr ]