import { z } from 'zod'

import {
  ALERT_STATUSES,
  ASSET_TYPE_KEYS,
  GRID_STATES,
  LOCALES,
  MAINTENANCE_STATUSES,
} from '../domain/enums'

/**
 * Request schemas.
 *
 * Every mutating endpoint validates through one of these before the handler runs, so a
 * route body is either well-formed or the request never reaches business logic. Bounds
 * are deliberately tight — a scenario asking for a 10 000 % load increase is a bug or an
 * attack, not a question worth answering.
 */

export const assetCodeSchema = z
  .string()
  .trim()
  .min(1)
  .max(40)
  .regex(/^[A-Za-z0-9-]+$/, 'Asset codes contain letters, digits and hyphens only')

export const scenarioParamsSchema = z.object({
  loadDeltaPct: z.number().min(-100).max(200).optional(),
  solarDeltaPct: z.number().min(-100).max(200).optional(),
  windDeltaPct: z.number().min(-100).max(200).optional(),
  tempDeltaC: z.number().min(-25).max(25).optional(),
  assetOutage: z.boolean().optional(),
  lineOutage: z.boolean().optional(),
  batteryOutage: z.boolean().optional(),
  evSurgeMw: z.number().min(0).max(5000).optional(),
  industrialMw: z.number().min(0).max(5000).optional(),
  regionCode: z.string().trim().max(8).nullable().optional(),
  assetCode: assetCodeSchema.nullable().optional(),
  durationMin: z.number().int().min(5).max(1440).optional(),
})

export const runSimulationSchema = z.discriminatedUnion('kind', [
  z.object({
    kind: z.literal('scenario'),
    scenarioKey: z.string().trim().max(64).optional(),
    params: scenarioParamsSchema.optional(),
    name: z.string().trim().max(160).optional(),
  }),
  z.object({
    kind: z.literal('action'),
    assetCode: assetCodeSchema,
    actionKeys: z.array(z.string().trim().max(48)).max(8).optional(),
  }),
])

export const createScenarioSchema = z.object({
  name: z.string().trim().min(2).max(120),
  nameAr: z.string().trim().max(120).optional(),
  description: z.string().trim().max(600).optional(),
  descriptionAr: z.string().trim().max(600).optional(),
  category: z.string().trim().max(40).default('custom'),
  params: scenarioParamsSchema,
})

export const recommendationDecisionSchema = z.object({
  decision: z.enum(['approve', 'reject', 'execute']),
  note: z.string().trim().max(500).optional(),
})

export const alertActionSchema = z.object({
  action: z.enum(['acknowledge', 'assign', 'resolve', 'close']),
  assigneeId: z.string().trim().max(40).optional(),
  resolution: z.string().trim().max(600).optional(),
  resolutionAr: z.string().trim().max(600).optional(),
})

export const copilotSchema = z.object({
  question: z.string().trim().min(2).max(400),
})

export const settingsUpdateSchema = z.object({
  values: z.record(z.string().max(64), z.string().max(64)).refine(
    (record) => Object.keys(record).length > 0 && Object.keys(record).length <= 40,
    'Between 1 and 40 settings may be updated at once',
  ),
})

export const userUpdateSchema = z.object({
  isActive: z.boolean().optional(),
  roleKey: z.string().trim().max(48).optional(),
  locale: z.enum(LOCALES).optional(),
})

export const maintenanceScheduleSchema = z.object({
  assetCode: assetCodeSchema,
  kind: z.enum(['preventive', 'corrective', 'predictive', 'inspection']),
  priority: z.enum(['low', 'medium', 'high', 'critical']).default('high'),
  scheduledAt: z.string().datetime(),
  findings: z.string().trim().max(600).optional(),
})

export const demoControlSchema = z.object({
  action: z.enum(['start', 'next', 'previous', 'goto', 'reset']),
  stage: z.number().int().min(1).max(20).optional(),
})

export const assetQuerySchema = z.object({
  type: z.enum(ASSET_TYPE_KEYS).optional(),
  state: z.enum(GRID_STATES).optional(),
  region: z.string().trim().max(8).optional(),
})

export const alertQuerySchema = z.object({
  status: z.enum(ALERT_STATUSES).optional(),
  level: z.string().trim().max(20).optional(),
})

export const maintenanceQuerySchema = z.object({
  status: z.enum(MAINTENANCE_STATUSES).optional(),
})

