openapi: 3.0.3
info:
  title: CPS.core API
  description: |
    API REST para configuração, pricing e quotas de produtos configuráveis.

    ## Autenticação

    Todas as chamadas requerem uma API Key no header:
    ```
    X-API-Key: your-api-key
    ```

    As API Keys são geridas no backoffice em **Stores > API Keys**.

    ## Rate Limiting

    | Plano | Pedidos/minuto | Pedidos/dia |
    |-------|----------------|-------------|
    | Free | 60 | 1,000 |
    | Pro | 300 | 10,000 |
    | Enterprise | Ilimitado | Ilimitado |

    Headers de resposta incluem informação de rate limit:
    - `X-RateLimit-Limit`: Limite máximo
    - `X-RateLimit-Remaining`: Pedidos restantes
    - `X-RateLimit-Reset`: Timestamp de reset

  version: 1.9.0
  contact:
    name: PeopleWare Support
    email: support@peopleware.pt
    url: https://peopleware.pt
  license:
    name: Proprietary
    url: https://cps.peopleware.pt/terms

servers:
  - url: https://cps.peopleware.pt/api
    description: Produção
  - url: http://localhost:8280
    description: Desenvolvimento

tags:
  - name: Health
    description: Verificação de estado do serviço
  - name: Products
    description: Gestão de produtos e formulários
  - name: Calculate
    description: Cálculo de preços e BOM
  - name: Validate
    description: Validação de schemas DSL

