///|
pub enum WorkflowMode {
  Development
  Validation
  Release
} derive(Debug, Eq, ToJson, FromJson)

///|
pub enum ArtifactKind {
  Atlas
  Animation
  Runtime
  Diagnostics
} derive(Debug, Eq, ToJson, FromJson)

///|
pub struct WorkflowOptions {
  name : String
  mode : WorkflowMode
  atlas_strategy : AtlasStrategy
  max_width : Int
  padding : Int
  runtime : RuntimeTarget
  include_diagnostics : Bool
} derive(Debug, Eq, ToJson, FromJson)

///|
pub fn WorkflowOptions::development(name : String) -> WorkflowOptions {
  {
    name,
    mode: Development,
    atlas_strategy: Rows,
    max_width: 1024,
    padding: 1,
    runtime: Generic,
    include_diagnostics: true,
  }
}

///|
pub fn WorkflowOptions::validation(name : String) -> WorkflowOptions {
  {
    name,
    mode: Validation,
    atlas_strategy: CompactRows,
    max_width: 2048,
    padding: 1,
    runtime: Generic,
    include_diagnostics: true,
  }
}

///|
pub fn WorkflowOptions::release(name : String) -> WorkflowOptions {
  {
    name,
    mode: Release,
    atlas_strategy: PowerOfTwo,
    max_width: 4096,
    padding: 2,
    runtime: MoonBit,
    include_diagnostics: false,
  }
}

///|
pub fn WorkflowOptions::with_atlas(
  self : WorkflowOptions,
  strategy : AtlasStrategy,
  max_width : Int,
  padding : Int,
) -> WorkflowOptions {
  { ..self, atlas_strategy: strategy, max_width, padding }
}

///|
pub fn WorkflowOptions::with_runtime(
  self : WorkflowOptions,
  runtime : RuntimeTarget,
) -> WorkflowOptions {
  { ..self, runtime, }
}

///|
pub fn WorkflowOptions::with_diagnostics(
  self : WorkflowOptions,
  enabled : Bool,
) -> WorkflowOptions {
  { ..self, include_diagnostics: enabled }
}

///|
pub struct WorkflowArtifact {
  name : String
  kind : ArtifactKind
  payload : String
  bytes : Int
} derive(Debug, Eq, ToJson, FromJson)

///|
pub struct WorkflowReport {
  name : String
  mode : WorkflowMode
  artifacts : Array[WorkflowArtifact]
  errors : Int
  warnings : Int
  frame_count : Int
  animation_count : Int
  passed : Bool
} derive(Debug, Eq, ToJson, FromJson)

///|
pub fn WorkflowReport::artifact_named(
  self : WorkflowReport,
  name : String,
) -> WorkflowArtifact? {
  match self.artifacts.search_by(fn(artifact) { artifact.name == name }) {
    Some(index) => Some(self.artifacts[index])
    None => None
  }
}

///|
pub fn WorkflowReport::has_kind(
  self : WorkflowReport,
  kind : ArtifactKind,
) -> Bool {
  for artifact in self.artifacts {
    if artifact.kind == kind {
      return true
    }
  }
  false
}

///|
pub fn WorkflowReport::total_bytes(self : WorkflowReport) -> Int {
  self.artifacts.fold(init=0, (total, artifact) => total + artifact.bytes)
}

///|
pub fn WorkflowReport::to_json_string(
  self : WorkflowReport,
  indent? : Int = 2,
) -> String {
  self.to_json().stringify(indent~)
}

///|
fn workflow_artifact(
  name : String,
  kind : ArtifactKind,
  payload : String,
) -> WorkflowArtifact {
  { name, kind, payload, bytes: payload.length() }
}

///|
fn workflow_options_checked(options : WorkflowOptions) -> Array[Diagnostic] {
  let result : Array[Diagnostic] = []
  if options.name == "" {
    result.push({
      severity: Error,
      code: "empty_workflow_name",
      message: "workflow name must not be empty",
    })
  }
  if options.max_width <= 0 {
    result.push({
      severity: Error,
      code: "invalid_workflow_width",
      message: "workflow atlas width must be positive",
    })
  }
  if options.padding < 0 {
    result.push({
      severity: Error,
      code: "invalid_workflow_padding",
      message: "workflow padding must not be negative",
    })
  }
  result
}

