///|
/// Output formats supported by the deterministic export pipeline.
pub(all) enum ArtifactKind {
  Svg
  Html
  DataUri
  Css
  MetricsCsv
  Manifest
} derive(Eq, Debug, ToJson)

///|
/// A named in-memory export artifact.
pub(all) struct ExportArtifact {
  name : String
  kind : ArtifactKind
  content : String
  byte_length : Int
  checksum : Int
} derive(Eq, Debug, ToJson)

///|
/// Export switches for applications and command-line integrations.
pub(all) struct PipelineOptions {
  title : String
  filename : String
  include_html : Bool
  include_data_uri : Bool
  include_css : Bool
  include_metrics : Bool
  include_manifest : Bool
} derive(Eq, Debug, ToJson)

///|
pub fn PipelineOptions::new(
  title : String,
  filename : String,
) -> PipelineOptions {
  {
    title,
    filename,
    include_html: true,
    include_data_uri: false,
    include_css: true,
    include_metrics: true,
    include_manifest: true,
  }
}

///|
pub fn PipelineOptions::html(
  self : PipelineOptions,
  enabled : Bool,
) -> PipelineOptions {
  { ..self, include_html: enabled }
}

///|
pub fn PipelineOptions::data_uri(
  self : PipelineOptions,
  enabled : Bool,
) -> PipelineOptions {
  { ..self, include_data_uri: enabled }
}

///|
pub fn PipelineOptions::css(
  self : PipelineOptions,
  enabled : Bool,
) -> PipelineOptions {
  { ..self, include_css: enabled }
}

///|
pub fn PipelineOptions::metrics(
  self : PipelineOptions,
  enabled : Bool,
) -> PipelineOptions {
  { ..self, include_metrics: enabled }
}

///|
pub fn PipelineOptions::manifest(
  self : PipelineOptions,
  enabled : Bool,
) -> PipelineOptions {
  { ..self, include_manifest: enabled }
}

///|
/// An export pipeline joining a composition with its theme and metadata policy.
pub(all) struct PatternPipeline {
  composition : Composition
  theme : ColorTheme
  options : PipelineOptions
} derive(Debug)

///|
pub fn PatternPipeline::new(
  composition : Composition,
  theme : ColorTheme,
  options : PipelineOptions,
) -> PatternPipeline {
  { composition, theme, options }
}

///|
pub fn PatternPipeline::with_options(
  self : PatternPipeline,
  options : PipelineOptions,
) -> PatternPipeline {
  { ..self, options, }
}

///|
pub fn PatternPipeline::svg(self : PatternPipeline) -> String {
  self.composition.render().to_svg_string()
}

///|
pub fn PatternPipeline::html(self : PatternPipeline) -> String {
  html_preview(self.composition.render(), self.options.title)
}

///|
pub fn PatternPipeline::data_uri(self : PatternPipeline) -> String {
  svg_data_uri(self.svg())
}

///|
pub fn PatternPipeline::css(self : PatternPipeline) -> String {
  self.theme.to_css()
}

///|
fn pipeline_metrics(composition : Composition) -> LayoutMetrics {
  let mut count = 0
  let mut bounds = Rect::new(0.0, 0.0, composition.width, composition.height)
  let mut path_length = 0.0
  let mut occupied_area = 0.0
  for layer in composition.layers {
    if layer.visible {
      for motif in layer.motifs {
        let path = motif.transformed_path()
        count += 1
        let path_bounds = path.bounds()
        bounds = bounds.union(path_bounds)
        path_length += path.length()
        occupied_area += path_bounds.width * path_bounds.height
      }
    }
  }
  let canvas_area = composition.width * composition.height
  {
    element_count: count,
    occupied_area,
    path_length,
    density: if canvas_area <= 0.0 {
      0.0
    } else {
      occupied_area / canvas_area
    },
    bounds,
  }
}

///|
pub fn PatternPipeline::metrics(self : PatternPipeline) -> LayoutMetrics {
  pipeline_metrics(self.composition)
}

///|
pub fn PatternPipeline::validate(self : PatternPipeline) -> Array[String] {
  let errors = []
  if self.composition.width <= 0.0 {
    errors.push("composition width must be positive")
  }
  if self.composition.height <= 0.0 {
    errors.push("composition height must be positive")
  }
  if self.options.title.length() == 0 {
    errors.push("export title must not be empty")
  }
  if self.options.filename.length() == 0 {
    errors.push("export filename must not be empty")
  }
  let audit = audit_theme(self.theme, 3.0)
  if !audit.passes {
    errors.push("theme contains a contrast pair below the requested ratio")
  }
  errors
}

///|
fn artifact_checksum(value : String) -> Int {
  let mut result = 0
  for code in value.to_array() {
    result = (result * 33 + code.to_int()) % 1000000007
  }
  result
}

///|
fn make_artifact(
  name : String,
  kind : ArtifactKind,
  content : String,
) -> ExportArtifact {
  {
    name,
    kind,
    byte_length: content.length(),
    checksum: artifact_checksum(content),
    content,
  }
}

///|
fn artifact_name(filename : String, suffix : String) -> String {
  filename + suffix
}

