// PKL-132x: stdlib coverage probe. The table below pins one minimal
// fixture per documented stdlib surface area so the support claims in
// README.md ("Full" / "Partial" / "Stub") are mechanically verified.
// `mpkl stdlib` evaluates each probe through the same `eval_source`
// path that user code takes, renders the result through the PCF
// renderer, and matches against an expected substring. Failures
// surface as `[FAIL]` lines plus a non-zero exit code so CI / a
// release-check pass catches the moment a documented capability
// regresses.

///|
priv struct StdlibProbe {
  group : String
  name : String
  source : String
  expect : String
}

///|
priv struct StdlibPublicSymbol {
  kind : String
  name : String
}

///|
fn stdlib_symbol_name(token : String) -> String {
  let buf = StringBuilder()
  for c in token {
    if c == ':' || c == '=' || c == '(' || c == '<' || c == '{' {
      break
    }
    buf.write_char(c)
  }
  buf.to_string()
}

///|
fn stdlib_decl_modifier(word : String) -> Bool {
  word == "abstract" ||
  word == "open" ||
  word == "external" ||
  word == "hidden" ||
  word == "fixed" ||
  word == "const"
}

///|
fn stdlib_public_symbols(source : String) -> Array[StdlibPublicSymbol] {
  let result : Array[StdlibPublicSymbol] = []
  for raw_line in source.split("\n") {
    let line = raw_line.to_owned()
    if line == "" {
      continue
    }
    let first_char = line[0].to_int().unsafe_to_char()
    if first_char == ' ' ||
      first_char == '\t' ||
      first_char == '/' ||
      first_char == '@' {
      continue
    }
    let words = line.split(" ").collect()
    let compact : Array[String] = []
    let mut has_local_modifier = false
    for raw_word in words {
      let word = raw_word.to_owned()
      if word == "" {
        continue
      }
      if word == "local" {
        has_local_modifier = true
      }
      compact.push(word)
    }
    if has_local_modifier || compact.length() == 0 {
      continue
    }
    let mut index = 0
    while index < compact.length() && stdlib_decl_modifier(compact[index]) {
      index += 1
    }
    if index >= compact.length() {
      continue
    }
    let head = compact[index]
    if head == "class" || head == "function" || head == "typealias" {
      if index + 1 < compact.length() {
        let name = stdlib_symbol_name(compact[index + 1])
        if name != "" {
          result.push({ kind: head, name })
        }
      }
      continue
    }
    if head == "module" ||
      head == "import" ||
      head == "amends" ||
      head == "extends" {
      continue
    }
    if line.find(":") is Some(_) || line.find("=") is Some(_) {
      let name = stdlib_symbol_name(head)
      if name != "" {
        result.push({ kind: "property", name })
      }
    }
  }
  result
}

///|
fn stdlib_uri_for_path(path : String) -> String? {
  let parts = path.split("/").collect()
  if parts.length() == 0 {
    return None
  }
  let filename = parts[parts.length() - 1].to_owned()
  if filename == "doc-package-info.pkl" || !filename.has_suffix(".pkl") {
    return None
  }
  let name = String::unsafe_substring(
    filename,
    start=0,
    end=filename.length() - 4,
  )
  Some("pkl:" + name)
}

///|
fn stdlib_symbol_probe_source(
  uri : String,
  symbol : StdlibPublicSymbol,
) -> String {
  if symbol.kind == "class" || symbol.kind == "typealias" {
    "import \"\{uri}\" as stdlib\nprobe: stdlib.\{symbol.name}? = null"
  } else {
    "import \"\{uri}\" as stdlib\nprobe = stdlib.\{symbol.name}"
  }
}

///|
fn stdlib_missing_symbol_diagnostic(
  message : String,
  symbol : StdlibPublicSymbol,
) -> Bool {
  if symbol.kind == "class" || symbol.kind == "typealias" {
    message.has_prefix("Cannot find type `stdlib.") &&
    message.find("stdlib.\{symbol.name}") is Some(_)
  } else {
    message == "Cannot find property `\{symbol.name}`."
  }
}

///|
test "stdlib symbol differential ignores unrelated imported diagnostics" {
  let symbol : StdlibPublicSymbol = { kind: "property", name: "current" }
  inspect(
    stdlib_missing_symbol_diagnostic("Cannot find property `List`.", symbol),
    content="false",
  )
  inspect(
    stdlib_missing_symbol_diagnostic("Cannot find property `current`.", symbol),
    content="true",
  )
  let class_symbol : StdlibPublicSymbol = { kind: "class", name: "Release" }
  inspect(
    stdlib_missing_symbol_diagnostic(
      "Cannot find type `stdlib.Release?`.", class_symbol,
    ),
    content="true",
  )
}

