products:
  post:
    tags: [Provider Products]
    summary: Add product (create)
    operationId: createProviderProduct
    security:
      - SecretKeyAuth: []
        ProviderBearerAuth: []
    x-figma:
      mobile: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-24058&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1"
      mobileNodeId: "9967:24058"
      web: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10768-137676&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038"
      webNodeId: "10768:137676"
      variantMobile: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23682&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1"
      variantMobileNodeId: "9967:23682"
      variantWeb: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10767-134557&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038"
      variantWebNodeId: "10767:134557"
    description: |
      ## Purpose
      **Add Product** — `POST /api/products` only.
      This is create, not edit. Editing uses a separate endpoint:
      `PATCH /api/products?productId=<id>`.

      Create a product for the provider marketplace catalog (simple or variant).
      Used by Mobile and Web product-builder screens after taxonomy, pricing,
      attributes (when variant), and media are collected.

      <div class="figma-links"><span>Design:</span><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-24058&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1" target="_blank" rel="noopener">Mobile Simple</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23682&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1" target="_blank" rel="noopener">Mobile Variant</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10768-137676&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038" target="_blank" rel="noopener">Web Simple</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10767-134557&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038" target="_blank" rel="noopener">Web Variant</a></div>

      ## Auth
      Requires **SecretKeyAuth** and **ProviderBearerAuth**.
      Ownership comes from the JWT only. Client-supplied `provider` / `providerId`
      and other workflow fields are rejected.

      Headers:
      - `secretkey` — required (not `x-secret-key`)
      - `Authorization: Bearer <provider token>`
      - `lang` — `ar` (default) or `en`

      ## Transport
      `multipart/form-data` only.
      - Images (and optional video) are sent as **files**.
      - Optional video must be MP4/MOV and no larger than **20MB**.
        Mobile/Web clients must validate or compress it before starting upload.
      - Complex fields (`name`, `description`, `attributes`, `variants`) are sent
        as **JSON-encoded text** parts (Swagger field type = Text).
      - Do not replace this body with raw `application/json`.
      - Do not use nested multipart keys such as
        `variants[0][attributes][0][attributeId]`.
      - Required parts: `type`, `departmentId`, `subdepartmentId`, `name`,
        `description`, `condition`, `pricingMethod`, `images` (1–10).
      - Taxonomy IDs must be active, non-deleted, and parent-child consistent.
      - When `pricingMethod=ai`, `aiPricingRequestId` is required
        (`POST /pricing-request` → `data.id`).

      ## Product type rules
      `type` enum: `simple` | `variant`.

      1. **simple** — normal product without selectable variants.
         Use product-level `price` / `quantity` / discount.
         For simple products, omit `attributes` and `variants` entirely
         (empty leftovers are rejected).

      2. **variant** — product with selectable combinations (Size only, Color only,
         Size+Color, or any selected attributes supported by the catalogue).
         `attributes` and `variants` are required JSON strings.
         Omit top-level `price` / `quantity` / `discountType` / `discountValue`
         (each variant carries its own).

      | Field | `type=simple` | `type=variant` |
      |-------|---------------|----------------|
      | `price` / `quantity` | required | **omit** |
      | `discountType` / `discountValue` | optional (`none` \| `percentage` \| `fixed`) | **omit** |
      | `attributes` | **omit** | required JSON string (Attribute MongoId array) |
      | `variants` | **omit** | required JSON string (variant objects) |

      ## Attributes and variants mapping
      ### Correct attributeId / valueId usage
      Attribute values live in `AttributeValue` documents:

      ```json
      {
        "_id": "VALUE_ID",
        "attribute": "ATTRIBUTE_ID",
        "kind": "size"
      }
      ```

      In each variant pair send:
      - `attributeId` = parent Attribute `_id` (e.g. الحجم)
      - `valueId` = AttributeValue `_id` (e.g. XL)

      Do **not** use a value `_id` as `attributeId`.
      Do **not** attach a Color value under the Size attribute (or the reverse).

      ## Simple product payload
      Use `type=simple` when the product has one price and one stock quantity
      without selectable variants.

      Rules:
      - Do not send `attributes`.
      - Do not send `variants`.
      - Do not send empty strings for forbidden fields.
      - Do not check “Send empty value” in Swagger/Postman — empty leftovers are rejected.
      - Use product-level `price` / `quantity` / optional discount fields.

      Example form-data:
      - `type` = `simple`
      - `name` = `{"ar":"سماعة بلوتوث","en":"Bluetooth Headphones"}`
      - `description` = `{"ar":"سماعات بحالة ممتازة","en":"Headphones in excellent condition"}`
      - `departmentId` / `subdepartmentId` = active taxonomy MongoIds
      - `condition` = `new` \| `used`
      - `pricingMethod` = `manual`
      - `price` = `120`
      - `quantity` = `10`
      - `discountType` = `none`
      - `discountValue` = `0`
      - `images` = 1–10 files

      Important: **No `attributes`. No `variants`.**

      ## Variant product payload
      Use `type=variant` when the product has selectable options such as الحجم,
      اللون, or any attribute that changes stock/price.

      Rules:
      - `attributes` is required (multipart **Text** JSON string).
      - `variants` is required (multipart **Text** JSON string).
      - Each variant is **one** purchasable combination.
      - Each variant must include exactly one value for every selected top-level
        attribute.
      - **Wrong:** Size=S and Size=M inside the **same** variant.
      - **Right:** S as variant #1, M as variant #2.

      ### `attributes` (text / JSON string)
      Selected Attribute MongoIds, e.g. الحجم + اللون:

      ```json
      ["6a69c8eadef712b95ba86e82","6a69c8eadef712b95ba86e88"]
      ```

      ### `variants` (text / JSON string)
      Each object must include `attributes` (`attributeId` + `valueId`),
      `quantity`, `price`, `discountType`, `discountValue`.

      ### Mobile flow
      1. User chooses product type.
      2. If simple → send normal product fields only (omit attributes/variants).
      3. If variant → user selects attributes (e.g. الحجم, اللون).
      4. App sends selected attribute IDs in `attributes`.
      5. App builds purchasable combinations in `variants`
         (e.g. XL + أحمر, XL + أسود).
      6. Every combination has values, price, stock, and discount data.

      ## Examples
      **A — Simple:** Swagger example `simpleWithPercentage` (omit attributes/variants).

      **B — Size only (S and M are separate variants):** example `variantOneAttribute`.

      ```json
      {
        "attributes": ["6a69c8eadef712b95ba86e82"],
        "variants": [
          {
            "attributes": [
              {"attributeId":"6a69c8eadef712b95ba86e82","valueId":"6a69c8ebdef712b95ba86e90"}
            ],
            "quantity": 5,
            "price": 100,
            "discountType": "none",
            "discountValue": 0
          },
          {
            "attributes": [
              {"attributeId":"6a69c8eadef712b95ba86e82","valueId":"6a69c8ebdef712b95ba86e94"}
            ],
            "quantity": 8,
            "price": 110,
            "discountType": "none",
            "discountValue": 0
          }
        ]
      }
      ```

      **C — Size + Color:** example `variantSizeColor`
      (XL+Red, XL+Black as separate rows).

      Seeded sample IDs (from `npm run seed:product-attributes`):
      - Size attribute `6a69c8eadef712b95ba86e82` · S `6a69c8ebdef712b95ba86e90` · M `6a69c8ebdef712b95ba86e94` · XL `6a69c8ecdef712b95ba86e9c`
      - Color attribute `6a69c8eadef712b95ba86e88` · Red `6a69c8eddef712b95ba86eb0` · Black `6a69c8eddef712b95ba86ea8`

      ## Invalid examples (will fail validation)
      1. Same attribute twice in one variant (S + M together) — forbidden.
      2. Top-level `[Size, Color]` but variant only has Size — missing attribute.
      3. Top-level `[Size]` but variant includes Color — extra attribute.
      4. `valueId` that does not belong to `attributeId` — invalid relation.

      ## Common mistakes
      - Sending `attributes` or `variants` for `type=simple`.
      - Sending empty value for `attributes` or `variants` (including on `type=simple`).
      - Sending invalid JSON.
      - Sending nested multipart keys like `variants[0][attributes][0][attributeId]`.
      - Sending `valueId` without its related `attributeId`.
      - Sending an `attributeId` in variants that is not in top-level `attributes`.
      - Putting two values for the same attribute inside one variant (e.g. Size S and Size M together).
      - Using unsupported `discountType` (allowed: `none`, `percentage`, `fixed`).
      - Sending non-numeric quantity / price / discountValue.

      ## Response envelope
      Create returns a **message-only** success envelope (no `data` field):

      ```json
      { "key": "success", "message": "تم إنشاء المنتج بنجاح", "status": 200 }
      ```

      Clients must branch on `key`, not only HTTP status.
      Newly created products return `moderationStatus: wait` (pending administration review)
      and emit the `product_submitted_for_review` administration-review event.
      The administration review notification is created only after Product
      persistence succeeds. There is no transaction/outbox for that side-effect:
      a request that fails validation, media/taxonomy/AI checks, or Product
      persistence never creates a Product-review notification.
      The Notification record and administration counter are persisted before
      success. External push delivery is deferred and is not part of the HTTP
      response latency or success guarantee.

      ## Error notes
      Possible causes of product payload errors:
      - `products.invalidProductPayload` — simple/variant field mix, or invalid matrix
      - invalid JSON in `attributes` or `variants`
      - forbidden `attributes`/`variants` for simple, or missing for variant
      - invalid MongoId / taxonomy / attribute–value relation
      - unsupported `discountType` or invalid discount math
      - missing/invalid images (MIME, size, count) when required
      - invalid / non-owned `aiPricingRequestId`
      - protected ownership/workflow fields in the body
      - missing/invalid secret key or non-provider token (`unauthorized`)

      ## Compatibility notes
      - Server-controlled: provider, workflow, moderation, deletion fields.
      - API values `variant` and `percentage`; legacy `multi_attribute` / `ratio`
        remain readable and are normalized in responses.
      - Media uses direct multipart upload; **no media ownership registry**.
        New files are stored under `products/{productId}/`, while MongoDB stores only each final filename (basename).
        Previously stored full product-scoped references remain readable, and
        provider-scoped filenames remain readable without migration.
      - `aiSuggestedPrice` is server-copied; client AI prices are rejected.
        PricingRequest currently has no expiry or product-fingerprint fields, so
        those integrity checks are not enforceable. The upstream AI engine remains a stub.
      - Full field tables: `docs/PRODUCTS_API_CONTRACT.md`.
    parameters:
      - $ref: '../openapi.yaml#/components/parameters/LangHeader'
    requestBody:
      required: true
      content:
        multipart/form-data:
          schema:
            $ref: '../openapi.yaml#/components/schemas/ProviderProductCreateRequest'
          examples:
            simpleWithPercentage:
              summary: Simple product — omit attributes/variants
              value:
                type: simple
                departmentId: "6a65c62109d85c1dbe0e86e2"
                subdepartmentId: "6a65c81b09d85c1dbe0e8732"
                name: '{"ar":"سماعة بلوتوث","en":"Bluetooth Headphones"}'
                description: '{"ar":"سماعات بحالة ممتازة","en":"Headphones in excellent condition"}'
                condition: used
                pricingMethod: manual
                quantity: 10
                price: 120
                discountType: none
                discountValue: 0
                images: ["<binary image>"]
            simpleWithFixed:
              summary: Simple product with AI price — omit attributes/variants
              value:
                type: simple
                departmentId: "6a65c62109d85c1dbe0e86e2"
                subdepartmentId: "6a65c81b09d85c1dbe0e8732"
                name: '{"ar":"كاميرا احترافية","en":"Professional camera"}'
                description: '{"ar":"كاميرا جديدة","en":"New camera"}'
                condition: new
                pricingMethod: ai
                aiPricingRequestId: "665f1c2a9b4e1d0012ab3403"
                quantity: 1
                price: 1500
                discountType: fixed
                discountValue: 100
                images: ["<binary image>"]
            variantOneAttribute:
              summary: Variant — Size only (S and M are separate variants)
              value:
                type: variant
                departmentId: "6a65c62109d85c1dbe0e86e2"
                subdepartmentId: "6a65c81b09d85c1dbe0e8732"
                name: '{"ar":"قميص زراعي","en":"Field shirt"}'
                description: '{"ar":"متوفر بمقاسات","en":"Available in sizes"}'
                condition: new
                pricingMethod: manual
                attributes: '["6a69c8eadef712b95ba86e82"]'
                variants: '[{"attributes":[{"attributeId":"6a69c8eadef712b95ba86e82","valueId":"6a69c8ebdef712b95ba86e90"}],"quantity":5,"price":100,"discountType":"none","discountValue":0},{"attributes":[{"attributeId":"6a69c8eadef712b95ba86e82","valueId":"6a69c8ebdef712b95ba86e94"}],"quantity":8,"price":110,"discountType":"none","discountValue":0}]'
                images: ["<binary image>"]
            variantSizeColor:
              summary: Variant — Size + Color (XL+Red, XL+Black)
              value:
                type: variant
                departmentId: "6a65c62109d85c1dbe0e86e2"
                subdepartmentId: "6a65c81b09d85c1dbe0e8732"
                name: '{"ar":"قميص زراعي","en":"Field shirt"}'
                description: '{"ar":"متوفر بألوان ومقاسات","en":"Available in colors and sizes"}'
                condition: new
                pricingMethod: manual
                attributes: '["6a69c8eadef712b95ba86e82","6a69c8eadef712b95ba86e88"]'
                variants: '[{"attributes":[{"attributeId":"6a69c8eadef712b95ba86e82","valueId":"6a69c8ecdef712b95ba86e9c"},{"attributeId":"6a69c8eadef712b95ba86e88","valueId":"6a69c8eddef712b95ba86eb0"}],"quantity":5,"price":120,"discountType":"none","discountValue":0},{"attributes":[{"attributeId":"6a69c8eadef712b95ba86e82","valueId":"6a69c8ecdef712b95ba86e9c"},{"attributeId":"6a69c8eadef712b95ba86e88","valueId":"6a69c8eddef712b95ba86ea8"}],"quantity":3,"price":125,"discountType":"none","discountValue":0}]'
                images: ["<binary image>"]
                video: "<optional MP4/MOV binary>"
    responses:
      '200':
        description: "Product created. Message-only response with no `data` field; clients branch on `key: success`."
        content:
          application/json:
            schema:
              $ref: '../openapi.yaml#/components/schemas/ProviderProductCreateResponse'
            examples:
              created:
                value: { key: success, message: "تم إنشاء المنتج بنجاح", status: 200 }
      '400':
        description: Validation or product business-rule failure.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            examples:
              invalidTaxonomy:
                value: { key: fail, message: "القسم الفرعي النشط لا يتبع القسم النشط المحدد", status: 400 }
              invalidProductPayload:
                value: { key: fail, message: "حقول المنتج غير متوافقة مع نوع المنتج أو طريقة التسعير المحددة", status: 400 }
              invalidAiReference:
                value: { key: fail, message: "مرجع التسعير بالذكاء الاصطناعي غير صالح", status: 400 }
              protectedField:
                value: { key: fail, message: "لا يمكن للعميل إرسال حقول الملكية أو سير العمل", status: 400 }
      '419':
        description: Missing/invalid authentication or a non-provider token.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            example: { key: unauthorized, message: "غير مسموح لك بتنفيذ هذا الإجراء", status: 419 }
      '500':
        $ref: '../openapi.yaml#/components/responses/ServerError'

  get:
    tags: [Shared Products]
    summary: List products for the authenticated client or provider
    operationId: getSharedProducts
    security:
      - SecretKeyAuth: []
        ProviderBearerAuth: []
      - SecretKeyAuth: []
        ClientBearerAuth: []
    x-figma:
      mobile: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23316&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1"
      mobileNodeId: "9967:23316"
      web: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10766-121182&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038"
      webNodeId: "10766:121182"
    description: |
      ## Purpose
      Returns a paginated product list for an authenticated Client or Provider.
      The response DTO and database scope are selected from the bearer role.

      <div class="figma-links"><span>Design:</span><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23316&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1" target="_blank" rel="noopener">Mobile List</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10766-121182&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038" target="_blank" rel="noopener">Web List</a></div>

      ## Actor
      Client **or** Provider.

      ## Authorization
      - SecretKeyAuth + ProviderBearerAuth, **or**
      - SecretKeyAuth + ClientBearerAuth.

      ## Headers
      - `secretkey`
      - `Authorization: Bearer <client token | provider token>`
      - `lang` — `ar` | `en`

      ## Request
      Query filters only. Pagination uses `page` + `limit` (response `paginate.perPage`).

      ### Provider
      The existing owner inventory behavior and `ProviderProductCard` DTO are
      unchanged. Hidden products remain visible to their owner; soft-deleted
      products are excluded. The `visibility` filter applies to this branch.

      ### Client
      Returns accepted, visible, non-deleted products belonging to active providers.
      `visibility` never exposes hidden products in this branch. Each
      `ClientSharedProductCard` returns top-level `isFav` and:
      `actions.chatButton`, `actions.chatId`, `actions.detailsButton`,
      `actions.aiPricingButton`, and `actions.isFav`.
      `chatId` is empty when no direct chat exists; reading the list never creates one.

      Taxonomy filters `departmentId` and `subdepartmentId` are optional and can be
      combined; both must be valid ObjectIds and are applied on top of the owner scope.

      ## Success response
      `{ key, message, status, data, paginate }` —
      `paginate` is a **sibling** of `data` (not nested). Branch on `key`.
      Branch on bearer role for `ClientSharedProductCard[]` vs
      `ProviderProductCard[]`.

      `type=variant` includes both new `variant` and legacy `multi_attribute`
      records.

      ## Common failures
      - Invalid filters / sort
      - Missing/invalid secret key or unsupported token role

      ## QA notes
      - Money display uses `price` + `priceText` (no `finalPrice` / `createdAt` on list cards).
      - Visibility is `visibility` boolean derived from `isHidden`.
      - See `docs/PRODUCTS_API_CONTRACT.md`.
    parameters:
      - $ref: '../openapi.yaml#/components/parameters/LangHeader'
      - name: search
        in: query
        schema: { type: string, maxLength: 100 }
        description: Case-insensitive search across product name fields.
      - name: type
        in: query
        schema: { type: string, enum: [simple, variant] }
        description: Filter by product type. `variant` also matches legacy `multi_attribute`.
      - name: condition
        in: query
        schema: { type: string, enum: [new, used] }
        description: Product condition filter.
      - name: visibility
        in: query
        schema: { type: boolean }
        description: When set, filters by owner visibility (`true` = not hidden).
      - name: departmentId
        in: query
        schema: { type: string, format: objectid }
        description: Filter by main department id (from `GET /departments-list`).
      - name: subdepartmentId
        in: query
        schema: { type: string, format: objectid }
        description: Filter by sub department id (from `GET /subdepartments`). Combine with `departmentId` to narrow the list.
      - name: page
        in: query
        schema: { type: integer, minimum: 1, default: 1 }
        description: List page number.
      - name: limit
        in: query
        schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
        description: Page size; mirrored as `paginate.perPage` in the list response.
      - name: sort
        in: query
        schema:
          type: string
          enum: [createdAt, -createdAt, price, -price, quantity, -quantity, name, -name]
        description: Sort token. Prefix `-` for descending.
    responses:
      '200':
        description: Paginated product list. Branch on `key`.
        content:
          application/json:
            schema:
              $ref: '../openapi.yaml#/components/schemas/SharedProductListResponse'
      '400':
        description: Invalid filters or sort.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            examples:
              invalidSort:
                value: { key: fail, message: "حقل الترتيب غير مدعوم", status: 400 }
      '419':
        description: Missing/invalid authentication or a token other than Client/Provider.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            example: { key: unauthorized, message: "غير مسموح لك بتنفيذ هذا الإجراء", status: 419 }
      '500':
        $ref: '../openapi.yaml#/components/responses/ServerError'

  patch:
    tags: [Provider Products]
    summary: Edit product (update)
    operationId: updateProviderProduct
    security:
      - SecretKeyAuth: []
        ProviderBearerAuth: []
    x-figma:
      mobile: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-24294&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1"
      mobileNodeId: "9967:24294"
      web: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10767-134557&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038"
      webNodeId: "10767:134557"
      variantMobile: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23682&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1"
      variantMobileNodeId: "9967:23682"
    description: |
      ## Purpose
      **Edit Product** — `PATCH /api/products?productId=<MongoObjectId>`.
      This is **not** Add Product. Do **not** call `POST /api/products` to edit.

      Why query `productId` (not path `/api/products/{id}`)?
      That is the real Express route in `ProductRoute.registerRoutes()` —
      `this.router.patch('/products', …)`. Swagger matches the live API.

      Update an owned provider product (simple or variant) using the same
      simple/variant rules as Add Product. The final merged product must remain
      valid for the effective type.

      <div class="figma-links"><span>Design:</span><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-24294&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1" target="_blank" rel="noopener">Mobile Edit</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23682&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1" target="_blank" rel="noopener">Mobile Variant</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10767-134557&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038" target="_blank" rel="noopener">Web</a></div>

      ## Auth
      Requires **SecretKeyAuth** and **ProviderBearerAuth**. Ownership from JWT only.

      Headers: `secretkey`, `Authorization: Bearer <provider token>`, `lang`.

      ## Transport
      Required query parameter:
      - `productId` — MongoDB ObjectId of the product to update (24-hex).
        Required. Ownership is derived from the provider bearer token.

      Body: `multipart/form-data` allowlist (same fields as create; all optional).
      - Images/video are **files**.
      - `name` / `description` / `attributes` / `variants` are **JSON text** parts.
      - Do **not** use nested multipart keys such as
        `variants[0][attributes][0][attributeId]`.
      - Do **not** check “Send empty value” for attributes/variants.

      ## Product type rules
      Same as Add Product (`simple` | `variant`):

      - Effective `type=simple`: omit `attributes` and `variants`; use product-level
        `price` / `quantity` / optional `discountType` (`none` \| `percentage` \| `fixed`)
        and `discountValue`.
      - Effective `type=variant`: send `attributes` and `variants` as JSON strings;
        omit top-level price/quantity/discount.
      - Changing type requires a complete type-specific payload so the merged
        product stays valid.
      - When updating variants, send the **full** desired `variants` array
        (backend replaces the matrix; there is no partial variant-row patch API).

      ## Simple product payload
      Example form-data (with query `productId`):
      - `type` = `simple`
      - `name` = `{"ar":"سماعة بلوتوث محدثة","en":"Updated Bluetooth Headphones"}`
      - `price` / `quantity` / `discountType=none` / `discountValue=0`
      - **Omit** `attributes` and `variants`.

      ## Variant product payload
      `attributes` and `variants` required as multipart **Text** JSON strings when
      the effective type is `variant`.

      Critical rules (same as create):
      - Each variant = **one** purchasable combination.
      - Exactly one value per selected top-level attribute.
      - Same `attributeId` must **not** repeat inside one variant.
      - `attributeId` = parent Attribute `_id`; `valueId` = AttributeValue `_id`
        that belongs to that attribute.

      ## Attributes and variants mapping
      Same contract as create. Seeded sample IDs:
      - Size `6a69c8eadef712b95ba86e82` · S `6a69c8ebdef712b95ba86e90` · M `6a69c8ebdef712b95ba86e94` · XL `6a69c8ecdef712b95ba86e9c`
      - Color `6a69c8eadef712b95ba86e88` · Red `6a69c8eddef712b95ba86eb0` · Black `6a69c8eddef712b95ba86ea8`

      ### Mobile flow
      1. Choose type.
      2. Simple → normal fields only (omit attributes/variants).
      3. Variant → select attributes → send IDs in `attributes`.
      4. Build combinations → send full JSON in `variants`.

      ## Examples
      - `simpleOmitAttributesVariants` — simple edit; attributes/variants omitted.
      - `variantSizeColor` — Size + Color (XL+Red, XL+Black) as JSON strings.

      ## Invalid examples
      Same as create: duplicate attributeId in one variant; missing/extra attribute
      vs top-level list; `valueId` not owned by `attributeId`.

      ## Common mistakes
      - Calling `POST /api/products` instead of this PATCH edit endpoint.
      - Missing / invalid query `productId`.
      - Sending `attributes` / `variants` for simple products.
      - Empty values for forbidden fields / “Send empty value”.
      - Nested multipart keys / invalid JSON.
      - `valueId` without matching `attributeId`, or attributeId missing from top-level list.
      - Unsupported `discountType` or non-numeric quantity/price/discountValue.

      ## Response envelope
      HTTP 200 with `{ key, message, status, data }` where `data` is the product
      details DTO (not nested under `data.product`). Branch on `key`.

      Example:
      ```json
      {
        "key": "success",
        "message": "تم تحديث المنتج بنجاح",
        "status": 200,
        "data": {}
      }
      ```

      After an approved-content edit, the DTO may expose `moderationStatus: wait`.
      An effective content edit on an `accept` product returns it to `wait`.
      After a successful non-noop provider edit, exactly one admin notification is
      created with `eventType: product_updated` (deferred push). The provider also
      receives a return-to-review notice when moderation transitions from `accept`
      to `wait`. (Previously admin edits used `approved_product_content_updated`;
      provider edits now use the single `product_updated` admin event to avoid
      duplicates.) No admin notification is sent on validation failure or no-op.

      ## Error notes
      - Invalid / missing / deleted / unowned product
      - Partial update would leave an invalid product state
      - Invalid attribute matrix / JSON / media / `removedImages` / `imageMode`
      - Protected fields in body
      - Unauthorized / invalid secret
      - Example fail: `{ "key": "fail", "message": "حقول المنتج غير متوافقة مع نوع المنتج أو طريقة التسعير المحددة", "status": 400 }`

      ## Compatibility notes
      Images behavior (real backend):
      - Default `imageMode=append`: keep existing images; append uploaded files.
      - `removedImages` (JSON text array): delete only listed owned images.
      - `imageMode=replace` + new uploads: gallery becomes the new files only.
      - Omitting images/`removedImages`/`imageMode`: existing images unchanged.
      - New `video` replaces the previous video.
      - `removeVideo=true` clears video only (without requiring a new video file).
      Pricing: `pricing.aiReference.priceRangeMin` / `priceRangeMax` /
      `suggestedPrice` come from the saved PricingRequest document when populated.
      Files store under `products/{productId}/`; MongoDB stores filenames. See
      `docs/PRODUCTS_API_CONTRACT.md`.
    parameters:
      - $ref: '../openapi.yaml#/components/parameters/LangHeader'
      - $ref: '../openapi.yaml#/components/parameters/ProductIdQuery'
    requestBody:
      required: true
      content:
        multipart/form-data:
          schema:
            $ref: '../openapi.yaml#/components/schemas/ProviderProductUpdateRequest'
          examples:
            simpleOmitAttributesVariants:
              summary: Simple update — omit attributes/variants
              value:
                type: simple
                name: '{"ar":"سماعة بلوتوث محدثة","en":"Updated Bluetooth Headphones"}'
                quantity: 9
                price: 130
                discountType: none
                discountValue: 0
            variantSizeColor:
              summary: Variant update — Size + Color JSON strings
              value:
                type: variant
                attributes: '["6a69c8eadef712b95ba86e82","6a69c8eadef712b95ba86e88"]'
                variants: '[{"attributes":[{"attributeId":"6a69c8eadef712b95ba86e82","valueId":"6a69c8ecdef712b95ba86e9c"},{"attributeId":"6a69c8eadef712b95ba86e88","valueId":"6a69c8eddef712b95ba86eb0"}],"quantity":5,"price":120,"discountType":"none","discountValue":0},{"attributes":[{"attributeId":"6a69c8eadef712b95ba86e82","valueId":"6a69c8ecdef712b95ba86e9c"},{"attributeId":"6a69c8eadef712b95ba86e88","valueId":"6a69c8eddef712b95ba86ea8"}],"quantity":3,"price":125,"discountType":"none","discountValue":0}]'
    responses:
      '200':
        description: "Updated product details. Branch on `key: success`."
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ProviderProductDetailsResponse' }
            examples:
              updated:
                value:
                  key: success
                  message: "تم تحديث المنتج بنجاح"
                  status: 200
      '400':
        description: Invalid, missing, deleted, unowned, or domain-inconsistent product update.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            examples:
              invalidUpdate:
                value: { key: fail, message: "التعديل الجزئي المطلوب سيترك المنتج في حالة غير صالحة", status: 400 }
              notOwnedOrMissing:
                value: { key: fail, message: "المنتج غير موجود", status: 400 }
      '419':
        description: Missing/invalid authentication or a non-provider token.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            example: { key: unauthorized, message: "غير مسموح لك بتنفيذ هذا الإجراء", status: 419 }
      '500':
        $ref: '../openapi.yaml#/components/responses/ServerError'

  delete:
    tags: [Provider Products]
    summary: Soft-delete an owned product
    operationId: deleteProviderProduct
    security:
      - SecretKeyAuth: []
        ProviderBearerAuth: []
    x-figma:
      mobile: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23330&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1"
      mobileNodeId: "9967:23330"
      web: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10767-133155&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038"
      webNodeId: "10767:133155"
      listMobile: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23316&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1"
      listMobileNodeId: "9967:23316"
      listWeb: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10766-121182&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038"
      listWebNodeId: "10766:121182"
    description: |
      ## Purpose
      Soft-deletes an owned product from list or details screens. Sets deleted
      workflow status and records `deletedAt` and provider-derived `deletedBy`.
      Product, variants, media files, and historical references are preserved.

      <div class="figma-links"><span>Design:</span><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23330&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1" target="_blank" rel="noopener">Mobile Details</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23316&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1" target="_blank" rel="noopener">Mobile List</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10767-133155&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038" target="_blank" rel="noopener">Web Details</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10766-121182&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038" target="_blank" rel="noopener">Web List</a></div>

      ## Actor
      Provider.

      ## Authorization
      SecretKeyAuth + ProviderBearerAuth.

      ## Headers
      - `secretkey`
      - `Authorization: Bearer <provider token>`
      - `lang`

      ## Request
      Query `productId` only.

      ## Success response
      HTTP 200, `key: success`, `data: { productId, deleted: true }`.

      ## Common failures
      - Invalid/missing product, wrong owner, already deleted
      - An active Order still requiring payment, fulfillment, delivery, or cancellation handling
      - A scheduled/live Auction, or a finished bidding cycle with a winner that is
        still awaiting payment or delivery
      - Unauthorized / invalid secret

      Completed/cancelled Order history, cancelled Auctions, and delivered Auction
      history remain readable and do not block deletion. A repeated delete follows
      the existing convention and returns the same masked not-found failure.

      ## QA notes
      - Same endpoint for list and details delete actions.
      - Soft delete only; media is not erased from disk by this operation.
      - Does not change moderation; the existing admin deletion notification remains unchanged.
      - See `docs/PRODUCTS_API_CONTRACT.md`.
    parameters:
      - $ref: '../openapi.yaml#/components/parameters/LangHeader'
      - $ref: '../openapi.yaml#/components/parameters/ProductIdQuery'
    responses:
      '200':
        description: "Product soft-deleted. Branch on `key: success`."
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ProviderProductDeleteResponse' }
      '400':
        description: Invalid/missing product, wrong owner, already deleted, or an active operational relation.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            examples:
              notOwnedOrMissing:
                value: { key: fail, message: "المنتج غير موجود", status: 400 }
              activeOrder:
                value: { key: fail, message: "لا يمكن حذف المنتج أثناء ارتباطه بطلب نشط", status: 400 }
              activeAuction:
                value: { key: fail, message: "لا يمكن حذف المنتج أثناء ارتباطه بمزاد نشط أو مزاد ينتظر السداد أو التسليم", status: 400 }
      '419':
        description: Missing/invalid authentication or a non-provider token.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            example: { key: unauthorized, message: "غير مسموح لك بتنفيذ هذا الإجراء", status: 419 }
      '500':
        $ref: '../openapi.yaml#/components/responses/ServerError'

