///|
pub(all) enum ExperienceCaseKind {
  SuccessCase
  FailureCase
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct ExperienceMemoryEntry {
  id : String
  book_path : String
  title : String
  cycle_position : @domain.CyclePosition
  direction : @domain.Direction
  patterns : Array[@domain.PatternTag]
  timestamp_ms : Int64
  case_kind : ExperienceCaseKind
  summary : String
  lesson : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct ExperienceMemorySelection {
  cycle_position : @domain.CyclePosition
  direction : @domain.Direction
  requested_patterns : Array[@domain.PatternTag]
  entries : Array[ExperienceMemoryEntry]
  summary : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub fn experience_memory_entry(
  id : String,
  book_path : String,
  title : String,
  cycle_position : @domain.CyclePosition,
  direction : @domain.Direction,
  patterns : Array[@domain.PatternTag],
  timestamp_ms : Int64,
  case_kind : ExperienceCaseKind,
  summary : String,
  lesson : String,
) -> ExperienceMemoryEntry {
  {
    id,
    book_path,
    title,
    cycle_position,
    direction,
    patterns,
    timestamp_ms,
    case_kind,
    summary,
    lesson,
  }
}

///|
pub fn default_experience_memory_index() -> Array[ExperienceMemoryEntry] {
  [
    experience_memory_entry(
      "memory.tight-channel.always-in-h2.success",
      "wiki/imported-experience/tight_channel/success/always-in-h2.md",
      "Tight channel H2 continuation",
      TightChannel,
      Bullish,
      [AlwaysIn, H2],
      1780000000000L,
      SuccessCase,
      "bull tight channel continuation where H2 worked only after strong close and shallow pullback",
      "prefer wait or continuation only when follow-through stays strong; do not fade always-in pressure early",
    ),
    experience_memory_entry(
      "memory.tight-channel.middle-range.failure",
      "wiki/imported-experience/tight_channel/failure/middle-range-fade.md",
      "Tight channel middle-range fade failure",
      TightChannel,
      Bearish,
      [AlwaysIn, MiddleRange],
      1779900000000L,
      FailureCase,
      "countertrend fade failed because always-in pressure and EMA separation stayed intact",
      "require clear trend break before treating a channel as a reversal setup",
    ),
    experience_memory_entry(
      "memory.trading-range.no-trade.success",
      "wiki/imported-experience/trading_range/success/no-trade-overlap.md",
      "Trading range overlap no-trade",
      TradingRange,
      Neutral,
      [Barbwire, Overlap, MiddleRange],
      1779800000000L,
      SuccessCase,
      "range-middle overlap produced poor equation and no durable edge",
      "default to no-order in range middle until price reaches an edge or breakout test",
    ),
    experience_memory_entry(
      "memory.normal-channel.breakout-pullback.success",
      "wiki/imported-experience/normal_channel/success/breakout-pullback.md",
      "Normal channel breakout pullback",
      NormalChannel,
      Bullish,
      [BreakoutPullback, MeasuredMove],
      1779700000000L,
      SuccessCase,
      "breakout pullback aligned with measured-move target and held above prior resistance",
      "use measured move as target context, not as permission to chase weak pullbacks",
    ),
    experience_memory_entry(
      "memory.extreme-range.failed-breakout.failure",
      "wiki/imported-experience/extreme_range/failure/failed-breakout.md",
      "Extreme range failed breakout",
      ExtremeRange,
      Neutral,
      [BreakoutFailure, FailedSignal],
      1779600000000L,
      FailureCase,
      "late breakout failed back into the range after trapped entry signal",
      "treat failed breakouts near range extremes as review-required unless follow-through is immediate",
    ),
  ]
}

///|
fn overlap_score(
  entry : ExperienceMemoryEntry,
  patterns : Array[@domain.PatternTag],
) -> Int {
  patterns.fold(init=0, fn(score, pattern) {
    if entry.patterns.contains(pattern) {
      score + 1
    } else {
      score
    }
  })
}

///|
fn memory_score(
  entry : ExperienceMemoryEntry,
  diagnosis : @domain.Stage1Diagnosis,
) -> Int {
  let direction_score = if entry.direction == diagnosis.direction {
    2
  } else {
    0
  }
  direction_score + overlap_score(entry, diagnosis.patterns)
}

///|
fn better_memory(
  candidate : ExperienceMemoryEntry,
  existing : ExperienceMemoryEntry,
  diagnosis : @domain.Stage1Diagnosis,
) -> Bool {
  let candidate_score = memory_score(candidate, diagnosis)
  let existing_score = memory_score(existing, diagnosis)
  candidate_score > existing_score ||
  (
    candidate_score == existing_score &&
    candidate.timestamp_ms > existing.timestamp_ms
  )
}

///|
fn insert_ranked_memory(
  ranked : Array[ExperienceMemoryEntry],
  candidate : ExperienceMemoryEntry,
  diagnosis : @domain.Stage1Diagnosis,
) -> Array[ExperienceMemoryEntry] {
  let next : Array[ExperienceMemoryEntry] = []
  let mut inserted = false
  for existing in ranked {
    if !inserted && better_memory(candidate, existing, diagnosis) {
      next.push(candidate)
      inserted = true
    }
    next.push(existing)
  }
  if !inserted {
    next.push(candidate)
  }
  next
}

///|
fn cap_entries(
  entries : Array[ExperienceMemoryEntry],
  max_entries : Int,
) -> Array[ExperienceMemoryEntry] {
  let limit = if max_entries < 0 {
    0
  } else if max_entries > entries.length() {
    entries.length()
  } else {
    max_entries
  }
  let capped : Array[ExperienceMemoryEntry] = []
  for index in 0.. ExperienceMemorySelection {
  let mut ranked : Array[ExperienceMemoryEntry] = []
  for entry in index {
    if entry.cycle_position == diagnosis.cycle_position {
      ranked = insert_ranked_memory(ranked, entry, diagnosis)
    }
  }
  let entries = cap_entries(ranked, max_entries)
  {
    cycle_position: diagnosis.cycle_position,
    direction: diagnosis.direction,
    requested_patterns: diagnosis.patterns,
    entries,
    summary: if entries.is_empty() {
      "selected 0 experience memory entries"
    } else if entries.length() == 1 {
      "selected 1 experience memory entry for Stage 2"
    } else {
      "selected \{entries.length()} experience memory entries for Stage 2"
    },
  }
}