export type RunSimulationInput = z.infer<typeof runSimulationSchema>
export type CreateScenarioInput = z.infer<typeof createScenarioSchema>
export type RecommendationDecisionInput = z.infer<typeof recommendationDecisionSchema>
export type AlertActionInput = z.infer<typeof alertActionSchema>
export type CopilotInput = z.infer<typeof copilotSchema>
export type SettingsUpdateInput = z.infer<typeof settingsUpdateSchema>
export type UserUpdateInput = z.infer<typeof userUpdateSchema>
export type DemoControlInput = z.infer<typeof demoControlSchema>

// ─────────────────────────────────────────────────────────────────────────────
// Decision intelligence (2.0)
// ─────────────────────────────────────────────────────────────────────────────

/**
 * Scenario overlay accepted by the 2.0 analysis endpoints.
 *
 * Narrower than `scenarioParamsSchema` on purpose: these routes run the full agent
 * pipeline, and every extra degree of freedom is another axis a caller can use to make
 * the work expensive.
 */
export const analysisOverlaySchema = z.object({
  loadDeltaPct: z.number().min(-60).max(80).optional(),
  tempDeltaC: z.number().min(-15).max(15).optional(),
  solarDeltaPct: z.number().min(-100).max(100).optional(),
  windDeltaPct: z.number().min(-100).max(100).optional(),
  regionCode: z.string().trim().max(8).nullable().optional(),
})

export const orchestratorSchema = z.object({
  assetCode: assetCodeSchema.optional(),
  objectiveKey: z
    .enum(['balanced', 'risk_first', 'cost_first', 'speed_first', 'customer_first'])
    .optional(),
  overlay: analysisOverlaySchema.optional(),
  /** Persist the pass so it appears in the decision audit trail. */
  persist: z.boolean().optional(),
  includeLeadTime: z.boolean().optional(),
})

export const cascadeSimulateSchema = z.object({
  assetCode: assetCodeSchema,
  horizonMin: z.number().int().min(15).max(180).optional(),
  overlay: analysisOverlaySchema.optional(),
  persist: z.boolean().optional(),
})

export const counterfactualSchema = z.object({
  assetCode: assetCodeSchema,
  objective: z.enum(['risk_below', 'prevent_breach', 'stability_above']),
  targetValue: z.number().min(0).max(100),
  overlay: analysisOverlaySchema.optional(),
  persist: z.boolean().optional(),
})

export const optimizationSchema = z.object({
  assetCode: assetCodeSchema,
  objectiveKey: z
    .enum(['balanced', 'risk_first', 'cost_first', 'speed_first', 'customer_first'])
    .optional(),
  overlay: analysisOverlaySchema.optional(),
  persist: z.boolean().optional(),
})

export const stressTestSchema = z.object({
  regionCode: z.string().trim().max(8).nullable().optional(),
  maxDeltaPct: z.number().int().min(10).max(60).optional(),
  stepPct: z.number().int().min(1).max(15).optional(),
})

export const decisionCreateSchema = z.object({
  assetCode: assetCodeSchema,
  strategyCode: z.string().trim().max(80).nullable().optional(),
  orchestrationId: z.string().trim().max(80).nullable().optional(),
})

export const approvalSchema = z.object({
  decisionCode: z.string().trim().max(80),
  step: z.enum(['operator', 'supervisor']),
  decision: z.enum(['approved', 'rejected', 'modified']),
  comment: z.string().trim().max(500).optional(),
  commentAr: z.string().trim().max(500).optional(),
})

export const decisionExecuteSchema = z.object({
  decisionCode: z.string().trim().max(80),
})

export type OrchestratorInput = z.infer<typeof orchestratorSchema>
export type CascadeSimulateInput = z.infer<typeof cascadeSimulateSchema>
export type CounterfactualRequest = z.infer<typeof counterfactualSchema>
export type OptimizationInput = z.infer<typeof optimizationSchema>
export type StressTestInput = z.infer<typeof stressTestSchema>
export type DecisionCreateInput = z.infer<typeof decisionCreateSchema>
export type ApprovalRequest = z.infer<typeof approvalSchema>
export type DecisionExecuteInput = z.infer<typeof decisionExecuteSchema>

// ─────────────────────────────────────────────────────────────────────────────
// Digital twin (3.0)
// ─────────────────────────────────────────────────────────────────────────────

