///|
/// Journal entries preserve source-document boundaries before normalization.
pub struct JournalLine {
  account : String
  debit : Amount
  credit : Amount
} derive(Debug, Eq)

///|
pub fn JournalLine::new(
  account : String,
  debit : Amount,
  credit : Amount,
) -> JournalLine {
  { account, debit, credit }
}

///|
pub struct JournalEntry {
  id : String
  period : String
  description : String
  lines : Array[JournalLine]
} derive(Debug, Eq)

///|
pub fn JournalEntry::new(
  id : String,
  period : String,
  description : String,
) -> JournalEntry {
  { id, period, description, lines: [] }
}

///|
pub fn JournalEntry::add_line(self : JournalEntry, line : JournalLine) -> Unit {
  self.lines.push(line)
}

///|
pub enum JournalValidation {
  BalancedJournal
  EmptyJournal
  InvalidJournalPeriod
  UnbalancedJournal(Amount)
  InvalidJournalLine(String)
} derive(Debug, Eq)

///|
pub fn validate_journal(journal : JournalEntry) -> JournalValidation {
  if journal.lines.length() == 0 {
    return EmptyJournal
  }
  if validate_period(journal.period) is InvalidPeriod(_) {
    return InvalidJournalPeriod
  }
  let mut debit = 0
  let mut credit = 0
  for line in journal.lines {
    if line.account == "" ||
      line.debit < 0 ||
      line.credit < 0 ||
      (line.debit > 0 && line.credit > 0) {
      return InvalidJournalLine(line.account)
    }
    debit += line.debit
    credit += line.credit
  }
  if debit == credit {
    BalancedJournal
  } else {
    UnbalancedJournal(debit - credit)
  }
}

///|
pub fn flatten_journal(journal : JournalEntry) -> Array[Entry] {
  let entries : Array[Entry] = []
  for line in journal.lines {
    entries.push(
      Entry::new(line.account, journal.period, line.debit, line.credit),
    )
  }
  entries
}

///|
pub fn journal_totals(journal : JournalEntry) -> (Amount, Amount) {
  let mut debit = 0
  let mut credit = 0
  for line in journal.lines {
    debit += line.debit
    credit += line.credit
  }
  (debit, credit)
}