///|
/// A small machine-readable description of the stable public surface.
pub struct ApiEntry {
  name : String
  kind : String
  description : String
  since : String
} derive(Debug, Eq)

///|
pub fn ApiEntry::new(
  name : String,
  kind : String,
  description : String,
  since : String,
) -> ApiEntry {
  { name, kind, description, since }
}

///|
pub fn ApiEntry::to_markdown(self : ApiEntry) -> String {
  "| `" +
  self.name +
  "` | " +
  self.kind +
  " | " +
  self.description +
  " | " +
  self.since +
  " |"
}

///|
pub fn ApiEntry::to_json(self : ApiEntry) -> String {
  "{\"name\":\"" +
  json_escape(self.name) +
  "\",\"kind\":\"" +
  json_escape(self.kind) +
  "\",\"description\":\"" +
  json_escape(self.description) +
  "\",\"since\":\"" +
  json_escape(self.since) +
  "\"}"
}

///|
pub struct ApiContract {
  module_name : String
  version : String
  entries : Array[ApiEntry]
} derive(Debug, Eq)

///|
pub fn ApiContract::brandbook() -> ApiContract {
  {
    module_name: "@cyypyhon123/moonbit-brandbook",
    version: version(),
    entries: [
      ApiEntry::new(
        "parse_brand_document", "import", "Parse JSON or YAML brand input", "0.2.0",
      ),
      ApiEntry::new(
        "BrandBook::audit", "validation", "Validate schema and references", "0.2.0",
      ),
      ApiEntry::new(
        "BrandBook::to_markdown", "render", "Render a human-readable guide", "0.2.0",
      ),
      ApiEntry::new(
        "BrandBook::to_theme_json", "export", "Export interoperable theme JSON",
        "0.2.0",
      ),
      ApiEntry::new(
        "BrandBook::render_bundle", "export", "Build deterministic artifacts", "0.2.0",
      ),
      ApiEntry::new(
        "run_benchmarks", "benchmark", "Measure parser and render paths", "0.2.0",
      ),
    ],
  }
}

///|
pub fn ApiContract::len(self : ApiContract) -> Int {
  self.entries.length()
}

///|
pub fn ApiContract::contains(self : ApiContract, name : String) -> Bool {
  self.entries.any(fn(entry) { entry.name == name })
}

///|
pub fn ApiContract::entry(self : ApiContract, name : String) -> ApiEntry? {
  for entry in self.entries {
    if entry.name == name {
      return Some(entry)
    }
  }
  None
}

///|
pub fn ApiContract::names(self : ApiContract) -> Array[String] {
  self.entries.map(fn(entry) { entry.name })
}

///|
pub fn ApiContract::kinds(self : ApiContract) -> Array[String] {
  let result : Array[String] = []
  for entry in self.entries {
    if !result.contains(entry.kind) {
      result.push(entry.kind)
    }
  }
  result
}

///|
pub fn ApiContract::to_markdown(self : ApiContract) -> String {
  let lines : Array[String] = [
    "# Public API contract",
    "",
    "Module: `" + self.module_name + "`",
    "Version: `" + self.version + "`",
    "",
    "| Name | Kind | Description | Since |",
    "| --- | --- | --- | --- |",
  ]
  for entry in self.entries {
    lines.push(entry.to_markdown())
  }
  lines.join("\n")
}

///|
pub fn ApiContract::to_json(self : ApiContract) -> String {
  "{\"module\":\"" +
  json_escape(self.module_name) +
  "\",\"version\":\"" +
  json_escape(self.version) +
  "\",\"entries\":[" +
  self.entries.map(fn(entry) { entry.to_json() }).join(",") +
  "]}"
}

///|
pub fn ApiContract::release_notes(
  self : ApiContract,
  previous : ApiContract,
) -> Array[String] {
  let changes : Array[String] = []
  for entry in self.entries {
    if !previous.contains(entry.name) {
      changes.push("Added " + entry.name)
    }
  }
  for entry in previous.entries {
    if !self.contains(entry.name) {
      changes.push("Removed " + entry.name)
    }
  }
  changes
}

///|
pub fn public_api_contract() -> ApiContract {
  ApiContract::brandbook()
}

///|
pub fn public_api_markdown() -> String {
  public_api_contract().to_markdown()
}

///|
pub fn public_api_json() -> String {
  public_api_contract().to_json()
}