///|
/// One-call governance report.
///
/// The lint operation composes the existing static analyses into a
/// single pass a reviewer can gate on: schema type-checking (via the
/// check operation's rule path), static analysis findings
/// (tautologies, duplicate conditions, priority ties), and bundle
/// manifest integrity when versions are supplied. Every check runs
/// even if earlier ones report problems — the point is a complete
/// finding list, not fail-fast. The overall `ok` is false only when at
/// least one finding exists; unknown fact paths defer to runtime
/// exactly as check does, so lint never invents errors.

///|
fn lint_request(request : Value) -> Value raise {
  let findings : Array[Value] = []
  let sections : Array[Value] = []

  // Section 1: static rule analysis (dependencies, tautologies,
  // duplicates, ties). Raises only on malformed requests, not on
  // findings, so malformed rules fail the whole lint request.
  let analyzed = analyze_rules(request)
  sections.push(
    record([
      ("section", String("analysis")),
      ("finding_count", get(analyzed, "finding_count")),
    ]),
  )
  match get(analyzed, "findings") {
    Array(items) =>
      for item in items {
        findings.push(
          record([
            ("source", String("analysis")),
            ("rule", get(item, "rule")),
            ("kind", get(item, "kind")),
            ("detail", get(item, "detail")),
          ]),
        )
      }
    _ => ()
  }

  // Section 2: type checking against the optional schema. Reuses the
  // check operation verbatim so lint and check can never disagree;
  // unknown fact paths defer to runtime as in check.
  let checked = check_request(request)
  let check_section : Array[Value] = []
  match get(checked, "rules") {
    Array(rules) =>
      for rule in rules {
        if get(rule, "valid") is Bool(false) {
          match get(rule, "errors") {
            Array(errors) =>
              for err in errors {
                check_section.push(
                  record([("rule", get(rule, "id")), ("detail", err)]),
                )
              }
            _ => ()
          }
        }
      }
    _ => ()
  }
  sections.push(
    record([
      ("section", String("types")),
      ("finding_count", Number(check_section.length().to_double())),
    ]),
  )
  for item in check_section {
    findings.push(
      record([
        ("source", String("types")),
        ("rule", get(item, "rule")),
        ("kind", String("type_error")),
        ("detail", get(item, "detail")),
      ]),
    )
  }

  // Section 3: bundle integrity, only when a manifest is supplied.
  match get(request, "versions") {
    Null =>
      sections.push(
        record([("section", String("bundle")), ("finding_count", Number(0.0))]),
      )
    _ => {
      let bundle_bad : Array[Value] = []
      let bundle_ok : Bool = try {
        ignore(bundle_validate_request(request))
        true
      } catch {
        error => {
          bundle_bad.push(
            record([("rule", String("")), ("detail", String(error.to_string()))]),
          )
          false
        }
      }
      sections.push(
        record([
          ("section", String("bundle")),
          ("finding_count", Number(bundle_bad.length().to_double())),
        ]),
      )
      for item in bundle_bad {
        findings.push(
          record([
            ("source", String("bundle")),
            ("rule", get(item, "rule")),
            ("kind", String("bundle_error")),
            ("detail", get(item, "detail")),
          ]),
        )
      }
      let _ = bundle_ok
    }
  }

  let count = findings.length()
  let by_source : Map[String, Int32] = Map([])
  for item in findings {
    let source = match get(item, "source") {
      String(name) => name
      _ => "?"
    }
    match by_source.get(source) {
      Some(n) => by_source[source] = n + 1
      None => by_source[source] = 1
    }
  }
  let summary : Array[Value] = []
  for source in ["analysis", "types", "bundle"] {
    summary.push(
      record([
        ("source", String(source)),
        (
          "findings",
          Number(
            (match by_source.get(source) {
              Some(n) => n
              None => 0
            }).to_double(),
          ),
        ),
      ]),
    )
  }
  record([
    ("ok", Bool(count == 0)),
    ("finding_count", Number(count.to_double())),
    ("summary", Array(summary)),
    ("sections", Array(sections)),
    ("findings", Array(findings)),
  ])
}