"/admin/inventory/hint":
    get:
        tags: [Inventory]
        operationId: getInventoryHint
        summary: Identify a scanned serial number
        description: |
            Given a serial number or barcode, returns a hint about what kind of inventory
            item it represents. Used at factory scanning stations before the operator
            has chosen a destination workflow — the hint determines which UI to show.
        parameters:
            - in: query
              name: serialNumber
              required: true
              schema: { type: string }
              description: The serial number or barcode to identify.
              example: "BB-2024-001234"
        responses:
            "200":
                description: Hint resolved.
                content:
                    application/json:
                        schema:
                            type: object
                            required: [type, productType, isValid]
                            properties:
                                type:
                                    type: string
                                    description: Coarse category — e.g. `"beyond"`, `"cushion"`, `"lens"`.
                                    example: "beyond"
                                productType:
                                    $ref: "../admin-api.yaml#/components/schemas/BigProductType"
                                isValid:
                                    type: boolean
                                    description: Whether the serial number conforms to a known format.
                                message:
                                    type: string
                                    description: Optional human-readable note (e.g. "valid Beyond serial").
            "401": { $ref: "../admin-api.yaml#/components/responses/Unauthorized" }
            "403": { $ref: "../admin-api.yaml#/components/responses/Forbidden" }
        x-access-policies: [Admin, Inventory]
        x-scopes: ["admin:all", "inventory:read"]

"/admin/inventory/items":
    get:
        tags: [Inventory]
        operationId: listInventoryItems
        summary: List inventory items with filters and pagination
        description: |
            Returns inventory items matching the supplied filters. Most filters are exact
            matches; `serialNumberLike` does a substring match and `prefixSerialNumber` does
            a prefix match. Pagination uses `limit` / `offset` (no cursor).

            **Performance note.** With no filters this scans the entire `big_inventory_items`
            table. Always supply at least one of `status`, `type`, `bigShipmentId`, or
            `jobId` for production queries.
        parameters:
            - in: query
              name: type
              schema: { $ref: "../admin-api.yaml#/components/schemas/BigProductType" }
              description: Filter by product type.
            - in: query
              name: serialNumber
              schema: { type: string }
              description: Exact serial-number match.
            - in: query
              name: serialNumberLike
              schema: { type: string }
              description: Substring match against serial number.
            - in: query
              name: prefixSerialNumber
              schema: { type: string }
              description: Prefix match against serial number.
            - in: query
              name: status
              schema: { $ref: "../admin-api.yaml#/components/schemas/BigInventoryItemStatus" }
            - in: query
              name: statusNot
              schema: { $ref: "../admin-api.yaml#/components/schemas/BigInventoryItemStatus" }
              description: Exclude items in this status.
            - in: query
              name: trackingType
              schema: { $ref: "../admin-api.yaml#/components/schemas/BigInventoryTrackingType" }
            - in: query
              name: bigShipmentId
              schema: { type: string }
            - in: query
              name: jobId
              schema: { type: string }
            - in: query
              name: minCreatedAt
              schema: { type: integer, format: int64 }
              description: Minimum `createdAt` (Unix ms).
            - in: query
              name: maxCreatedAt
              schema: { type: integer, format: int64 }
            - in: query
              name: limit
              schema: { type: integer, default: 100, maximum: 1000 }
            - in: query
              name: offset
              schema: { type: integer, default: 0 }
            - in: query
              name: sortBy
              schema: { type: string, default: createdAt }
            - in: query
              name: sortDirection
              schema: { type: string, enum: [asc, desc], default: desc }
            - in: query
              name: createdAfter
              schema: { type: string, format: date-time }
              description: ISO-8601 timestamp; alternative to `minCreatedAt`.
            - in: query
              name: createdBefore
              schema: { type: string, format: date-time }
        responses:
            "200":
                description: Page of inventory items.
                content:
                    application/json:
                        schema:
                            type: object
                            required: [items, total, limit, offset]
                            properties:
                                items:
                                    type: array
                                    items: { $ref: "../admin-api.yaml#/components/schemas/BigInventoryItem" }
                                total:
                                    type: integer
                                    description: Total matching the filter (before pagination).
                                limit: { type: integer }
                                offset: { type: integer }
            "401": { $ref: "../admin-api.yaml#/components/responses/Unauthorized" }
            "403": { $ref: "../admin-api.yaml#/components/responses/Forbidden" }
        x-access-policies: [Admin, Inventory]
        x-scopes: ["admin:all", "inventory:read"]

"/admin/inventory/stats":
    get:
        tags: [Inventory]
        operationId: getInventoryStats
        summary: Inventory counts by type and status
        description: |
            Aggregate counts of inventory items grouped by `BigProductType` and
            `BigInventoryItemStatus`. Cheap to call (`COUNT(*) … GROUP BY …`) — safe
            for dashboards that poll every few seconds.
        responses:
            "200":
                description: Aggregate counts.
                content:
                    application/json:
                        schema:
                            type: object
                            additionalProperties: true
                            description: |
                                Shape returned by `InventoryAdminApi.getInventoryStats`. Top-level keys
                                are product types; values are counts-by-status objects. The exact key
                                set tracks `BigProductType` and `BigInventoryItemStatus` enums.
            "401": { $ref: "../admin-api.yaml#/components/responses/Unauthorized" }
            "403": { $ref: "../admin-api.yaml#/components/responses/Forbidden" }
        x-access-policies: [Admin, Inventory]
        x-scopes: ["admin:all", "inventory:read"]

