///|
pub(all) struct OutputPlan {
  slug : String
  relative_path : String
  index : Int
  template : String
  preset : String
  theme : String
} derive(Debug, Eq, ToJson)

///|
pub fn output_plan(output_dir : String, batch : BatchJob) -> Array[OutputPlan] {
  let plans : Array[OutputPlan] = []
  batch.jobs.eachi((index, job) => {
    let separator = if output_dir.has_suffix("/") || output_dir.has_suffix("\\") {
      ""
    } else {
      "/"
    }
    plans.push({
      slug: job.slug,
      relative_path: output_dir + separator + job.slug + ".svg",
      index,
      template: job.template,
      preset: job.preset,
      theme: job.theme,
    })
  })
  plans
}

///|
pub fn plan_has_duplicate_paths(plans : Array[OutputPlan]) -> Bool {
  let paths : Array[String] = []
  plans.any(plan => {
    if paths.any(path => path == plan.relative_path) {
      true
    } else {
      paths.push(plan.relative_path)
      false
    }
  })
}

///|
pub fn plan_summary(plans : Array[OutputPlan]) -> String {
  if plans.is_empty() {
    "empty-plan"
  } else {
    let first = plans[0]
    "outputs=" +
    plans.length().to_string() +
    ",first=" +
    first.relative_path +
    ",templates=" +
    plans.map(plan => plan.template).join(",")
  }
}

///|
pub fn valid_output_slug(slug : String) -> Bool {
  if slug.is_empty() || slug == "." || slug == ".." {
    false
  } else {
    slug
    .to_array()
    .all(char => {
      (char >= 'a' && char <= 'z') ||
      (char >= 'A' && char <= 'Z') ||
      (char >= '0' && char <= '9') ||
      char == '-' ||
      char == '_' ||
      char == '.'
    })
  }
}

///|
pub fn invalid_slugs(batch : BatchJob) -> Array[String] {
  batch.jobs.map(job => job.slug).filter(slug => !valid_output_slug(slug))
}