openapi: 3.0.3
info:
  title: Ocarz Lead Management API
  description: |
    API REST complète pour la plateforme Ocarz de gestion de leads automobile au Maroc.
    
    ## Authentification
    Toutes les requêtes authentifiées utilisent un Bearer Token JWT dans le header `Authorization`.
    
    ## Rôles utilisateur
    - `admin_ops` : Administrateur opérationnel (accès complet)
    - `dispatcher` : Dispatche les leads entrants
    - `lead_dispatcher` : Dispatcher spécialisé leads
    - `expert` : Expert terrain (test drives, inspections)
    - `lead_expert` : Expert lead senior
    - `client_auto` : Client automobile (accès portail client)
    - `salesman` : Commercial showroom
    
    ## Pagination
    Utiliser `limit` et `offset` en query params. Défaut : limit=50, max=1000.
    
    ## Filtrage
    Les endpoints de liste supportent des query params de filtrage documentés par endpoint.
  version: 1.0.0
  contact:
    name: Ocarz Dev Team

servers:
  - url: https://api.ocarz.ma/v1
    description: Production
  - url: https://staging-api.ocarz.ma/v1
    description: Staging

tags:
  - name: Auth
    description: Authentification et gestion de session
  - name: Users
    description: Gestion des utilisateurs et profils
  - name: Leads
    description: Gestion des leads (acheteurs/vendeurs)
  - name: Campaigns
    description: Gestion des campagnes publicitaires
  - name: Campaign Products
    description: Produits associés aux campagnes
  - name: Campaign Matching
    description: Règles et résultats de matching lead/campagne
  - name: Campaign Questionnaires
    description: Questionnaires associés aux campagnes
  - name: Bookings
    description: Réservations de test drives et missions
  - name: Experts
    description: Gestion des experts terrain
  - name: Vehicles
    description: Gestion du parc véhicules
  - name: Promotional Vehicles
    description: Véhicules promotionnels (catalogue)
  - name: Clients
    description: Clients automobiles (concessionnaires, importateurs)
  - name: Showrooms
    description: Points de vente des clients
  - name: Commercials
    description: Commerciaux rattachés aux showrooms
  - name: Dispatchers
    description: Gestion des dispatchers et assignation
  - name: Surveys
    description: Enquêtes et sondages
  - name: Survey Audiences
    description: Audiences cibles pour les sondages
  - name: Notifications
    description: Notifications in-app
  - name: Reference Data
    description: Données de référence (villes, marques, catégories, carburants)
  - name: Analytics
    description: Tableaux de bord et statistiques
  - name: Audit
    description: Logs d'audit
  - name: WhatsApp
    description: Envoi de messages WhatsApp
  - name: Media Budgets
    description: Budgets médias par campagne