export const twinCodeSchema = z
  .string()
  .trim()
  .min(1)
  .max(60)
  .regex(/^[A-Za-z0-9-]+$/, 'Twin codes contain letters, digits and hyphens only')

/** One event on a scenario timeline. Bounds are tight because these drive the engine. */
export const twinEventSchema = z.object({
  kind: z.enum([
    'heat_wave',
    'load_spike',
    'solar_drop',
    'wind_surge',
    'transformer_degradation',
    'line_failure',
    'battery_failure',
    'ev_surge',
    'industrial_surge',
    'asset_outage',
    'maintenance_outage',
    'battery_dispatch',
  ]),
  label: z.string().trim().max(80).optional(),
  labelAr: z.string().trim().max(80).optional(),
  offsetMin: z.number().int().min(0).max(1440).optional(),
  durationMin: z.number().int().min(5).max(1440).optional(),
  magnitude: z.number().min(-100).max(100),
  unit: z.string().trim().max(8).optional(),
  regionCode: z.string().trim().max(8).nullable().optional(),
  targetCode: assetCodeSchema.nullable().optional(),
})

export const twinSimulateSchema = z.object({
  events: z.array(twinEventSchema).min(1).max(12),
  horizonMin: z.number().int().min(15).max(1440).optional(),
  scenarioCode: z.string().trim().max(60).nullable().optional(),
  /** Persist the composed scenario so it can be re-run and shared. */
  saveAs: z.string().trim().max(80).optional(),
})

export const twinSnapshotSchema = z.object({
  reason: z.enum(['pre_simulation', 'manual', 'incident', 'baseline']).optional(),
})

export const twinTimeMachineSchema = z.object({
  offsets: z.array(z.number().int().min(0).max(1440)).min(1).max(12).optional(),
  events: z.array(twinEventSchema).max(12).optional(),
})

export const twinScenarioSchema = z.object({
  action: z.enum(['save', 'generate']),
  kind: z.enum(['worst_case', 'realistic', 'hidden_risk']).optional(),
  name: z.string().trim().max(80).optional(),
  nameAr: z.string().trim().max(80).optional(),
  regionCode: z.string().trim().max(8).nullable().optional(),
  events: z.array(twinEventSchema).max(12).optional(),
})

export const resilienceTestSchema = z.object({
  disturbance: z.enum([
    'heat_wave',
    'multi_failure',
    'demand_surge',
    'renewable_drop',
    'maintenance_outage',
  ]),
  magnitude: z.number().min(0).max(60).optional(),
  durationMin: z.number().int().min(30).max(480).optional(),
  persist: z.boolean().optional(),
})

export const adversarialSchema = z.object({
  assetCode: assetCodeSchema,
  targetRisk: z.number().min(50).max(100).optional(),
})

export type TwinEventInput = z.infer<typeof twinEventSchema>
export type TwinSimulateInput = z.infer<typeof twinSimulateSchema>
export type TwinSnapshotInput = z.infer<typeof twinSnapshotSchema>
export type TwinTimeMachineInput = z.infer<typeof twinTimeMachineSchema>
export type TwinScenarioInput = z.infer<typeof twinScenarioSchema>
export type ResilienceTestInput = z.infer<typeof resilienceTestSchema>
export type AdversarialInput = z.infer<typeof adversarialSchema>

// ─────────────────────────────────────────────────────────────────────────────
// 4.0 — physics, verification, discovery, memory, learning, governance
//
// Bounds are as tight as the domain allows. A discovery run asking for ten thousand
// simulations is not a request the server should try to satisfy on a demo machine, and
// refusing it at the schema boundary is cheaper than defending against it downstream
// (§94, §98).
// ─────────────────────────────────────────────────────────────────────────────

export const physicsValidateSchema = z.object({
  assetCode: assetCodeSchema,
  actions: z
    .array(
      z.object({
        type: z.string().trim().min(1).max(40),
        assetCode: assetCodeSchema,
        magnitudeMw: z.number().min(-5_000).max(5_000),
      }),
    )
    .min(1)
    .max(12),
  overlay: scenarioParamsSchema.optional(),
  persist: z.boolean().optional(),
})

export const verificationRecordSchema = z.object({
  assetCode: assetCodeSchema,
  twinCode: twinCodeSchema,
  horizonMin: z.number().int().min(5).max(1440),
})