///|
fn run_stdlib_symbol_check(args : Array[String], start : Int) -> Unit {
  let mut total = 0
  let mut failed = 0
  for i = start; i < args.length(); i = i + 1 {
    let path = args[i]
    let uri = match stdlib_uri_for_path(path) {
      Some(value) => value
      None => continue
    }
    let source = @fs.read_file_to_string(path) catch {
      _ => {
        failed += 1
        println("[FAIL] \{uri}: cannot read \{path}")
        continue
      }
    }
    let symbols = stdlib_public_symbols(source)
    let mut module_failed = 0
    for symbol in symbols {
      total += 1
      let session = @pkl.AnalysisSession::new()
      session.set_source(
        "__stdlib_symbol_probe__.pkl",
        stdlib_symbol_probe_source(uri, symbol),
      )
      match session.typecheck_path("__stdlib_symbol_probe__.pkl") {
        TypeOk(_) => ()
        TypeError(diagnostics) => {
          let missing = diagnostics.any(fn(diagnostic) {
            stdlib_missing_symbol_diagnostic(diagnostic.message, symbol)
          })
          if missing {
            failed += 1
            module_failed += 1
            println("[FAIL] \{uri} :: \{symbol.kind} \{symbol.name}")
            for diagnostic in diagnostics {
              if stdlib_missing_symbol_diagnostic(diagnostic.message, symbol) {
                println("       diagnostic: \{diagnostic.message}")
              }
            }
          }
        }
      }
    }
    if module_failed == 0 {
      println("[PASS] \{uri}: \{symbols.length()} public symbols")
    }
  }
  println("stdlib-symbols: \{total - failed} / \{total} resolved")
  if failed > 0 {
    @sys.exit(1)
  }
}

