///|
pub(all) struct Stage1PromptInput {
  request : @routine.RoutineRequest
  features : @domain.MarketFeatureFrame
  hints : @domain.Stage1FeatureHints
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct Stage2PromptInput {
  request : @routine.RoutineRequest
  diagnosis : @domain.Stage1Diagnosis
  strategy_files : Array[@domain.StrategyFile]
  experience_memories : Array[@memory.ExperienceMemoryEntry]
} derive(Debug, Eq, ToJson, FromJson)

///|
pub fn stage1_prompt_input(
  request : @routine.RoutineRequest,
  features : @domain.MarketFeatureFrame,
  hints : @domain.Stage1FeatureHints,
) -> Stage1PromptInput {
  { request, features, hints }
}

///|
pub fn stage2_prompt_input(
  request : @routine.RoutineRequest,
  diagnosis : @domain.Stage1Diagnosis,
  strategy_files : Array[@domain.StrategyFile],
  experience_memories? : Array[@memory.ExperienceMemoryEntry] = [],
) -> Stage2PromptInput {
  { request, diagnosis, strategy_files, experience_memories }
}

///|
fn snapshot_section(snapshot : @domain.KlineSnapshot) -> PromptSection {
  prompt_section(
    "market_snapshot",
    "source_id=\{snapshot.source_id}\nsymbol=\{snapshot.symbol}\ntimeframe=\{snapshot.timeframe}\nclosed_bars=\{snapshot.closed_bar_count()}",
  )
}

///|
fn feature_section(features : @domain.MarketFeatureFrame) -> PromptSection {
  prompt_section(
    "deterministic_features",
    "closed_bar_count=\{features.closed_bar_count}\natr=\{opt_double_label(features.atr)}\nema_fast=\{opt_double_label(features.ema_fast)}\nema_slow=\{opt_double_label(features.ema_slow)}\nwarmup_complete=\{features.warmup_complete}",
  )
}

///|
fn hints_section(hints : @domain.Stage1FeatureHints) -> PromptSection {
  prompt_section(
    "stage1_feature_hints",
    "cycle_bias=\{cycle_label(hints.cycle_bias)}\ndirection_bias=\{direction_label(hints.direction_bias)}\npattern_hints=\{pattern_list(hints.pattern_hints)}\nnotes=\{hints.notes.join("; ")}",
  )
}

///|
fn skill_asset_section(
  title : String,
  assets : Array[@skills.SkillAsset],
) -> PromptSection {
  prompt_section(
    title,
    assets
    .map(fn(asset) { "- \{asset.id} | \{asset.book_path} | \{asset.summary}" })
    .join("\n"),
  )
}

///|
pub fn assemble_stage1_prompt(input : Stage1PromptInput) -> PromptPacket {
  prompt_packet(
    Stage1,
    "stage1-diagnosis",
    [
      prompt_section(
        "task", "Diagnose the market state from bounded evidence. Return one JSON object matching schema `stage1-diagnosis`.",
      ),
      prompt_section("operator_request", input.request.operator_request),
      snapshot_section(input.request.snapshot),
      feature_section(input.features),
      hints_section(input.hints),
      skill_asset_section("stage1_skill_assets", @skills.stage1_skill_assets()),
      prompt_section(
        "constraints", "Do not place orders. Do not call tools. Use strategy files only as routing hints. Output bare JSON only.",
      ),
    ],
    ["compute_indicators", "classify_market_structure"],
  )
}

///|
fn diagnosis_section(diagnosis : @domain.Stage1Diagnosis) -> PromptSection {
  prompt_section(
    "stage1_diagnosis",
    "cycle_position=\{cycle_label(diagnosis.cycle_position)}\ndirection=\{direction_label(diagnosis.direction)}\nconfidence=\{diagnosis.confidence}\nmarket_phase=\{phase_label(diagnosis.market_phase)}\npatterns=\{pattern_list(diagnosis.patterns)}\nkey_signals=\{diagnosis.key_signals.join("; ")}",
  )
}

///|
fn experience_case_label(kind : @memory.ExperienceCaseKind) -> String {
  match kind {
    SuccessCase => "success"
    FailureCase => "failure"
  }
}

///|
fn experience_memory_section(
  entries : Array[@memory.ExperienceMemoryEntry],
) -> PromptSection {
  prompt_section(
    "experience_memory",
    if entries.is_empty() {
      "No matching MoonBook experience memory entries were selected."
    } else {
      entries
      .map(fn(entry) {
        "- \{entry.id} | \{entry.book_path} | \{experience_case_label(entry.case_kind)} | \{entry.summary} | lesson: \{entry.lesson}"
      })
      .join("\n")
    },
  )
}

///|
pub fn assemble_stage2_prompt(input : Stage2PromptInput) -> PromptPacket {
  prompt_packet(
    Stage2,
    "stage2-decision",
    [
      prompt_section(
        "task", "Decide whether a trade plan exists from Stage 1 evidence and routed strategy files. Return one JSON object matching schema `stage2-decision`.",
      ),
      prompt_section("operator_request", input.request.operator_request),
      snapshot_section(input.request.snapshot),
      diagnosis_section(input.diagnosis),
      skill_asset_section(
        "base_trade_decision_skills",
        @skills.stage2_base_skill_assets(),
      ),
      prompt_section("strategy_files", input.strategy_files.join("\n")),
      experience_memory_section(input.experience_memories),
      prompt_section(
        "constraints", "No broker execution path exists. If location or equation is weak, choose NoOrder or Wait with rationale.",
      ),
    ],
    [
      "route_strategy_files", "select_experience_memories", "validate_stage_json",
    ],
  )
}

///|
pub fn decision_summary(decision : @domain.Stage2Decision) -> PromptSection {
  prompt_section(
    "stage2_decision_summary",
    "action=\{action_label(decision.action)}\nconfidence=\{decision.confidence}\nrationale=\{decision.rationale}\nrisk_notes=\{decision.risk_notes.join("; ")}",
  )
}