import Lean.Meta

/-!
# Tests for hint suggestion messages

Tests rendering of hint suggestions in message data. Note that we can only assess the Unicode
versions here.
-/

open Lean Meta Hint

/-! ## Basic hint functionality -/

elab foo:"foo" bar:"bar" "baz" : term => do
  let suggestions := #[
    { suggestion := "hi", preInfo? := "generic: " },
    { suggestion := "cheers", postInfo? := " (at `bar`)", span? := bar }
  ]
  let hint ← MessageData.hint
    m!"Consider adding a greeting to your program to make it friendlier"
    suggestions
    foo
    "Add greeting: "
  let msg := m!"Your program is insufficiently friendly" ++ hint
  throwErrorAt foo msg

/--
error: Your program is insufficiently friendly

Hint: Consider adding a greeting to your program to make it friendlier
  • generic: f̵o̵o̵h̲i̲
  • b̵a̵r̵c̲h̲e̲e̲r̲s̲ (at `bar`)
-/
#guard_msgs (whitespace := exact) in
#check foo bar baz

/-! ## Well-behavedness with preceding nested environments -/

/--
info: Top level
  Upper
  Lower

Hint: Hint message
  r̵u̵n̵_̵m̵e̵t̵a̵s̲u̲g̲g̲e̲s̲t̲i̲o̲n̲
-/
#guard_msgs (whitespace := exact) in
run_meta do
  let hint ← MessageData.hint m!"Hint message" #["suggestion"]
  let msg := "Top level" ++ indentD "Upper\nLower" ++ hint
  logInfo msg

/--
info: Outer
  A
  B
    C
    D

Hint: Hint message
  r̵u̵n̵_̵m̵e̵t̵a̵s̲u̲g̲g̲e̲s̲t̲i̲o̲n̲
-/
#guard_msgs (whitespace := exact) in
run_meta do
  let hint ← MessageData.hint m!"Hint message" #["suggestion"]
  let msg := MessageData.compose m!"Outer" <| (indentD <| m!"A\nB" ++ indentD "C\nD")
  logInfo (msg ++ hint)

/--
info: Outer
  A
  B
    C
    D

Hint: Hint message
  • r̵u̵n̵_̵m̵e̵t̵a̵s̲u̲g̲g̲e̲s̲t̲i̲o̲n̲1̲
  • r̵u̵n̵_̵m̵e̵t̵a̵s̲u̲g̲g̲e̲s̲t̲i̲o̲n̲2̲
-/
#guard_msgs (whitespace := exact) in
run_meta do
  let hint ← MessageData.hint m!"Hint message" #["suggestion1", "suggestion2"]
  let msg := MessageData.compose m!"Outer" <| (indentD <| m!"A\nB" ++ indentD "C\nD")
  logInfo (msg ++ hint)

/-! ## Multi-line suggestion alignment -/

/--
info:

Hint: Hint message
  r̵u̵n̵_̵m̵e̵t̵a̵A̲
  ̲B̲
  ̲C̲
-/
#guard_msgs (whitespace := exact) in
run_meta do
  let hint ← MessageData.hint m!"Hint message" #["A\nB\nC"]
  logInfo hint

/--
info:

Hint: Hint message
  • r̵u̵n̵_̵m̵e̵t̵a̵A̲
    ̲B̲
    ̲C̲
  • r̵u̵n̵_̵m̵e̵t̵a̵D̲
    ̲ ̲ ̲E̲
    ̲ ̲ ̲F̲
-/
#guard_msgs (whitespace := exact) in
run_meta do
  let hint ← MessageData.hint m!"Hint message" #["A\nB\nC", "D\n  E\n  F"]
  logInfo hint

/-! ## Well-behavedness with other message subcomponents -/

set_option pp.fieldNotation.generalized false in
/--
info: Message with expression
  Nat.succ Nat.zero

Hint: Before
  Nat.succ Nat.zero
After
  r̵u̵n̵_̵m̵e̵t̵a̵s̲u̲g̲g̲e̲s̲t̲i̲o̲n̲

Note: Before
  Nat.succ Nat.zero
After
-/
#guard_msgs (whitespace := exact) in
run_meta do
  let e := Expr.app (.const `Nat.succ []) (.const `Nat.zero [])
  let hint ← MessageData.hint m!"Before{indentExpr e}\nAfter" #["suggestion"]
  logInfo (m!"Message with expression{indentExpr e}" ++ hint ++ .note m!"Before{indentExpr e}\nAfter")