///|
let stdlib_probes : Array[StdlibProbe] = [
  // pkl:base — value-variant + builtin operations.
  {
    group: "pkl:base",
    name: "Listing.fold",
    source: "v = new Listing { 1; 2; 3 }.fold(0, (acc, x) -> acc + x)",
    expect: "v = 6",
  },
  {
    group: "pkl:base",
    name: "Mapping.keys",
    source: "v = (new Mapping { [\"a\"] = 1; [\"b\"] = 2 }).keys",
    expect: "\"a\"",
  },
  {
    group: "pkl:base",
    name: "String.toUpperCase",
    source: "v = \"hi\".toUpperCase()",
    expect: "v = \"HI\"",
  },
  {
    group: "pkl:base",
    name: "Int.toString(radix)",
    source: "v = 255.toString(16)",
    expect: "v = \"ff\"",
  },
  {
    group: "pkl:base",
    name: "Pair value variant",
    source: "v = Pair(\"a\", 42).second",
    expect: "v = 42",
  },
  {
    group: "pkl:base",
    name: "IntSeq value variant",
    source: "v = IntSeq(1, 3).fold(0, (acc, x) -> acc + x)",
    expect: "v = 6",
  },
  {
    group: "pkl:base",
    name: "IntSeq sequence equality (empty)",
    source: "v = IntSeq(0, -1) == IntSeq(10, -10)",
    expect: "v = true",
  },
  {
    group: "pkl:base",
    name: "IntSeq sequence equality (step-aware)",
    source: "v = IntSeq(-10, 10).step(2) == IntSeq(-10, 11).step(2)",
    expect: "v = true",
  },
  {
    group: "pkl:base",
    name: "Set value variant",
    source: "v = Set(1, 2, 3, 2, 1).length",
    expect: "v = 3",
  },
  {
    group: "pkl:base",
    name: "Map value variant",
    source: "v = Map(\"a\", 1, \"b\", 2).getOrNull(\"a\")",
    expect: "v = 1",
  },
  {
    group: "pkl:base",
    name: "JsonRenderer class",
    source: "r = new JsonRenderer { indent = \"    \" }\nv = r.indent",
    expect: "v = \"    \"",
  },
  {
    group: "pkl:base",
    name: "YamlRenderer class",
    source: "r = new YamlRenderer { indentWidth = 4 }\nv = r.indentWidth",
    expect: "v = 4",
  },
  {
    group: "pkl:base",
    name: "Duration mixed-unit addition (normalizes to coarser unit)",
    source: "v = (3.s + 500.ms).value",
    expect: "v = 3.5",
  },
  {
    group: "pkl:base",
    name: "DataSize literal",
    source: "v = 4.mb.unit",
    expect: "v = \"mb\"",
  },
  {
    group: "pkl:base",
    name: "Regex.matches",
    source: "v = Regex(\"^a+$\").matches(\"aaa\")",
    expect: "v = true",
  },
  {
    group: "pkl:base",
    name: "Bytes base64 round-trip",
    source: "v = Bytes.fromBase64(\"aGVsbG8=\").base64",
    expect: "v = \"aGVsbG8=\"",
  },
  // pkl:math — Int + Float helpers.
  {
    group: "pkl:math",
    name: "constants (maxInt32)",
    source: "import \"pkl:math\" as math\nv = math.maxInt32",
    expect: "v = 2147483647",
  },
  {
    group: "pkl:math",
    name: "Int helpers (max)",
    source: "import \"pkl:math\" as math\nv = math.max(3, 8)",
    expect: "v = 8",
  },
  {
    group: "pkl:math",
    name: "Float helper (sqrt)",
    source: "import \"pkl:math\" as math\nv = math.sqrt(16.0)",
    expect: "v = 4.0",
  },
  {
    group: "pkl:math",
    name: "Float helper (pow)",
    source: "import \"pkl:math\" as math\nv = math.pow(2.0, 10.0)",
    expect: "v = 1024.0",
  },
  {
    group: "pkl:math",
    name: "pi constant",
    source: "import \"pkl:math\" as math\nv = math.pi",
    expect: "v = 3.141592653589793",
  },
  // pkl:semver — Version parsing + comparison.
  {
    group: "pkl:semver",
    name: "parse decomposition",
    source: "import \"pkl:semver\" as s\nv = s.parse(\"1.2.3\").minor",
    expect: "v = 2",
  },
  {
    group: "pkl:semver",
    name: "isLessThan with pre-release",
    source: "import \"pkl:semver\" as s\nv = s.isLessThan(s.parse(\"1.0.0-rc\"), s.parse(\"1.0.0\"))",
    expect: "v = true",
  },
  {
    group: "pkl:semver",
    name: "parseOrNull on bad input",
    source: "import \"pkl:semver\" as s\nv = s.parseOrNull(\"oops\")",
    expect: "v = null",
  },
  // pkl:platform — stub values.
  {
    group: "pkl:platform",
    name: "current.operatingSystem.name (stub)",
    source: "import \"pkl:platform\" as p\nv = p.current.operatingSystem.name",
    expect: "v = \"stub-os\"",
  },
  // pkl:test — catch flow.
  // Apple Pkl's `test.catch` returns null on no-throw and the
  // thrown-message string otherwise. pkl-mbt only implements the
  // throw-side branch (which is what `classes/constraints8.pkl`
  // exercises); the no-throw branch falls back to evaluating the
  // lambda as if catch wasn't there, so the probe is intentionally
  // scoped to the throw path.
  {
    group: "pkl:test",
    name: "catch captures thrown message",
    source: "import \"pkl:test\"\nv = test.catch(() -> throw(\"boom\"))",
    expect: "v = \"boom\"",
  },
  // pkl:reflect — mirror constants + introspection. `reflect.intType`
  // returns a Class mirror (`name = "Int"` + typeArguments / referent
  // slots), not a flat `"pkl.base#Int"` string — the latter was the
  // shape of the very-early stub, before the full reflect surface
  // landed.
  {
    group: "pkl:reflect",
    name: "intType mirror constant",
    source: "import \"pkl:reflect\" as r\nv = r.intType.name",
    expect: "v = \"Int\"",
  },
  {
    group: "pkl:reflect",
    name: "Class factory reflectee",
    source: "import \"pkl:reflect\" as r\nv = r.Class(\"Foo\").reflectee",
    expect: "v = \"Foo\"",
  },
  {
    group: "pkl:reflect",
    name: "Class.properties walks class declaration",
    source: "import \"pkl:reflect\" as r\nclass Person { name: String; age: Int }\nv = r.Class(\"Person\").properties[1].typeName",
    expect: "v = \"Int\"",
  },
  {
    group: "pkl:reflect",
    name: "Class.methods walks class declaration",
    source: "import \"pkl:reflect\" as r\nclass G { function greet(n: String): String = n }\nv = r.Class(\"G\").methods[0].returnTypeName",
    expect: "v = \"String\"",
  },
  {
    group: "pkl:reflect",
    name: "Class.supertype follows parent_name",
    source: "import \"pkl:reflect\" as r\nclass Animal {}\nclass Dog extends Animal {}\nv = r.Class(\"Dog\").supertype.reflectee",
    expect: "v = \"Animal\"",
  },
  {
    group: "pkl:reflect",
    name: "Class.isSubclassOf walks the parent chain",
    source: "import \"pkl:reflect\" as r\nclass A {}\nclass B extends A {}\nv = r.Class(\"B\").isSubclassOf(r.Class(\"A\"))",
    expect: "v = true",
  },
  {
    group: "pkl:reflect",
    name: "Module.classes lists declared classes",
    source: "import \"pkl:reflect\" as r\nclass A {}\nclass B {}\nv = r.Module(\"self\").classes[1].reflectee",
    expect: "v = \"B\"",
  },
  // pkl:json — Parser shell + parse() implementation.
  {
    group: "pkl:json",
    name: "Parser class instantiable",
    source: "import \"pkl:json\" as json\np = new json.Parser { useMapping = true }\nv = p.useMapping",
    expect: "v = true",
  },
  {
    group: "pkl:json",
    name: "Parser.parse decodes primitives (Dynamic mode)",
    source: "import \"pkl:json\" as json\nv = (new json.Parser {}).parse(\"{\\\"n\\\": 42}\").n",
    expect: "v = 42",
  },
  {
    group: "pkl:json",
    name: "Parser.parse honors useMapping",
    source: "import \"pkl:json\" as json\nv = (new json.Parser { useMapping = true }).parse(\"{\\\"k\\\": \\\"v\\\"}\")",
    expect: "[\"k\"] = \"v\"",
  },
  {
    group: "pkl:json",
    name: "Parser.parse handles nested arrays + objects",
    source: "import \"pkl:json\" as json\nv = (new json.Parser {}).parse(\"[1, {\\\"x\\\": [2, 3]}]\")[1].x[0]",
    expect: "v = 2",
  },
  // pkl:yaml — Parser shell + parse() implementation (via
  // moonbit-community/yaml).
  {
    group: "pkl:yaml",
    name: "Parser class instantiable",
    source: "import \"pkl:yaml\" as yaml\np = new yaml.Parser { useMapping = false }\nv = p.useMapping",
    expect: "v = false",
  },
  {
    group: "pkl:yaml",
    name: "Parser.parse decodes primitives (Dynamic mode)",
    source: "import \"pkl:yaml\" as yaml\nv = (new yaml.Parser {}).parse(\"n: 42\\n\").n",
    expect: "v = 42",
  },
  {
    group: "pkl:yaml",
    name: "Parser.parse honors useMapping",
    source: "import \"pkl:yaml\" as yaml\nv = (new yaml.Parser { useMapping = true }).parse(\"k: v\\n\")",
    expect: "[\"k\"] = \"v\"",
  },
  {
    group: "pkl:yaml",
    name: "Parser.parse handles nested arrays + maps",
    source: "import \"pkl:yaml\" as yaml\nv = (new yaml.Parser {}).parse(\"items:\\n  - 1\\n  - 2\\nnested:\\n  inner: hello\\n\").nested.inner",
    expect: "v = \"hello\"",
  },
  // pkl:xml — Renderer class surface.
  {
    group: "pkl:xml",
    name: "Renderer class instantiable",
    source: "import \"pkl:xml\" as xml\nr = new xml.Renderer { rootElementName = \"config\" }\nv = r.rootElementName",
    expect: "v = \"config\"",
  },
  // pkl:protobuf — Renderer class surface.
  {
    group: "pkl:protobuf",
    name: "Renderer class instantiable",
    source: "import \"pkl:protobuf\" as proto\nr = new proto.Renderer {}\nv = r.indent",
    expect: "v = \"  \"",
  },
  // Remaining public Pkl 0.32 modules — at least one executable contract
  // per URI so adding a synthetic source without a usable surface cannot
  // accidentally count as stdlib coverage.
  {
    group: "pkl:release",
    name: "current version metadata",
    source: "import \"pkl:release\" as release\nv = release.current.version.major",
    expect: "v = 0",
  },
  {
    group: "pkl:Benchmark",
    name: "Microbenchmark result shape",
    source: "import \"pkl:Benchmark\" as Benchmark\nv = (new Benchmark.Microbenchmark { expression = 1 }).run().mean",
    expect: "v = 1.ms",
  },
  {
    group: "pkl:analyze",
    name: "Import record class",
    source: "import \"pkl:analyze\" as analyze\nv = new analyze.Import { uri = \"main.pkl\" }.uri",
    expect: "v = \"main.pkl\"",
  },
  {
    group: "pkl:jsonnet",
    name: "Renderer class surface",
    source: "import \"pkl:jsonnet\" as jsonnet\nv = new jsonnet.Renderer {}.indent",
    expect: "v = \"  \"",
  },
  {
    group: "pkl:shell",
    name: "single quote escaping",
    source: "import \"pkl:shell\" as shell\nv = shell.escapeWithSingleQuotes(\"it's\")",
    expect: "v = \"'it'\\\\''s'\"",
  },
  {
    group: "pkl:EvaluatorSettings",
    name: "Proxy configuration class",
    source: "import \"pkl:EvaluatorSettings\" as s\nv = new s.Proxy { address = \"http://localhost:8080\" }.address",
    expect: "v = \"http://localhost:8080\"",
  },
  {
    group: "pkl:Project",
    name: "Project module amendment",
    source: "import \"pkl:Project\" as Project\nlocal p = (Project) { tests { \"test.pkl\" } }\nv = p.tests.length",
    expect: "v = 1",
  },
  {
    group: "pkl:pklbinary",
    name: "MessagePack byte renderer",
    source: "import \"pkl:pklbinary\" as binary\nv = new binary.Renderer {}.renderValue(Pair(1, \"x\")).base64",
    expect: "v = \"kwkBoXg=\"",
  },
  {
    group: "pkl:Command",
    name: "Flag annotation class",
    source: "import \"pkl:Command\" as Command\nv = new Command.Flag { shortName = \"v\" }.shortName",
    expect: "v = \"v\"",
  },
  {
    group: "pkl:DocPackageInfo",
    name: "PackageDependency class",
    source: "import \"pkl:DocPackageInfo\" as Doc\nv = new Doc.PackageDependency { name = \"birds\"; version = \"1.0.0\"; sourceCode = \"https://example.com/\" }.name",
    expect: "v = \"birds\"",
  },
  {
    group: "pkl:DocsiteInfo",
    name: "descriptor module is amendable",
    source: "import \"pkl:DocsiteInfo\" as Doc\nlocal site = (Doc) { title = \"Example\" }\nv = site.title",
    expect: "v = \"Example\"",
  },
  {
    group: "pkl:settings",
    name: "Editor configuration class",
    source: "import \"pkl:settings\" as settings\nv = new settings.Editor { urlScheme = \"editor://%{path}\" }.urlScheme",
    expect: "v = \"editor://%{path}\"",
  },
]