# ─────────────────────────── SECURITY ───────────────────────────
security:
  - BearerAuth: []

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

  # ─────────────────────────── ENUMS ───────────────────────────
  schemas:
    AppRole:
      type: string
      enum:
        - admin_ops
        - dispatcher
        - expert
        - client_auto
        - lead_dispatcher
        - lead_expert
        - salesman

    LeadStatus:
      type: string
      enum:
        - new
        - contacted
        - qualified
        - converted
        - lost
        - no_answer
        - callback_requested
        - scheduled
        - invalid_number

    LeadSource:
      type: string
      enum:
        - ocarz_digital
        - client_push

    LeadTemperature:
      type: string
      enum:
        - hot
        - warm
        - cold

    LeadType:
      type: string
      enum:
        - buyer
        - seller
        - both

    LeadFunnelStage:
      type: string
      enum:
        - conversation_started
        - vehicle_info_provided
        - estimation_received
        - purchase_intent_declared
        - preferences_set
        - budget_declared
        - test_drive_interested
        - appointment_booked

    PurchasePreference:
      type: string
      enum:
        - new
        - used
        - both

    BookingStatus:
      type: string
      enum:
        - scheduled
        - confirmed
        - in_progress
        - completed
        - no_show
        - cancelled
        - rescheduled

    ExpertStatus:
      type: string
      enum:
        - online
        - busy
        - offline
        - on_leave

    VehicleStatus:
      type: string
      enum:
        - available
        - in_use
        - maintenance
        - retired

    NotificationType:
      type: string
      enum:
        - new_lead_client_push
        - lead_sla_breach
        - booking_created
        - booking_reminder
        - booking_cancelled
        - booking_rescheduled
        - booking_expert_assigned
        - no_show_recorded
        - mission_completed
        - test_drive_completed
        - quota_alert
        - quota_warning
        - campaign_urgent
        - campaign_delivered
        - lead_assigned
        - lead_qualified
        - lead_reassigned
        - expert_checked_in
        - expert_availability_changed
        - survey_completed
        - survey_results_validated
        - match_buyer_seller
        - user_pending_validation
        - user_validated
        - dispatcher_unavailable
        - whatsapp_campaign_completed
        - conversion_recorded

    # SSE Event types for /notifications/stream
    SSEEventType:
      type: string
      description: |
        Event types emitted by the SSE notifications stream.
        Each event carries a JSON payload in the `data` field.
      enum:
        - notification:new
        - notification:read
        - notification:read_all
        - heartbeat

    NotificationChannel:
      type: string
      enum:
        - web
        - email
        - whatsapp

    # ─────────────────────── PAGINATION ─────────────────────────
    PaginationMeta:
      type: object
      properties:
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer
        has_more:
          type: boolean

    # ─────────────────────── AUTH ─────────────────────────
    LoginRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          format: password

    SignupRequest:
      type: object
      required: [email, password, full_name]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          format: password
          minLength: 8
        full_name:
          type: string
        phone:
          type: string
        pending_role:
          $ref: '#/components/schemas/AppRole'

    AuthResponse:
      type: object
      properties:
        access_token:
          type: string
        refresh_token:
          type: string
        expires_in:
          type: integer
        user:
          $ref: '#/components/schemas/Profile'

    # ─────────────────────── PROFILE ─────────────────────────
    Profile:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        full_name:
          type: string
          nullable: true
        phone:
          type: string
          nullable: true
        avatar_url:
          type: string
          nullable: true
        is_validated:
          type: boolean
        pending_role:
          $ref: '#/components/schemas/AppRole'
        validated_at:
          type: string
          format: date-time
          nullable: true
        validated_by:
          type: string
          format: uuid
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    UserRole:
      type: object
      properties:
        id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        role:
          $ref: '#/components/schemas/AppRole'
        created_at:
          type: string
          format: date-time

    # ─────────────────────── LEAD ─────────────────────────
    Lead:
      type: object
      properties:
        id:
          type: string
          format: uuid
        customer_name:
          type: string
        customer_phone:
          type: string
        customer_email:
          type: string
          nullable: true
        customer_city:
          type: string
          nullable: true
        customer_address:
          type: string
          nullable: true
        source:
          $ref: '#/components/schemas/LeadSource'
        status:
          $ref: '#/components/schemas/LeadStatus'
        temperature:
          $ref: '#/components/schemas/LeadTemperature'
        lead_type:
          $ref: '#/components/schemas/LeadType'
        funnel_stage:
          $ref: '#/components/schemas/LeadFunnelStage'
        agent_funnel_stage:
          $ref: '#/components/schemas/LeadFunnelStage'
        score:
          type: integer
          nullable: true
        priority_score:
          type: integer
          nullable: true
        # Buying intent
        intent_to_buy:
          type: boolean
          nullable: true
        purchase_preference:
          $ref: '#/components/schemas/PurchasePreference'
        purchase_timeline:
          type: string
          nullable: true
        desired_brands:
          type: array
          items:
            type: string
          nullable: true
        desired_models:
          type: array
          items:
            type: string
          nullable: true
        desired_category:
          type: string
          nullable: true
        desired_fuel_types:
          type: array
          items:
            type: string
          nullable: true
        budget_min:
          type: number
          nullable: true
        budget_max:
          type: number
          nullable: true
        wants_test_drive:
          type: boolean
          nullable: true
        # Selling intent
        current_vehicle_brand:
          type: string
          nullable: true
        current_vehicle_model:
          type: string
          nullable: true
        current_vehicle_year:
          type: integer
          nullable: true
        current_vehicle_fuel:
          type: string
          nullable: true
        current_vehicle_transmission:
          type: string
          nullable: true
        current_vehicle_mileage:
          type: integer
          nullable: true
        estimated_value:
          type: number
          nullable: true
        # Assignment
        campaign_id:
          type: string
          format: uuid
          nullable: true
        client_id:
          type: string
          format: uuid
          nullable: true
        assigned_dispatcher_id:
          type: string
          format: uuid
          nullable: true
        # Contact tracking
        call_attempts:
          type: integer
          default: 0
        first_contact_at:
          type: string
          format: date-time
          nullable: true
        last_contact_at:
          type: string
          format: date-time
          nullable: true
        agent_contacted_at:
          type: string
          format: date-time
          nullable: true
        # Bot
        bot_summary:
          type: string
          nullable: true
        buying_intent_completed_at:
          type: string
          format: date-time
          nullable: true
        buying_intent_source:
          type: string
          nullable: true
        # Misc
        preferred_time_slot:
          type: string
          nullable: true
        dispatcher_notes:
          type: string
          nullable: true
        whatsapp_appointment_booked:
          type: boolean
          nullable: true
        funnel_completed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    LeadCreate:
      type: object
      required: [customer_name, customer_phone, source]
      properties:
        customer_name:
          type: string
        customer_phone:
          type: string
        customer_email:
          type: string
        customer_city:
          type: string
        source:
          $ref: '#/components/schemas/LeadSource'
        campaign_id:
          type: string
          format: uuid
        client_id:
          type: string
          format: uuid
        status:
          $ref: '#/components/schemas/LeadStatus'
        current_vehicle_brand:
          type: string
        current_vehicle_model:
          type: string
        current_vehicle_year:
          type: integer
        estimated_value:
          type: number
        intent_to_buy:
          type: boolean
        desired_brands:
          type: array
          items:
            type: string
        desired_models:
          type: array
          items:
            type: string
        budget_min:
          type: number
        budget_max:
          type: number

    LeadUpdate:
      type: object
      properties:
        customer_name:
          type: string
        customer_phone:
          type: string
        customer_email:
          type: string
        customer_city:
          type: string
        status:
          $ref: '#/components/schemas/LeadStatus'
        funnel_stage:
          $ref: '#/components/schemas/LeadFunnelStage'
        agent_funnel_stage:
          $ref: '#/components/schemas/LeadFunnelStage'
        temperature:
          $ref: '#/components/schemas/LeadTemperature'
        intent_to_buy:
          type: boolean
        desired_brands:
          type: array
          items:
            type: string
        desired_models:
          type: array
          items:
            type: string
        desired_category:
          type: string
        desired_fuel_types:
          type: array
          items:
            type: string
        budget_min:
          type: number
        budget_max:
          type: number
        purchase_preference:
          $ref: '#/components/schemas/PurchasePreference'
        purchase_timeline:
          type: string
        wants_test_drive:
          type: boolean
        current_vehicle_brand:
          type: string
        current_vehicle_model:
          type: string
        current_vehicle_year:
          type: integer
        current_vehicle_fuel:
          type: string
        current_vehicle_transmission:
          type: string
        current_vehicle_mileage:
          type: integer
        estimated_value:
          type: number
        call_attempts:
          type: integer
        last_contact_at:
          type: string
          format: date-time
        dispatcher_notes:
          type: string
        assigned_dispatcher_id:
          type: string
          format: uuid
        campaign_id:
          type: string
          format: uuid
        client_id:
          type: string
          format: uuid
        bot_summary:
          type: string
        buying_intent_completed_at:
          type: string
          format: date-time
        buying_intent_source:
          type: string

    # ─────────────────────── CLIENT ─────────────────────────
    Client:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        logo_url:
          type: string
          nullable: true
        contact_first_name:
          type: string
          nullable: true
        contact_last_name:
          type: string
          nullable: true
        contact_email:
          type: string
          nullable: true
        contact_phone:
          type: string
          nullable: true
        crm_used:
          type: string
          nullable: true
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ClientCreate:
      type: object
      required: [name]
      properties:
        name:
          type: string
        logo_url:
          type: string
        contact_first_name:
          type: string
        contact_last_name:
          type: string
        contact_email:
          type: string
        contact_phone:
          type: string
        crm_used:
          type: string
        is_active:
          type: boolean
          default: true

    # ─────────────────────── CAMPAIGN ─────────────────────────
    Campaign:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        brand:
          type: string
        model:
          type: string
          nullable: true
        description:
          type: string
          nullable: true
        client_id:
          type: string
          format: uuid
        is_active:
          type: boolean
        start_date:
          type: string
          format: date
          nullable: true
        end_date:
          type: string
          format: date
          nullable: true
        target_zone:
          type: array
          items:
            type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        # Expanded relations
        client:
          $ref: '#/components/schemas/Client'
        campaign_products:
          type: array
          items:
            $ref: '#/components/schemas/CampaignProduct'

    CampaignCreate:
      type: object
      required: [name, brand, client_id]
      properties:
        name:
          type: string
        brand:
          type: string
        model:
          type: string
        description:
          type: string
        client_id:
          type: string
          format: uuid
        is_active:
          type: boolean
          default: true
        start_date:
          type: string
          format: date
        end_date:
          type: string
          format: date
        target_zone:
          type: array
          items:
            type: string

    CampaignUpdate:
      type: object
      properties:
        name:
          type: string
        brand:
          type: string
        model:
          type: string
        description:
          type: string
        client_id:
          type: string
          format: uuid
        is_active:
          type: boolean
        start_date:
          type: string
          format: date
        end_date:
          type: string
          format: date
        target_zone:
          type: array
          items:
            type: string

    # ─────────────────── CAMPAIGN PRODUCT ───────────────────
    CampaignProduct:
      type: object
      properties:
        id:
          type: string
          format: uuid
        campaign_id:
          type: string
          format: uuid
        product_id:
          type: string
          format: uuid
        promotional_vehicle_id:
          type: string
          format: uuid
          nullable: true
        unit_price_mad:
          type: number
        quota:
          type: integer
          nullable: true
        delivered_count:
          type: integer
          nullable: true
        revenue_generated_mad:
          type: number
          nullable: true
        exclusivity_type:
          type: string
          enum: [exclusive, standard]
        max_resales:
          type: integer
          nullable: true
        max_lead_age_days:
          type: integer
        billing_cycle:
          type: string
        is_active:
          type: boolean
        vehicle_brand:
          type: string
          nullable: true
        vehicle_model:
          type: string
          nullable: true
        vehicle_photos:
          type: array
          items:
            type: string
          nullable: true
        vehicle_specs:
          type: object
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    CampaignProductCreate:
      type: object
      required: [campaign_id, product_id, unit_price_mad]
      properties:
        campaign_id:
          type: string
          format: uuid
        product_id:
          type: string
          format: uuid
        promotional_vehicle_id:
          type: string
          format: uuid
        unit_price_mad:
          type: number
        quota:
          type: integer
        exclusivity_type:
          type: string
          enum: [exclusive, standard]
        max_resales:
          type: integer
        max_lead_age_days:
          type: integer
          default: 45
        billing_cycle:
          type: string
          default: monthly
        vehicle_brand:
          type: string
        vehicle_model:
          type: string

    # ──────────────── CAMPAIGN MATCHING RULES ────────────────
    CampaignMatchingRule:
      type: object
      properties:
        id:
          type: string
          format: uuid
        campaign_id:
          type: string
          format: uuid
        product_id:
          type: string
          format: uuid
        target_categories:
          type: array
          items:
            type: string
          nullable: true
        target_brands:
          type: array
          items:
            type: string
          nullable: true
        target_models:
          type: array
          items:
            type: string
          nullable: true
        target_fuel_types:
          type: array
          items:
            type: string
          nullable: true
        budget_min_mad:
          type: number
          nullable: true
        budget_max_mad:
          type: number
          nullable: true
        match_priority:
          type: integer
          nullable: true
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    CampaignMatchingRuleCreate:
      type: object
      required: [campaign_id, product_id]
      properties:
        campaign_id:
          type: string
          format: uuid
        product_id:
          type: string
          format: uuid
        target_categories:
          type: array
          items:
            type: string
        target_brands:
          type: array
          items:
            type: string
        target_models:
          type: array
          items:
            type: string
        target_fuel_types:
          type: array
          items:
            type: string
        budget_min_mad:
          type: number
        budget_max_mad:
          type: number
        match_priority:
          type: integer
        is_active:
          type: boolean
          default: true

    # ──────────────── LEAD CAMPAIGN MATCH ────────────────
    LeadCampaignMatch:
      type: object
      properties:
        id:
          type: string
          format: uuid
        lead_id:
          type: string
          format: uuid
        campaign_id:
          type: string
          format: uuid
        product_id:
          type: string
          format: uuid
        match_level:
          type: string
          enum: [model, brand, category]
        match_score:
          type: number
          nullable: true
        match_origin:
          type: string
          nullable: true
          description: "Origin: auto, manual, retro_scan"
        is_delivered:
          type: boolean
        delivered_at:
          type: string
          format: date-time
          nullable: true
        sale_count:
          type: integer
          nullable: true
        fuel_type_bonus:
          type: boolean
          nullable: true
        created_at:
          type: string
          format: date-time

    # ─────────────────────── BOOKING ─────────────────────────
    Booking:
      type: object
      properties:
        id:
          type: string
          format: uuid
        lead_id:
          type: string
          format: uuid
        expert_id:
          type: string
          format: uuid
          nullable: true
        vehicle_id:
          type: string
          format: uuid
          nullable: true
        commercial_id:
          type: string
          format: uuid
          nullable: true
        showroom_id:
          type: string
          format: uuid
          nullable: true
        scheduled_date:
          type: string
          format: date
        scheduled_time_start:
          type: string
          description: "Format HH:mm"
        scheduled_time_end:
          type: string
          description: "Format HH:mm"
        status:
          $ref: '#/components/schemas/BookingStatus'
        location_address:
          type: string
        location_city:
          type: string
        location_type:
          type: string
          nullable: true
        location_coordinates:
          type: string
          nullable: true
        expert_notes:
          type: string
          nullable: true
        expert_arrived_at:
          type: string
          format: date-time
          nullable: true
        test_drive_started_at:
          type: string
          format: date-time
          nullable: true
        test_drive_ended_at:
          type: string
          format: date-time
          nullable: true
        customer_interest_rating:
          type: integer
          nullable: true
          minimum: 1
          maximum: 5
        blocking_point:
          type: string
          nullable: true
        next_step:
          type: string
          nullable: true
        inspection_photos:
          type: array
          items:
            type: string
          nullable: true
        whatsapp_confirmation_sent:
          type: boolean
          nullable: true
        whatsapp_reminder_sent:
          type: boolean
          nullable: true
        created_by:
          type: string
          format: uuid
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    BookingCreate:
      type: object
      required: [lead_id, scheduled_date, scheduled_time_start, scheduled_time_end, location_address, location_city]
      properties:
        lead_id:
          type: string
          format: uuid
        expert_id:
          type: string
          format: uuid
        vehicle_id:
          type: string
          format: uuid
        commercial_id:
          type: string
          format: uuid
        showroom_id:
          type: string
          format: uuid
        scheduled_date:
          type: string
          format: date
        scheduled_time_start:
          type: string
        scheduled_time_end:
          type: string
        location_address:
          type: string
        location_city:
          type: string
        location_type:
          type: string
        status:
          $ref: '#/components/schemas/BookingStatus'

    BookingUpdate:
      type: object
      properties:
        expert_id:
          type: string
          format: uuid
        vehicle_id:
          type: string
          format: uuid
        commercial_id:
          type: string
          format: uuid
        showroom_id:
          type: string
          format: uuid
        scheduled_date:
          type: string
          format: date
        scheduled_time_start:
          type: string
        scheduled_time_end:
          type: string
        status:
          $ref: '#/components/schemas/BookingStatus'
        location_address:
          type: string
        location_city:
          type: string
        expert_notes:
          type: string
        expert_arrived_at:
          type: string
          format: date-time
        test_drive_started_at:
          type: string
          format: date-time
        test_drive_ended_at:
          type: string
          format: date-time
        customer_interest_rating:
          type: integer
        blocking_point:
          type: string
        next_step:
          type: string
        inspection_photos:
          type: array
          items:
            type: string

    # ─────────────────────── EXPERT ─────────────────────────
    Expert:
      type: object
      properties:
        id:
          type: string
          format: uuid
        first_name:
          type: string
        last_name:
          type: string
        phone:
          type: string
        email:
          type: string
          nullable: true
        avatar_url:
          type: string
          nullable: true
        cin:
          type: string
          nullable: true
        permis:
          type: string
          nullable: true
        zones:
          type: array
          items:
            type: string
        status:
          $ref: '#/components/schemas/ExpertStatus'
        is_active:
          type: boolean
        user_id:
          type: string
          format: uuid
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ExpertCreate:
      type: object
      required: [first_name, last_name, phone]
      properties:
        first_name:
          type: string
        last_name:
          type: string
        phone:
          type: string
        email:
          type: string
        cin:
          type: string
        permis:
          type: string
        zones:
          type: array
          items:
            type: string
        user_id:
          type: string
          format: uuid

    ExpertAvailability:
      type: object
      properties:
        id:
          type: string
          format: uuid
        expert_id:
          type: string
          format: uuid
        date:
          type: string
          format: date
        start_time:
          type: string
        end_time:
          type: string
        is_available:
          type: boolean
        reason:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time

    # ─────────────────────── VEHICLE ─────────────────────────
    Vehicle:
      type: object
      properties:
        id:
          type: string
          format: uuid
        brand:
          type: string
        model:
          type: string
        registration_plate:
          type: string
        year:
          type: integer
          nullable: true
        color:
          type: string
          nullable: true
        fuel_type:
          type: string
          nullable: true
        transmission:
          type: string
          nullable: true
        mileage:
          type: integer
          nullable: true
        status:
          $ref: '#/components/schemas/VehicleStatus'
        campaign_id:
          type: string
          format: uuid
          nullable: true
        current_expert_id:
          type: string
          format: uuid
          nullable: true
        last_known_location:
          type: string
          nullable: true
        exterior_photos:
          type: array
          items:
            type: string
          nullable: true
        interior_photos:
          type: array
          items:
            type: string
          nullable: true
        documents:
          type: array
          items:
            type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    # ────────────────── PROMOTIONAL VEHICLE ──────────────────
    PromotionalVehicle:
      type: object
      properties:
        id:
          type: string
          format: uuid
        brand:
          type: string
        model:
          type: string
        category:
          type: string
          nullable: true
        description:
          type: string
          nullable: true
        photos:
          type: array
          items:
            type: string
          nullable: true
        technical_specs:
          type: array
          items:
            type: string
          nullable: true
        target_audience:
          type: string
          nullable: true
        release_date:
          type: string
          format: date
          nullable: true
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    # ─────────────────────── SHOWROOM ─────────────────────────
    Showroom:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        city:
          type: string
        address:
          type: string
          nullable: true
        phone:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
        client_id:
          type: string
          format: uuid
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    # ─────────────────────── COMMERCIAL ─────────────────────────
    Commercial:
      type: object
      properties:
        id:
          type: string
          format: uuid
        full_name:
          type: string
        phone:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
        showroom_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
          nullable: true
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    # ──────────────── DISPATCHER ASSIGNMENT ────────────────
    DispatcherAssignment:
      type: object
      properties:
        id:
          type: string
          format: uuid
        dispatcher_user_id:
          type: string
          format: uuid
        assignment_order:
          type: integer
        is_active:
          type: boolean
        status:
          type: string
          enum: [available, busy, on_leave, absent]
        total_assigned:
          type: integer
        last_assigned_at:
          type: string
          format: date-time
          nullable: true
        unavailable_from:
          type: string
          format: date
          nullable: true
        unavailable_until:
          type: string
          format: date
          nullable: true
        unavailability_reason:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    # ─────────────────────── SURVEY ─────────────────────────
    Survey:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
          nullable: true
        status:
          type: string
          enum: [draft, active, completed, archived]
        delivery_method:
          type: string
        client_id:
          type: string
          format: uuid
          nullable: true
        target_audience:
          type: string
          nullable: true
        target_count:
          type: integer
          nullable: true
        responses_count:
          type: integer
          nullable: true
        start_date:
          type: string
          format: date
          nullable: true
        end_date:
          type: string
          format: date
          nullable: true
        results_validated:
          type: boolean
          nullable: true
        validated_at:
          type: string
          format: date-time
          nullable: true
        validated_by:
          type: string
          format: uuid
          nullable: true
        created_by:
          type: string
          format: uuid
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    SurveyQuestion:
      type: object
      properties:
        id:
          type: string
          format: uuid
        survey_id:
          type: string
          format: uuid
        question_text:
          type: string
        question_type:
          type: string
          enum: [text, rating, multiple_choice, yes_no]
        options:
          type: object
          nullable: true
        display_order:
          type: integer
        is_required:
          type: boolean
        created_at:
          type: string
          format: date-time

    SurveyResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        survey_id:
          type: string
          format: uuid
        lead_id:
          type: string
          format: uuid
          nullable: true
        dispatcher_id:
          type: string
          format: uuid
          nullable: true
        responded_via:
          type: string
        is_complete:
          type: boolean
        started_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time

    SurveyAnswer:
      type: object
      properties:
        id:
          type: string
          format: uuid
        response_id:
          type: string
          format: uuid
        question_id:
          type: string
          format: uuid
        answer_text:
          type: string
          nullable: true
        answer_option:
          type: string
          nullable: true
        answer_rating:
          type: integer
          nullable: true
        created_at:
          type: string
          format: date-time

    SurveyAudience:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
          nullable: true
        filters:
          type: object
        lead_count:
          type: integer
        created_by:
          type: string
          format: uuid
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    # ─────────────────── NOTIFICATION ─────────────────────
    Notification:
      type: object
      properties:
        id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        type:
          $ref: '#/components/schemas/NotificationType'
        channel:
          $ref: '#/components/schemas/NotificationChannel'
        title:
          type: string
        message:
          type: string
        data:
          type: object
          nullable: true
        is_read:
          type: boolean
        read_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time

    # ──────────────── UPLOAD RESPONSE ────────────────
    UploadResponse:
      type: object
      properties:
        url:
          type: string
          format: uri
          description: URL publique du fichier uploadé
        path:
          type: string
          description: Chemin relatif dans le storage
        size:
          type: integer
          description: Taille du fichier en bytes
        content_type:
          type: string
          description: MIME type du fichier

    # ──────────────── BUYER/SELLER MATCH ────────────────
    BuyerSellerMatch:
      type: object
      properties:
        id:
          type: string
          format: uuid
        buyer_lead_id:
          type: string
          format: uuid
        seller_lead_id:
          type: string
          format: uuid
        match_score:
          type: number
        match_tier:
          type: string
        score_breakdown:
          type: object
          nullable: true
        status:
          type: string
          enum: [pending, approved, rejected]
        notes:
          type: string
          nullable: true
        approved_at:
          type: string
          format: date-time
          nullable: true
        approved_by:
          type: string
          format: uuid
          nullable: true
        rejected_at:
          type: string
          format: date-time
          nullable: true
        whatsapp_sent_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    # ──────────────── MEDIA BUDGET ────────────────
    MediaBudget:
      type: object
      properties:
        id:
          type: string
          format: uuid
        campaign_id:
          type: string
          format: uuid
        source:
          type: string
        budget_allocated_mad:
          type: number
        budget_spent_mad:
          type: number
        period_start:
          type: string
          format: date
        period_end:
          type: string
          format: date
        notes:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    # ──────────────── PRODUCT ────────────────
    Product:
      type: object
      properties:
        id:
          type: string
          format: uuid
        code:
          type: string
        name:
          type: string
        product_type:
          type: string
        description:
          type: string
          nullable: true
        display_order:
          type: integer
          nullable: true
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time

    # ──────────────── REFERENCE DATA ────────────────
    MoroccanCity:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        region:
          type: string
          nullable: true
        is_zone:
          type: boolean
        is_active:
          type: boolean
        display_order:
          type: integer
          nullable: true

    VehicleBrand:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        logo_url:
          type: string
          nullable: true
        is_active:
          type: boolean
        display_order:
          type: integer
          nullable: true

    VehicleModel:
      type: object
      properties:
        id:
          type: string
          format: uuid
        brand_id:
          type: string
          format: uuid
        name:
          type: string
        is_active:
          type: boolean
        display_order:
          type: integer
          nullable: true

    VehicleCategory:
      type: object
      properties:
        id:
          type: string
          format: uuid
        code:
          type: string
        name:
          type: string
        name_fr:
          type: string
        example_models:
          type: array
          items:
            type: string
          nullable: true
        is_active:
          type: boolean
        display_order:
          type: integer
          nullable: true

    FuelType:
      type: object
      properties:
        id:
          type: string
          format: uuid
        code:
          type: string
        name:
          type: string
        name_fr:
          type: string
        is_active:
          type: boolean
        display_order:
          type: integer
          nullable: true

    # ──────────────── AUDIT LOG ────────────────
    AuditLog:
      type: object
      properties:
        id:
          type: string
          format: uuid
        table_name:
          type: string
        record_id:
          type: string
          nullable: true
        action:
          type: string
          enum: [INSERT, UPDATE, DELETE]
        user_id:
          type: string
          format: uuid
          nullable: true
        old_values:
          type: object
          nullable: true
        new_values:
          type: object
          nullable: true
        ip_address:
          type: string
          nullable: true
        user_agent:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time

    # ──────────── QUESTIONNAIRE TEMPLATES ────────────
    QuestionnaireTemplate:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
          nullable: true
        is_default:
          type: boolean
        created_by:
          type: string
          format: uuid
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    # ──────────────── GENERIC RESPONSES ────────────────
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
        message:
          type: string
        status_code:
          type: integer

    SuccessResponse:
      type: object
      properties:
        success:
          type: boolean
        message:
          type: string

    # ──────────── ADMIN DASHBOARD STATS ────────────
    AdminDashboardStats:
      type: object
      properties:
        total_revenue_mad:
          type: number
        total_leads:
          type: integer
        total_bookings:
          type: integer
        completed_bookings:
          type: integer
        active_campaigns:
          type: integer
        quotas_reached:
          type: integer
        total_quotas:
          type: integer
        total_media_spent:
          type: number
        active_alerts:
          type: integer
        leads_by_status:
          type: object
          additionalProperties:
            type: integer
        bookings_by_status:
          type: object
          additionalProperties:
            type: integer
        revenue_by_client:
          type: array
          items:
            type: object
            properties:
              client_name:
                type: string
              revenue:
                type: number
        leads_trend:
          type: array
          items:
            type: object
            properties:
              date:
                type: string
                format: date
              count:
                type: integer
        bookings_trend:
          type: array
          items:
            type: object
            properties:
              date:
                type: string
                format: date
              count:
                type: integer

    # ──────────── MATCHING RESULT ────────────
    MatchingResult:
      type: object
      properties:
        campaign_id:
          type: string
          format: uuid
        product_id:
          type: string
          format: uuid
        match_level:
          type: string
          enum: [model, brand, category]
        match_score:
          type: number

    MatchingLeadResult:
      type: object
      properties:
        lead_id:
          type: string
          format: uuid
        customer_name:
          type: string
        customer_phone:
          type: string
        customer_city:
          type: string
        funnel_stage:
          type: string
        temperature:
          type: string
        priority_score:
          type: integer
        match_score:
          type: number
        match_reasons:
          type: array
          items:
            type: string
        already_matched:
          type: boolean

    CostPerAcquisition:
      type: object
      properties:
        product_type:
          type: string
        total_budget_spent:
          type: number
        total_delivered:
          type: integer
        cost_per_acquisition:
          type: number

