///|
pub enum FieldRequirement {
  Required
  Optional
} derive(Debug, Eq)

///|
pub fn required() -> FieldRequirement {
  Required
}

///|
pub fn optional() -> FieldRequirement {
  Optional
}

///|
pub struct TemplateField {
  name : String
  requirement : FieldRequirement
} derive(Debug, Eq)

///|
pub fn template_field(
  name : String,
  requirement : FieldRequirement,
) -> TemplateField {
  { name, requirement }
}

///|
pub struct TemplateValue {
  name : String
  value : String
} derive(Debug, Eq)

///|
pub fn template_value(name : String, value : String) -> TemplateValue {
  { name, value }
}

///|
pub struct ReportTemplate {
  name : String
  fields : Array[TemplateField]
} derive(Debug, Eq)

///|
pub fn report_template(
  name : String,
  fields : Array[TemplateField],
) -> ReportTemplate {
  { name, fields }
}

///|
fn template_find(values : Array[TemplateValue], name : String) -> String? {
  for value in values {
    if value.name == name {
      return Some(value.value)
    }
  }
  None
}

///|
pub fn ReportTemplate::field_count(self : ReportTemplate) -> Int {
  self.fields.length()
}

///|
pub fn ReportTemplate::required_count(self : ReportTemplate) -> Int {
  let mut count = 0
  for field in self.fields {
    if field.requirement is Required {
      count = count + 1
    }
  }
  count
}

///|
pub fn ReportTemplate::validate(
  self : ReportTemplate,
  values : Array[TemplateValue],
) -> Array[String] {
  let errors : Array[String] = []
  for field in self.fields {
    match template_find(values, field.name) {
      Some(value) =>
        if field.requirement is Required && value.trim().length() == 0 {
          errors.push(field.name + " must not be empty")
        }
      None =>
        if field.requirement is Required {
          errors.push(field.name + " is required")
        }
    }
  }
  errors
}

///|
pub fn ReportTemplate::render(
  self : ReportTemplate,
  values : Array[TemplateValue],
) -> String {
  let lines : Array[String] = ["# " + self.name, ""]
  for field in self.fields {
    let value = template_find(values, field.name).unwrap_or("")
    lines.push("- **" + field.name + "**: " + value)
  }
  lines.join("\n")
}

///|
pub fn ReportTemplate::to_report(
  self : ReportTemplate,
  values : Array[TemplateValue],
  metadata : ReportMetadata,
) -> ChemReport {
  let assumptions : Array[Assumption] = []
  let inputs : Array[InputValue] = []
  for field in self.fields {
    let value = template_find(values, field.name).unwrap_or("")
    inputs.push({ name: field.name, value, unit: None, note: None })
  }
  report(
    metadata,
    self.render(values),
    assumptions,
    inputs,
    [],
    [],
    [],
    [],
    [{ title: self.name, kind: Notes, body: self.render(values) }],
    ["template", self.name],
  )
}

///|
pub fn ReportTemplate::missing_fields(
  self : ReportTemplate,
  values : Array[TemplateValue],
) -> Array[String] {
  self.validate(values)
}

///|
pub fn ReportTemplate::has_field(self : ReportTemplate, name : String) -> Bool {
  for field in self.fields {
    if field.name == name {
      return true
    }
  }
  false
}

///|
pub fn ReportTemplate::field_names(self : ReportTemplate) -> Array[String] {
  self.fields.map(fn(field) { field.name })
}

///|
pub fn ReportTemplate::add_field(
  self : ReportTemplate,
  field : TemplateField,
) -> ReportTemplate {
  let fields = self.fields.copy()
  fields.push(field)
  { ..self, fields, }
}

///|
pub fn ReportTemplate::with_name(
  self : ReportTemplate,
  name : String,
) -> ReportTemplate {
  { ..self, name, }
}

///|
pub struct TemplateCatalog {
  templates : Array[ReportTemplate]
} derive(Debug, Eq)

///|
pub fn template_catalog(templates : Array[ReportTemplate]) -> TemplateCatalog {
  { templates, }
}

///|
pub fn TemplateCatalog::count(self : TemplateCatalog) -> Int {
  self.templates.length()
}

///|
pub fn TemplateCatalog::find(
  self : TemplateCatalog,
  name : String,
) -> ReportTemplate? {
  for template in self.templates {
    if template.name == name {
      return Some(template)
    }
  }
  None
}

///|
pub fn TemplateCatalog::names(self : TemplateCatalog) -> Array[String] {
  self.templates.map(fn(template) { template.name })
}

///|
pub fn TemplateCatalog::add(
  self : TemplateCatalog,
  template : ReportTemplate,
) -> TemplateCatalog {
  let templates = self.templates.copy()
  templates.push(template)
  { templates, }
}

///|
pub fn TemplateCatalog::render_index(self : TemplateCatalog) -> String {
  let lines : Array[String] = ["# Report templates", ""]
  for template in self.templates {
    lines.push(
      "- " +
      template.name +
      " (" +
      template.field_count().to_string() +
      " fields)",
    )
  }
  lines.join("\n")
}

///|
pub fn standard_operations_template() -> ReportTemplate {
  report_template("operations", [
    template_field("owner", Required),
    template_field("shift", Optional),
    template_field("process_unit", Required),
    template_field("scenario", Required),
    template_field("summary", Required),
  ])
}

///|
pub fn standard_safety_template() -> ReportTemplate {
  report_template("safety-review", [
    template_field("owner", Required),
    template_field("hazard", Required),
    template_field("safeguard", Required),
    template_field("status", Required),
    template_field("next_review", Optional),
  ])
}

///|
pub fn standard_balance_template() -> ReportTemplate {
  report_template("balance-review", [
    template_field("owner", Required),
    template_field("basis", Required),
    template_field("inlet", Required),
    template_field("outlet", Required),
    template_field("residual", Required),
  ])
}

///|
pub fn standard_template_catalog() -> TemplateCatalog {
  template_catalog([
    standard_operations_template(),
    standard_safety_template(),
    standard_balance_template(),
  ])
}

///|
pub fn template_values_from_inputs(
  inputs : Array[InputValue],
) -> Array[TemplateValue] {
  inputs.map(fn(item) { template_value(item.name, item.value) })
}

///|
pub fn template_completeness(
  template : ReportTemplate,
  values : Array[TemplateValue],
) -> Float {
  let total = template.field_count()
  if total == 0 {
    1.0
  } else {
    Float::from_int(total - template.validate(values).length()) /
    Float::from_int(total)
  }
}

///|
pub fn template_quality_gate(
  template : ReportTemplate,
  values : Array[TemplateValue],
) -> Bool {
  template.validate(values).length() == 0
}