import Lean

open Lean Linter EnvLinter Meta

/-! ## Dummy env linter for testing -/

/-- A dummy linter that flags any declaration whose last name component starts with "bad". -/
@[builtin_env_linter]
public meta def dummyBadName : EnvLinter where
  test declName := do
    if let .str _ s := declName then
      if s.startsWith "bad" then
        return some m!"declaration name starts with 'bad'"
    return none
  noErrorsFound := "no bad names found"
  errorsFound := "found bad names"

/-! ## Test: extension registration -/

def testExtContains (name : Name) : CoreM Bool := do
  let ext := envLinterExt.getState (← getEnv)
  return ext.contains name

/-- info: true -/
#guard_msgs in
#eval testExtContains `dummyBadName

/-- info: false -/
#guard_msgs in
#eval testExtContains `nonexistentLinter

/-! ## Test: getEnvLinter resolves the linter -/

def testResolveLinter : CoreM String := do
  let some (declName, _) := (envLinterExt.getState (← getEnv)).find? `dummyBadName
    | throwError "not found"
  let linter ← getEnvLinter `dummyBadName declName
  return toString linter.name

/-- info: "dummyBadName" -/
#guard_msgs in
#eval testResolveLinter

/-! ## Test: dummy linter detects bad names -/

def badDef : Nat := 42
def goodDef : Nat := 42

def testLinterDetects (declName : Name) : MetaM Bool := do
  let some (linterDeclName, _) := (envLinterExt.getState (← getEnv)).find? `dummyBadName
    | throwError "not found"
  let linter ← getEnvLinter `dummyBadName linterDeclName
  return (← linter.test declName).isSome

/-- info: true -/
#guard_msgs in
#eval testLinterDetects `badDef

/-- info: false -/
#guard_msgs in
#eval testLinterDetects `goodDef

/-! ## Test: builtin_nolint -/

@[builtin_nolint dummyBadName]
def badButNolinted : Nat := 99

def testShouldBeLinted (linter decl : Name) : CoreM Bool := do
  shouldBeLinted linter decl

/-- info: false -/
#guard_msgs in
#eval testShouldBeLinted `dummyBadName `badButNolinted

/-- info: true -/
#guard_msgs in
#eval testShouldBeLinted `dummyBadName `badDef

/-! ## Test: builtin_env_linter extra -/

@[builtin_env_linter extra]
public meta def dummyExtraLinter : EnvLinter where
  test _ := return none
  noErrorsFound := "ok"
  errorsFound := "err"

-- The extension stores (declName, isDefault). Extra means isDefault = false.

def testIsDefault (name : Name) : CoreM (Option Bool) := do
  let ext := envLinterExt.getState (← getEnv)
  match ext.find? name with
  | some (_, isDefault) => return some isDefault
  | none => return none

/-- info: some false -/
#guard_msgs in
#eval testIsDefault `dummyExtraLinter

/-- info: some true -/
#guard_msgs in
#eval testIsDefault `dummyBadName

/-! ## Test: duplicate linter name is rejected -/

/--
error: invalid attribute `builtin_env_linter`, linter `dummyBadName` has already been declared
-/
#guard_msgs in
@[builtin_env_linter] public meta def Other.dummyBadName : EnvLinter :=
  { test := fun _ => return none, noErrorsFound := "", errorsFound := "" }

/-! ## Test: isAutoDecl -/

/-- info: true -/
#guard_msgs in
#eval isAutoDecl `Nat.casesOn

/-- info: true -/
#guard_msgs in
#eval isAutoDecl `Nat.recOn

/-- info: false -/
#guard_msgs in
#eval isAutoDecl `Nat.add

/-! ## Test: getChecks -/

-- Default mode: only isDefault=true linters
def testGetChecksDefault : CoreM (Array Name) := do
  let checks ← getChecks (scope := .default) (runOnly := none)
  return checks.map (·.name)

-- dummyBadName and checkUnivs are default, dummyExtraLinter is not
/-- info: #[`checkUnivs, `defLemma, `dummyBadName] -/
#guard_msgs in
#eval testGetChecksDefault

-- Extra mode: default linters together with non-default ones
def testGetChecksExtra : CoreM (Array Name) := do
  let checks ← getChecks (scope := .extra) (runOnly := none)
  return checks.map (·.name)

/-- info: #[`checkUnivs, `defLemma, `dummyBadName, `dummyExtraLinter] -/
#guard_msgs in
#eval testGetChecksExtra

