///|
fn json_number(value : Double?) -> Json {
  match value {
    Some(v) => Json::number(v)
    None => Json::null()
  }
}

///|
fn summary_json(s : Summary) -> Json {
  Json::object({
    "present": Json::number(s.present.to_double()),
    "missing": Json::number(s.missing.to_double()),
    "coverage": Json::number(s.coverage),
    "min": json_number(s.minimum),
    "max": json_number(s.maximum),
    "mean": json_number(s.mean),
    "population_variance": json_number(s.variance),
    "median": json_number(s.median),
  })
}

///|
/// Versioned machine report. Statistics remain distinct from rule-based QC findings.
pub fn quality_report(
  log : Log,
  rules : Array[QcRule],
  max_depth_gap : Double,
) -> Json raise WellError {
  let issues = quality_check(log, rules, max_depth_gap)
  let curves : Array[Json] = []
  for c in log.curves {
    let spans = missing_spans(log, c.name)
    curves.push(
      Json::object({
        "name": Json::string(c.name),
        "unit": Json::string(c.unit),
        "summary": summary_json(summarize(log, c.name)),
        "missing_spans": Json::array(
          spans.map(s => {
            Json::object({
              "first_row": Json::number(s.first_row.to_double()),
              "last_row": Json::number(s.last_row.to_double()),
              "start_depth": Json::number(s.start_depth),
              "stop_depth": Json::number(s.stop_depth),
              "bounded": Json::boolean(s.bounded),
            })
          }),
        ),
      }),
    )
  }
  Json::object({
    "schema": Json::string("moonwell.qa/v1"),
    "rows": Json::number(log.rows.length().to_double()),
    "curves": Json::array(curves),
    "issues": Json::array(
      issues.map(i => {
        Json::object({
          "code": Json::string(i.code),
          "curve": Json::string(i.curve),
          "row": Json::number(i.row.to_double()),
          "depth": Json::number(i.depth),
          "message": Json::string(i.message),
        })
      }),
    ),
  })
}

///|
/// Contains complete per-cell sources, including explicit null and synthetic depth cells.
pub fn assembly_report(assembly : Assembly) -> Json raise WellError {
  validate_assembly(assembly)
  let sources : Array[Json] = []
  for row in assembly.sources {
    sources.push(
      Json::array(
        row.map(cell => {
          match cell {
            None => Json::null()
            Some(s) =>
              Json::object({
                "run": Json::number(s.run.to_double()),
                "interpolated": Json::boolean(s.interpolated),
              })
          }
        }),
      ),
    )
  }
  let conflicts = assembly.conflicts.map(c => {
    Json::object({
      "row": Json::number(c.row.to_double()),
      "depth": Json::number(c.depth),
      "curve": Json::string(c.curve),
      "previous_run": Json::number(c.kept_run.to_double()),
      "incoming_run": Json::number(c.incoming_run.to_double()),
      "previous_value": Json::number(c.kept_value),
      "incoming_value": Json::number(c.incoming_value),
    })
  })
  Json::object({
    "schema": Json::string("moonwell.assembly/v1"),
    "runs": Json::array(assembly.run_names.map(Json::string)),
    "curves": Json::array(assembly.log.curves.map(c => Json::string(c.name))),
    "depths": Json::array(depths(assembly.log).map(d => Json::number(d))),
    "sources": Json::array(sources),
    "conflicts": Json::array(conflicts),
  })
}

///|
pub fn interval_report(summary : IntervalSummary) -> Json {
  Json::object({
    "schema": Json::string("moonwell.interval/v1"),
    "start": Json::number(summary.start),
    "stop": Json::number(summary.stop),
    "covered": Json::number(summary.covered),
    "coverage": Json::number(summary.coverage),
    "integral": Json::number(summary.integral),
    "weighted_mean": json_number(summary.weighted_mean),
    "segments": Json::number(summary.segments.to_double()),
  })
}

///|
pub fn report_text(report : Json) -> String {
  report.stringify(indent=2)
}

///|
/// Bounded report decoder for downstream integrations.
pub fn read_report(text : String) -> Json raise WellError {
  if text.length() > 16000000 {
    fail("report size limit exceeded")
  }
  let mut depth = 0
  let mut quoted = false
  let mut escaped = false
  for c in text.iter() {
    if quoted {
      if escaped {
        escaped = false
      } else if c == '\\' {
        escaped = true
      } else if c == '"' {
        quoted = false
      }
    } else if c == '"' {
      quoted = true
    } else if c == '[' || c == '{' {
      depth += 1
      if depth > 32 {
        fail("report nesting limit is 32")
      }
    } else if c == ']' || c == '}' {
      depth -= 1
    }
  }
  @json.parse(text) catch {
    _ => {
      fail("invalid report JSON")
      Json::null()
    }
  }
}