module Language.PureScript.Ide.CompletionSpec where

import Protolude

import Language.PureScript qualified as P
import Language.PureScript.Ide.Test as Test
import Language.PureScript.Ide.Command as Command
import Language.PureScript.Ide.Completion (CompletionOptions(..), applyCompletionOptions, defaultCompletionOptions)
import Language.PureScript.Ide.Filter.Declaration qualified as DeclarationType
import Language.PureScript.Ide.Types (Completion(..), IdeDeclarationAnn, Match(..), Success(..))
import Test.Hspec (Spec, describe, it, shouldBe, shouldMatchList, shouldSatisfy)

reexportMatches :: [Match IdeDeclarationAnn]
reexportMatches =
  map (\d -> Match (mn "A", d)) moduleA
  ++ map (\d -> Match (mn "B", d)) moduleB
  where
    moduleA = [ideKind "Kind"]
    moduleB = [ideKind "Kind" `annExp` "A"]

matches :: [(Match IdeDeclarationAnn, [P.ModuleName])]
matches = map (\d -> (Match (mn "Main", d), [mn "Main"])) [ ideKind "Kind", ideType "Type" Nothing [] ]

typ :: Text -> Command
typ txt = Type txt [] Nothing

load :: [Text] -> Command
load = LoadSync . map Test.mn

spec :: Spec
spec = describe "Applying completion options" $ do
  it "keeps all matches if maxResults is not specified" $ do
    applyCompletionOptions (defaultCompletionOptions { coMaxResults = Nothing })
      (map fst matches) `shouldMatchList` matches
  it "keeps only the specified amount of maxResults" $ do
    applyCompletionOptions (defaultCompletionOptions { coMaxResults = Just 1 })
      (map fst matches) `shouldMatchList` take 1 matches
  it "groups reexports for a single identifier" $ do
    applyCompletionOptions (defaultCompletionOptions { coGroupReexports = True })
      reexportMatches `shouldBe` [(Match (mn "A", ideKind "Kind"), [mn "A", mn "B"])]

  it "gets simple docs on definition itself" $ do
    ([_, Right (CompletionResult [ result ])], _) <- Test.inProject $
      Test.runIde [ load ["CompletionSpecDocs"]
                  , typ "something"
                  ]
    result `shouldSatisfy` \res -> complDocumentation res == Just "Doc x\n"

  it "gets multiline docs" $ do
    ([_, Right (CompletionResult [ result ])], _) <- Test.inProject $
      Test.runIde [ load ["CompletionSpecDocs"]
                  , typ "multiline"
                  ]
    result `shouldSatisfy` \res -> complDocumentation res == Just "This is\na multi-line\ncomment\n"

  it "gets simple docs on type annotation" $ do
    ([_, Right (CompletionResult [ result ])], _) <- Test.inProject $
      Test.runIde [ load ["CompletionSpecDocs"]
                  , typ "withType"
                  ]
    result `shouldSatisfy` \res -> complDocumentation res == Just "Doc *123*\n"

  it "gets docs on module declaration" $ do
    ([_, Right (CompletionResult [ result ])], _) <- Test.inProject $
      Test.runIde [ load ["CompletionSpecDocs"]
                  , typ "CompletionSpecDocs"
                  ]
    result `shouldSatisfy` \res -> complDocumentation res == Just "Module Documentation\n"

  it "gets docs on type class declaration" $ do
    ([_, Right (CompletionResult [ result ])], _) <- Test.inProject $
      Test.runIde [ load ["CompletionSpecDocs"]
                  , typ "DocClass"
                  ]
    result `shouldSatisfy` \res -> complDocumentation res == Just "Doc for class\n"

  it "gets docs on type class members" $ do
    ([_, Right (CompletionResult [ result ])], _) <- Test.inProject $
      Test.runIde [ load ["CompletionSpecDocs"]
                  , typ "member"
                  ]
    result `shouldSatisfy` \res -> complDocumentation res == Just "doc for member\n"

  it "includes declarationType in completions for values" $ do
    ([_, Right (CompletionResult [ result ])], _) <- Test.inProject $
      Test.runIde [ load ["CompletionSpec"]
                  , typ "exampleValue"
                  ]
    result `shouldSatisfy` \res ->
      complDeclarationType res == Just DeclarationType.Value

  it "includes declarationType in completions for functions" $ do
    ([_, Right (CompletionResult [ result ])], _) <- Test.inProject $
      Test.runIde [ load ["CompletionSpec"]
                  , typ "exampleFunction"
                  ]
    result `shouldSatisfy` \res ->
      complDeclarationType res == Just DeclarationType.Value

  it "includes declarationType in completions for inferred values" $ do
    ([_, Right (CompletionResult [ result ])], _) <- Test.inProject $
      Test.runIde [ load ["CompletionSpec"]
                  , typ "exampleInferredString"
                  ]
    result `shouldSatisfy` \res ->
      complDeclarationType res == Just DeclarationType.Value

  it "includes declarationType in completions for operators" $ do
    ([_, Right (CompletionResult results)], _) <- Test.inProject $
      Test.runIde [ load ["CompletionSpec"]
                  , typ "\\°/"
                  ]
    length results `shouldBe` 2
    results `shouldSatisfy` any (\res ->
      complDeclarationType res == Just DeclarationType.ValueOperator)
    results `shouldSatisfy` any (\res ->
      complDeclarationType res == Just DeclarationType.TypeOperator)

  it "includes declarationType in completions for type constructors with \
      \conflicting names" $ do
    ([_, Right (CompletionResult results)], _) <- Test.inProject $
      Test.runIde [ load ["CompletionSpec"]
                  , typ "ExampleTypeConstructor"
                  ]
    length results `shouldBe` 2
    results `shouldSatisfy` any (\res ->
        complDeclarationType res == Just DeclarationType.DataConstructor)
    results `shouldSatisfy` any (\res ->
        complDeclarationType res == Just DeclarationType.Type)

  it "includes declarationType in completions for type classes" $ do
    ([_, Right (CompletionResult [result])], _) <- Test.inProject $
      Test.runIde [ load ["CompletionSpec"]
                  , typ "ExampleClass"
                  ]
    result `shouldSatisfy` \res ->
      complDeclarationType res == Just DeclarationType.TypeClass

  it "includes declarationType in completions for type class members" $ do
    ([_, Right (CompletionResult [result])], _) <- Test.inProject $
      Test.runIde [ load ["CompletionSpec"]
                  , typ "exampleMember"
                  ]
    result `shouldSatisfy` \res ->
      complDeclarationType res == Just DeclarationType.Value