paths:
  /health:
    get:
      tags:
        - Health
      summary: Verificar estado do serviço
      description: Retorna o estado de saúde do serviço. Não requer autenticação.
      operationId: healthCheck
      security: []
      responses:
        '200':
          description: Serviço operacional
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
              example:
                status: healthy
                version: "1.9"

  /v1/products/{product_id}/form:
    get:
      tags:
        - Products
      summary: Obter formulário de produto
      description: |
        Retorna o schema do formulário para um configurador de produto.

        O formulário inclui:
        - Grupos de campos organizados
        - Tipos de campo (number, select, checkbox, etc.)
        - Validações (min, max, required)
        - Condições de visibilidade (`visible_when`)
        - Traduções no idioma solicitado
      operationId: getProductForm
      parameters:
        - name: product_id
          in: path
          required: true
          description: ID do produto (external_id)
          schema:
            type: string
          example: janela-aluminio
        - name: tenant_id
          in: query
          required: true
          description: ID do tenant
          schema:
            type: string
          example: demo
        - name: lang
          in: query
          required: false
          description: Código de idioma para traduções
          schema:
            type: string
            default: en
          example: pt-PT
      responses:
        '200':
          description: Formulário do produto
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductForm'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /v1/calculate:
    post:
      tags:
        - Calculate
      summary: Calcular preço e BOM
      description: |
        Calcula o preço e outputs (incluindo BOM) com base na configuração fornecida.

        **Fluxo típico:**
        1. Obter formulário com `GET /v1/products/{id}/form`
        2. Utilizador preenche configuração
        3. Chamar este endpoint para calcular preço
        4. Mostrar resultado ao utilizador
        5. Adicionar ao carrinho com configuração + preço

        **Output Groups:**
        - Use `output_groups: ["*"]` para obter todos os grupos
        - Use `output_groups: ["production"]` para grupos específicos
        - O grupo `default` é sempre incluído em `totals`
      operationId: calculatePrice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CalculateRequest'
            example:
              tenant_id: demo
              product_id: janela-aluminio
              configuration:
                largura: 1200
                altura: 1000
                vidro: temperado
                cor: branco
              quantity: 1
              lang: pt-PT
              output_groups: ["*"]
      responses:
        '200':
          description: Resultado do cálculo
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalculateResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Erro de validação do produto
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
              example:
                product_id: janela-aluminio
                configuration:
                  largura: 5000
                  altura: 1000
                errors:
                  - "Largura 5000mm excede o máximo de 2400mm"
                warnings: []

  /v1/validate:
    post:
      tags:
        - Validate
      summary: Validar schema DSL
      description: |
        Valida um schema DSL sem o guardar. Útil para:
        - Verificar sintaxe antes de guardar
        - Testar schemas em desenvolvimento
        - Integração com editores externos
      operationId: validateSchema
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ValidateRequest'
            example:
              schema: |
                let base = 100
                add price: base * 1.23
      responses:
        '200':
          description: Resultado da validação
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidateResponse'
              examples:
                valid:
                  summary: Schema válido
                  value:
                    valid: true
                    errors: []
                    warnings: []
                invalid:
                  summary: Schema com erro
                  value:
                    valid: false
                    errors:
                      - line: 3
                        column: 5
                        message: "undefined variable 'y'"
                    warnings: []
        '401':
          $ref: '#/components/responses/Unauthorized'

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API Key obtida no backoffice (Stores > API Keys)

  schemas:
    HealthResponse:
      type: object
      properties:
        status:
          type: string
          enum: [healthy, degraded, unhealthy]
        version:
          type: string
      required:
        - status
        - version

    ProductForm:
      type: object
      properties:
        product_id:
          type: string
          description: ID do produto
        layout:
          type: string
          enum: [sections, tabs, accordion, inline]
          description: Tipo de layout do formulário
        dynamic:
          type: string
          enum: [fields, groups, none]
          description: Nível de dinamismo do formulário
        groups:
          type: array
          items:
            $ref: '#/components/schemas/FormGroup'
        quantity:
          $ref: '#/components/schemas/QuantityConfig'
      required:
        - product_id
        - groups

    FormGroup:
      type: object
      properties:
        id:
          type: string
        label:
          type: string
        order:
          type: integer
        collapsed:
          type: boolean
        visible_when:
          type: string
          nullable: true
          description: Expressão de visibilidade condicional
        fields:
          type: array
          items:
            $ref: '#/components/schemas/FormField'
      required:
        - id
        - fields

    FormField:
      type: object
      properties:
        id:
          type: string
        type:
          type: string
          enum: [number, text, select, checkbox, radio, swatch, range, date, file, hidden]
        label:
          type: string
        required:
          type: boolean
        default:
          description: Valor por defeito (tipo depende do field type)
        validation:
          type: object
          properties:
            min:
              type: number
            max:
              type: number
            step:
              type: number
            minLength:
              type: integer
            maxLength:
              type: integer
            pattern:
              type: string
        options:
          type: array
          items:
            $ref: '#/components/schemas/FieldOption'
          description: Opções para select, radio, swatch
        unit:
          type: string
          description: Unidade de medida (mm, kg, etc.)
        visible_when:
          type: string
          nullable: true
        disabled_when:
          type: string
          nullable: true
      required:
        - id
        - type

    FieldOption:
      type: object
      properties:
        value:
          type: string
        label:
          type: string
        image:
          type: string
          description: URL da imagem (para swatch)
        color:
          type: string
          description: Código de cor (para swatch)
      required:
        - value
        - label

    QuantityConfig:
      type: object
      properties:
        min:
          type: integer
          default: 1
        max:
          type: integer
          default: 10000
        step:
          type: integer
          default: 1
        presets:
          type: array
          items:
            type: integer
          description: Valores pré-definidos para seleção rápida

    CalculateRequest:
      type: object
      properties:
        tenant_id:
          type: string
          description: ID do tenant
        product_id:
          type: string
          description: ID do produto
        configuration:
          type: object
          additionalProperties: true
          description: Valores dos campos do formulário
        quantity:
          type: integer
          default: 1
          minimum: 1
        lang:
          type: string
          default: en
          description: Código de idioma para mensagens
        output_groups:
          type: array
          items:
            type: string
          default: ["*"]
          description: Grupos de output a incluir (* para todos)
      required:
        - tenant_id
        - product_id
        - configuration

    CalculateResponse:
      type: object
      properties:
        product_id:
          type: string
        configuration:
          type: object
          additionalProperties: true
        result:
          type: object
          properties:
            totals:
              type: object
              additionalProperties:
                type: number
              description: Valores acumulados (price, weight, etc.)
            groups:
              type: object
              additionalProperties:
                type: object
              description: Output groups nomeados (production, shipping, etc.)
        quantity:
          $ref: '#/components/schemas/QuantityConfig'
        formats:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/CurrencyFormat'
        available_groups:
          type: array
          items:
            type: string
        warnings:
          type: array
          items:
            type: string
        errors:
          type: array
          items:
            type: string
      required:
        - product_id
        - configuration
        - result

    CurrencyFormat:
      type: object
      properties:
        name:
          type: string
        locale:
          type: string
        symbol:
          type: string
        decimals:
          type: integer
        position:
          type: string
          enum: [left, right]
        thousandsSep:
          type: string
        decimalSep:
          type: string
        space:
          type: boolean

    ValidateRequest:
      type: object
      properties:
        schema:
          type: string
          description: Schema DSL a validar
      required:
        - schema

    ValidateResponse:
      type: object
      properties:
        valid:
          type: boolean
        errors:
          type: array
          items:
            $ref: '#/components/schemas/SchemaError'
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/SchemaError'
      required:
        - valid
        - errors
        - warnings

    SchemaError:
      type: object
      properties:
        line:
          type: integer
        column:
          type: integer
        message:
          type: string
      required:
        - message

    ValidationErrorResponse:
      type: object
      properties:
        product_id:
          type: string
        configuration:
          type: object
        errors:
          type: array
          items:
            type: string
        warnings:
          type: array
          items:
            type: string

    Error:
      type: object
      properties:
        error:
          type: string
          description: Mensagem de erro
        code:
          type: string
          description: Código de erro
      required:
        - error

  responses:
    BadRequest:
      description: Pedido inválido
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "Invalid request body"
            code: "BAD_REQUEST"

    Unauthorized:
      description: API Key em falta ou inválida
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "Missing or invalid API key"
            code: "UNAUTHORIZED"

    Forbidden:
      description: Sem permissão para este recurso
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "API key does not have access to this tenant"
            code: "FORBIDDEN"

    NotFound:
      description: Recurso não encontrado
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "Product not found"
            code: "NOT_FOUND"

security:
  - ApiKeyAuth: []