-- Generally, we should avoid nesting hints, but we nonetheless check that it works in principle:
/--
info: Outer error
  Nested error
  ⏎
  Hint: Inner
    test
  hint
    r̵u̵n̵_̵m̵e̵t̵a̵s̲u̲g̲g̲e̲s̲t̲i̲o̲n̲1̲a̲
    ̲s̲u̲g̲g̲e̲s̲t̲i̲o̲n̲1̲b̲
  ⏎
  Note: internal
    test
  note

Hint: Outer
  test
hint
  r̵u̵n̵_̵m̵e̵t̵a̵s̲u̲g̲g̲e̲s̲t̲i̲o̲n̲2̲a̲
  ̲s̲u̲g̲g̲e̲s̲t̲i̲o̲n̲2̲b̲
-/
#guard_msgs (whitespace := exact) in
run_meta do
  let hint₁ ← MessageData.hint m!"Inner{indentD "test"}\nhint" #["suggestion1a\nsuggestion1b"]
  let hint₂ ← MessageData.hint m!"Outer{indentD "test"}\nhint" #["suggestion2a\nsuggestion2b"]
  let note := MessageData.note m!"internal{indentD "test"}\nnote"
  let msg := m!"Outer error" ++ (indentD ("Nested error" ++ hint₁ ++ note)) ++ hint₂
  logInfo msg

/-! ## Diff behavior -/

elab "longWordWithoutAnySpaces" : term => do
  let hint ← MessageData.hint m!"Use one of these instead"
    #["longWordWithoutSpaces", "longWord", "paces", "ordSpaces"]
  throwError m!"Invalid" ++ hint

/--
error: Invalid

Hint: Use one of these instead
  • longWordWithoutA̵n̵y̵Spaces
  • longWordW̵i̵t̵h̵o̵u̵t̵A̵n̵y̵S̵p̵a̵c̵e̵s̵
  • l̵o̵n̵g̵W̵o̵r̵d̵W̵i̵t̵h̵o̵u̵t̵A̵n̵y̵S̵paces
  • l̵o̵n̵g̵W̵o̵r̵d̵W̵i̵t̵h̵o̵u̵t̵A̵n̵y̵S̵p̵a̵c̵e̵s̵o̲r̲d̲S̲p̲a̲c̲e̲s̲
-/
#guard_msgs (whitespace := exact) in
#check longWordWithoutAnySpaces

elab "first" "second" "third" "fourth" "fifth" : term => do
  let hint ← MessageData.hint m!"Use one of these instead"
    #["first second thi", "second fourth fifth", "fi se th fo fi", "second"]
  throwError m!"Invalid" ++ hint

/--
error: Invalid

Hint: Use one of these instead
  • first second thir̵d̵ ̵f̵o̵u̵r̵t̵h̵ ̵f̵i̵f̵t̵h̵
  • f̵i̵r̵st̵ ̵s̵econd t̵h̵i̵r̵d̵ ̵fourth fifth
  • f̵i̵r̵s̵t̵ ̵s̵e̵c̵o̵n̵d̵ ̵t̵h̵i̵r̵d̵ ̵f̵o̵u̵r̵t̵h̵ ̵f̵i̵f̵t̵h̵f̲i̲ ̲s̲e̲ ̲t̲h̲ ̲f̲o̲ ̲f̲i̲
  • f̵i̵r̵s̵t̵ ̵s̵e̵c̵o̵n̵d̵ ̵t̵h̵i̵r̵d̵ ̵f̵o̵u̵r̵t̵h̵ ̵f̵i̵f̵t̵h̵s̲e̲c̲o̲n̲d̲
-/
#guard_msgs (whitespace := exact) in
#check first second third fourth fifth

elab "suggest_replacement%" t:term : term => do
  let msg ← MessageData.hint m!"Try one of these"
    #["let x := 31; x", "let (a, b, c) := (1, 2, 3)\n  b", "x"] t
  throwErrorAt t m!"Invalid{msg}"

/--
error: Invalid

Hint: Try one of these
  • let x := 4̵2̵
    ̵3̲1̲;̲  ̵2̵ ̵*̵ ̵x
  • l̵e̵t̵ ̵x̵ ̵:̵=̵ ̵4̵2̵
    ̵ ̵ ̵2̵ ̵*̵ ̵x̵l̲e̲t̲ ̲(̲a̲,̲ ̲b̲,̲ ̲c̲)̲ ̲:̲=̲ ̲(̲1̲,̲ ̲2̲,̲ ̲3̲)̲
    ̲ ̲ ̲b̲
  • l̵e̵t̵ ̵x ̵:̵=̵ ̵4̵2̵
    ̵ ̵ ̵2̵ ̵*̵ ̵x̵
-/
#guard_msgs (whitespace := exact) in
#check suggest_replacement%
  let x := 42
  2 * x
