src/lightspeed/integration/stack.gleam

//// Reference integration stacks with reproducible setup/teardown (M26).

import gleam/list
import lightspeed/integration/data
import lightspeed/integration/jobs
import lightspeed/integration/mail
import lightspeed/integration/ops

/// Setup actions for integration fixtures.
pub type SetupStep {
  PrepareDataLayer
  StartJobWorkers
  StartMailer
  EnableTelemetryPipelines
  SeedReferenceData
}

/// Teardown actions for integration fixtures.
pub type TeardownStep {
  DrainJobs
  FlushOutbox
  DisableTelemetryPipelines
  CleanupDataFixtures
}

/// One production-like reference stack.
pub type ReferenceStack {
  ReferenceStack(
    name: String,
    data_pattern: data.Pattern,
    jobs_pattern: jobs.Pattern,
    mail_pattern: mail.Pattern,
    ops_pattern: ops.Pattern,
    setup_steps: List(SetupStep),
    teardown_steps: List(TeardownStep),
  )
}

/// Commerce-oriented stack.
pub fn commerce_stack() -> ReferenceStack {
  ReferenceStack(
    name: "commerce_stack",
    data_pattern: data.crud_pattern(),
    jobs_pattern: jobs.critical_path_pattern(),
    mail_pattern: mail.transactional_pattern(),
    ops_pattern: ops.production_pattern(),
    setup_steps: default_setup_steps(),
    teardown_steps: default_teardown_steps(),
  )
}

/// Collaboration/chat-oriented stack.
pub fn collaboration_stack() -> ReferenceStack {
  ReferenceStack(
    name: "collaboration_stack",
    data_pattern: data.event_stream_pattern(),
    jobs_pattern: jobs.mixed_runtime_pattern(),
    mail_pattern: mail.digest_pattern(),
    ops_pattern: ops.mixed_runtime_pattern(),
    setup_steps: default_setup_steps(),
    teardown_steps: default_teardown_steps(),
  )
}

/// Mixed-runtime baseline stack.
pub fn mixed_runtime_stack() -> ReferenceStack {
  ReferenceStack(
    name: "mixed_runtime_stack",
    data_pattern: data.crud_pattern(),
    jobs_pattern: jobs.mixed_runtime_pattern(),
    mail_pattern: mail.digest_pattern(),
    ops_pattern: ops.mixed_runtime_pattern(),
    setup_steps: default_setup_steps(),
    teardown_steps: default_teardown_steps(),
  )
}

/// Validate stack-level integration constraints.
pub fn valid(stack: ReferenceStack) -> Bool {
  data.valid(stack.data_pattern)
  && jobs.valid(stack.jobs_pattern)
  && mail.valid(stack.mail_pattern)
  && ops.valid(stack.ops_pattern)
  && contains_setup(stack.setup_steps, PrepareDataLayer)
  && contains_setup(stack.setup_steps, StartJobWorkers)
  && contains_setup(stack.setup_steps, StartMailer)
  && contains_setup(stack.setup_steps, EnableTelemetryPipelines)
  && contains_setup(stack.setup_steps, SeedReferenceData)
  && contains_teardown(stack.teardown_steps, DrainJobs)
  && contains_teardown(stack.teardown_steps, FlushOutbox)
  && contains_teardown(stack.teardown_steps, DisableTelemetryPipelines)
  && contains_teardown(stack.teardown_steps, CleanupDataFixtures)
}

/// Stable stack signature for fixtures and docs.
pub fn signature(stack: ReferenceStack) -> String {
  "stack:"
  <> stack.name
  <> "|"
  <> data.signature(stack.data_pattern)
  <> "|"
  <> jobs.signature(stack.jobs_pattern)
  <> "|"
  <> mail.signature(stack.mail_pattern)
  <> "|"
  <> ops.signature(stack.ops_pattern)
}

/// Stable setup signature.
pub fn setup_signature(stack: ReferenceStack) -> String {
  "setup=" <> join_with(",", list.map(stack.setup_steps, setup_step_label))
}

/// Stable teardown signature.
pub fn teardown_signature(stack: ReferenceStack) -> String {
  "teardown="
  <> join_with(",", list.map(stack.teardown_steps, teardown_step_label))
}

/// Stable lifecycle signature.
pub fn lifecycle_signature(stack: ReferenceStack) -> String {
  signature(stack)
  <> "|"
  <> setup_signature(stack)
  <> "|"
  <> teardown_signature(stack)
}

/// Stack name.
pub fn name(stack: ReferenceStack) -> String {
  stack.name
}

/// Setup-step label.
pub fn setup_step_label(step: SetupStep) -> String {
  case step {
    PrepareDataLayer -> "prepare_data_layer"
    StartJobWorkers -> "start_job_workers"
    StartMailer -> "start_mailer"
    EnableTelemetryPipelines -> "enable_telemetry_pipelines"
    SeedReferenceData -> "seed_reference_data"
  }
}

/// Teardown-step label.
pub fn teardown_step_label(step: TeardownStep) -> String {
  case step {
    DrainJobs -> "drain_jobs"
    FlushOutbox -> "flush_outbox"
    DisableTelemetryPipelines -> "disable_telemetry_pipelines"
    CleanupDataFixtures -> "cleanup_data_fixtures"
  }
}

/// Setup steps.
pub fn setup_steps(stack: ReferenceStack) -> List(SetupStep) {
  stack.setup_steps
}

/// Teardown steps.
pub fn teardown_steps(stack: ReferenceStack) -> List(TeardownStep) {
  stack.teardown_steps
}

fn default_setup_steps() -> List(SetupStep) {
  [
    PrepareDataLayer,
    StartJobWorkers,
    StartMailer,
    EnableTelemetryPipelines,
    SeedReferenceData,
  ]
}

fn default_teardown_steps() -> List(TeardownStep) {
  [DrainJobs, FlushOutbox, DisableTelemetryPipelines, CleanupDataFixtures]
}

fn contains_setup(steps: List(SetupStep), expected: SetupStep) -> Bool {
  case steps {
    [] -> False
    [step, ..rest] ->
      case step == expected {
        True -> True
        False -> contains_setup(rest, expected)
      }
  }
}

fn contains_teardown(
  steps: List(TeardownStep),
  expected: TeardownStep,
) -> Bool {
  case steps {
    [] -> False
    [step, ..rest] ->
      case step == expected {
        True -> True
        False -> contains_teardown(rest, expected)
      }
  }
}

fn join_with(separator: String, values: List(String)) -> String {
  case values {
    [] -> ""
    [value] -> value
    [value, ..rest] -> value <> separator <> join_with(separator, rest)
  }
}