///|
pub struct Entity {
  name : String
  entries : Array[Entry]
} derive(Debug, Eq)

///|
pub fn Entity::new(name : String, entries : Array[Entry]) -> Entity {
  { name, entries }
}

///|
/// A rule identifies reciprocal intercompany balances to remove.
pub struct EliminationRule {
  debit_account : String
  credit_account : String
} derive(Debug, Eq)

///|
pub fn EliminationRule::new(
  debit_account : String,
  credit_account : String,
) -> EliminationRule {
  { debit_account, credit_account }
}

///|
pub fn consolidate(
  period : String,
  entities : Array[Entity],
  mappings : Array[AccountMapping],
) -> Report {
  consolidate_with_eliminations(period, entities, mappings, [])
}

///|
pub fn consolidate_with_eliminations(
  period : String,
  entities : Array[Entity],
  mappings : Array[AccountMapping],
  rules : Array[EliminationRule],
) -> Report {
  let all : Array[Entry] = []
  for entity in entities {
    for entry in entity.entries {
      if entry.period == period {
        all.push(entry)
      }
    }
  }
  let base = generate(BalanceSheet, period, all, mappings)
  let adjusted : Array[LineItem] = []
  for item in base.lines {
    adjusted.push(item)
  }
  for rule in rules {
    let debit_amount = consolidation_account_balance(
      all,
      rule.debit_account,
      true,
    )
    let credit_amount = consolidation_account_balance(
      all,
      rule.credit_account,
      false,
    )
    let amount = if debit_amount < credit_amount {
      debit_amount
    } else {
      credit_amount
    }
    if amount > 0 {
      adjust_line(adjusted, mappings, rule.debit_account, -amount)
      adjust_line(adjusted, mappings, rule.credit_account, amount)
    }
  }
  let mut total = 0
  for item in adjusted {
    total += item.amount
  }
  { kind: BalanceSheet, period, lines: adjusted, total }
}

///|
fn consolidation_account_balance(
  entries : Array[Entry],
  account : String,
  debit_normal : Bool,
) -> Amount {
  let mut total = 0
  for entry in entries {
    if entry.account == account {
      if debit_normal {
        total += entry.debit - entry.credit
      } else {
        total += entry.credit - entry.debit
      }
    }
  }
  if total < 0 {
    0
  } else {
    total
  }
}

///|
fn adjust_line(
  lines : Array[LineItem],
  mappings : Array[AccountMapping],
  account : String,
  delta : Amount,
) -> Unit {
  match find_mapping(mappings, account) {
    None => ()
    Some(mapping) =>
      for i, item in lines {
        if item.line == mapping.line {
          lines[i] = { ..item, amount: item.amount + delta * mapping.factor }
          return
        }
      }
  }
}