///|
/// Multi-series robust aggregation for portfolio and sensor groups.
pub struct PortfolioMetric {
  name : String
  weight : Double
  center : Double
  scale : Double
  contribution : Double
}

///|
pub struct PortfolioSummary {
  value : Array[Double]
  metrics : Array[PortfolioMetric]
  diversification : Double
  concentration : Double
}

///|
pub fn portfolio_metric(
  name : String,
  weight : Double,
  data : Array[Double],
) -> PortfolioMetric {
  {
    name,
    weight,
    center: mean(data),
    scale: mad(data),
    contribution: weight * mean(data),
  }
}

///|
pub fn portfolio_weights(weights : Array[Double]) -> Array[Double] {
  let result = []
  let mut total = 0.0
  for weight in weights {
    let safe = if weight < 0.0 { 0.0 } else { weight }
    result.push(safe)
    total += safe
  }
  if total == 0.0 {
    return result
  }
  for index = 0; index < result.length(); index = index + 1 {
    result[index] = result[index] / total
  }
  result
}

///|
pub fn portfolio_combine(
  series : Array[Array[Double]],
  weights : Array[Double],
) -> Array[Double] {
  let result = []
  if series.length() == 0 {
    return result
  }
  let normalized = portfolio_weights(weights)
  let length = series[0].length()
  for index = 0; index < length; index = index + 1 {
    let mut value = 0.0
    for group = 0; group < series.length(); group = group + 1 {
      if index < series[group].length() {
        let weight = if group < normalized.length() {
          normalized[group]
        } else {
          0.0
        }
        value += weight * series[group][index]
      }
    }
    result.push(value)
  }
  result
}

///|
pub fn portfolio_summary(
  names : Array[String],
  series : Array[Array[Double]],
  weights : Array[Double],
) -> PortfolioSummary {
  let normalized = portfolio_weights(weights)
  let values = portfolio_combine(series, normalized)
  let metrics = []
  for index = 0; index < series.length(); index = index + 1 {
    let name = if index < names.length() {
      names[index]
    } else {
      "series-" + index.to_string()
    }
    let weight = if index < normalized.length() {
      normalized[index]
    } else {
      0.0
    }
    metrics.push(portfolio_metric(name, weight, series[index]))
  }
  let mut concentration = 0.0
  for weight in normalized {
    concentration += weight * weight
  }
  {
    value: values,
    metrics,
    diversification: 1.0 - concentration,
    concentration,
  }
}

///|
pub fn portfolio_value(summary : PortfolioSummary) -> Array[Double] {
  summary.value.copy()
}

///|
pub fn portfolio_metric_names(summary : PortfolioSummary) -> Array[String] {
  let result = []
  for metric in summary.metrics {
    result.push(metric.name)
  }
  result
}

///|
pub fn portfolio_contributions(summary : PortfolioSummary) -> Array[Double] {
  let result = []
  for metric in summary.metrics {
    result.push(metric.contribution)
  }
  result
}

///|
pub fn portfolio_risk(summary : PortfolioSummary) -> Double {
  mad(summary.value)
}

///|
pub fn portfolio_quality(summary : PortfolioSummary) -> Double {
  if summary.value.length() == 0 {
    0.0
  } else {
    robust_signal_quality(summary.value)
  }
}

///|
pub fn portfolio_summary_vector(summary : PortfolioSummary) -> Array[Double] {
  [
    summary.value.length().to_double(),
    summary.metrics.length().to_double(),
    summary.diversification,
    summary.concentration,
    portfolio_risk(summary),
    portfolio_quality(summary),
  ]
}

///|
pub fn portfolio_summary_lines(summary : PortfolioSummary) -> Array[String] {
  let lines = [
    "count=" + summary.value.length().to_string(),
    "metrics=" + summary.metrics.length().to_string(),
    "diversification=" + summary.diversification.to_string(),
    "concentration=" + summary.concentration.to_string(),
    "risk=" + portfolio_risk(summary).to_string(),
    "quality=" + portfolio_quality(summary).to_string(),
  ]
  for metric in summary.metrics {
    lines.push(
      metric.name +
      "|weight=" +
      metric.weight.to_string() +
      "|center=" +
      metric.center.to_string() +
      "|scale=" +
      metric.scale.to_string() +
      "|contribution=" +
      metric.contribution.to_string(),
    )
  }
  lines
}

///|
pub fn portfolio_summary_string(summary : PortfolioSummary) -> String {
  portfolio_summary_lines(summary).join("\n")
}

///|
pub fn portfolio_rebalance(
  summary : PortfolioSummary,
  weights : Array[Double],
) -> Array[Double] {
  portfolio_combine([summary.value], weights)
}

///|
pub fn portfolio_compare(
  left : PortfolioSummary,
  right : PortfolioSummary,
) -> Array[Double] {
  [
    portfolio_quality(left),
    portfolio_quality(right),
    portfolio_risk(left),
    portfolio_risk(right),
    left.diversification,
    right.diversification,
  ]
}

///|
pub fn portfolio_stable(
  left : PortfolioSummary,
  right : PortfolioSummary,
  tolerance : Double,
) -> Bool {
  if left.value.length() != right.value.length() {
    return false
  }
  for index = 0; index < left.value.length(); index = index + 1 {
    if abs_double(left.value[index] - right.value[index]) > tolerance {
      return false
    }
  }
  true
}

///|
pub fn portfolio_batch(
  series_sets : Array[Array[Array[Double]]],
  weights : Array[Double],
) -> Array[Double] {
  let result = []
  for series in series_sets {
    result.push(portfolio_quality(portfolio_summary([], series, weights)))
  }
  result
}