products-similar:
  get:
    tags: [Client Products]
    summary: List all similar products with pagination
    operationId: getSimilarProducts
    x-figma:
      mobile: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9509-8480&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1"
      mobileNodeId: "9509:8480"
      web: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10242-54434&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038"
      webNodeId: "10242:54434"
    security:
      - SecretKeyAuth: []
      - SecretKeyAuth: []
        ClientBearerAuth: []
    description: |
      Returns the full paginated similar-product catalogue used after the
      Product Details "show more" action.

      <div class="figma-links"><span>Design:</span><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9509-8480&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1" target="_blank" rel="noopener">App</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10242-54434&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038" target="_blank" rel="noopener">Web</a></div>

      Similarity and stable ordering:
      1. accepted, active, visible, non-deleted products in the same
         `subDepartment`;
      2. accepted, active, visible, non-deleted products in the same
         `department` as fallback;
      3. within each priority, featured products first, then newest first,
         then `_id` descending as the stable tie-breaker.

      The current product and products owned by blocked/deleted providers are
      excluded. One combined aggregation applies pagination after priority
      ordering, so department fallback never jumps ahead of same-subdepartment
      results and duplicates are not introduced.

      `secretkey` is required. A Client bearer token is optional and only
      personalizes `isFavorite`; authenticated Provider tokens are rejected.
      Guest items always return `isFavorite: false`.

      `page` defaults to `1`. `limit` defaults to `10` and cannot exceed `30`.
      `paginate.total` counts every valid similar product after filtering.

      If the source product has neither department nor subDepartment, the
      endpoint returns an empty `items` array with a zero-total paginate block.
      It never returns unrelated random products.

      This endpoint is independent from Product Details. Product Details keeps
      its existing preview of at most 4 items, `hasMore`, and no paginate block.
    parameters:
      - $ref: '../openapi.yaml#/components/parameters/LangHeader'
      - name: productId
        in: query
        required: true
        description: Active, accepted, visible product MongoDB ObjectId.
        schema:
          type: string
          pattern: '^[a-fA-F0-9]{24}$'
          example: "665f1c2a9b4e1d0012ab34d0"
      - name: page
        in: query
        required: false
        description: Combined-result page number.
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        required: false
        description: Items per page, maximum 30.
        schema:
          type: integer
          minimum: 1
          maximum: 30
          default: 10
    responses:
      '200':
        description: Similar products loaded with nested pagination metadata.
        content:
          application/json:
            schema:
              $ref: '../openapi.yaml#/components/schemas/SimilarProductsResponse'
            example:
              key: success
              message: "تم جلب الإعلانات المشابهة بنجاح"
              status: 200
              data:
                items:
                  - id: "665f1c2a9b4e1d0012ab34d1"
                    name: "سماعة سوني"
                    image: "https://example.com/assets/uploads/products/665f1c2a9b4e1d0012ab34d1/image.png"
                    total: 1400
                    currency: SAR
                    provider:
                      id: "665f1c2a9b4e1d0012ab34ce"
                      name: "أحمد محمد"
                      location:
                        title: "الرياض"
                        description: "الرياض، المملكة العربية السعودية"
                        lat: 24.7136
                        lng: 46.6753
                    isFavorite: false
                    isFeatured: true
                    actions:
                      details: true
                      chat: true
                      hasAiPricing: true
                      aiPricing:
                        suggestedPrice: 1350
                        priceRangeMin: 1250
                        priceRangeMax: 1450
                paginate:
                  currentPage: 1
                  lastPage: 3
                  perPage: 10
                  total: 23
      '400':
        description: Invalid productId/page/limit/request shape, or source product unavailable.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            examples:
              invalidId:
                value: { key: fail, message: "معرف المنتج غير صالح", status: 400 }
              productNotFound:
                value: { key: fail, message: "المنتج غير موجود", status: 400 }
              invalidPagination:
                value: { key: fail, message: "بيانات طلب الإعلانات المشابهة غير صالحة", status: 400 }
      '419':
        description: Authenticated Provider tokens cannot access this guest/client endpoint.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            example: { key: unauthorized, message: "غير مسموح لك بتنفيذ هذا الإجراء", status: 419 }
      '500':
        $ref: '../openapi.yaml#/components/responses/ServerError'

