///|
pub(all) enum DecisionPolicyRule {
  NoExecutableMarketOrder
  ReversalRiskBlocksOrder
  OrderRequiresRiskNotes
  OrderRequiresSufficientConfidence
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) enum DecisionPolicyStatus {
  PolicyPass
  PolicyFail
} derive(Debug, Eq, ToJson, FromJson)

///|
/// Responsibility classification for legacy price-action decisions. Even the
/// conditional shape remains research/advisory data; it is never a broker
/// instruction.
pub(all) enum DecisionActionResponsibility {
  PassiveAdvisory
  ConditionalAdvisory
  ForbiddenExecutableShape
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct DecisionPolicyFinding {
  rule : DecisionPolicyRule
  status : DecisionPolicyStatus
  path : String
  message : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct DecisionPolicyInput {
  run_id : @domain.RunId
  diagnosis : @domain.Stage1Diagnosis
  decision : @domain.Stage2Decision
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct DecisionPolicyReport {
  run_id : @domain.RunId
  allowed : Bool
  findings : Array[DecisionPolicyFinding]
  summary : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub fn decision_policy_input(
  run_id : @domain.RunId,
  diagnosis : @domain.Stage1Diagnosis,
  decision : @domain.Stage2Decision,
) -> DecisionPolicyInput {
  { run_id, diagnosis, decision }
}

///|
fn finding(
  rule : DecisionPolicyRule,
  status : DecisionPolicyStatus,
  path : String,
  message : String,
) -> DecisionPolicyFinding {
  { rule, status, path, message }
}

///|
pub fn decision_action_responsibility(
  action : @domain.DecisionAction,
) -> DecisionActionResponsibility {
  match action {
    NoOrder | Wait => PassiveAdvisory
    LimitOrder | BreakoutOrder => ConditionalAdvisory
    MarketOrder => ForbiddenExecutableShape
  }
}

///|
fn order_shaped(action : @domain.DecisionAction) -> Bool {
  decision_action_responsibility(action) != PassiveAdvisory
}

///|
fn reversal_risk_pattern(pattern : @domain.PatternTag) -> Bool {
  pattern is ReversalAttempt ||
  pattern is MajorTrendReversal ||
  pattern is FinalFlag ||
  pattern is DoubleTopBottom
}

///|
fn reversal_risk(diagnosis : @domain.Stage1Diagnosis) -> Bool {
  diagnosis.patterns.any(reversal_risk_pattern)
}

///|
fn no_market_order_finding(
  decision : @domain.Stage2Decision,
) -> DecisionPolicyFinding {
  if decision_action_responsibility(decision.action) is ForbiddenExecutableShape {
    finding(
      NoExecutableMarketOrder,
      PolicyFail,
      "stage2.action",
      "market orders are not allowed in Moonfish advisory output",
    )
  } else {
    finding(
      NoExecutableMarketOrder,
      PolicyPass,
      "stage2.action",
      "decision has no immediate executable market-order shape",
    )
  }
}

///|
fn reversal_risk_finding(
  diagnosis : @domain.Stage1Diagnosis,
  decision : @domain.Stage2Decision,
) -> DecisionPolicyFinding {
  if reversal_risk(diagnosis) && order_shaped(decision.action) {
    finding(
      ReversalRiskBlocksOrder,
      PolicyFail,
      "stage1.patterns",
      "reversal-risk patterns may only produce wait or no-order decisions",
    )
  } else {
    finding(
      ReversalRiskBlocksOrder,
      PolicyPass,
      "stage1.patterns",
      "reversal-risk hard ban is satisfied",
    )
  }
}

///|
fn risk_notes_finding(
  decision : @domain.Stage2Decision,
) -> DecisionPolicyFinding {
  if order_shaped(decision.action) && decision.risk_notes.length() == 0 {
    finding(
      OrderRequiresRiskNotes,
      PolicyFail,
      "stage2.risk_notes",
      "order-shaped decisions must cite explicit risk notes",
    )
  } else {
    finding(
      OrderRequiresRiskNotes,
      PolicyPass,
      "stage2.risk_notes",
      "risk notes requirement is satisfied",
    )
  }
}

///|
fn confidence_finding(
  decision : @domain.Stage2Decision,
) -> DecisionPolicyFinding {
  if order_shaped(decision.action) && decision.confidence < 50 {
    finding(
      OrderRequiresSufficientConfidence,
      PolicyFail,
      "stage2.confidence",
      "order-shaped decisions require confidence of at least 50",
    )
  } else {
    finding(
      OrderRequiresSufficientConfidence,
      PolicyPass,
      "stage2.confidence",
      "order-shaped confidence threshold is satisfied",
    )
  }
}

///|
fn count_status(
  findings : Array[DecisionPolicyFinding],
  status : DecisionPolicyStatus,
) -> Int {
  findings.fold(init=0, fn(count, item) {
    if item.status == status {
      count + 1
    } else {
      count
    }
  })
}

///|
fn policy_summary(report : DecisionPolicyReport) -> String {
  let pass_count = count_status(report.findings, PolicyPass)
  if report.allowed {
    "decision policy passed \{pass_count}/\{report.findings.length()} checks for \{report.run_id}"
  } else {
    let fail_count = count_status(report.findings, PolicyFail)
    "decision policy blocks \{fail_count}/\{report.findings.length()} check(s) for \{report.run_id}"
  }
}

///|
pub fn evaluate_decision_policy(
  input : DecisionPolicyInput,
) -> DecisionPolicyReport {
  let findings = [
    no_market_order_finding(input.decision),
    reversal_risk_finding(input.diagnosis, input.decision),
    risk_notes_finding(input.decision),
    confidence_finding(input.decision),
  ]
  let report : DecisionPolicyReport = {
    run_id: input.run_id,
    allowed: count_status(findings, PolicyFail) == 0,
    findings,
    summary: "",
  }
  { ..report, summary: policy_summary(report) }
}

///|
pub fn decision_policy_validation(
  report : DecisionPolicyReport,
) -> @domain.ValidationReport {
  if report.allowed {
    @domain.validation_ok()
  } else {
    let issues = report.findings.filter_map(fn(item) {
      if item.status is PolicyFail {
        Some(@domain.validation_issue(Error, item.path, item.message))
      } else {
        None
      }
    })
    { ok: false, issues }
  }
}