export const discoveryRunSchema = z.object({
  objective: z.enum([
    'hidden_risk',
    'cascade_trigger',
    'breaking_point',
    'worst_combination',
    'minimum_failure',
  ]),
  regionCode: z.string().trim().max(8).nullable().optional(),
  assetCode: assetCodeSchema.nullable().optional(),
  seed: z.number().int().min(0).max(2_147_483_647).optional(),
  maxSimulations: z.number().int().min(5).max(200).optional(),
  persist: z.boolean().optional(),
})

export const redBlueSchema = z.object({
  stressKind: z.enum([
    'heat',
    'demand',
    'renewable',
    'outage',
    'battery',
    'maintenance',
    'comms',
    'sensors',
    'combined',
  ]),
  regionCode: z.string().trim().max(8).nullable().optional(),
  assetCode: assetCodeSchema.nullable().optional(),
  twinCode: twinCodeSchema.nullable().optional(),
  persist: z.boolean().optional(),
})

export const memoryOutcomeSchema = z.object({
  memoryCode: z.string().trim().min(1).max(60),
  result: z.enum(['prevented', 'occurred', 'partial', 'not_observed']),
  observedRisk: z.number().min(0).max(100).nullable().optional(),
  /**
   * Defaults to `simulated` when absent, and the client has to opt in to `observed`.
   * A memory that quietly recorded simulated results as observed ones would let the
   * platform claim it had prevented real failures (§30, §122).
   */
  evidenceKind: z.enum(['simulated', 'observed']).optional(),
  notes: z.string().trim().max(1_000).optional(),
  notesAr: z.string().trim().max(1_000).optional(),
})

export const learningReviewSchema = z.object({
  code: z.string().trim().min(1).max(60),
  decision: z.enum(['approve', 'reject']),
  note: z.string().trim().max(1_000).optional(),
})

export const modelTransitionSchema = z.object({
  modelId: z.string().trim().min(1).max(60),
  next: z.enum(['validation', 'approved', 'active', 'retired', 'draft']),
  note: z.string().trim().max(500).optional(),
})

export const paretoSchema = z.object({
  assetCode: assetCodeSchema,
  overlay: scenarioParamsSchema.optional(),
  persist: z.boolean().optional(),
})

export const evidenceSchema = z.object({
  assetCode: assetCodeSchema,
  twinCode: twinCodeSchema.nullable().optional(),
  overlay: scenarioParamsSchema.optional(),
  persist: z.boolean().optional(),
})

export type PhysicsValidateInput = z.infer<typeof physicsValidateSchema>
export type VerificationRecordInput = z.infer<typeof verificationRecordSchema>
export type DiscoveryRunInput = z.infer<typeof discoveryRunSchema>
export type RedBlueInput = z.infer<typeof redBlueSchema>
export type MemoryOutcomeInput = z.infer<typeof memoryOutcomeSchema>
export type LearningReviewInput = z.infer<typeof learningReviewSchema>
export type ModelTransitionInput = z.infer<typeof modelTransitionSchema>
export type ParetoInput = z.infer<typeof paretoSchema>
export type EvidenceInput = z.infer<typeof evidenceSchema>

// ─────────────────────────────────────────────────────────────────────────────
// 4.1 — Integration Hub, Risk Hunter, Decision Proof & Replay
//
// The connector schemas deliberately have no field for a credential. A secret is named
// by the environment variable it lives in (`secretRef`) and resolved server-side; a
// schema that accepted a password would be a schema that put one in a request body, a
// log and an audit entry (§SECURITY).
// ─────────────────────────────────────────────────────────────────────────────

export const connectorCodeSchema = z
  .string()
  .trim()
  .min(1)
  .max(40)
  .regex(/^[A-Za-z0-9_-]+$/, 'Connector codes contain letters, digits, hyphens and underscores')