///|
pub fn build_workflow(
  sheet : SpriteSheet,
  options : WorkflowOptions,
) -> WorkflowReport {
  let option_diagnostics = workflow_options_checked(options)
  let enriched = attach_collision_slices(sheet)
  let diagnostics = validate_sheet(enriched) + option_diagnostics
  let summary = summarize_diagnostics(diagnostics)
  let atlas = pack_strategy(enriched, strategy=options.atlas_strategy, options={
    max_width: options.max_width,
    padding: options.padding,
  })
  let machine = build_machine(enriched)
  let atlas_name = options.name + ".atlas"
  let animation_name = options.name + ".animations"
  let runtime_name = options.name + "." + runtime_extension(options.runtime)
  let artifacts : Array[WorkflowArtifact] = [
    workflow_artifact(atlas_name, Atlas, atlas.to_json_string(indent=2)),
    workflow_artifact(
      animation_name,
      Animation,
      machine.to_json_string(indent=2),
    ),
    workflow_artifact(
      runtime_name,
      Runtime,
      export_runtime_bundle(
        {
          sheet: enriched,
          atlas,
          machine,
          nine_patches: collect_nine_patches(enriched),
          diagnostics,
        },
        target=options.runtime,
      ),
    ),
  ]
  if options.include_diagnostics {
    artifacts.push(
      workflow_artifact(
        options.name + ".diagnostics",
        Diagnostics,
        diagnostics_to_json(diagnostics),
      ),
    )
  }
  {
    name: options.name,
    mode: options.mode,
    artifacts,
    errors: summary.errors,
    warnings: summary.warnings,
    frame_count: enriched.frames.length(),
    animation_count: machine.clips.length(),
    passed: summary.errors == 0,
  }
}

///|
fn runtime_extension(target : RuntimeTarget) -> String {
  match target {
    Generic => "json"
    Godot => "tres"
    Phaser => "json"
    MoonBit => "mbt.json"
  }
}

///|
pub fn validate_workflow(
  sheet : SpriteSheet,
  options : WorkflowOptions,
) -> WorkflowReport {
  build_workflow(sheet, {
    ..options,
    mode: Validation,
    include_diagnostics: true,
  })
}

///|
pub fn release_workflow(sheet : SpriteSheet, name : String) -> WorkflowReport {
  build_workflow(sheet, WorkflowOptions::release(name))
}

///|
pub fn development_workflow(
  sheet : SpriteSheet,
  name : String,
) -> WorkflowReport {
  build_workflow(sheet, WorkflowOptions::development(name))
}

///|
pub fn workflow_is_releasable(report : WorkflowReport) -> Bool {
  report.passed &&
  report.has_kind(Atlas) &&
  report.has_kind(Animation) &&
  report.has_kind(Runtime)
}

///|
pub fn workflow_status(report : WorkflowReport) -> String {
  if report.passed {
    "passed"
  } else {
    "failed"
  }
}

///|
pub fn workflow_artifact_names(report : WorkflowReport) -> Array[String] {
  report.artifacts.map(fn(artifact) { artifact.name })
}

///|
pub fn workflow_artifact_kinds(report : WorkflowReport) -> Array[ArtifactKind] {
  report.artifacts.map(fn(artifact) { artifact.kind })
}

///|
pub fn workflow_diagnostics(report : WorkflowReport) -> DiagnosticSummary {
  { errors: report.errors, warnings: report.warnings }
}

///|
pub fn workflow_rebuild_is_deterministic(
  sheet : SpriteSheet,
  options : WorkflowOptions,
) -> Bool {
  let left = build_workflow(sheet, options)
  let right = build_workflow(sheet, options)
  left.to_json_string(indent=0) == right.to_json_string(indent=0)
}

///|
pub fn workflow_target_options(
  name : String,
  target : RuntimeTarget,
) -> WorkflowOptions {
  WorkflowOptions::release(name).with_runtime(target)
}

///|
pub fn workflow_for_editor(name : String) -> WorkflowOptions {
  WorkflowOptions::development(name).with_runtime(Generic)
}

///|
pub fn workflow_for_ci(name : String) -> WorkflowOptions {
  WorkflowOptions::validation(name).with_diagnostics(true)
}

///|
pub fn workflow_for_shipping(name : String) -> WorkflowOptions {
  WorkflowOptions::release(name).with_diagnostics(false)
}