///|
fn manifest_content(
  options : PipelineOptions,
  artifacts : Array[ExportArtifact],
) -> String {
  let builder = StringBuilder()
  builder.write_string("{\n")
  builder.write_string("  \"title\": \"")
  builder.write_string(escape_svg(options.title))
  builder.write_string("\",\n  \"artifacts\": [\n")
  for index, artifact in artifacts {
    builder.write_string("    {\"name\": \"")
    builder.write_string(escape_svg(artifact.name))
    builder.write_string("\", \"kind\": \"")
    builder.write_string(artifact_kind_name(artifact.kind))
    builder.write_string("\", \"bytes\": ")
    builder.write_string(artifact.byte_length.to_string())
    builder.write_string(", \"checksum\": ")
    builder.write_string(artifact.checksum.to_string())
    builder.write_string("}")
    if index + 1 < artifacts.length() {
      builder.write_string(",")
    }
    builder.write_string("\n")
  }
  builder.write_string("  ]\n}\n")
  builder.to_string()
}

///|
fn artifact_kind_name(kind : ArtifactKind) -> String {
  match kind {
    Svg => "svg"
    Html => "html"
    DataUri => "data-uri"
    Css => "css"
    MetricsCsv => "metrics-csv"
    Manifest => "manifest"
  }
}

///|
/// Exports all enabled artifacts in stable order.
pub fn PatternPipeline::artifacts(
  self : PatternPipeline,
) -> Array[ExportArtifact] {
  let result = []
  let svg = self.svg()
  result.push(make_artifact(self.options.filename, Svg, svg))
  if self.options.include_html {
    result.push(
      make_artifact(
        artifact_name(self.options.filename, ".html"),
        Html,
        self.html(),
      ),
    )
  }
  if self.options.include_data_uri {
    result.push(
      make_artifact(
        artifact_name(self.options.filename, ".uri.txt"),
        DataUri,
        self.data_uri(),
      ),
    )
  }
  if self.options.include_css {
    result.push(
      make_artifact(
        artifact_name(self.options.filename, ".css"),
        Css,
        self.css(),
      ),
    )
  }
  if self.options.include_metrics {
    result.push(
      make_artifact(
        artifact_name(self.options.filename, ".metrics.csv"),
        MetricsCsv,
        metrics_csv(self.metrics()),
      ),
    )
  }
  if self.options.include_manifest {
    result.push(
      make_artifact(
        artifact_name(self.options.filename, ".manifest.json"),
        Manifest,
        manifest_content(self.options, result),
      ),
    )
  }
  result
}

///|
/// Returns a concise CSV report for one layout metrics value.
pub fn metrics_csv(metrics : LayoutMetrics) -> String {
  "element_count,occupied_area,path_length,density,x,y,width,height\n" +
  "\{metrics.element_count},\{metrics.occupied_area},\{metrics.path_length},\{metrics.density},\{metrics.bounds.x},\{metrics.bounds.y},\{metrics.bounds.width},\{metrics.bounds.height}\n"
}

///|
/// Joins artifact metadata into a stable table suitable for build logs.
pub fn artifact_report(artifacts : Array[ExportArtifact]) -> String {
  let builder = StringBuilder()
  builder.write_string("name,kind,bytes,checksum\n")
  for artifact in artifacts {
    builder.write_string(artifact.name)
    builder.write_string(",")
    builder.write_string(artifact_kind_name(artifact.kind))
    builder.write_string(",")
    builder.write_string(artifact.byte_length.to_string())
    builder.write_string(",")
    builder.write_string(artifact.checksum.to_string())
    builder.write_string("\n")
  }
  builder.to_string()
}

///|
/// Returns a stable checksum for all artifact metadata and content.
pub fn artifact_bundle_checksum(artifacts : Array[ExportArtifact]) -> Int {
  artifact_checksum(artifact_report(artifacts))
}

///|
/// Creates a pipeline around a standard scene recipe.
pub fn scene_pipeline(
  seed : UInt,
  width : Double,
  height : Double,
  palette : Palette,
  options : PipelineOptions,
) -> PatternPipeline {
  let document = make_design_document(seed, width, height, palette)
  let theme = ColorTheme::new(palette.name, palette)
  PatternPipeline::new(document.composition, theme, options)
}

///|
/// Creates a pipeline for a tile motif and its generated surface.
pub fn tile_pipeline(
  bounds : Rect,
  path : Path,
  style : SvgStyle,
  grid : TileGrid,
  palette : Palette,
  options : PipelineOptions,
) -> PatternPipeline {
  let composition = Composition::new(
    bounds.width,
    bounds.height,
    background=palette.at(0).unwrap_or(Color::rgb8(255, 255, 255)),
  )
  let layer = PatternLayer::new("tile")
  for placed in tile_path(path, grid) {
    layer.add(Motif::new("tile", placed, style))
  }
  composition.add_layer(layer)
  PatternPipeline::new(
    composition,
    ColorTheme::new(palette.name, palette),
    options,
  )
}

///|
/// Returns a one-line quality summary for CI logs.
pub fn pipeline_summary(pipeline : PatternPipeline) -> String {
  let metrics = pipeline.metrics()
  let errors = pipeline.validate()
  "title=\{pipeline.options.title}; elements=\{metrics.element_count}; density=\{metrics.density}; valid=\{errors.length() == 0}"
}