///|
/// Host-verified OCI image configuration metadata for one pinned FROM image.
/// The host must verify the index -> platform manifest -> config byte digests.
/// The core intentionally consumes only identity, platform and ONBUILD data;
/// base-image ENV remains unknown.
pub(all) struct ImageContextMetadata {
  reference : String
  platform : String
  index_digest : String
  manifest_digest : String
  config_digest : String
  onbuild : Array[String]
} derive(Debug, Eq, ToJson, @json.FromJson)

///|
pub extend ImageContextMetadata with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend ImageContextMetadata with Eq::{not_equal, equal}

///|
pub extend ImageContextMetadata with ToJson::{to_json}

///|
pub extend ImageContextMetadata with @json.FromJson::{from_json}

///|
/// Explains whether a matching image profile suppressed ONBUILD uncertainty.
pub(all) struct ImageContextMetadataEvidence {
  stage : Int
  line : Int
  reference : String
  platform : String
  status : String
  reason : String
  index_digest : String
  manifest_digest : String
  config_digest : String
} derive(Debug, Eq, ToJson)

///|
pub extend ImageContextMetadataEvidence with @moonbitlang/core/debug.Debug::{
  to_repr,
}

///|
pub extend ImageContextMetadataEvidence with Eq::{not_equal, equal}

///|
pub extend ImageContextMetadataEvidence with ToJson::{to_json}

///|
priv struct ImageContextMetadataDecision {
  safe_empty_onbuild : Bool
  evidence : ImageContextMetadataEvidence
}

///|
fn image_context_digest_is_sha256(value : String) -> Bool {
  let chars = value.to_array()
  if chars.length() != 71 || String::from_array(chars[:7]) != "sha256:" {
    return false
  }
  for c in chars[7:] {
    if !"0123456789abcdef".contains(c.to_string()) {
      return false
    }
  }
  true
}

///|
fn image_context_reference_digest(reference : String) -> String? {
  let parts = reference.split("@").collect()
  if parts.length() != 2 || parts[0].is_empty() {
    return None
  }
  Some(parts[1].to_owned())
}

///|
fn image_context_literal(value : String) -> Bool {
  if value.is_empty() {
    return false
  }
  for c in value.iter() {
    if "$*?\\\"' \t\n".contains(c.to_string()) {
      return false
    }
  }
  true
}

///|
fn image_context_linux_platform(value : String) -> Bool {
  let parts = value.split("/").collect()
  if parts.length() < 2 || parts.length() > 3 || parts[0] != "linux" {
    return false
  }
  for part in parts[1:] {
    if part.is_empty() ||
      !part
      .to_owned()
      .iter()
      .all(c => "abcdefghijklmnopqrstuvwxyz0123456789".contains(c.to_string())) {
      return false
    }
  }
  true
}

///|
fn image_context_evidence(
  stage : Stage,
  reference : String,
  platform : String,
  status : String,
  reason : String,
  index_digest : String,
  manifest_digest : String,
  config_digest : String,
) -> ImageContextMetadataEvidence {
  {
    stage: stage.index,
    line: stage.line,
    reference,
    platform,
    status,
    reason,
    index_digest,
    manifest_digest,
    config_digest,
  }
}

///|
fn image_context_from_platform(
  instruction : Instruction,
  requested_platform : String,
) -> (String, String) raise ParseError {
  let args = instruction.split_arguments()
  let mut platform_flag = ""
  let mut platform_flag_count = 0
  for flag in leading_flags(instruction.arguments) {
    let parts = flag.split("=").collect()
    if parts[0] == "--platform" {
      platform_flag_count += 1
      platform_flag = flag
    }
  }
  if platform_flag_count > 1 {
    return ("", "duplicate FROM --platform flags are not supported")
  }
  if platform_flag_count == 1 {
    if !platform_flag.has_prefix("--platform=") {
      return ("", "FROM --platform must use a literal value")
    }
    match args.flags.get("platform") {
      None => ("", "FROM --platform value is unavailable")
      Some(value) =>
        if !image_context_literal(value) || !image_context_linux_platform(value) {
          ("", "dynamic FROM --platform is not supported")
        } else {
          (value, "")
        }
    }
  } else if !image_context_literal(requested_platform) ||
    !image_context_linux_platform(requested_platform) {
    ("", "an explicit literal target platform is required")
  } else {
    (requested_platform, "")
  }
}