///|
/// Run every entry in `stdlib_probes`, print a `[PASS] group :: name`
/// or `[FAIL] group :: name` line per probe (with the actual output on
/// failure), and exit non-zero when any probe fails. Designed to be
/// called as `mpkl stdlib`.
fn run_stdlib_check() -> Unit {
  let mut failed = 0
  let mut last_group = ""
  for probe in stdlib_probes {
    if probe.group != last_group {
      if last_group != "" {
        println("")
      }
      println("# \{probe.group}")
      last_group = probe.group
    }
    // Route through `AnalysisSession` (not the import-less `eval_source`)
    // so probes that `import "pkl:math"` etc. resolve through the
    // synthetic stdlib loader.
    let session = @pkl.AnalysisSession::new()
    session.set_source("__probe__.pkl", probe.source)
    match session.eval_path("__probe__.pkl") {
      EvalOk(value) => {
        let rendered = @pkl.render_value(value)
        if rendered.contains(probe.expect) {
          println("[PASS] \{probe.name}")
        } else {
          failed = failed + 1
          println("[FAIL] \{probe.name}")
          println("       expected substring: \{probe.expect}")
          println("       actual rendered:    \{rendered}")
        }
      }
      EvalError(diagnostics) => {
        failed = failed + 1
        println("[FAIL] \{probe.name}")
        for diagnostic in diagnostics {
          println("       diagnostic: \{diagnostic.message}")
        }
      }
    }
  }
  println("")
  let total = stdlib_probes.length()
  let passed = total - failed
  println("stdlib: \{passed} / \{total} passed")
  if failed > 0 {
    @sys.exit(1)
  }
}