///|
pub(all) enum SourceKind {
  OwnedCode
  ThirdPartyCode
  Dataset
  Asset
  GeneratedText
} derive(Eq, Debug)

///|
pub(all) struct DocSnippet {
  index : Int
  language : String
  body : String
  runnable : Bool
  label : String
} derive(Eq, Debug)

///|
pub(all) struct SourceEvidence {
  name : String
  kind : SourceKind
  source_url : String
  license : String
  note : String
} derive(Eq, Debug)

///|
pub fn SourceEvidence::SourceEvidence(
  name? : String = "",
  kind? : SourceKind = OwnedCode,
  source_url? : String = "",
  license? : String = "",
  note? : String = "",
) -> SourceEvidence {
  { name, kind, source_url, license, note }
}

///|
pub(all) struct DocProofReport {
  snippet_count : Int
  runnable_snippet_count : Int
  source_count : Int
  unclear_source_count : Int
  license_issue_count : Int
  verdict : String
  snippets : Array[DocSnippet]
  sources : Array[SourceEvidence]
} derive(Eq, Debug)

///|
fn source_kind_text(kind : SourceKind) -> String {
  match kind {
    OwnedCode => "owned-code"
    ThirdPartyCode => "third-party-code"
    Dataset => "dataset"
    Asset => "asset"
    GeneratedText => "generated-text"
  }
}

///|
fn fence_language(line : String) -> String {
  let trimmed = line.trim().to_owned()
  if trimmed.length() <= 3 || !trimmed.has_prefix("```") {
    ""
  } else {
    trimmed[3:trimmed.length()].trim().to_owned()
  }
}

///|
fn snippet_is_runnable(language : String, body : String) -> Bool {
  let lang = lower(language)
  let code = lower(body)
  (lang == "moonbit" || lang == "mbt" || lang.contains("moonbit")) &&
  (
    code.contains("fn main") ||
    code.contains("test {") ||
    code.contains("inspect(") ||
    code.contains("moon run")
  )
}

///|
pub fn extract_doc_snippets(markdown : String) -> Array[DocSnippet] {
  let snippets = Array::new()
  let mut in_fence = false
  let mut language = ""
  let mut body = StringBuilder()
  let mut index = 0
  for raw in normalized(markdown).split("\n") {
    let line = raw.to_owned()
    if line.trim().has_prefix("```") {
      if in_fence {
        let text = body.to_string()
        index += 1
        snippets.push({
          index,
          language,
          body: text,
          runnable: snippet_is_runnable(language, text),
          label: "snippet-" + index.to_string(),
        })
        in_fence = false
        language = ""
        body = StringBuilder()
      } else {
        in_fence = true
        language = fence_language(line)
        body = StringBuilder()
      }
    } else if in_fence {
      body.write_string(line)
      body.write_string("\n")
    }
  }
  snippets
}

///|
fn source_has_clear_origin(source : SourceEvidence) -> Bool {
  match source.kind {
    OwnedCode => has_text(source.note) || has_text(source.name)
    _ => has_text(source.source_url) && has_text(source.name)
  }
}

///|
fn source_has_clear_license(source : SourceEvidence) -> Bool {
  match source.kind {
    OwnedCode => source.license == "MIT" || known_osi_license(source.license)
    _ =>
      known_osi_license(source.license) ||
      source.license == "CC0-1.0" ||
      source.license == "CC-BY-4.0" ||
      source.license == "Public-Domain"
  }
}

///|
pub fn doc_proof(
  markdown : String,
  sources : Array[SourceEvidence],
) -> DocProofReport {
  let snippets = extract_doc_snippets(markdown)
  let mut runnable = 0
  for snippet in snippets {
    if snippet.runnable {
      runnable += 1
    }
  }
  let mut unclear = 0
  let mut license_issues = 0
  for source in sources {
    if !source_has_clear_origin(source) {
      unclear += 1
    }
    if !source_has_clear_license(source) {
      license_issues += 1
    }
  }
  let verdict = if snippets.length() == 0 {
    "needs-examples"
  } else if runnable == 0 {
    "needs-runnable-example"
  } else if unclear > 0 || license_issues > 0 {
    "needs-source-proof"
  } else {
    "ready"
  }
  {
    snippet_count: snippets.length(),
    runnable_snippet_count: runnable,
    source_count: sources.length(),
    unclear_source_count: unclear,
    license_issue_count: license_issues,
    verdict,
    snippets,
    sources,
  }
}

///|
pub fn DocProofReport::to_markdown(self : DocProofReport) -> String {
  let out = StringBuilder()
  out.write_string("# HarborCheck Doc Proof\n\n")
  out.write_string("- Verdict: " + self.verdict + "\n")
  out.write_string("- Snippets: " + self.snippet_count.to_string() + "\n")
  out.write_string(
    "- Runnable snippets: " + self.runnable_snippet_count.to_string() + "\n",
  )
  out.write_string("- Source records: " + self.source_count.to_string() + "\n")
  out.write_string(
    "- Unclear sources: " + self.unclear_source_count.to_string() + "\n",
  )
  out.write_string(
    "- License issues: " + self.license_issue_count.to_string() + "\n\n",
  )
  out.write_string("## Snippets\n\n")
  for snippet in self.snippets {
    out.write_string("- " + snippet.label)
    out.write_string(" language=" + snippet.language)
    out.write_string(" runnable=" + snippet.runnable.to_string() + "\n")
  }
  out.write_string("\n## Source Evidence\n\n")
  if self.sources.length() == 0 {
    out.write_string("- No external source records declared.\n")
  } else {
    for source in self.sources {
      out.write_string("- " + source.name)
      out.write_string(" kind=" + source_kind_text(source.kind))
      out.write_string(" license=" + source.license + "\n")
    }
  }
  out.to_string()
}

///|
pub fn doc_proof_markdown(
  markdown : String,
  sources : Array[SourceEvidence],
) -> String {
  doc_proof(markdown, sources).to_markdown()
}