///|
pub(all) struct AnalysisPlanInput {
  run_id : @domain.RunId
  operator_request : String
  data_source : @domain.DataSource
  symbol : @domain.Symbol
  timeframe : @domain.Timeframe
  limit : Int
  captured_at_ms : Int64
  suite_status : @model.SuiteStatusRef
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct AnalysisPlan {
  run_id : @domain.RunId
  market_request : @data.MarketSourceRequest
  market_plan : @data.MarketSourcePlan
  refresh_plan : @data.MarketRefreshPlan
  gateway : @model.ModelGateway
  routine_plan : @routine.RoutinePlan
  issues : Array[@domain.ValidationIssue]
  can_start : Bool
  can_complete_without_external_data : Bool
  summary : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub fn analysis_plan_input(
  run_id : @domain.RunId,
  operator_request : String,
  data_source : @domain.DataSource,
  symbol : @domain.Symbol,
  timeframe : @domain.Timeframe,
  suite_status : @model.SuiteStatusRef,
  limit? : Int = 60,
  captured_at_ms? : Int64 = 0L,
) -> AnalysisPlanInput {
  {
    run_id,
    operator_request,
    data_source,
    symbol,
    timeframe,
    limit,
    captured_at_ms,
    suite_status,
  }
}

///|
fn push_issue(
  issues : Array[@domain.ValidationIssue],
  severity : @domain.ValidationSeverity,
  path : String,
  message : String,
) -> Unit {
  issues.push(@domain.validation_issue(severity, path, message))
}

///|
fn collect_issues(
  input : AnalysisPlanInput,
  market_plan : @data.MarketSourcePlan,
  refresh_plan : @data.MarketRefreshPlan,
  gateway : @model.ModelGateway,
) -> Array[@domain.ValidationIssue] {
  let issues : Array[@domain.ValidationIssue] = []
  if input.run_id == "" {
    push_issue(issues, Error, "run_id", "run ID is required")
  }
  if input.operator_request == "" {
    push_issue(issues, Warning, "operator_request", "operator request is empty")
  }
  if input.symbol == "" {
    push_issue(issues, Error, "symbol", "symbol is required")
  }
  if input.timeframe == "" {
    push_issue(issues, Error, "timeframe", "timeframe is required")
  }
  if gateway.default_model == "" {
    push_issue(issues, Error, "suite_status.default_model", "model is required")
  }
  if gateway.openai_base_url == "" {
    push_issue(
      issues,
      Error,
      "suite_status.openai_base_url",
      "OpenAI-compatible base URL is required",
    )
  }
  if gateway.health is Unavailable {
    push_issue(
      issues,
      Error,
      "suite_status.ready",
      "MoonGate reports the model gateway is unavailable",
    )
  } else if gateway.health is Degraded {
    push_issue(
      issues,
      Warning,
      "suite_status.ready",
      "MoonGate reports degraded model gateway health",
    )
  }
  if gateway.usage.quota_exhausted {
    push_issue(
      issues,
      Error,
      "suite_status.usage.quota_remaining",
      "MoonGate reports provider quota exhausted",
    )
  }
  for contract in gateway.contracts {
    if !contract.ready {
      push_issue(
        issues,
        Warning,
        "suite_status.contracts.\{contract.name}",
        "MoonGate integration contract is not ready at \{contract.path}",
      )
    }
  }
  if market_plan.availability is ExternalPlanned {
    push_issue(
      issues,
      Warning,
      "data_source",
      "external market adapter is planned and will await fetched data",
    )
  }
  if refresh_plan.action is ReviewTimeframe {
    push_issue(
      issues,
      Error,
      "timeframe",
      "timeframe is unsupported by the market refresh planner",
    )
  } else if refresh_plan.action is WaitForBarClose {
    push_issue(
      issues,
      Warning,
      "market_refresh.wait_seconds",
      "newest market bar is still forming; wait for closed-bar evidence",
    )
  }
  issues
}

///|
fn has_errors(issues : Array[@domain.ValidationIssue]) -> Bool {
  issues.any(fn(issue) { issue.severity is Error })
}

///|
fn can_complete_without_external_data(
  market_plan : @data.MarketSourcePlan,
  issues : Array[@domain.ValidationIssue],
) -> Bool {
  market_plan.availability is LocalFixture && !has_errors(issues)
}

///|
fn plan_summary(
  input : AnalysisPlanInput,
  market_plan : @data.MarketSourcePlan,
  refresh_plan : @data.MarketRefreshPlan,
  gateway : @model.ModelGateway,
  can_start : Bool,
  can_complete : Bool,
) -> String {
  let start_label = if can_start { "ready" } else { "blocked" }
  let completion_label = if can_complete {
    "complete-offline"
  } else {
    "requires-external-evidence"
  }
  "\{start_label} \{completion_label} plan for \{input.symbol} \{input.timeframe} via \{market_plan.adapter} with refresh \{refresh_plan.action.label()} using \{gateway.default_model} from \{gateway.catalog.length()} catalog model(s)"
}

///|
pub fn prepare_analysis_plan(input : AnalysisPlanInput) -> AnalysisPlan {
  let market_request = @data.market_source_request(
    input.data_source,
    input.symbol,
    input.timeframe,
    limit=input.limit,
    captured_at_ms=input.captured_at_ms,
  )
  let market_plan = @data.plan_market_source(market_request)
  let refresh_plan = @data.prepare_market_refresh_plan(
    @data.market_refresh_input(market_request),
  )
  let gateway = input.suite_status.to_gateway()
  let routine_plan = @routine.routine_plan(input.run_id)
  let issues = collect_issues(input, market_plan, refresh_plan, gateway)
  let can_start = !has_errors(issues)
  let can_complete = can_complete_without_external_data(market_plan, issues)
  {
    run_id: input.run_id,
    market_request,
    market_plan,
    refresh_plan,
    gateway,
    routine_plan,
    issues,
    can_start,
    can_complete_without_external_data: can_complete,
    summary: plan_summary(
      input, market_plan, refresh_plan, gateway, can_start, can_complete,
    ),
  }
}