"/admin/inventory/item":
    post:
        tags: [Inventory]
        operationId: createInventoryItem
        summary: Create an inventory item
        description: |
            Creates a single inventory item. The exact required fields depend on
            `trackingType`:

            - **`Personalized`** — `serialNumber` is required; `quantity` is forced to `1`.
            - **`QuantityTracked`** — `productType` and `quantity` are required; `serialNumber`
              is optional and represents a batch identifier.
            - **`Untracked`** — `productType` is required; `serialNumber` and `quantity` are ignored.

            Items are typically created by the manufacturing pipeline (a finished cushion comes
            off the line, scans its NFC tag, and an item is created). Manual creation here is
            used for accessories, restocks, and test setup.
        requestBody:
            required: true
            content:
                application/json:
                    schema: { $ref: "../admin-api.yaml#/components/schemas/BigInventoryItemCreationParams" }
                    examples:
                        personalizedCushion:
                            summary: Personalized cushion off the line
                            value:
                                serialNumber: "BC-2025-007842"
                                productType: "BeyondCushionV1"
                                trackingType: "Personalized"
                                jobId: "job_01HXY8ZW7Q9V0K3M5RPQ4ABCDE"
                        untrackedAccessory:
                            summary: Restocking soft straps
                            value:
                                productType: "BeyondSoftStrapV1"
                                trackingType: "QuantityTracked"
                                quantity: 200
        responses:
            "200":
                description: The created inventory item.
                content:
                    application/json:
                        schema: { $ref: "../admin-api.yaml#/components/schemas/BigInventoryItem" }
            "401": { $ref: "../admin-api.yaml#/components/responses/Unauthorized" }
            "403": { $ref: "../admin-api.yaml#/components/responses/Forbidden" }
            "422":
                description: Validation error — missing required fields for the chosen `trackingType`, or invalid `productType`.
                content:
                    application/json:
                        schema: { $ref: "../admin-api.yaml#/components/schemas/Error" }
        x-access-policies: [Admin, Inventory]
        x-scopes: ["admin:all", "inventory:write"]

"/admin/inventory/item/{id}":
    parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
          description: The inventory item id.
    get:
        tags: [Inventory]
        operationId: getInventoryItem
        summary: Fetch one inventory item
        description: |
            Returns a single inventory item by id, with its embedded `history` array.
            `history` is appended to on every state-machine transition and includes the
            acting account id where applicable.
        responses:
            "200":
                description: The inventory item.
                content:
                    application/json:
                        schema: { $ref: "../admin-api.yaml#/components/schemas/BigInventoryItem" }
            "401": { $ref: "../admin-api.yaml#/components/responses/Unauthorized" }
            "403": { $ref: "../admin-api.yaml#/components/responses/Forbidden" }
            "404":
                description: No item with this id.
                content:
                    application/json:
                        schema: { $ref: "../admin-api.yaml#/components/schemas/Error" }
        x-access-policies: [Admin, Inventory]
        x-scopes: ["admin:all", "inventory:read"]
    put:
        tags: [Inventory]
        operationId: updateInventoryItem
        summary: Update an item's quantity
        description: |
            Updates the `quantity` field on a `QuantityTracked` inventory item. This endpoint
            does **not** mutate `status` — that is driven by the shipping pipeline. To change
            state manually, use the relevant pipeline endpoint (`PUT /admin/shipper/shipment/:id`
            with the appropriate `action`).
        requestBody:
            required: true
            content:
                application/json:
                    schema:
                        type: object
                        required: [quantity]
                        properties:
                            quantity:
                                type: integer
                                minimum: 0
                                description: New quantity. Must be a non-negative integer.
                                example: 150
        responses:
            "200":
                description: The updated item.
                content:
                    application/json:
                        schema: { $ref: "../admin-api.yaml#/components/schemas/BigInventoryItem" }
            "401": { $ref: "../admin-api.yaml#/components/responses/Unauthorized" }
            "403": { $ref: "../admin-api.yaml#/components/responses/Forbidden" }
            "404":
                description: No item with this id.
                content:
                    application/json:
                        schema: { $ref: "../admin-api.yaml#/components/schemas/Error" }
            "422":
                description: Invalid `quantity` (negative or non-integer).
                content:
                    application/json:
                        schema: { $ref: "../admin-api.yaml#/components/schemas/Error" }
        x-access-policies: [Admin, Inventory]
        x-scopes: ["admin:all", "inventory:write"]
    delete:
        tags: [Inventory]
        operationId: deleteInventoryItem
        summary: Delete an inventory item
        description: |
            Soft-deletes the item by transitioning its status to `Deleted` (the row stays
            in the table). Pass `?force=1` to hard-delete the row entirely; this requires
            an elevated access policy (`FabricatorAdmin`, `Admin`, or `SuperUser`) and is
            not recoverable.

            Soft-deleted items are excluded from `GET /admin/inventory/items` and
            `/stats` by default. They remain visible to `GET /admin/inventory/item/{id}`
            so the audit history is not lost.
        parameters:
            - in: query
              name: force
              schema: { type: string, enum: ["1"] }
              description: |
                  If set to `"1"`, hard-deletes the row instead of soft-deleting. Requires
                  `FabricatorAdmin`, `Admin`, or `SuperUser`.
        responses:
            "200":
                description: Deletion result.
                content:
                    application/json:
                        schema:
                            type: object
                            required: [success]
                            properties:
                                success: { type: boolean }
                                message: { type: string }
                        examples:
                            soft:
                                summary: Soft delete
                                value: { success: true, message: "Inventory item soft-deleted" }
                            hard:
                                summary: Hard delete with force=1
                                value: { success: true, message: "Inventory item deleted" }
            "401": { $ref: "../admin-api.yaml#/components/responses/Unauthorized" }
            "403":
                description: Caller lacks the elevated policy required for `force=1`.
                content:
                    application/json:
                        schema: { $ref: "../admin-api.yaml#/components/schemas/Error" }
            "404":
                description: No item with this id.
                content:
                    application/json:
                        schema: { $ref: "../admin-api.yaml#/components/schemas/Error" }
        x-access-policies: [Admin, Inventory, FabricatorAdmin]
        x-scopes: ["admin:all", "inventory:write"]

