# Filter API Specification

## Purpose

`FilterController` provides product discovery with combined filters for:
- text search,
- pricing,
- taxonomy (`category_id`, `label_id`, `brand_id`),
- vehicle compatibility,
- product options,
- sorting and pagination.

It is designed for both app developers and QA testers.

---

## Endpoints

### 1) `GET|POST /api/filter`

General filter endpoint.

- `GET`: send filters in query params.
- `POST`: send filters in JSON/form body.

### 2) `POST /api/filter/options`

Option-driven filter endpoint.

- Requires `options` array (validation-enforced).
- Also supports common filters (price/category/label/brand/sort + keyword).

---

## Authentication Behavior

Auth is optional for filtering itself, but token affects `recently_viewed`.

- If bearer token belongs to `customer`: `recently_viewed` uses that user history.
- If bearer token belongs to `guest`: `recently_viewed` uses guest history.
- No valid token: `recently_viewed` is typically empty.

---

## Request Parameters

## Common Parameters (supported by `/api/filter`, and mostly by `/api/filter/options`)

| Key | Type | Example | Notes |
|---|---|---|---|
| `q` | string | `oil` | text search on `name_ar` and `name_en` |
| `keyword` | string | `synthetic` | alias of `q` |
| `price_min` | numeric | `10` | `price >= price_min` |
| `price_max` | numeric | `300` | `price <= price_max` |
| `category_id` | int | `1` | product must belong to category |
| `label_id` | int | `2` | product must have dynamic label |
| `brand_id` | int | `3` | product store must be linked to this brand |
| `sort` | enum | `price_asc` / `price_desc` | price sorting |
| `page` | int | `2` | pagination page |

## Vehicle and direct option keys (`/api/filter`)

| Key | Type | Example | Notes |
|---|---|---|---|
| `vehicle_id` | int | `5` | load vehicle and apply compatibility logic |
| `car_make` | string | `Toyota` | used only when `vehicle_id` is not active |
| `car_model` | string | `Corolla` | same |
| `year` | string | `2020` | same |
| `engine_type` | string | `Petrol` | same |
| `engine_size` | string | `1.8` | same |
| `fuel_type` | string | `Gasoline` | same |
| `color` | string | `Black` | same |

## Options payload (`/api/filter/options` only)

```json
{
  "options": [
    { "name": "car_make", "value": "Toyota" },
    { "name": "car_model", "value": "Corolla" }
  ]
}
```

Validation rules:
- `options`: required, array, minimum 1 item.
- `options.*.name`: required string.
- `options.*.value`: required string.
- `sort`: `price_asc | price_desc`.
- `price_min`, `price_max`: numeric if provided.
- `category_id`, `label_id`, `brand_id`: integer if provided.

---

## Business Logic Details

## Text Search

`q` or `keyword` applies:
- `name_ar LIKE %kw%` OR `name_en LIKE %kw%`.

## `brand_id`

Filter is applied through store-brand relation:
- product -> store -> brands contains `brand_id`.

## `label_id`

Filter is applied through product labels relation:
- product has dynamic label with given id.

## `vehicle_id` logic (`/api/filter`)

If vehicle exists:

1. A compatibility block is added as:
   - **spec-based match** using option keys (`car_make`, `car_model`, `year`, `engine_type`, `engine_size`) when full make+model is available,
   - OR **brand-based fallback** via store brands (`vehicle.brand_id`, or vehicle make name).

2. While vehicle filter is active, direct option keys loop (`car_make`, `car_model`, etc.) is skipped.

## `/filter/options` logic

Each option entry adds a `whereHas` condition.
All options are combined with **AND** semantics.

---

## Response Contract

Both endpoints return:

```json
{
  "success": true,
  "message": "ok",
  "data": {
    "items": [],
    "pagination": {
      "current_page": 1,
      "last_page": 1,
      "per_page": 20,
      "total": 0
    },
    "top_sold": [],
    "recently_viewed": []
  },
  "errors": null
}
```

### `items`
Paginated filtered products (`ProductResource`).

### `top_sold`
Top-selling products (currently constrained by price min/max only).

### `recently_viewed`
Latest viewed products based on customer/guest token context.

---

## Practical Request Examples

## A) Search only

`POST /api/filter`

```json
{
  "q": "oil"
}
```

## B) Taxonomy + range + sort

`POST /api/filter`

```json
{
  "category_id": 1,
  "label_id": 2,
  "brand_id": 3,
  "price_min": 50,
  "price_max": 500,
  "sort": "price_desc"
}
```

## C) Vehicle compatibility

`POST /api/filter`

```json
{
  "vehicle_id": 5,
  "price_max": 400
}
```

## D) Options endpoint (minimum valid)

`POST /api/filter/options`

```json
{
  "options": [
    { "name": "car_make", "value": "Toyota" }
  ]
}
```

## E) Options endpoint (multi-option AND)

`POST /api/filter/options`

```json
{
  "options": [
    { "name": "car_make", "value": "Toyota" },
    { "name": "car_model", "value": "Corolla" },
    { "name": "year", "value": "2020" }
  ],
  "brand_id": 3,
  "sort": "price_asc"
}
```

---

## QA Test Checklist

- `q` in GET query filters correctly.
- `q` in POST body filters correctly.
- `label_id` returns only products carrying that label.
- `brand_id` returns only products whose store is linked to that brand.
- `vehicle_id` activates compatibility block; direct option keys are ignored in this case.
- `/filter/options` without `options` returns `422`.
- `/filter/options` with multiple options behaves as AND.
- `sort=price_asc` and `price_desc` order is correct.
- Pagination metadata changes with `page`.
- `recently_viewed` differs between customer token, guest token, and no token.

---

## Notes for Integrators

- Prefer `POST /api/filter` for complex payloads and body-based clients.
- Use `GET /api/filter` when query-string based navigation/shareable URLs are needed.
- For strict compatibility use `POST /api/filter/options`.
- Keep `base_url` pointed to Laravel app public entry + `/api`.