products-details:
  get:
    tags: [Shared Products]
    summary: Get product details (client or provider)
    operationId: getSharedProductDetails
    security:
      - SecretKeyAuth: []
        ProviderBearerAuth: []
      - SecretKeyAuth: []
        ClientBearerAuth: []
    x-figma:
      mobile: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23330&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1"
      mobileNodeId: "9967:23330"
      web: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10767-133155&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038"
      webNodeId: "10767:133155"
    description: |
      ## Purpose
      Returns product details for an authenticated **Client** or **Provider**.
      Switch the bearer token the same way as other dual-token endpoints.

      <div class="figma-links"><span>Design:</span><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23330&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1" target="_blank" rel="noopener">Mobile Details</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10767-133155&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038" target="_blank" rel="noopener">Web Details</a></div>

      ## Actor
      Client **or** Provider (OR security schemes below).

      ## Authorization
      - `SecretKeyAuth` + `ProviderBearerAuth`, **or**
      - `SecretKeyAuth` + `ClientBearerAuth`

      ## Headers
      - `secretkey`
      - `Authorization: Bearer <client token | provider token>`
      - `lang` — `ar` | `en`

      ## Request
      Required query `productId` only.

      ## Role behavior
      ### Provider
      Owner-only admin details (`ProviderProductDetails`).
      Hidden and pending products remain readable by their owner.
      Soft-deleted products and another provider's identifiers are masked with the
      same not-found response.

      ### Client
      Public storefront details for an accepted, visible product.
      Response matches the client product-details contract and includes
      `similarAds` for the same product (same subDepartment first, then
      department fallback; max 4 items + `hasMore`; no nested `paginate`).
      It also returns top-level `isFav` plus
      `actions.{chatButton,chatId,detailsButton,aiPricingButton,isFav}`.
      `chatId` is the existing direct chat id or an empty string; this read
      never creates a chat.
      Hidden, pending, rejected, deleted, or non-accepted products return the
      same not-found mask.

      ## Success response
      `{ key, message, status, data }`. Branch on `key` and actor role.

      ### Variant read shape (provider details only)
      `attributes` is the **option catalogue** for this product (grouped attribute +
      values used by its variants only).

      `variants` is the list of **purchasable combinations**. Each row is compact:
      - `label` — human-readable summary (e.g. `XS / أسود`)
      - `selectedValues` — map `{ [attributeId]: valueId }`
      - `quantity` / `price` / `discountType` / `discountValue` / `isAvailable`

      Full attribute/value display data lives once under top-level `attributes`.
      Variants do **not** repeat nested `{ attribute, value }` objects.

      Product Add (`POST /products`) and Edit (`PATCH /products`) request bodies
      are unchanged — they still send `attributes` / `variants` as JSON strings.

      Example (variant excerpt):
      ```json
      {
        "attributes": [
          {
            "id": "6a69c8eadef712b95ba86e82",
            "name": "الحجم",
            "kind": "size",
            "values": [
              { "id": "6a69c8eadef712b95ba86e8c", "name": "XS", "kind": "size", "colorCode": null }
            ]
          },
          {
            "id": "6a69c8eadef712b95ba86e88",
            "name": "اللون",
            "kind": "color",
            "values": [
              { "id": "6a69c8eddef712b95ba86ea8", "name": "أسود", "kind": "color", "colorCode": "#111827" }
            ]
          }
        ],
        "variants": [
          {
            "id": "6a69c8eadef712b95ba86e82:6a69c8eadef712b95ba86e8c|6a69c8eadef712b95ba86e88:6a69c8eddef712b95ba86ea8",
            "label": "XS / أسود",
            "selectedValues": {
              "6a69c8eadef712b95ba86e82": "6a69c8eadef712b95ba86e8c",
              "6a69c8eadef712b95ba86e88": "6a69c8eddef712b95ba86ea8"
            },
            "quantity": 5,
            "price": 120,
            "discountType": "none",
            "discountValue": 0,
            "isAvailable": true
          }
        ]
      }
      ```

      Simple products return `attributes: []` and `variants: []` (no fake option data).

      ## Common failures
      - Invalid / missing `productId`
      - Product not found for this actor (provider ownership / client visibility)
      - Missing/invalid secret key or unauthorized token role

      ## QA notes
      - List remains `GET /products`; details are this dedicated path.
      - See `docs/PRODUCTS_API_CONTRACT.md`.
    parameters:
      - $ref: '../openapi.yaml#/components/parameters/LangHeader'
      - name: productId
        in: query
        required: true
        schema: { type: string, pattern: '^[a-fA-F0-9]{24}$' }
        description: Product identifier (owned for provider; public-eligible for client).
    responses:
      '200':
        description: |
          Product details. Provider responses use `ProviderProductDetails`.
          Client responses use the client product-details DTO including `similarAds`.
          Branch on `key` and actor role.
        content:
          application/json:
            schema:
              $ref: '../openapi.yaml#/components/schemas/SharedProductDetailsResponse'
            example:
              key: success
              message: "تفاصيل المنتج"
              status: 200
              data:
                id: "665f1c2a9b4e1d0012ab3402"
                type: simple
                typeText: "بسيط"
                status: active
                statusText: "نشط"
                moderationStatus: wait
                moderationStatusText: "قيد المراجعة"
                rejectionReason: null
                isVisible: true
                visibilityText: "ظاهر"
                name: "منتج"
                description: "وصف المنتج"
                primaryImage: "https://dashboard.kam-teswa.4hoste.com/assets/uploads/products/665f1c2a9b4e1d0012ab3402/image.png"
                condition: new
                conditionText: "جديد"
                quantity: 100
                quantityText: "100 قطعه"
                isPremium: false
                isPremiumText: "غير مميز"
                images:
                  - "https://dashboard.kam-teswa.4hoste.com/assets/uploads/products/665f1c2a9b4e1d0012ab3402/image.png"
                price: 122000
                priceText: "122000 ر.س"
                pricingMethod: manual
                pricingMethodText: "يدوي"
                currency: "ر.س"
                pricing:
                  method: manual
                  methodText: "يدوي"
                  manualPrice: 122000
                  manualPriceText: "122000 ر.س"
                  aiSuggestedPrice: null
                  aiSuggestedPriceText: null
                  aiReference: null
                discount:
                  type: none
                  typeText: "بدون خصم"
                  value: 0
                date: "2025-01-25"
                time: "04:53 مساء"
                actions:
                  edit: true
                  visibility: true
                  delete: true
      '400':
        description: Invalid productId or product not found under this owner.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            examples:
              invalidProductId:
                value: { key: fail, message: "معرف المنتج غير صحيح", status: 400 }
              notOwnedOrMissing:
                value: { key: fail, message: "المنتج غير موجود", status: 400 }
      '419':
        description: Missing/invalid authentication or a token other than Client/Provider.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            example: { key: unauthorized, message: "غير مسموح لك بتنفيذ هذا الإجراء", status: 419 }
      '500':
        $ref: '../openapi.yaml#/components/responses/ServerError'