"/admin/inventory/item/{id}/history":
    get:
        tags: [Inventory]
        operationId: getInventoryItemHistory
        summary: Per-item audit history
        description: |
            Returns every row from the `big_inventory_item_history` table for this item,
            most-recent-first. There is no pagination — operators reviewing a single
            cushion's history want the full timeline.

            Distinct from the `history` array embedded on the item itself: that one is a
            truncated snapshot for display, while this endpoint reads the underlying audit
            table verbatim.
        parameters:
            - in: path
              name: id
              required: true
              schema: { type: string }
        responses:
            "200":
                description: Array of raw history rows.
                content:
                    application/json:
                        schema:
                            type: array
                            items:
                                type: object
                                description: |
                                    Raw row from `big_inventory_item_history`. The handler returns
                                    `SELECT *` — new columns surface here without a code change.
                                properties:
                                    id: { type: string }
                                    bigInventoryItemId: { type: string }
                                    createdAt: { type: string, format: date-time }
                                additionalProperties: true
            "401": { $ref: "../admin-api.yaml#/components/responses/Unauthorized" }
            "403": { $ref: "../admin-api.yaml#/components/responses/Forbidden" }
            "422":
                description: Empty `id`.
                content:
                    application/json:
                        schema: { $ref: "../admin-api.yaml#/components/schemas/Error" }
        x-access-policies: [Admin, Inventory]
        x-scopes: ["admin:all", "inventory:read"]

"/admin/inventory/report":
    get:
        tags: [Inventory]
        operationId: getInventoryReport
        summary: Comprehensive inventory report
        description: |
            Generates a full inventory dashboard: per-product-type and per-status counts,
            today's activity, and low-stock warnings. Heavier than `/stats` — fan-outs to
            multiple aggregations and is intended for the daily-check Looper job, not
            interactive polling.
        responses:
            "200":
                description: Report payload.
                content:
                    application/json:
                        schema: { $ref: "../admin-api.yaml#/components/schemas/InventoryReport" }
            "401": { $ref: "../admin-api.yaml#/components/responses/Unauthorized" }
            "403": { $ref: "../admin-api.yaml#/components/responses/Forbidden" }
        x-access-policies: [Admin, FabricatorReadOnly]
        x-scopes: ["admin:all", "inventory:read"]

"/admin/inventory/sync":
    get:
        tags: [Inventory]
        operationId: syncInventory
        summary: Reconcile inventory from completed jobs
        description: |
            Walks fabricator jobs in state `AwaitingFulfilment` and creates the matching
            inventory items if they don't already exist. Idempotent — items already linked
            to a job are skipped.

            Used as a recovery path when an item failed to be created automatically at the
            end of a job (rare; usually triggered by a process crash mid-flow).
        responses:
            "200":
                description: Sync result — items created and skipped, plus any errors.
                content:
                    application/json:
                        schema:
                            type: object
                            properties:
                                created: { type: integer }
                                skipped: { type: integer }
                                errors:
                                    type: array
                                    items: { type: string }
            "401": { $ref: "../admin-api.yaml#/components/responses/Unauthorized" }
            "403": { $ref: "../admin-api.yaml#/components/responses/Forbidden" }
        x-access-policies: [Admin]
        x-scopes: ["admin:all"]
