# NOTE: Стартовая заглушка AsyncAPI 3.0 для WebSocket public API.
# Источник истины — реализация subscription manager в `services/public-api/api/ws/`.

asyncapi: 3.0.0
info:
  title: Tracium Public WebSocket API
  version: 1.0.0-draft
  description: |
    Подписочная модель: один WS-коннект, типизированные каналы.
    См.: 30-services/public-api/README.md, ADR-0015.

servers:
  production:
    host: api.tracium.ru
    pathname: /ws/v1
    protocol: wss
    security:
      - $ref: '#/components/securitySchemes/bearerAuth'
  staging:
    host: api.staging.tracium.ru
    pathname: /ws/v1
    protocol: wss

defaultContentType: application/json

channels:
  entityChanges:
    address: entity_changes
    description: Подписка на изменения сущностей (canonical product, supplier offer, ...).
    messages:
      subscribe: { $ref: '#/components/messages/SubscribeEntityChanges' }
      event: { $ref: '#/components/messages/EntityChangedEvent' }

  priceUpdates:
    address: price_updates
    description: Подписка на обновления цен (per canonical_id или filter).
    messages:
      subscribe: { $ref: '#/components/messages/SubscribePriceUpdates' }
      event: { $ref: '#/components/messages/PriceChangedEvent' }

  stockUpdates:
    address: stock_updates
    description: Подписка на изменения остатков.
    messages:
      subscribe: { $ref: '#/components/messages/SubscribeStockUpdates' }
      event: { $ref: '#/components/messages/StockChangedEvent' }

  estimateUpdates:
    address: estimate_updates
    description: Live-обновления сметы во время оптимизации.
    messages:
      subscribe: { $ref: '#/components/messages/SubscribeEstimateUpdates' }
      event: { $ref: '#/components/messages/EstimateUpdatedEvent' }

  enrichmentJob:
    address: enrichment_job
    description: Статус async EnrichmentJob.
    messages:
      subscribe: { $ref: '#/components/messages/SubscribeEnrichmentJob' }
      event: { $ref: '#/components/messages/EnrichmentJobEvent' }

  discoveryResults:
    address: discovery_results
    description: Результаты DiscoveryJob.
    messages:
      subscribe: { $ref: '#/components/messages/SubscribeDiscoveryResults' }
      event: { $ref: '#/components/messages/DiscoveryResultEvent' }

  control:
    address: control
    description: Управляющие сообщения (auth, ping/pong, dropped, error).
    messages:
      handshake: { $ref: '#/components/messages/Handshake' }
      ping: { $ref: '#/components/messages/Ping' }
      pong: { $ref: '#/components/messages/Pong' }
      dropped: { $ref: '#/components/messages/Dropped' }
      error: { $ref: '#/components/messages/ErrorMessage' }

operations:
  receiveEvents:
    action: receive
    channel: { $ref: '#/channels/entityChanges' }
  sendSubscribe:
    action: send
    channel: { $ref: '#/channels/entityChanges' }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  messages:
    Handshake:
      payload:
        type: object
        required: [type, token]
        properties:
          type: { const: auth }
          token: { type: string, description: JWT in first message }
    Ping:
      payload:
        type: object
        properties: { type: { const: ping } }
    Pong:
      payload:
        type: object
        properties: { type: { const: pong } }
    Dropped:
      payload:
        type: object
        properties:
          type: { const: dropped }
          subscription_id: { type: string }
          dropped_count: { type: integer }
          reason: { type: string, enum: [backpressure, rate_limit, server_overload] }
    ErrorMessage:
      payload:
        type: object
        properties:
          type: { const: error }
          code: { type: string }
          message: { type: string }

    SubscribeEntityChanges:
      payload:
        type: object
        required: [type, entity_type]
        properties:
          type: { const: subscribe }
          entity_type: { type: string, enum: [canonical_product, supplier_offer, identity_profile] }
          entity_id: { type: string }

    SubscribePriceUpdates:
      payload:
        type: object
        properties:
          type: { const: subscribe }
          canonical_id: { type: string }
          filter: { type: object, additionalProperties: true }

    SubscribeStockUpdates:
      payload:
        type: object
        properties:
          type: { const: subscribe }
          canonical_id: { type: string }
          warehouse_codes: { type: array, items: { type: string } }

    SubscribeEstimateUpdates:
      payload:
        type: object
        properties:
          type: { const: subscribe }
          estimate_id: { type: string }

    SubscribeEnrichmentJob:
      payload:
        type: object
        properties:
          type: { const: subscribe }
          job_id: { type: string }

    SubscribeDiscoveryResults:
      payload:
        type: object
        properties:
          type: { const: subscribe }
          discovery_job_id: { type: string }

    EntityChangedEvent: { payload: { $ref: '#/components/schemas/EventEnvelope' } }
    PriceChangedEvent: { payload: { $ref: '#/components/schemas/EventEnvelope' } }
    StockChangedEvent: { payload: { $ref: '#/components/schemas/EventEnvelope' } }
    EstimateUpdatedEvent: { payload: { $ref: '#/components/schemas/EventEnvelope' } }
    EnrichmentJobEvent: { payload: { $ref: '#/components/schemas/EventEnvelope' } }
    DiscoveryResultEvent: { payload: { $ref: '#/components/schemas/EventEnvelope' } }

  schemas:
    EventEnvelope:
      type: object
      required: [type, subscription_id, event_type, ts]
      properties:
        type: { const: event }
        subscription_id: { type: string }
        event_type: { type: string }
        entity: { type: object, additionalProperties: true }
        freshness:
          type: object
          properties:
            status: { type: string, enum: [fresh, stale, incomplete, empty] }
            observed_at: { type: string, format: date-time }
        ts: { type: string, format: date-time }
