///|
/// A local production workflow that validates, renders, measures and packages artwork.
pub(all) enum WorkflowStatus {
  WorkflowReady
  WorkflowRendered
  WorkflowValidated
  WorkflowPackaged
  WorkflowFailed
} derive(Eq, Debug, ToJson)

///|
pub(all) struct WorkflowStep {
  name : String
  status : WorkflowStatus
  duration_hint : Int
  detail : String
} derive(Eq, Debug, ToJson)

///|
pub(all) struct ProductionWorkflow {
  name : String
  seed : UInt
  palette : Palette
  width : Double
  height : Double
  steps : Array[WorkflowStep]
  warnings : Array[String]
} derive(Debug)

///|
pub(all) struct WorkflowArtifact {
  document : DesignDocument
  plan : ExportPlan
  quality : DesignQualityMetrics
  validation : Array[String]
  warnings : Array[String]
  status : WorkflowStatus
} derive(Debug)

///|
pub fn ProductionWorkflow::new(
  name : String,
  seed : UInt,
  width : Double,
  height : Double,
  palette : Palette,
) -> ProductionWorkflow {
  {
    name,
    seed,
    palette,
    width: width.max(1.0),
    height: height.max(1.0),
    steps: [],
    warnings: [],
  }
}

///|
pub fn ProductionWorkflow::step(
  self : ProductionWorkflow,
  name : String,
  status : WorkflowStatus,
  detail : String,
) -> Unit {
  self.steps.push({ name, status, duration_hint: detail.length(), detail })
}

///|
pub fn ProductionWorkflow::warning(
  self : ProductionWorkflow,
  message : String,
) -> Unit {
  if message.length() > 0 && !self.warnings.contains(message) {
    self.warnings.push(message)
  }
}

///|
pub fn ProductionWorkflow::validate_input(
  self : ProductionWorkflow,
) -> Array[String] {
  let errors = []
  if self.name.length() == 0 {
    errors.push("workflow name is empty")
  }
  if self.width <= 0.0 || self.height <= 0.0 {
    errors.push("workflow canvas must be positive")
  }
  if self.palette.colors.length() == 0 {
    errors.push("workflow palette is empty")
  }
  if self.palette.colors.length() > 24 {
    self.warning("palette contains more than 24 colors")
  }
  errors
}

///|
pub fn workflow_default_job(workflow : ProductionWorkflow) -> ExportJob {
  ExportJob::new(
    workflow.name,
    workflow.seed,
    workflow.width,
    workflow.height,
    workflow.palette,
  )
  .tag("workflow")
  .tag("validated")
  .ensure_default_variants()
}

///|
pub fn workflow_compose(workflow : ProductionWorkflow) -> DesignDocument {
  let composition = SceneRecipe::new(
    workflow.seed,
    workflow.width,
    workflow.height,
    24,
    24,
    workflow.palette,
  ).build()
  let metadata = DocumentMetadata::new(
      workflow.name,
      "MoonBit Generative Patterns",
      workflow.seed,
      "deterministic production workflow",
    )
    .tag("workflow")
    .tag("deterministic")
    .tag("svg")
  DesignDocument::new(composition, metadata)
}

///|
pub fn workflow_validate_document(document : DesignDocument) -> Array[String] {
  let errors = document.validate()
  let metrics = composition_quality_metrics(document.composition)
  if metrics.path_count == 0 {
    errors.push("document contains no motifs")
  }
  if metrics.coverage > 1.0 {
    errors.push("document coverage exceeds canvas")
  }
  errors
}

///|
pub fn workflow_render_asset(
  workflow : ProductionWorkflow,
  document : DesignDocument,
) -> SvgAsset {
  svg_asset_from_document(
    "workflow",
    workflow.name,
    1,
    document.composition.render(),
    "workflow master",
  )
}

///|
pub fn workflow_build(workflow : ProductionWorkflow) -> WorkflowArtifact {
  let document = workflow_compose(workflow)
  let validation = workflow_validate_document(document)
  let job = workflow_default_job(workflow)
  let plan = export_plan(job)
  let quality = composition_quality_metrics(document.composition)
  let status = if validation.length() == 0 &&
    export_plan_validate(plan).length() == 0 {
    WorkflowPackaged
  } else {
    WorkflowFailed
  }
  { document, plan, quality, validation, warnings: workflow.warnings, status }
}

///|
pub fn workflow_artifact_svg(artifact : WorkflowArtifact) -> String {
  artifact.document.to_svg()
}

///|
pub fn workflow_artifact_manifest(artifact : WorkflowArtifact) -> String {
  export_plan_manifest(artifact.plan)
}

///|
pub fn workflow_artifact_report(artifact : WorkflowArtifact) -> String {
  let builder = StringBuilder()
  builder.write_string("status=" + workflow_status_name(artifact.status) + "\n")
  builder.write_string(design_quality_report_text(artifact.quality) + "\n")
  builder.write_string(export_plan_report(artifact.plan) + "\n")
  if artifact.validation.length() > 0 {
    builder.write_string("validation=" + artifact.validation.join("|") + "\n")
  }
  if artifact.warnings.length() > 0 {
    builder.write_string("warnings=" + artifact.warnings.join("|") + "\n")
  }
  builder.to_string()
}

///|
pub fn workflow_artifact_checksum(artifact : WorkflowArtifact) -> UInt {
  svg_document_checksum(artifact.document.composition.render())
}

///|
pub fn workflow_status_name(status : WorkflowStatus) -> String {
  match status {
    WorkflowReady => "ready"
    WorkflowRendered => "rendered"
    WorkflowValidated => "validated"
    WorkflowPackaged => "packaged"
    WorkflowFailed => "failed"
  }
}