products-visibility:
  patch:
    tags: [Provider Products]
    summary: Toggle visibility for an owned accepted product
    operationId: setProviderProductVisibility
    security:
      - SecretKeyAuth: []
        ProviderBearerAuth: []
    x-figma:
      mobile: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23330&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1"
      mobileNodeId: "9967:23330"
      web: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10767-133155&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038"
      webNodeId: "10767:133155"
      listMobile: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23316&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1"
      listMobileNodeId: "9967:23316"
      listWeb: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10766-121182&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038"
      listWebNodeId: "10766:121182"
    description: |
      ## Purpose
      Toggles owner visibility for one product (`isHidden` flipped in DB).
      Request sends **only** query `productId` — no body and no `isVisible` field.
      Validates that the product exists, is owned by the provider, and is not
      deleted. Does **not** require `approvalStatus = accept`, premium
      subscription, or `isPremium` checks. The handler only flips `isHidden`.

      Hidden products remain in provider list/details responses but are excluded
      by public product listing filters. Visibility never creates a Product-review notification.

      <div class="figma-links"><span>Design:</span><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23330&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1" target="_blank" rel="noopener">Mobile Details</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-23316&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1" target="_blank" rel="noopener">Mobile List</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10767-133155&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038" target="_blank" rel="noopener">Web Details</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10766-121182&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038" target="_blank" rel="noopener">Web List</a></div>

      ## Actor
      Provider.

      ## Authorization
      SecretKeyAuth + ProviderBearerAuth.

      ## Headers
      - `secretkey`
      - `Authorization: Bearer <provider token>`
      - `lang`

      ## Request
      Query `productId` only (required Mongo ObjectId). No request body.

      ## Preconditions (validated before the handler runs)
      - Product exists, is owned by the authenticated provider, and is not soft-deleted

      Premium subscription and administration approval are **not** required.
      The handler toggles `isHidden` (`true` ↔ `false`).

      ## Success response
      HTTP 200. Message-only envelope `{ key, message, status }` where `key` is
      `success`. The response intentionally has no `data` field. Clients must
      branch on `key`, not only HTTP status.

      Message depends on the new visibility state:
      - Product was shown → hidden: `تم إخفاء المنتج بنجاح`
      - Product was hidden → shown: `تم إظهار المنتج بنجاح`

      ## Common failures
      - Missing / invalid `productId`
      - Product missing, deleted, or not owned by the provider
      - Unauthorized / invalid secret / non-provider token

      ## QA notes
      - Toggle from list and details uses the same endpoint with `productId` only.
      - Unlike `PATCH /products/premium`, visibility does not require
        `approvalStatus = accept` or an active premium subscription.
      - Does not change moderation or workflow status and does not notify review.
      - See `docs/PRODUCTS_API_CONTRACT.md`.
    parameters:
      - $ref: '../openapi.yaml#/components/parameters/LangHeader'
      - $ref: '../openapi.yaml#/components/parameters/ProductIdQuery'
    responses:
      '200':
        description: "Visibility toggled. Message-only response with no `data` field; clients branch on `key: success`."
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ProviderProductVisibilityResponse' }
            example:
              key: success
              message: تم إخفاء المنتج بنجاح
              status: 200
            examples:
              hidden:
                summary: Product was hidden
                value:
                  key: success
                  message: تم إخفاء المنتج بنجاح
                  status: 200
              shown:
                summary: Product was shown
                value:
                  key: success
                  message: تم إظهار المنتج بنجاح
                  status: 200
      '400':
        description: Invalid productId, ownership, or deleted product.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            examples:
              invalidProductId:
                value: { key: fail, message: "معرف المنتج غير صحيح", status: 400 }
              notOwnedOrMissing:
                value: { key: fail, message: "المنتج غير موجود", status: 400 }
      '419':
        description: Missing/invalid authentication or a non-provider token.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            example: { key: unauthorized, message: "غير مسموح لك بتنفيذ هذا الإجراء", status: 419 }
      '500':
        $ref: '../openapi.yaml#/components/responses/ServerError'