///|
fn image_context_metadata_decision(
  stage : Stage,
  from_instruction : Instruction?,
  requested_platform : String,
  metadata : Array[ImageContextMetadata],
  source_onbuild : Bool,
) -> ImageContextMetadataDecision raise ParseError {
  let mut effective_platform = ""
  let mut platform_reason = ""
  match from_instruction {
    None => platform_reason = "FROM instruction could not be matched to stage"
    Some(instruction) => {
      let (platform, reason) = image_context_from_platform(
        instruction, requested_platform,
      )
      effective_platform = platform
      platform_reason = reason
    }
  }
  let matching : Array[ImageContextMetadata] = []
  let mut same_reference = false
  for item in metadata {
    if item.reference == stage.base {
      same_reference = true
      if effective_platform != "" && item.platform == effective_platform {
        matching.push(item)
      }
    }
  }
  let item = if matching.length() == 1 { Some(matching[0]) } else { None }
  let mut status = if metadata.is_empty() { "missing" } else { "refused" }
  let mut reason = if metadata.is_empty() {
    "external base image ONBUILD metadata is unavailable"
  } else {
    platform_reason
  }
  let mut index_digest = ""
  let mut manifest_digest = ""
  let mut config_digest = ""
  let mut profile_platform = effective_platform
  if reason == "" && matching.length() > 1 {
    reason = "duplicate metadata for the same image reference and platform"
  } else if reason == "" && item is None {
    status = "missing"
    reason = if metadata.is_empty() {
      "external base image ONBUILD metadata is unavailable"
    } else if same_reference {
      "image metadata platform does not match the effective FROM platform"
    } else {
      "no host-verified image metadata matches the pinned FROM reference"
    }
  }
  if item is Some(profile) && reason == "" {
    profile_platform = profile.platform
    index_digest = profile.index_digest
    manifest_digest = profile.manifest_digest
    config_digest = profile.config_digest
    let reference_digest = image_context_reference_digest(stage.base)
    if !image_context_literal(stage.base) || reference_digest is None {
      reason = "FROM image must be a literal reference pinned by @sha256"
    } else if !image_context_digest_is_sha256(profile.index_digest) ||
      !image_context_digest_is_sha256(profile.manifest_digest) ||
      !image_context_digest_is_sha256(profile.config_digest) {
      reason = "image metadata contains a malformed SHA-256 digest"
    } else if reference_digest != Some(profile.index_digest) {
      reason = "image metadata index digest does not match the pinned FROM reference"
    } else if profile.index_digest == profile.manifest_digest {
      reason = "a distinct platform manifest digest is required for an index-pinned FROM"
    } else if profile.platform != effective_platform {
      reason = "image metadata platform does not match the effective FROM platform"
    } else if source_onbuild {
      reason = "Dockerfile-defined ONBUILD instructions are not modeled"
    } else if !profile.onbuild.is_empty() {
      reason = "nonempty image ONBUILD triggers are not interpreted"
    } else {
      status = "used-empty-onbuild"
      reason = "host-verified OCI config matches the pinned FROM and platform; ONBUILD is empty"
    }
  }
  let accepted = status == "used-empty-onbuild"
  {
    safe_empty_onbuild: accepted,
    evidence: image_context_evidence(
      stage,
      stage.base,
      profile_platform,
      status,
      reason,
      index_digest,
      manifest_digest,
      config_digest,
    ),
  }
}