///|
pub fn workflow_steps_csv(workflow : ProductionWorkflow) -> String {
  let builder = StringBuilder()
  builder.write_string("name,status,duration_hint,detail\n")
  for step in workflow.steps {
    builder.write_string(
      step.name +
      "," +
      workflow_status_name(step.status) +
      "," +
      step.duration_hint.to_string() +
      "," +
      step.detail +
      "\n",
    )
  }
  builder.to_string()
}

///|
pub fn workflow_validate_palette(
  workflow : ProductionWorkflow,
) -> Array[String] {
  let errors = []
  let report = analyze_palette(workflow.palette)
  if report.color_count == 0 {
    errors.push("palette has no colors")
  }
  if report.contrast_max < 2.0 && report.color_count > 1 {
    errors.push("palette contrast range is too narrow")
  }
  for item in audit_palette_for_modes(workflow.palette) {
    let mode = item.0
    let collision = item.1
    if collision > workflow.palette.colors.length() {
      errors.push("color vision collision in " + ca_mode_name(mode))
    }
  }
  errors
}

///|
pub fn workflow_quality_gate(
  artifact : WorkflowArtifact,
  minimum : Double,
) -> Bool {
  artifact.status is WorkflowPackaged &&
  artifact.quality.score >= minimum &&
  artifact.validation.length() == 0
}

///|
pub fn workflow_preview(
  artifact : WorkflowArtifact,
  width : Int,
  height : Int,
) -> Raster {
  let raster = Raster::new(width, height)
  let composition = artifact.document.composition
  for layer in composition.layers {
    for motif in layer.motifs {
      raster.fill_polygon(
        motif.transformed_path().flatten(subdivisions=8),
        Color::rgb8(40, 40, 40),
      )
    }
  }
  raster
}

///|
pub fn workflow_preview_quality(
  artifact : WorkflowArtifact,
  width : Int,
  height : Int,
) -> RasterQualityMetrics {
  raster_quality_metrics(workflow_preview(artifact, width, height), 0.02)
}

///|
pub fn workflow_with_standard_steps(
  workflow : ProductionWorkflow,
) -> ProductionWorkflow {
  workflow.step("input", WorkflowReady, "parameters accepted")
  workflow.step("compose", WorkflowRendered, "scene recipe expanded")
  workflow.step("validate", WorkflowValidated, "document and palette audited")
  workflow.step("package", WorkflowPackaged, "SVG variants prepared")
  workflow
}

///|
pub fn workflow_run(
  name : String,
  seed : UInt,
  width : Double,
  height : Double,
  palette : Palette,
) -> WorkflowArtifact {
  let workflow = workflow_with_standard_steps(
    ProductionWorkflow::new(name, seed, width, height, palette),
  )
  workflow_build(workflow)
}

///|
pub fn workflow_batch(
  jobs : Array[ProductionWorkflow],
) -> Array[WorkflowArtifact] {
  let result = []
  for job in jobs {
    result.push(workflow_build(job))
  }
  result
}

///|
pub fn workflow_batch_report(artifacts : Array[WorkflowArtifact]) -> String {
  let builder = StringBuilder()
  builder.write_string("index,status,quality,coverage,bytes,checksum,errors\n")
  for index, artifact in artifacts {
    builder.write_string(
      index.to_string() +
      "," +
      workflow_status_name(artifact.status) +
      "," +
      artifact.quality.score.to_string() +
      "," +
      artifact.quality.coverage.to_string() +
      "," +
      artifact.plan.total_bytes.to_string() +
      "," +
      workflow_artifact_checksum(artifact).to_string() +
      "," +
      artifact.validation.length().to_string() +
      "\n",
    )
  }
  builder.to_string()
}

///|
pub fn workflow_compare(
  left : WorkflowArtifact,
  right : WorkflowArtifact,
) -> String {
  let delta = right.quality.score - left.quality.score
  "quality_delta=" +
  delta.to_string() +
  " bytes_delta=" +
  (right.plan.total_bytes - left.plan.total_bytes).to_string() +
  " checksum_left=" +
  workflow_artifact_checksum(left).to_string() +
  " checksum_right=" +
  workflow_artifact_checksum(right).to_string()
}

///|
pub fn workflow_document_bundle(artifact : WorkflowArtifact) -> SvgAssetBundle {
  let result = SvgAssetBundle::new(artifact.plan.job.name, "workflow-1")
  let _ = result.add(
    workflow_render_asset(
      ProductionWorkflow::new(
        artifact.plan.job.name,
        artifact.plan.job.seed,
        artifact.document.composition.width,
        artifact.document.composition.height,
        artifact.plan.job.palette,
      ),
      artifact.document,
    ),
  )
  for asset in artifact.plan.bundle.assets {
    let _ = result.add(asset)
  }
  result
}

///|
pub fn workflow_document_css(artifact : WorkflowArtifact) -> String {
  color_analysis_css(artifact.plan.job.palette, artifact.plan.job.name)
}

///|
pub fn workflow_reproducible_pair(
  left : WorkflowArtifact,
  right : WorkflowArtifact,
) -> Bool {
  workflow_artifact_checksum(left) == workflow_artifact_checksum(right) &&
  left.quality.score == right.quality.score
}

///|
pub fn workflow_safe_to_publish(artifact : WorkflowArtifact) -> Bool {
  workflow_quality_gate(artifact, 0.2) &&
  svg_bundle_validate(artifact.plan.bundle).length() == 0
}