# ═══════════════════════════════════════════════════════════════
# PATHS
# ═══════════════════════════════════════════════════════════════
paths:
  # ─────────────────────── AUTH ─────────────────────────
  /auth/login:
    post:
      tags: [Auth]
      summary: Connexion utilisateur
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Authentification réussie
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Identifiants invalides
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /auth/signup:
    post:
      tags: [Auth]
      summary: Inscription utilisateur
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignupRequest'
      responses:
        '201':
          description: Utilisateur créé (en attente de validation)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'

  /auth/refresh:
    post:
      tags: [Auth]
      summary: Rafraîchir le token JWT
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [refresh_token]
              properties:
                refresh_token:
                  type: string
      responses:
        '200':
          description: Nouveau token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'

  /auth/logout:
    post:
      tags: [Auth]
      summary: Déconnexion
      responses:
        '200':
          description: Déconnecté
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'

  /auth/me:
    get:
      tags: [Auth]
      summary: Profil de l'utilisateur connecté avec ses rôles
      responses:
        '200':
          description: Profil
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Profile'
                  - type: object
                    properties:
                      roles:
                        type: array
                        items:
                          $ref: '#/components/schemas/AppRole'

  # ─────────────────────── USERS ─────────────────────────
  /users:
    get:
      tags: [Users]
      summary: Liste des utilisateurs (admin only)
      description: Retourne les profils avec leurs rôles. Requiert le rôle admin_ops.
      parameters:
        - name: is_validated
          in: query
          schema:
            type: boolean
        - name: role
          in: query
          schema:
            $ref: '#/components/schemas/AppRole'
        - name: search
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Liste des utilisateurs
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      allOf:
                        - $ref: '#/components/schemas/Profile'
                        - type: object
                          properties:
                            roles:
                              type: array
                              items:
                                $ref: '#/components/schemas/UserRole'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'

  /users/{user_id}/validate:
    post:
      tags: [Users]
      summary: Valider un utilisateur et lui assigner un rôle (admin only)
      parameters:
        - name: user_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [role]
              properties:
                role:
                  $ref: '#/components/schemas/AppRole'
      responses:
        '200':
          description: Utilisateur validé
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'

  /users/{user_id}/reject:
    post:
      tags: [Users]
      summary: Rejeter un utilisateur (admin only)
      parameters:
        - name: user_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Utilisateur rejeté
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'

  /users/create:
    post:
      tags: [Users]
      summary: Créer un utilisateur avec rôle (admin only)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password, full_name, role]
              properties:
                email:
                  type: string
                password:
                  type: string
                full_name:
                  type: string
                phone:
                  type: string
                role:
                  $ref: '#/components/schemas/AppRole'
      responses:
        '201':
          description: Utilisateur créé et validé
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Profile'

  # ─────────────────────── LEADS ─────────────────────────
  /leads:
    get:
      tags: [Leads]
      summary: Liste des leads avec filtres
      parameters:
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/LeadStatus'
        - name: temperature
          in: query
          schema:
            $ref: '#/components/schemas/LeadTemperature'
        - name: lead_type
          in: query
          schema:
            $ref: '#/components/schemas/LeadType'
        - name: source
          in: query
          schema:
            $ref: '#/components/schemas/LeadSource'
        - name: city
          in: query
          schema:
            type: string
        - name: campaign_id
          in: query
          schema:
            type: string
            format: uuid
        - name: client_id
          in: query
          schema:
            type: string
            format: uuid
        - name: assigned_dispatcher_id
          in: query
          schema:
            type: string
            format: uuid
        - name: search
          in: query
          schema:
            type: string
          description: Recherche par nom ou téléphone
        - name: date_from
          in: query
          schema:
            type: string
            format: date
        - name: date_to
          in: query
          schema:
            type: string
            format: date
        - name: sort_by
          in: query
          schema:
            type: string
            enum: [created_at, priority_score, customer_name]
            default: created_at
        - name: sort_order
          in: query
          schema:
            type: string
            enum: [asc, desc]
            default: desc
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Liste paginée de leads
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Lead'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'
    post:
      tags: [Leads]
      summary: Créer un lead
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LeadCreate'
      responses:
        '201':
          description: Lead créé
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Lead'

  /leads/{id}:
    get:
      tags: [Leads]
      summary: Détail d'un lead
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Détail du lead avec relations
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Lead'
                  - type: object
                    properties:
                      campaign:
                        $ref: '#/components/schemas/Campaign'
                      client:
                        $ref: '#/components/schemas/Client'
                      bookings:
                        type: array
                        items:
                          $ref: '#/components/schemas/Booking'
                      matches:
                        type: array
                        items:
                          $ref: '#/components/schemas/LeadCampaignMatch'
    patch:
      tags: [Leads]
      summary: Mettre à jour un lead
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LeadUpdate'
      responses:
        '200':
          description: Lead mis à jour
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Lead'
    delete:
      tags: [Leads]
      summary: Supprimer un lead
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Lead supprimé

  /leads/{id}/match:
    post:
      tags: [Campaign Matching]
      summary: Lancer le matching d'un lead avec les campagnes actives
      description: |
        Exécute l'algorithme de matching pour trouver les campagnes correspondantes.
        Scoring : model=90, brand=70, category=50, +10 fuel bonus, +5 budget match.
        Respecte les règles d'exclusivité et quotas.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                origin:
                  type: string
                  default: manual
                  description: "Origin du matching : manual, auto, retro_scan"
      responses:
        '200':
          description: Résultats du matching
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MatchingResult'

  /leads/{id}/audit-log:
    get:
      tags: [Audit]
      summary: Historique des modifications d'un lead
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Logs d'audit
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AuditLog'

  /leads/export:
    get:
      tags: [Leads]
      summary: Export CSV des leads
      parameters:
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/LeadStatus'
        - name: date_from
          in: query
          schema:
            type: string
            format: date
        - name: date_to
          in: query
          schema:
            type: string
            format: date
      responses:
        '200':
          description: Fichier CSV
          content:
            text/csv:
              schema:
                type: string

  # ─────────────────────── CAMPAIGNS ─────────────────────────
  /campaigns:
    get:
      tags: [Campaigns]
      summary: Liste des campagnes
      parameters:
        - name: client_id
          in: query
          schema:
            type: string
            format: uuid
        - name: is_active
          in: query
          schema:
            type: boolean
        - name: search
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Liste des campagnes avec client et produits
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Campaign'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'
    post:
      tags: [Campaigns]
      summary: Créer une campagne
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CampaignCreate'
      responses:
        '201':
          description: Campagne créée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Campaign'

  /campaigns/{id}:
    get:
      tags: [Campaigns]
      summary: Détail d'une campagne
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Campagne avec produits, règles et stats
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Campaign'
    patch:
      tags: [Campaigns]
      summary: Mettre à jour une campagne
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CampaignUpdate'
      responses:
        '200':
          description: Campagne mise à jour
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Campaign'
    delete:
      tags: [Campaigns]
      summary: Supprimer une campagne
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Campagne supprimée

  /campaigns/{id}/matching-leads:
    get:
      tags: [Campaign Matching]
      summary: Trouver les leads correspondants à une campagne
      description: |
        Retourne les leads éligibles avec score de matching.
        Utilise les règles de matching configurées (target_brands, target_models, target_categories, etc.)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Leads correspondants
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MatchingLeadResult'

  # ──────────────── CAMPAIGN PRODUCTS ────────────────
  /campaigns/{campaign_id}/products:
    get:
      tags: [Campaign Products]
      summary: Produits d'une campagne
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Liste des produits
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CampaignProduct'
    post:
      tags: [Campaign Products]
      summary: Ajouter un produit à une campagne
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CampaignProductCreate'
      responses:
        '201':
          description: Produit ajouté
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignProduct'

  /campaigns/{campaign_id}/products/{product_id}:
    patch:
      tags: [Campaign Products]
      summary: Modifier un produit de campagne
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: product_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CampaignProductCreate'
      responses:
        '200':
          description: Produit modifié
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignProduct'
    delete:
      tags: [Campaign Products]
      summary: Retirer un produit d'une campagne
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: product_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Produit retiré

  # ──────────────── MATCHING RULES ────────────────
  /campaigns/{campaign_id}/matching-rules:
    get:
      tags: [Campaign Matching]
      summary: Règles de matching d'une campagne
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Règles de matching
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CampaignMatchingRule'
    post:
      tags: [Campaign Matching]
      summary: Créer une règle de matching
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CampaignMatchingRuleCreate'
      responses:
        '201':
          description: Règle créée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignMatchingRule'

  /campaigns/{campaign_id}/matching-rules/{rule_id}:
    patch:
      tags: [Campaign Matching]
      summary: Modifier une règle de matching
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: rule_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CampaignMatchingRuleCreate'
      responses:
        '200':
          description: Règle modifiée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignMatchingRule'
    delete:
      tags: [Campaign Matching]
      summary: Supprimer une règle
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: rule_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Règle supprimée

  # ──────────────── LEAD CAMPAIGN MATCHES ────────────────
  /lead-campaign-matches:
    get:
      tags: [Campaign Matching]
      summary: Liste des matchs lead/campagne
      parameters:
        - name: campaign_id
          in: query
          schema:
            type: string
            format: uuid
        - name: lead_id
          in: query
          schema:
            type: string
            format: uuid
        - name: is_delivered
          in: query
          schema:
            type: boolean
        - name: match_level
          in: query
          schema:
            type: string
            enum: [model, brand, category]
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Matchs
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/LeadCampaignMatch'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'

  /lead-campaign-matches/{id}/deliver:
    post:
      tags: [Campaign Matching]
      summary: Marquer un match comme livré
      description: Met à jour is_delivered=true, incrémente le compteur et le revenue
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Match livré
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LeadCampaignMatch'

  # ─────────────────────── BOOKINGS ─────────────────────────
  /bookings:
    get:
      tags: [Bookings]
      summary: Liste des réservations
      parameters:
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/BookingStatus'
        - name: expert_id
          in: query
          schema:
            type: string
            format: uuid
        - name: date_from
          in: query
          schema:
            type: string
            format: date
        - name: date_to
          in: query
          schema:
            type: string
            format: date
        - name: city
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Liste paginée de bookings
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      allOf:
                        - $ref: '#/components/schemas/Booking'
                        - type: object
                          properties:
                            lead:
                              $ref: '#/components/schemas/Lead'
                            expert:
                              $ref: '#/components/schemas/Expert'
                            vehicle:
                              $ref: '#/components/schemas/Vehicle'
                            showroom:
                              $ref: '#/components/schemas/Showroom'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'
    post:
      tags: [Bookings]
      summary: Créer une réservation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BookingCreate'
      responses:
        '201':
          description: Booking créé
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Booking'

  /bookings/{id}:
    get:
      tags: [Bookings]
      summary: Détail d'un booking
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Booking avec relations
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Booking'
                  - type: object
                    properties:
                      lead:
                        $ref: '#/components/schemas/Lead'
                      expert:
                        $ref: '#/components/schemas/Expert'
                      vehicle:
                        $ref: '#/components/schemas/Vehicle'
    patch:
      tags: [Bookings]
      summary: Mettre à jour un booking
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BookingUpdate'
      responses:
        '200':
          description: Booking mis à jour
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Booking'
    delete:
      tags: [Bookings]
      summary: Supprimer un booking
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Booking supprimé

  # ─────────────────────── EXPERTS ─────────────────────────
  /experts:
    get:
      tags: [Experts]
      summary: Liste des experts
      parameters:
        - name: is_active
          in: query
          schema:
            type: boolean
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/ExpertStatus'
        - name: zone
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Liste des experts
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Expert'
    post:
      tags: [Experts]
      summary: Créer un expert
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExpertCreate'
      responses:
        '201':
          description: Expert créé
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Expert'

  /experts/{id}:
    patch:
      tags: [Experts]
      summary: Mettre à jour un expert
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExpertCreate'
      responses:
        '200':
          description: Expert mis à jour
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Expert'

  /experts/{id}/availability:
    get:
      tags: [Experts]
      summary: Disponibilités d'un expert
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: date_from
          in: query
          schema:
            type: string
            format: date
        - name: date_to
          in: query
          schema:
            type: string
            format: date
      responses:
        '200':
          description: Disponibilités
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ExpertAvailability'
    post:
      tags: [Experts]
      summary: Définir une disponibilité
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [date, start_time, end_time]
              properties:
                date:
                  type: string
                  format: date
                start_time:
                  type: string
                end_time:
                  type: string
                is_available:
                  type: boolean
                  default: true
                reason:
                  type: string
      responses:
        '201':
          description: Disponibilité créée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExpertAvailability'

  # ─────────────────────── VEHICLES ─────────────────────────
  /vehicles:
    get:
      tags: [Vehicles]
      summary: Liste des véhicules
      parameters:
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/VehicleStatus'
        - name: campaign_id
          in: query
          schema:
            type: string
            format: uuid
        - name: search
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Liste des véhicules
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Vehicle'
    post:
      tags: [Vehicles]
      summary: Créer un véhicule
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [brand, model, registration_plate]
              properties:
                brand:
                  type: string
                model:
                  type: string
                registration_plate:
                  type: string
                year:
                  type: integer
                color:
                  type: string
                fuel_type:
                  type: string
                transmission:
                  type: string
                mileage:
                  type: integer
                campaign_id:
                  type: string
                  format: uuid
      responses:
        '201':
          description: Véhicule créé
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Vehicle'

  /vehicles/{id}:
    patch:
      tags: [Vehicles]
      summary: Mettre à jour un véhicule
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Véhicule mis à jour
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Vehicle'
    delete:
      tags: [Vehicles]
      summary: Supprimer un véhicule
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Véhicule supprimé

  # ────────────────── PROMOTIONAL VEHICLES ──────────────────
  /promotional-vehicles:
    get:
      tags: [Promotional Vehicles]
      summary: Catalogue de véhicules promotionnels
      parameters:
        - name: brand
          in: query
          schema:
            type: string
        - name: is_active
          in: query
          schema:
            type: boolean
      responses:
        '200':
          description: Liste
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PromotionalVehicle'
    post:
      tags: [Promotional Vehicles]
      summary: Ajouter un véhicule promotionnel
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [brand, model]
              properties:
                brand:
                  type: string
                model:
                  type: string
                category:
                  type: string
                description:
                  type: string
                target_audience:
                  type: string
                release_date:
                  type: string
                  format: date
      responses:
        '201':
          description: Créé
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PromotionalVehicle'

  /promotional-vehicles/{id}:
    patch:
      tags: [Promotional Vehicles]
      summary: Modifier un véhicule promotionnel
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Modifié
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PromotionalVehicle'
    delete:
      tags: [Promotional Vehicles]
      summary: Supprimer
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Supprimé

  # ─────────────────────── CLIENTS ─────────────────────────
  /clients:
    get:
      tags: [Clients]
      summary: Liste des clients
      parameters:
        - name: is_active
          in: query
          schema:
            type: boolean
        - name: search
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Liste des clients
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Client'
    post:
      tags: [Clients]
      summary: Créer un client
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ClientCreate'
      responses:
        '201':
          description: Client créé
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Client'

  /clients/{id}:
    get:
      tags: [Clients]
      summary: Détail d'un client avec showrooms
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Client avec showrooms
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Client'
                  - type: object
                    properties:
                      showrooms:
                        type: array
                        items:
                          $ref: '#/components/schemas/Showroom'
    patch:
      tags: [Clients]
      summary: Modifier un client
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Client modifié
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Client'

  # ─────────────────────── SHOWROOMS ─────────────────────────
  /showrooms:
    get:
      tags: [Showrooms]
      summary: Liste des showrooms
      parameters:
        - name: client_id
          in: query
          schema:
            type: string
            format: uuid
        - name: city
          in: query
          schema:
            type: string
        - name: is_active
          in: query
          schema:
            type: boolean
      responses:
        '200':
          description: Liste
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Showroom'
    post:
      tags: [Showrooms]
      summary: Créer un showroom
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, city, client_id]
              properties:
                name:
                  type: string
                city:
                  type: string
                address:
                  type: string
                phone:
                  type: string
                email:
                  type: string
                client_id:
                  type: string
                  format: uuid
      responses:
        '201':
          description: Showroom créé
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Showroom'

  /showrooms/{id}:
    patch:
      tags: [Showrooms]
      summary: Modifier un showroom
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Modifié
    delete:
      tags: [Showrooms]
      summary: Supprimer un showroom
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Supprimé

  # ─────────────────────── COMMERCIALS ─────────────────────────
  /commercials:
    get:
      tags: [Commercials]
      summary: Liste des commerciaux
      parameters:
        - name: showroom_id
          in: query
          schema:
            type: string
            format: uuid
        - name: is_active
          in: query
          schema:
            type: boolean
      responses:
        '200':
          description: Liste
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Commercial'
    post:
      tags: [Commercials]
      summary: Créer un commercial
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [full_name, showroom_id]
              properties:
                full_name:
                  type: string
                phone:
                  type: string
                email:
                  type: string
                showroom_id:
                  type: string
                  format: uuid
                user_id:
                  type: string
                  format: uuid
      responses:
        '201':
          description: Créé

  # ─────────────────── DISPATCHERS ───────────────────
  /dispatchers:
    get:
      tags: [Dispatchers]
      summary: Liste des dispatchers avec stats
      responses:
        '200':
          description: Liste
          content:
            application/json:
              schema:
                type: array
                items:
                  allOf:
                    - $ref: '#/components/schemas/DispatcherAssignment'
                    - type: object
                      properties:
                        profile:
                          $ref: '#/components/schemas/Profile'

  /dispatchers/{id}/status:
    patch:
      tags: [Dispatchers]
      summary: Changer le statut d'un dispatcher
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: string
                  enum: [available, busy, on_leave, absent]
                unavailable_from:
                  type: string
                  format: date
                unavailable_until:
                  type: string
                  format: date
                unavailability_reason:
                  type: string
      responses:
        '200':
          description: Statut mis à jour

  /dispatchers/{id}/redistribute:
    post:
      tags: [Dispatchers]
      summary: Redistribuer les leads d'un dispatcher
      description: Réassigne les leads en attente aux autres dispatchers disponibles (round-robin)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Nombre de leads redistribués
          content:
            application/json:
              schema:
                type: object
                properties:
                  redistributed_count:
                    type: integer

  /dispatchers/{from_id}/transfer/{to_id}:
    post:
      tags: [Dispatchers]
      summary: Transférer tout l'historique d'un dispatcher vers un autre
      parameters:
        - name: from_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: to_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Transfert effectué
          content:
            application/json:
              schema:
                type: object
                properties:
                  transferred_count:
                    type: integer

  # ─────────────────────── SURVEYS ─────────────────────────
  /surveys:
    get:
      tags: [Surveys]
      summary: Liste des sondages
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [draft, active, completed, archived]
        - name: client_id
          in: query
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Liste
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Survey'
    post:
      tags: [Surveys]
      summary: Créer un sondage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                description:
                  type: string
                delivery_method:
                  type: string
                client_id:
                  type: string
                  format: uuid
                target_audience:
                  type: string
                target_count:
                  type: integer
                start_date:
                  type: string
                  format: date
                end_date:
                  type: string
                  format: date
                questions:
                  type: array
                  items:
                    type: object
                    required: [question_text, question_type]
                    properties:
                      question_text:
                        type: string
                      question_type:
                        type: string
                      options:
                        type: object
                      display_order:
                        type: integer
                      is_required:
                        type: boolean
      responses:
        '201':
          description: Sondage créé
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Survey'

  /surveys/{id}:
    get:
      tags: [Surveys]
      summary: Détail d'un sondage avec questions et stats
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Sondage détaillé
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Survey'
                  - type: object
                    properties:
                      questions:
                        type: array
                        items:
                          $ref: '#/components/schemas/SurveyQuestion'
    patch:
      tags: [Surveys]
      summary: Modifier un sondage
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Modifié
    delete:
      tags: [Surveys]
      summary: Supprimer un sondage
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Supprimé

  /surveys/{id}/responses:
    get:
      tags: [Surveys]
      summary: Réponses d'un sondage
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Réponses avec détails
          content:
            application/json:
              schema:
                type: array
                items:
                  allOf:
                    - $ref: '#/components/schemas/SurveyResponse'
                    - type: object
                      properties:
                        answers:
                          type: array
                          items:
                            $ref: '#/components/schemas/SurveyAnswer'
                        lead:
                          $ref: '#/components/schemas/Lead'
    post:
      tags: [Surveys]
      summary: Soumettre une réponse
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [answers]
              properties:
                lead_id:
                  type: string
                  format: uuid
                dispatcher_id:
                  type: string
                  format: uuid
                responded_via:
                  type: string
                  default: phone
                answers:
                  type: array
                  items:
                    type: object
                    required: [question_id]
                    properties:
                      question_id:
                        type: string
                        format: uuid
                      answer_text:
                        type: string
                      answer_option:
                        type: string
                      answer_rating:
                        type: integer
      responses:
        '201':
          description: Réponse enregistrée

  /surveys/{id}/validate:
    post:
      tags: [Surveys]
      summary: Valider les résultats d'un sondage
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Résultats validés

  # ──────────────── SURVEY AUDIENCES ────────────────
  /survey-audiences:
    get:
      tags: [Survey Audiences]
      summary: Liste des audiences
      responses:
        '200':
          description: Liste
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SurveyAudience'
    post:
      tags: [Survey Audiences]
      summary: Créer une audience
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                description:
                  type: string
                filters:
                  type: object
                lead_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
      responses:
        '201':
          description: Audience créée

  /survey-audiences/{id}:
    patch:
      tags: [Survey Audiences]
      summary: Modifier une audience
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Modifiée
    delete:
      tags: [Survey Audiences]
      summary: Supprimer une audience
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Supprimée

  # ─────────────────── NOTIFICATIONS ───────────────────
  /notifications:
    get:
      tags: [Notifications]
      summary: Notifications de l'utilisateur connecté
      parameters:
        - name: is_read
          in: query
          schema:
            type: boolean
        - name: type
          in: query
          schema:
            $ref: '#/components/schemas/NotificationType'
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
      responses:
        '200':
          description: Liste des notifications
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Notification'

  /notifications/{id}/read:
    patch:
      tags: [Notifications]
      summary: Marquer comme lue
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Marquée comme lue

  /notifications/read-all:
    patch:
      tags: [Notifications]
      summary: Marquer toutes les notifications comme lues
      responses:
        '200':
          description: Toutes marquées

  /notifications/stream:
    get:
      tags: [Notifications]
      summary: Flux temps réel de notifications (Server-Sent Events)
      description: |
        Ouvre une connexion SSE persistante pour recevoir les notifications en temps réel.
        Remplace le mécanisme Supabase Realtime par un flux SSE standard.
        
        ## Authentification
        Le JWT est passé via query param `token` (les headers custom ne sont pas supportés par EventSource).
        
        ## Event types
        
        | Event | Payload | Description |
        |-------|---------|-------------|
        | `notification:new` | `Notification` object | Nouvelle notification créée |
        | `notification:read` | `{ id: uuid }` | Notification marquée comme lue |
        | `notification:read_all` | `{}` | Toutes les notifications marquées comme lues |
        | `heartbeat` | `{ ts: ISO8601 }` | Keep-alive toutes les 30s |
        
        ## Exemple d'utilisation frontend
        ```javascript
        const es = new EventSource('/v1/notifications/stream?token=JWT_TOKEN');
        es.addEventListener('notification:new', (e) => {
          const notification = JSON.parse(e.data);
          // handle new notification
        });
        es.addEventListener('heartbeat', (e) => {
          // connection alive
        });
        ```
        
        ## Reconnexion
        Le client doit implémenter une reconnexion automatique avec backoff exponentiel.
        Le header `Last-Event-ID` peut être envoyé pour reprendre depuis le dernier event reçu.
      parameters:
        - name: token
          in: query
          required: true
          description: JWT Bearer token (EventSource ne supporte pas les headers custom)
          schema:
            type: string
        - name: Last-Event-ID
          in: header
          required: false
          description: ID du dernier event reçu pour la reconnexion
          schema:
            type: string
      responses:
        '200':
          description: Flux SSE ouvert
          content:
            text/event-stream:
              schema:
                type: string
                description: |
                  Flux SSE avec les events suivants :
                  - event: notification:new / data: Notification JSON
                  - event: notification:read / data: { id }
                  - event: notification:read_all / data: {}
                  - event: heartbeat / data: { ts }
        '401':
          description: Token invalide ou expiré

  # ──────────────── BUYER/SELLER MATCHES ────────────────
  /buyer-seller-matches:
    get:
      tags: [Campaign Matching]
      summary: Liste des matchs acheteur/vendeur
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, approved, rejected]
        - name: match_tier
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Matchs
          content:
            application/json:
              schema:
                type: array
                items:
                  allOf:
                    - $ref: '#/components/schemas/BuyerSellerMatch'
                    - type: object
                      properties:
                        buyer_lead:
                          $ref: '#/components/schemas/Lead'
                        seller_lead:
                          $ref: '#/components/schemas/Lead'

  /buyer-seller-matches/{id}/approve:
    post:
      tags: [Campaign Matching]
      summary: Approuver un match
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Match approuvé

  /buyer-seller-matches/{id}/reject:
    post:
      tags: [Campaign Matching]
      summary: Rejeter un match
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Match rejeté

  # ──────────────── MEDIA BUDGETS ────────────────
  /media-budgets:
    get:
      tags: [Media Budgets]
      summary: Liste des budgets média
      parameters:
        - name: campaign_id
          in: query
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Liste
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MediaBudget'
    post:
      tags: [Media Budgets]
      summary: Créer un budget média
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [campaign_id, period_start, period_end]
              properties:
                campaign_id:
                  type: string
                  format: uuid
                source:
                  type: string
                budget_allocated_mad:
                  type: number
                budget_spent_mad:
                  type: number
                period_start:
                  type: string
                  format: date
                period_end:
                  type: string
                  format: date
                notes:
                  type: string
      responses:
        '201':
          description: Budget créé

  /media-budgets/{id}:
    patch:
      tags: [Media Budgets]
      summary: Modifier un budget
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Modifié
    delete:
      tags: [Media Budgets]
      summary: Supprimer un budget
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Supprimé

  # ──────────────── REFERENCE DATA ────────────────
  /reference/cities:
    get:
      tags: [Reference Data]
      summary: Villes marocaines
      security: []
      parameters:
        - name: is_zone
          in: query
          schema:
            type: boolean
      responses:
        '200':
          description: Liste des villes
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MoroccanCity'

  /reference/vehicle-brands:
    get:
      tags: [Reference Data]
      summary: Marques de véhicules
      security: []
      responses:
        '200':
          description: Marques
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/VehicleBrand'

  /reference/vehicle-brands/{brand_id}/models:
    get:
      tags: [Reference Data]
      summary: Modèles d'une marque
      security: []
      parameters:
        - name: brand_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Modèles
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/VehicleModel'

  /reference/vehicle-categories:
    get:
      tags: [Reference Data]
      summary: Catégories de véhicules
      security: []
      responses:
        '200':
          description: Catégories
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/VehicleCategory'

  /reference/fuel-types:
    get:
      tags: [Reference Data]
      summary: Types de carburant
      security: []
      responses:
        '200':
          description: Types
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FuelType'

  /reference/products:
    get:
      tags: [Reference Data]
      summary: Catalogue de produits (Lead VN, Lead VO, Test Drive, etc.)
      responses:
        '200':
          description: Produits
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Product'

  # ──────────────── ANALYTICS ────────────────
  /analytics/dashboard:
    get:
      tags: [Analytics]
      summary: Statistiques du dashboard admin
      description: |
        Retourne les KPIs agrégés : revenue, leads, bookings, tendances, etc.
        Requiert le rôle admin_ops.
      parameters:
        - name: start_date
          in: query
          schema:
            type: string
            format: date
        - name: end_date
          in: query
          schema:
            type: string
            format: date
      responses:
        '200':
          description: Stats dashboard
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminDashboardStats'

  /analytics/cost-per-acquisition:
    get:
      tags: [Analytics]
      summary: Coût par acquisition par type de produit
      parameters:
        - name: start_date
          in: query
          schema:
            type: string
            format: date
        - name: end_date
          in: query
          schema:
            type: string
            format: date
      responses:
        '200':
          description: CPA par produit
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CostPerAcquisition'

  /analytics/dispatcher-conversions:
    get:
      tags: [Analytics]
      summary: Conversions par dispatcher
      parameters:
        - name: dispatcher_id
          in: query
          schema:
            type: string
            format: uuid
        - name: start_date
          in: query
          schema:
            type: string
            format: date
        - name: end_date
          in: query
          schema:
            type: string
            format: date
      responses:
        '200':
          description: Conversions
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    dispatcher_id:
                      type: string
                      format: uuid
                    conversion_type:
                      type: string
                    count:
                      type: integer

  # ──────────────── WHATSAPP ────────────────
  /whatsapp/send:
    post:
      tags: [WhatsApp]
      summary: Envoyer un message WhatsApp template
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [to, template_name]
              properties:
                to:
                  type: string
                  description: Numéro de téléphone (format international)
                template_name:
                  type: string
                template_params:
                  type: array
                  items:
                    type: string
                lead_id:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Message envoyé
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_id:
                    type: string

  # ──────────────── QUESTIONNAIRE TEMPLATES ────────────────
  /questionnaire-templates:
    get:
      tags: [Campaign Questionnaires]
      summary: Templates de questionnaires test drive
      responses:
        '200':
          description: Templates
          content:
            application/json:
              schema:
                type: array
                items:
                  allOf:
                    - $ref: '#/components/schemas/QuestionnaireTemplate'
                    - type: object
                      properties:
                        questions:
                          type: array
                          items:
                            type: object
                            properties:
                              id:
                                type: string
                                format: uuid
                              question_text:
                                type: string
                              question_type:
                                type: string
                              options:
                                type: object
                              display_order:
                                type: integer
                              is_required:
                                type: boolean

  /campaigns/{campaign_id}/questionnaire:
    get:
      tags: [Campaign Questionnaires]
      summary: Questionnaire d'une campagne
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Questionnaire avec questions
    post:
      tags: [Campaign Questionnaires]
      summary: Configurer le questionnaire d'une campagne
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                template_id:
                  type: string
                  format: uuid
                questions:
                  type: array
                  items:
                    type: object
                    required: [question_text, question_type]
                    properties:
                      question_text:
                        type: string
                      question_type:
                        type: string
                      options:
                        type: object
                      display_order:
                        type: integer
                      is_required:
                        type: boolean
      responses:
        '201':
          description: Questionnaire configuré

  /bookings/{booking_id}/feedback:
    get:
      tags: [Campaign Questionnaires]
      summary: Réponses feedback d'un booking
      parameters:
        - name: booking_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Réponses feedback
    post:
      tags: [Campaign Questionnaires]
      summary: Soumettre les réponses feedback d'un test drive
      parameters:
        - name: booking_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [answers]
              properties:
                answers:
                  type: array
                  items:
                    type: object
                    required: [question_id]
                    properties:
                      question_id:
                        type: string
                        format: uuid
                      answer_text:
                        type: string
                      answer_option:
                        type: string
                      answer_rating:
                        type: integer
                      answer_boolean:
                        type: boolean
      responses:
        '201':
          description: Feedback enregistré

  # ──────────────── AUDIT LOGS ────────────────
  /audit-logs:
    get:
      tags: [Audit]
      summary: Logs d'audit (admin only)
      parameters:
        - name: table_name
          in: query
          schema:
            type: string
        - name: action
          in: query
          schema:
            type: string
            enum: [INSERT, UPDATE, DELETE]
        - name: record_id
          in: query
          schema:
            type: string
            format: uuid
        - name: user_id
          in: query
          schema:
            type: string
            format: uuid
        - name: date_from
          in: query
          schema:
            type: string
            format: date
        - name: date_to
          in: query
          schema:
            type: string
            format: date
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Logs
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/AuditLog'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'

  # ──────────────── APP SETTINGS ────────────────
  /settings:
    get:
      tags: [Analytics]
      summary: Paramètres de l'application
      responses:
        '200':
          description: Settings
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      format: uuid
                    key:
                      type: string
                    value:
                      type: object
                    description:
                      type: string
                      nullable: true

  /settings/{key}:
    put:
      tags: [Analytics]
      summary: Mettre à jour un paramètre
      parameters:
        - name: key
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [value]
              properties:
                value:
                  type: object
      responses:
        '200':
          description: Paramètre mis à jour

  # ──────────────── DOMAIN-SPECIFIC FILE UPLOADS ────────────────

  /vehicles/{id}/photos:
    post:
      tags: [Vehicles]
      summary: Upload de photos pour un véhicule (extérieur/intérieur)
      description: |
        Upload une ou plusieurs photos pour un véhicule de la flotte.
        Les fichiers sont validés côté serveur (MIME type, taille max).
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file, photo_type]
              properties:
                file:
                  type: string
                  format: binary
                  description: "Formats acceptés : image/jpeg, image/png, image/webp. Max 10 MB."
                photo_type:
                  type: string
                  enum: [exterior, interior]
                  description: Type de photo (extérieur ou intérieur)
      responses:
        '200':
          description: Photo uploadée avec succès
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadResponse'
        '400':
          description: Format de fichier non supporté ou taille dépassée
        '404':
          description: Véhicule non trouvé

  /vehicles/{id}/documents:
    post:
      tags: [Vehicles]
      summary: Upload de documents pour un véhicule (carte grise, assurance, etc.)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: "Formats acceptés : application/pdf, image/jpeg, image/png. Max 15 MB."
                document_type:
                  type: string
                  description: "Type de document (ex: carte_grise, assurance, controle_technique)"
      responses:
        '200':
          description: Document uploadé
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadResponse'
        '400':
          description: Format non supporté ou taille dépassée
        '404':
          description: Véhicule non trouvé

  /promotional-vehicles/{id}/photos:
    post:
      tags: [Promotional Vehicles]
      summary: Upload de photos pour un véhicule promotionnel
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: "Formats acceptés : image/jpeg, image/png, image/webp. Max 10 MB."
      responses:
        '200':
          description: Photo uploadée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadResponse'
        '400':
          description: Format non supporté ou taille dépassée
        '404':
          description: Véhicule promotionnel non trouvé

  /experts/{id}/avatar:
    post:
      tags: [Experts]
      summary: Upload de l'avatar d'un expert
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: "Formats acceptés : image/jpeg, image/png. Max 5 MB."
      responses:
        '200':
          description: Avatar uploadé
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadResponse'
        '400':
          description: Format non supporté ou taille dépassée
        '404':
          description: Expert non trouvé

  /clients/{id}/logo:
    post:
      tags: [Clients]
      summary: Upload du logo d'un client
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: "Formats acceptés : image/jpeg, image/png, image/svg+xml. Max 5 MB."
      responses:
        '200':
          description: Logo uploadé
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadResponse'
        '400':
          description: Format non supporté ou taille dépassée
        '404':
          description: Client non trouvé

  /bookings/{id}/inspection-photos:
    post:
      tags: [Bookings]
      summary: Upload de photos d'inspection lors d'une mission
      description: |
        Permet à un expert de télécharger les photos prises pendant l'inspection
        du véhicule lors d'un test drive.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: "Formats acceptés : image/jpeg, image/png. Max 10 MB."
      responses:
        '200':
          description: Photo d'inspection uploadée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadResponse'
        '400':
          description: Format non supporté ou taille dépassée
        '404':
          description: Booking non trouvé