-- All mode: all linters
def testGetChecksAll : CoreM (Array Name) := do
  let checks ← getChecks (scope := .all) (runOnly := none)
  return checks.map (·.name)

/-- info: #[`checkUnivs, `defLemma, `dummyBadName, `dummyExtraLinter] -/
#guard_msgs in
#eval testGetChecksAll

-- runOnly: only specified linters
def testGetChecksRunOnly : CoreM (Array Name) := do
  let checks ← getChecks (runOnly := some [`dummyExtraLinter])
  return checks.map (·.name)

/-- info: #[`dummyExtraLinter] -/
#guard_msgs in
#eval testGetChecksRunOnly

/-! ## Test: getDeclsInCurrModule -/

def testDeclsInCurrModule : CoreM Bool := do
  let decls ← getDeclsInCurrModule
  -- Our test declarations should be in the current module
  return decls.contains `badDef && decls.contains `goodDef && decls.contains `badButNolinted

/-- info: true -/
#guard_msgs in
#eval testDeclsInCurrModule

/-! ## Test: lintCore -/

-- lintCore should find badDef but not goodDef or badButNolinted
def testLintCore : CoreM (Array (Name × Nat)) := do
  let linters ← getChecks (scope := .default) (runOnly := none)
  let results ← lintCore #[`badDef, `goodDef, `badButNolinted] linters
  return results.map fun (linter, msgs) => (linter.name, msgs.size)

/-- info: #[(`checkUnivs, 0), (`defLemma, 0), (`dummyBadName, 1)] -/
#guard_msgs in
#eval testLintCore

-- Verify which declaration was flagged
def testLintCoreDetail : CoreM (Array Name) := do
  let linters ← getChecks (scope := .default) (runOnly := none)
  let results ← lintCore #[`badDef, `goodDef, `badButNolinted] linters
  let mut flagged := #[]
  for (_, msgs) in results do
    for (name, _) in msgs.toArray do
      flagged := flagged.push name
  return flagged

/-- info: #[`badDef] -/
#guard_msgs in
#eval testLintCoreDetail

/-! ## Test: formatLinterResults -/

def testFormatResults : CoreM Format := do
  let linters ← getChecks (scope := .default) (runOnly := none)
  let results ← lintCore #[`badDef, `goodDef] linters
  let msg ← formatLinterResults results #[`badDef, `goodDef]
    (groupByFilename := false) (whereDesc := "in test") (scope := .all)
    (verbose := .medium) (numLinters := linters.size)
  return (← msg.format)

/--
info: -- Found 1 error in 2 declarations (plus 0 automatically generated ones) in test with 3 linters

/- The `dummyBadName` linter reports:
found bad names -/
#check badDef /- declaration name starts with 'bad' -/
-/
#guard_msgs in
#eval testFormatResults

-- No errors case
def testFormatResultsClean : CoreM Format := do
  let linters ← getChecks (scope := .default) (runOnly := none)
  let results ← lintCore #[`goodDef] linters
  let msg ← formatLinterResults results #[`goodDef]
    (groupByFilename := false) (whereDesc := "in test") (scope := .all)
    (verbose := .medium) (numLinters := linters.size)
  return (← msg.format)

/--
info: -- Found 0 errors in 1 declarations (plus 0 automatically generated ones) in test with 3 linters
-/
#guard_msgs in
#eval testFormatResultsClean

/-! ## Test: checkUnivs -/

-- Good: each universe parameter occurs alone somewhere
universe u v in
def goodUnivs (α : Type u) (β : Type v) : Type (max u v) := α × β

-- Good: one universe dominates the other (max u v where u occurs alone)
universe u v in
def goodUnivsDominated (α : Type u) (β : Type (max u v)) : Type (max u v) := α × β

-- Bad: neither u nor v occur alone
universe u v in
def badUnivs (α : Type (max u v)) : Type (max u v) := α

def testCheckUnivs (declName : Name) : MetaM Bool := do
  let some (linterDeclName, _) := (envLinterExt.getState (← getEnv)).find? `checkUnivs
    | throwError "not found"
  let linter ← getEnvLinter `checkUnivs linterDeclName
  return (← linter.test declName).isSome

/-- info: false -/
#guard_msgs in
#eval testCheckUnivs `goodUnivs

/-- info: false -/
#guard_msgs in
#eval testCheckUnivs `goodUnivsDominated

/-- info: true -/
#guard_msgs in
#eval testCheckUnivs `badUnivs
