///|
fn package_parts(name : StringView) -> (String, String) {
  match name.split_once("/") {
    Some((group, package_name)) => (group.to_owned(), package_name.to_owned())
    None => ("", name.to_owned())
  }
}

///|
fn purl(name : StringView, version : StringView) -> String {
  let suffix = if version.is_empty() { "" } else { "@\{version}" }
  "pkg:mooncakes/\{name}\{suffix}"
}

///|
fn component_json(
  name : String,
  version : String,
  license : String,
  kind : String,
) -> Json {
  let (group, package_name) = package_parts(name)
  let licenses : Array[Json] = if license.is_empty() {
    []
  } else {
    [{ "license": { "id": license } }]
  }
  {
    "type": kind,
    "bom-ref": purl(name, version),
    "group": group,
    "name": package_name,
    "version": version,
    "purl": purl(name, version),
    "licenses": licenses,
  }
}

///|
/// Generate a deterministic CycloneDX 1.6 SBOM as JSON.
pub fn cyclonedx(project : Project) -> Json {
  let components : Array[Json] = []
  let dependency_refs : Array[Json] = []
  let graph : Array[Json] = []
  for dependency in project.dependencies {
    components.push(
      component_json(dependency.name, dependency.version, "", "library"),
    )
    dependency_refs.push(
      Json::string(purl(dependency.name, dependency.version)),
    )
    graph.push({
      "ref": purl(dependency.name, dependency.version),
      "dependsOn": [],
    })
  }
  graph.push({
    "ref": purl(project.name, project.version),
    "dependsOn": dependency_refs,
  })
  {
    "$schema": "https://cyclonedx.org/schema/bom-1.6.schema.json",
    "bomFormat": "CycloneDX",
    "specVersion": "1.6",
    "version": 1,
    "metadata": {
      "tools": {
        "components": [
          { "type": "application", "name": "MoonSeal", "version": "0.2.1" },
        ],
      },
      "component": component_json(
        project.name,
        project.version,
        normalize_license(project.license),
        "application",
      ),
    },
    "components": components,
    "dependencies": graph,
  }
}

///|
pub fn cyclonedx_string(project : Project, indent? : Int = 2) -> String {
  cyclonedx(project).stringify(indent~)
}