///|
pub(all) enum ChartAnnotationKind {
  SnapshotSummary
  StructureLevelSummary
  DecisionSummary
  RiskNote
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct CandlePoint {
  seq : Int
  open : Double
  high : Double
  low : Double
  close : Double
  volume : Double
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct PriceRange {
  low : Double
  high : Double
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct ChartAnnotation {
  kind : ChartAnnotationKind
  label : String
  body : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct ChartFrame {
  artifact_path : String
  source_id : @domain.SourceId
  symbol : @domain.Symbol
  timeframe : @domain.Timeframe
  width : Int
  height : Int
  candles : Array[CandlePoint]
  price_range : PriceRange?
  annotations : Array[ChartAnnotation]
} derive(Debug, Eq, ToJson, FromJson)

///|
pub fn candle_point(bar : @domain.KlineBar) -> CandlePoint {
  {
    seq: bar.seq,
    open: bar.open,
    high: bar.high,
    low: bar.low,
    close: bar.close,
    volume: bar.volume,
  }
}

///|
pub fn price_range(low : Double, high : Double) -> PriceRange {
  { low, high }
}

///|
pub fn chart_annotation(
  kind : ChartAnnotationKind,
  label : String,
  body : String,
) -> ChartAnnotation {
  { kind, label, body }
}

///|
fn min_double(a : Double, b : Double) -> Double {
  if a < b {
    a
  } else {
    b
  }
}

///|
fn max_double(a : Double, b : Double) -> Double {
  if a > b {
    a
  } else {
    b
  }
}

///|
fn closed_candles(snapshot : @domain.KlineSnapshot) -> Array[CandlePoint] {
  snapshot.bars
  .filter(fn(bar) { bar.closed })
  .map(fn(bar) { candle_point(bar) })
}

///|
fn candle_price_range(candles : Array[CandlePoint]) -> PriceRange? {
  if candles.is_empty() {
    return None
  }
  let mut low = candles[0].low
  let mut high = candles[0].high
  for candle in candles {
    low = min_double(low, candle.low)
    high = max_double(high, candle.high)
  }
  Some(price_range(low, high))
}

///|
fn snapshot_annotation(
  snapshot : @domain.KlineSnapshot,
  candles : Array[CandlePoint],
) -> ChartAnnotation {
  chart_annotation(
    SnapshotSummary,
    "snapshot",
    "\{snapshot.symbol} \{snapshot.timeframe} closed_bars=\{candles.length()}",
  )
}

///|
fn structure_annotation(
  levels : @domain.StructureLevelFrame?,
) -> Array[ChartAnnotation] {
  match levels {
    None => []
    Some(frame) =>
      [
        chart_annotation(
          StructureLevelSummary,
          "structure",
          "\{frame.summary}; midline=\{frame.midline is Some(_)}",
        ),
      ]
  }
}

///|
fn action_label(action : @domain.DecisionAction) -> String {
  match action {
    NoOrder => "NoOrder"
    Wait => "Wait"
    LimitOrder => "LimitOrder"
    BreakoutOrder => "BreakoutOrder"
    MarketOrder => "MarketOrder"
  }
}

///|
fn decision_annotations(
  decision : @domain.Stage2Decision?,
) -> Array[ChartAnnotation] {
  match decision {
    None => []
    Some(decision) => {
      let annotations = [
        chart_annotation(
          DecisionSummary,
          "decision",
          "action=\{action_label(decision.action)}\nconfidence=\{decision.confidence}\nrationale=\{decision.rationale}",
        ),
      ]
      for note in decision.risk_notes {
        annotations.push(chart_annotation(RiskNote, "risk", note))
      }
      annotations
    }
  }
}

///|
pub fn render_chart_frame(
  snapshot : @domain.KlineSnapshot,
  decision? : @domain.Stage2Decision,
  structure_levels? : @domain.StructureLevelFrame,
  width? : Int = 1280,
  height? : Int = 720,
) -> ChartFrame {
  let candles = closed_candles(snapshot)
  {
    artifact_path: "apps/moondesk-price-action/chart-frames/\{snapshot.source_id}.json",
    source_id: snapshot.source_id,
    symbol: snapshot.symbol,
    timeframe: snapshot.timeframe,
    width,
    height,
    candles,
    price_range: candle_price_range(candles),
    annotations: [snapshot_annotation(snapshot, candles)] +
    structure_annotation(structure_levels) +
    decision_annotations(decision),
  }
}