///|
priv struct Checkpoint {
  input : DataFormat?
  output : DataFormat
  codecs : @data.Registry
}

///|
pub(all) struct ContentArtifact {
  path : String
  source : String
  content : @data.Content
  format : String
}

///|
pub(all) struct ContentReport {
  artifacts : Array[ContentArtifact]
  projects : Array[LibraryProject]
  diagnostics : Array[Diagnostic]
  warnings : Array[Diagnostic]
  processed : Int32
}

///|
pub fn ContentReport::successful(self : ContentReport) -> Bool {
  self.diagnostics.is_empty()
}

///|
pub(all) struct BoundarySelection {
  source : String
  unit_id : String
  input_format : String
  output_format : String
  profile : String?
  read_bytes : Bool
  write_bytes : Bool
}

///|
fn Engine::data_format(self : Engine, name : String) -> DataFormat raise {
  match self.data_formats.get(name) {
    Some(format) => format
    None => raise @configuration.Invalid("data.unknown_codec: " + name)
  }
}

///|
/// Describe file selection and transport independently of the registered codec.
pub(all) struct DataFormat {
  codec : String
  extensions : Array[String]
  suffix : String
  binary : Bool
}

///|
pub fn Engine::register_data_format(
  self : Engine,
  name : String,
  format : DataFormat,
) -> Unit raise {
  if name == "" ||
    self.data_formats.contains(name) ||
    self.frontends.contains(name) ||
    self.source_providers
    .values()
    .any(p => p.languages.any(l => l.id.to_string() == name)) ||
    name == "ir-json" {
    raise @configuration.Invalid("data.format_conflict: " + name)
  }
  fn valid_suffix(suffix : String) -> Bool {
    suffix.length() > 1 &&
    suffix.has_prefix(".") &&
    !suffix.contains("/") &&
    !suffix.contains("\\") &&
    !suffix.contains(":") &&
    !suffix.contains("\u0000")
  }
  if format.codec == "" ||
    !valid_suffix(format.suffix) ||
    format.extensions.is_empty() ||
    !format.extensions.all(valid_suffix) {
    raise @configuration.Invalid("data.invalid_format: " + name)
  }
  self.data_formats[name] = { ..format, extensions: format.extensions.copy(), }
}

///|
pub fn Plan::boundaries(self : Plan) -> Array[BoundarySelection] {
  let result = []
  for job in self.jobs {
    for source in job.paths {
      result.push({
        source,
        unit_id: match job.retained_unit {
          Some(unit) => unit.id
          None =>
            stem(
              if job.source_root == "" {
                source
              } else {
                source[job.source_root.length() + 1:].to_owned()
              },
            )
        },
        input_format: match job.boundary {
          Some(b) =>
            match b.input {
              Some(input) => input.codec
              None => job.settings.frontend
            }
          None => job.settings.frontend
        },
        output_format: match job.boundary {
          Some(b) =>
            if job.generator is Some(_) {
              job.settings.backend
            } else {
              b.output.codec
            }
          None => job.settings.backend
        },
        profile: match job.boundary {
          Some(_) => Some(@data.PROFILE)
          None => None
        },
        read_bytes: job.read_bytes,
        write_bytes: match job.boundary {
          Some(b) =>
            match job.generator {
              Some(g) => g.write_bytes
              None => b.output.binary
            }
          None => false
        },
      })
    }
  }
  result
}

///|
pub fn Plan::requires_byte_read(self : Plan) -> Bool {
  self.jobs.any(job => job.read_bytes)
}

///|
pub fn Plan::requires_byte_write(self : Plan) -> Bool {
  self.jobs.any(job => {
    job.unit_backend ||
    (match job.boundary {
      Some(boundary) =>
        match job.generator {
          Some(g) => g.write_bytes
          None => boundary.output.binary
        }
      None => false
    })
  })
}

///|
/// The existing text executor cannot represent a binary checkpoint result.
pub fn Engine::execute(
  self : Engine,
  plan : Plan,
  host : Host,
  cancelled~ : () -> Bool = fn() { false },
) -> Report {
  if plan.requires_byte_read() || plan.requires_byte_write() {
    return {
      artifacts: [],
      processed: 0,
      diagnostics: [
        {
          path: "",
          stage: "capability",
          message: "host.bytes_required: Binary plans require execute_content",
          details: None,
          problem: None,
        },
      ],
    }
  }
  self
  .execute_content(
    plan,
    fn(path, _) raise { @data.Text((host.read)(path)) },
    cancelled~,
  )
  .text_report()
}

///|
pub fn ContentReport::text_report(self : ContentReport) -> Report {
  let artifacts = []
  for artifact in self.artifacts {
    let content = match artifact.content {
      Text(text) => text
      Binary(_) =>
        return {
          artifacts: [],
          processed: 0,
          diagnostics: self.diagnostics +
          [
            {
              path: artifact.path,
              stage: "capability",
              message: "host.byte_write_required: Binary artifacts cannot be represented by a text report",
              details: None,
              problem: None,
            },
          ],
        }
    }
    artifacts.push({ path: artifact.path, source: artifact.source, content, })
  }
  { artifacts, diagnostics: self.diagnostics, processed: self.processed, }
}