export const connectorCreateSchema = z.object({
  code: connectorCodeSchema,
  name: z.string().trim().min(1).max(80),
  nameAr: z.string().trim().min(1).max(80),
  kind: z.enum([
    'scada',
    'ems',
    'dms',
    'ami',
    'iot',
    'mqtt',
    'opcua',
    'rest',
    'websocket',
    'csv',
    'weather',
    'gis',
    'sap',
    'cmms',
    'historian',
  ]),
  protocol: z.string().trim().max(24).optional(),
  /**
   * `live` is accepted so the platform is honest about being ready for a real source,
   * but a live connector with no endpoint reports itself unreachable rather than
   * pretending — see `syncConnector`.
   */
  mode: z.enum(['live', 'demo']).optional(),
  direction: z.enum(['inbound', 'outbound', 'bidirectional']).optional(),
  description: z.string().trim().max(400).optional(),
  descriptionAr: z.string().trim().max(400).optional(),
  endpoint: z.string().trim().max(300).optional(),
  authKind: z.enum(['none', 'api_key', 'basic', 'oauth2', 'certificate']).optional(),
  /** The *name* of an environment variable, never a value. */
  secretRef: z
    .string()
    .trim()
    .max(80)
    .regex(/^[A-Z0-9_]*$/, 'A secret reference is an environment variable name')
    .optional(),
  pollSeconds: z.number().int().min(1).max(86_400).optional(),
  timeoutMs: z.number().int().min(100).max(120_000).optional(),
})

export const connectorSyncSchema = z.object({
  batchSize: z.number().int().min(1).max(500).optional(),
})

export const connectorFaultSchema = z.object({
  faultKind: z
    .enum(['connection_loss', 'high_latency', 'stale_data', 'sensor_failure', 'bad_data', 'partial_data', ''])
    .optional()
    .default(''),
})

export const connectorMappingSchema = z.object({
  sourceField: z.string().trim().min(1).max(80),
  targetField: z.string().trim().min(1).max(80),
  dataType: z.enum(['number', 'string', 'boolean', 'timestamp']),
  sourceUnit: z.string().trim().max(16).optional().default(''),
  targetUnit: z.string().trim().max(16).optional().default(''),
  defaultValue: z.string().trim().max(40).optional().default(''),
  minValue: z.number().min(-1_000_000).max(1_000_000).nullable().optional(),
  maxValue: z.number().min(-1_000_000).max(1_000_000).nullable().optional(),
  isRequired: z.boolean().optional(),
  notes: z.string().trim().max(240).optional(),
})

export const riskHuntSchema = z.object({
  kind: z.enum([
    'hidden_risk',
    'weakest_link',
    'cascade_trigger',
    'breaking_point',
    'minimum_failure',
    'prevention',
  ]),
  /** Deep searches are bounded by the service; the schema stops an absurd request early. */
  mode: z.enum(['quick', 'balanced', 'deep', 'custom']).optional(),
  regionCode: z.string().trim().max(8).nullable().optional(),
  assetCode: assetCodeSchema.nullable().optional(),
  seed: z.number().int().min(0).max(2_147_483_647).optional(),
  maxSimulations: z.number().int().min(5).max(160).optional(),
  persist: z.boolean().optional(),
})

export const riskPreventSchema = z.object({
  discoveryCode: z.string().trim().min(1).max(60),
  persist: z.boolean().optional(),
})

export const proofCreateSchema = z.object({
  assetCode: assetCodeSchema,
  decisionCode: z.string().trim().max(60).nullable().optional(),
  twinCode: twinCodeSchema.nullable().optional(),
  overlay: scenarioParamsSchema.optional(),
})

export type ConnectorCreateInput = z.infer<typeof connectorCreateSchema>
export type ConnectorSyncInput = z.infer<typeof connectorSyncSchema>
export type ConnectorFaultInput = z.infer<typeof connectorFaultSchema>
export type ConnectorMappingInput = z.infer<typeof connectorMappingSchema>
export type RiskHuntInput = z.infer<typeof riskHuntSchema>
export type RiskPreventInput = z.infer<typeof riskPreventSchema>
export type ProofCreateInput = z.infer<typeof proofCreateSchema>

// ─────────────────────────────────────────────────────────────────────────────
// 4.2 — Live 3D Grid
// ─────────────────────────────────────────────────────────────────────────────

/**
 * The scenario keys the grid can be driven with. Enumerated rather than free text so an
 * unknown scenario is a validation failure at the edge instead of a silent fall-through
 * to "normal" deep inside the service.
 */
export const gridScenarioSchema = z.enum([
  'normal',
  'heat_wave',
  'demand_surge',
  'transformer_degradation',
  'renewable_drop',
  'line_overload',
  'battery_support',
  'cascade_event',
])

export const liveGridTimeMachineSchema = z.object({
  offsetMin: z.number().int().min(0).max(1440).optional(),
  offsets: z.array(z.number().int().min(0).max(1440)).min(1).max(12).optional(),
  twinCode: z.string().trim().max(60).optional(),
  scenario: gridScenarioSchema.optional(),
  overlay: analysisOverlaySchema.optional(),
})

