///|
// Header section models for ISO-10303-21 STEP Part 21 files.
//
// We model the most common/required header entities:
// - FILE_DESCRIPTION
// - FILE_NAME
// - FILE_SCHEMA
//
// Any other header statements can be preserved as raw strings.

///|
pub(all) enum StringOrDollar {
  Dollar
  Str(String)
}

///|
pub(all) enum StringListOrDollar {
  Dollar
  List(Array[String])
}

///|
pub struct FileDescription {
  description : Array[String]
  implementation_level : String
}

///|
pub fn FileDescription::new(
  description : Array[String],
  implementation_level : String,
) -> FileDescription {
  FileDescription::{ description, implementation_level, }
}

///|
pub struct FileName {
  name : StringOrDollar
  time_stamp : StringOrDollar
  author : StringListOrDollar
  organization : StringListOrDollar
  preprocessor_version : StringOrDollar
  originating_system : StringOrDollar
  authorization : StringOrDollar
}

///|
pub fn FileName::new(
  name : StringOrDollar,
  time_stamp : StringOrDollar,
  author : StringListOrDollar,
  organization : StringListOrDollar,
  preprocessor_version : StringOrDollar,
  originating_system : StringOrDollar,
  authorization : StringOrDollar,
) -> FileName {
  FileName::{
    name,
    time_stamp,
    author,
    organization,
    preprocessor_version,
    originating_system,
    authorization,
  }
}

///|
pub struct FileSchema {
  schema_identifiers : Array[String]
}

///|
pub fn FileSchema::new(schema_identifiers : Array[String]) -> FileSchema {
  FileSchema::{ schema_identifiers, }
}

///|
pub(all) enum HeaderEntity {
  FileDescription(FileDescription)
  FileName(FileName)
  FileSchema(FileSchema)
  // Comment is emitted as a Part 21 /* ... */ comment block.
  Comment(String)
  Raw(String)
}

///|
pub struct HeaderSection {
  entities : Array[HeaderEntity]
}

///|
pub fn HeaderSection::empty() -> HeaderSection {
  HeaderSection::{ entities: [], }
}

///|
pub fn HeaderSection::new(entities : Array[HeaderEntity]) -> HeaderSection {
  HeaderSection::{ entities, }
}

///|
fn step_string_or_dollar_to_step(s : StringOrDollar) -> String {
  match s {
    Dollar => "$"
    Str(v) => @step.step_str(v)
  }
}

///|
fn step_string_list_to_step(values : Array[String]) -> String {
  let joined = values.map(@step.step_str).join(",")
  "(\{joined})"
}

///|
fn step_string_list_or_dollar_to_step(s : StringListOrDollar) -> String {
  match s {
    Dollar => "$"
    List(values) => step_string_list_to_step(values)
  }
}

///|
fn file_description_to_step(fd : FileDescription) -> String {
  let desc = step_string_list_to_step(fd.description)
  let impl_level = @step.step_str(fd.implementation_level)
  "FILE_DESCRIPTION(\{desc},\{impl_level})"
}

///|
fn file_name_to_step(fn_ : FileName) -> String {
  let a0 = step_string_or_dollar_to_step(fn_.name)
  let a1 = step_string_or_dollar_to_step(fn_.time_stamp)
  let a2 = step_string_list_or_dollar_to_step(fn_.author)
  let a3 = step_string_list_or_dollar_to_step(fn_.organization)
  let a4 = step_string_or_dollar_to_step(fn_.preprocessor_version)
  let a5 = step_string_or_dollar_to_step(fn_.originating_system)
  let a6 = step_string_or_dollar_to_step(fn_.authorization)
  "FILE_NAME(\{a0},\{a1},\{a2},\{a3},\{a4},\{a5},\{a6})"
}

///|
fn file_schema_to_step(fs : FileSchema) -> String {
  let schemas = step_string_list_to_step(fs.schema_identifiers)
  "FILE_SCHEMA(\{schemas})"
}

///|
// Format a Part 21 comment block. STEP comments are delimited by /* and */
// and may span multiple lines, but may not themselves contain "*/".
fn comment_to_step(text : String) -> String {
  let safe = text.replace(old="*/", new="* /")
  let body = safe.split("\n").map(fn(line) { "  \{line}" }).join("\n")
  "/*\n\{body}\n*/"
}

///|
fn header_entity_to_step(e : HeaderEntity) -> String {
  match e {
    FileDescription(fd) => file_description_to_step(fd)
    FileName(fn_) => file_name_to_step(fn_)
    FileSchema(fs) => file_schema_to_step(fs)
    Comment(text) => comment_to_step(text)
    Raw(s) => s.trim().to_owned()
  }
}

///|
// Serialize only the statements inside HEADER...ENDSEC.
// Each statement is terminated with a semicolon and separated by newlines.
// Comments are emitted without a trailing semicolon.
pub fn HeaderSection::to_step_statements(self : HeaderSection) -> String {
  self.entities
  .map(fn(e) {
    match e {
      Comment(_) => header_entity_to_step(e)
      _ => "\{header_entity_to_step(e)};"
    }
  })
  .join("\n")
}

///|
// Serialize the full HEADER section, including HEADER/ENDSEC wrappers.
pub fn HeaderSection::to_step_header_section(self : HeaderSection) -> String {
  let body = self.to_step_statements()
  if body.length() == 0 {
    "HEADER;\nENDSEC;"
  } else {
    "HEADER;\n\{body}\nENDSEC;"
  }
}