products-premium:
  patch:
    tags: [Provider Products]
    summary: Mark an owned accepted product as premium
    operationId: markProviderProductPremium
    security:
      - SecretKeyAuth: []
        ProviderBearerAuth: []
    x-figma:
      mobile: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-24058&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1"
      mobileNodeId: "9967:24058"
      web: "https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10768-137676&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038"
      webNodeId: "10768:137676"
    description: |
      ## Purpose
      Marks one owned product as premium (`isPremium = true`) when the provider
      has an active premium package subscription. Used after selecting a product
      from the non-premium list / feature flow on Mobile and Web.

      <div class="figma-links"><span>Design:</span><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?node-id=9967-24058&viewport=176%2C570%2C0.02&t=c4yTtQLlUc9FQir5-1&scaling=scale-down&content-scaling=fixed&starting-point-node-id=9064%3A6087&page-id=0%3A1" target="_blank" rel="noopener">Mobile</a><a href="https://www.figma.com/proto/mTRILiWaS8pzLcNnmAUr2k/%D9%83%D9%85-%D8%AA%D8%B3%D9%88%D9%8A?page-id=10227%3A31854&node-id=10768-137676&viewport=-948%2C0%2C0.12&t=PNIRCKFxGV24peHK-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10449%3A65038" target="_blank" rel="noopener">Web</a></div>

      ## Actor
      Provider.

      ## Authorization
      SecretKeyAuth + ProviderBearerAuth.

      ## Headers
      - `secretkey`
      - `Authorization: Bearer <provider token>`
      - `lang`

      ## Request
      Query `productId` only (required Mongo ObjectId).

      ## Preconditions (validated before the handler runs)
      - Product exists, is owned by the authenticated provider, and is not soft-deleted
      - `approvalStatus = accept` (administration-approved)
      - `isPremium = false`
      - Provider has an **active** premium package subscription (`status = active`
        and `expireAt` missing or in the future)

      The handler only sets `isPremium = true` (plus `premiumSuspended = false` and
      `premiumExpireAt` from the validated subscription).

      ## Success response
      HTTP 200. Message-only envelope `{ key, message, status }` where `key` is
      `success`. The response intentionally has no `data` field. Clients must
      branch on `key`, not only HTTP status.

      On success the product is updated to `isPremium = true`,
      `premiumSuspended = false`, and `premiumExpireAt` from the active
      subscription (or `null` when open-ended).

      ## Common failures
      - Missing / invalid `productId`
      - Product missing, deleted, or not owned by the provider
      - Product already premium
      - Product not administration-accepted (`approvalStatus != accept`)
      - No active premium package subscription
      - Unauthorized / invalid secret / non-provider token

      ## QA notes
      - Companion list: `GET /provider/products/non-premium`.
      - Does not change moderation or visibility (`isHidden`).
      - See `docs/PRODUCTS_API_CONTRACT.md`.
    parameters:
      - $ref: '../openapi.yaml#/components/parameters/LangHeader'
      - $ref: '../openapi.yaml#/components/parameters/ProductIdQuery'
    responses:
      '200':
        description: "Product marked premium. Message-only response with no `data` field; clients branch on `key: success`."
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ProviderProductMarkPremiumResponse' }
            example:
              key: success
              message: تم تمييز المنتج بنجاح
              status: 200
      '400':
        description: Invalid productId, ownership, approval, premium state, or missing premium subscription.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            examples:
              invalidProductId:
                value: { key: fail, message: "معرف المنتج غير صحيح", status: 400 }
              notOwnedOrMissing:
                value: { key: fail, message: "المنتج غير موجود", status: 400 }
              alreadyPremium:
                value: { key: fail, message: "هذا المنتج مميز بالفعل", status: 400 }
              notApproved:
                value: { key: fail, message: "المنتج غير معتمد من الإدارة", status: 400 }
              subscriptionRequired:
                value: { key: fail, message: "يجب الاشتراك في باقة تمييز نشطة لتمييز المنتج", status: 400 }
      '419':
        description: Missing/invalid authentication or a non-provider token.
        content:
          application/json:
            schema: { $ref: '../openapi.yaml#/components/schemas/ErrorResponse' }
            example: { key: unauthorized, message: "غير مسموح لك بتنفيذ هذا الإجراء", status: 419 }
      '500':
        $ref: '../openapi.yaml#/components/responses/ServerError'