export const liveGridSimulateSchema = z.object({
  twinCode: z.string().trim().max(60).optional(),
  /**
   * Run against the platform's pinned demonstration instant instead of now.
   *
   * A summer-peak afternoon is when a grid has something worth preventing; at two in the
   * morning it has reserve to spare and the honest recommendation is to do nothing. The
   * flag exists so a demonstration can show the interesting case without the platform ever
   * pretending the quiet case is the interesting one — every payload it produces is still
   * labelled simulated.
   */
  demoClock: z.boolean().optional(),
  scenario: gridScenarioSchema.optional(),
  overlay: analysisOverlaySchema.optional(),
  /** Apply the recommended strategy for this asset and re-solve the flow (§46). */
  preventForAsset: assetCodeSchema.optional(),
  offsetMin: z.number().int().min(0).max(1440).optional(),
})

export const liveGridTraceSchema = z.object({
  assetCode: assetCodeSchema,
  direction: z.enum(['source', 'dependents']),
  twinCode: z.string().trim().max(60).optional(),
  scenario: gridScenarioSchema.optional(),
})

export type GridScenarioInput = z.infer<typeof gridScenarioSchema>
export type LiveGridTimeMachineInput = z.infer<typeof liveGridTimeMachineSchema>
export type LiveGridSimulateInput = z.infer<typeof liveGridSimulateSchema>
export type LiveGridTraceInput = z.infer<typeof liveGridTraceSchema>
export type CompareFuturesInput = z.infer<typeof compareFuturesSchema>
export type BatteryDispatchInput = z.infer<typeof batteryDispatchSchema>

// ─────────────────────────────────────────────────────────────────────────────
// 5.0 — physics-grade analysis
// ─────────────────────────────────────────────────────────────────────────────

export const compareFuturesSchema = z.object({
  twinCode: z.string().trim().max(60).optional(),
  demoClock: z.boolean().optional(),
  scenario: gridScenarioSchema.optional(),
  overlay: analysisOverlaySchema.optional(),
  /** Plan against this asset. Defaults to whichever member is worst off (§11). */
  assetCode: assetCodeSchema.optional(),
  offsetMin: z.number().int().min(0).max(1440).optional(),
  /**
   * Solve the third pane as well (§12).
   *
   * Opt-in because it is a second full scene solve, and a two-way comparison is the one
   * an operator asks for far more often than a three-way.
   */
  includeAlternative: z.boolean().optional(),
})

export const batteryDispatchSchema = z.object({
  twinCode: z.string().trim().max(60).optional(),
  assetCode: assetCodeSchema,
  /**
   * Positive discharges, negative charges. The sign is carried rather than a separate
   * direction flag: a battery absorbing is not a battery delivering, and one signed number
   * cannot disagree with itself the way a number and a flag can.
   */
  requestMw: z.number().min(-2_000).max(2_000),
  durationMin: z.number().int().min(1).max(1_440),
  demoClock: z.boolean().optional(),
})

export const contingencySchema = z.object({
  twinCode: z.string().trim().max(60).optional(),
  /** 1 for N-1, 2 for the pair scan. Never defaulted to 2: §14 says on-demand. */
  depth: z.union([z.literal(1), z.literal(2)]).optional(),
  /**
   * Cap on cases evaluated. Bounded hard because a national pair scan is quadratic and a
   * request that could run for minutes is a request that can be used to exhaust a server.
   */
  maxCases: z.number().int().min(1).max(400).optional(),
  /** Restrict the scan to the electrical area containing this asset. */
  scopeAsset: assetCodeSchema.optional(),
  demoClock: z.boolean().optional(),
})

export const sensorFaultSchema = z.object({
  faultKind: z
    .enum([
      'offline',
      'frozen',
      'spike',
      'drift',
      'noise',
      'delay',
      'loss',
      'wrong_unit',
      'bad_timestamp',
      'partial_data',
    ])
    .nullable()
    .optional(),
  /**
   * The mode the caller believes it is in. The server refuses `live` and `replay`
   * regardless of what a client asserts — the enum exists so the refusal can name the
   * mode, not so the client can choose to be exempt.
   */
  mode: z.enum(['live', 'demo', 'simulation', 'replay']).optional(),
})

export type ContingencyInput = z.infer<typeof contingencySchema>
export type SensorFaultInput = z.infer<typeof sensorFaultSchema>
