///|
pub struct CostItem {
  name : String
  quantity : Float
  unit_cost : Float
  category : String
} derive(Debug, Eq)

///|
pub fn cost_item(
  name : String,
  quantity : Float,
  unit_cost : Float,
  category : String,
) -> CostItem {
  { name, quantity, unit_cost, category }
}

///|
pub fn CostItem::total(self : CostItem) -> Float {
  self.quantity * self.unit_cost
}

///|
pub fn CostItem::is_valid(self : CostItem) -> Bool {
  self.name.trim().length() > 0 && self.quantity >= 0.0 && self.unit_cost >= 0.0
}

///|
pub struct CostModel {
  items : Array[CostItem]
  currency : String
  period : String
} derive(Debug, Eq)

///|
pub fn cost_model(
  items : Array[CostItem],
  currency : String,
  period : String,
) -> CostModel {
  { items, currency, period }
}

///|
pub fn CostModel::total(self : CostModel) -> Float {
  let mut total : Float = 0.0
  for item in self.items {
    total = total + item.total()
  }
  total
}

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

///|
pub fn CostModel::valid(self : CostModel) -> Bool {
  for item in self.items {
    if !item.is_valid() {
      return false
    }
  }
  true
}

///|
pub fn CostModel::by_category(self : CostModel, category : String) -> Float {
  let mut total : Float = 0.0
  for item in self.items {
    if item.category == category {
      total = total + item.total()
    }
  }
  total
}

///|
pub fn CostModel::categories(self : CostModel) -> Array[String] {
  let result : Array[String] = []
  for item in self.items {
    if !result.contains(item.category) {
      result.push(item.category)
    }
  }
  result
}

///|
pub fn CostModel::category_table(self : CostModel) -> ReportTable {
  let rows : Array[Array[String]] = []
  for category in self.categories() {
    rows.push([
      category,
      "{self.by_category(category)}",
      self.currency,
      self.period,
    ])
  }
  table(["category", "total", "currency", "period"], rows)
}

///|
pub fn CostModel::item_table(self : CostModel) -> ReportTable {
  let rows : Array[Array[String]] = []
  for item in self.items {
    rows.push([
      item.name,
      item.category,
      "{item.quantity}",
      "{item.unit_cost}",
      "{item.total()}",
    ])
  }
  table(["item", "category", "quantity", "unit cost", "total"], rows)
}

///|
pub fn CostModel::add(self : CostModel, item : CostItem) -> CostModel {
  let items = self.items.copy()
  items.push(item)
  { ..self, items, }
}

///|
pub fn CostModel::scale(self : CostModel, factor : Float) -> CostModel {
  {
    ..self,
    items: self.items.map(fn(item) {
      { ..item, quantity: item.quantity * factor }
    }),
  }
}

///|
pub struct EnergyCost {
  energy : Float
  rate : Float
  demand_charge : Float
  fixed_charge : Float
} derive(Debug, Eq)

///|
pub fn energy_cost(
  energy : Float,
  rate : Float,
  demand_charge : Float,
  fixed_charge : Float,
) -> EnergyCost {
  { energy, rate, demand_charge, fixed_charge }
}

///|
pub fn EnergyCost::variable(self : EnergyCost) -> Float {
  self.energy * self.rate
}

///|
pub fn EnergyCost::total(self : EnergyCost) -> Float {
  self.variable() + self.demand_charge + self.fixed_charge
}

///|
pub fn EnergyCost::average_rate(self : EnergyCost) -> Float {
  if self.energy <= 0.0 {
    0.0
  } else {
    self.total() / self.energy
  }
}

///|
pub struct ProductionEconomics {
  revenue : Float
  operating_cost : Float
  capital_cost : Float
  production : Float
  lifetime : Int
} derive(Debug, Eq)

///|
pub fn production_economics(
  revenue : Float,
  operating_cost : Float,
  capital_cost : Float,
  production : Float,
  lifetime : Int,
) -> ProductionEconomics {
  { revenue, operating_cost, capital_cost, production, lifetime }
}

///|
pub fn ProductionEconomics::gross_margin(self : ProductionEconomics) -> Float {
  self.revenue - self.operating_cost
}

///|
pub fn ProductionEconomics::net_margin(self : ProductionEconomics) -> Float {
  self.gross_margin() - self.capital_cost
}

///|
pub fn ProductionEconomics::margin_rate(self : ProductionEconomics) -> Float {
  if self.revenue == 0.0 {
    0.0
  } else {
    self.gross_margin() / self.revenue
  }
}

///|
pub fn ProductionEconomics::unit_cost(self : ProductionEconomics) -> Float {
  if self.production <= 0.0 {
    0.0
  } else {
    self.operating_cost / self.production
  }
}

///|
pub fn ProductionEconomics::payback_period(self : ProductionEconomics) -> Float {
  let annual = self.gross_margin()
  if annual <= 0.0 {
    0.0
  } else {
    self.capital_cost / annual
  }
}

///|
pub fn ProductionEconomics::simple_roi(self : ProductionEconomics) -> Float {
  if self.capital_cost <= 0.0 {
    0.0
  } else {
    self.net_margin() / self.capital_cost
  }
}

///|
pub fn ProductionEconomics::is_viable(
  self : ProductionEconomics,
  maximum_payback : Float,
) -> Bool {
  self.gross_margin() > 0.0 &&
  self.payback_period() <= maximum_payback &&
  self.lifetime > 0
}

///|
pub fn annual_cost(model : CostModel, periods : Int) -> Float {
  model.total() * Float::from_int(periods)
}

///|
pub fn unit_revenue(price : Float, quantity : Float) -> Float {
  price * quantity
}

///|
pub fn contribution_margin(price : Float, variable_cost : Float) -> Float {
  price - variable_cost
}

///|
pub fn break_even_quantity(
  fixed_cost : Float,
  price : Float,
  variable_cost : Float,
) -> Float {
  let margin = contribution_margin(price, variable_cost)
  if margin <= 0.0 {
    0.0
  } else {
    fixed_cost / margin
  }
}

///|
pub fn escalation(value : Float, annual_rate : Float, years : Int) -> Float {
  let mut result = value
  for _ in 0.. Float {
  let mut denominator : Float = 1.0
  for _ in 0.. Float {
  let mut total : Float = 0.0
  for period, cashflow in cashflows {
    total = total + cashflow * discount_factor(rate, period)
  }
  total
}

///|
pub fn net_present_value(
  initial : Float,
  cashflows : Array[Float],
  rate : Float,
) -> Float {
  present_value(cashflows, rate) - initial
}

///|
pub fn cost_quality_gate(model : CostModel) -> Bool {
  model.valid() && model.total() >= 0.0
}