# Query logic

Combine conditions with AND/OR logic. Conditions on different attributes are combined with AND, conditions on the same attribute with OR. For any other combination, use addGroup with an explicit groupType. Build complex boolean expressions for advanced filtering with faceted search patterns.

## Syntax

```typescript
list.catalog.query.addGroup({
  groupKey?: string,
  groupType?: 'and' | 'or',
  queries: Array<Condition | Group>
})
```

## Parameters

- `groupKey` (`string`, optional) — Names the group. Adding a group with an existing groupKey extends it; setGroup() replaces it; removeGroup(groupKey) removes it. The keyed group is found at any nesting depth (a top-level match first) and changed where it is. Removing a group that leaves its wrapper group empty removes the wrapper too (and every wrapper above it that becomes empty). addGroup() always adds a group without a key as a new group; setGroup() without a key replaces the first keyless group.
- `groupType` (`'and' | 'or'`, optional) — Logic within the group. Set it explicitly: a group without groupType is stored as 'or', whether it is new or replaces an existing group with setGroup() (addGroup() without groupType extending a keyed group keeps that group's groupType), while a nested group inside queries without groupType is evaluated as 'and'.
- `queries` (`Array<Condition | Group>`, required) — Conditions or nested groups in this group

## Return type

addGroup(), setGroup() and addCondition() return the updated query (DcuplGlobalQueryOptions). Use current() to inspect it, clear() to reset:

```typescript
// Shape of the returned query, e.g. after addCondition() on category and brand
type DcuplGlobalQueryOptions<T> = {
  modelKey: string;
  groupKey: 'root';
  groupType: 'and'; // top-level groups are ANDed
  queries: Array<DcuplQuery | DcuplQueryGroup>; // here: [{ groupKey: 'category', groupType: 'or', queries: [...] },
                                                //        { groupKey: 'brand', groupType: 'or', queries: [...] }]
};
```

## Usage notes

- Default: addCondition() puts each condition into a group named after its attribute, so different attributes use AND and the same attribute uses OR
- Use addGroup with groupType: 'or' for OR across different attributes
- Use addGroup with groupType: 'and' for ranges on one attribute
- Different groups are ANDed together at the top level
- Use setCondition() for single-select dropdowns (replaces the conditions on that attribute)
- Use addCondition() for multi-select checkboxes (accumulates, OR within the attribute)
- removeCondition() removes one condition (pass the full condition); removeGroup(groupKey) removes a whole group
- clear() removes all conditions

## Related methods

- `catalog.query.addGroup()` — Add or extend a group of conditions
- `catalog.query.setGroup()` — Replace a group by its groupKey
- `catalog.query.current()` — Get current query structure
- `catalog.query.isEmpty()` — Check if any conditions exist
- `catalog.query.clear()` — Remove all conditions
- `catalog.query.removeCondition()` — Remove specific condition

## Examples

**Default AND logic**

Multiple conditions on different attributes use AND by default.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

// These conditions are ANDed automatically
productList.catalog.query.addCondition({
  attribute: 'category',
  operator: 'eq',
  value: 'Electronics',
});

productList.catalog.query.addCondition({
  attribute: 'inStock',
  operator: 'isTruthy',
  value: true,
});

// Result: Electronics AND in stock
const results = productList.catalog.query.items();
```

**OR logic with same attribute**

Multiple values for same attribute create OR logic by default.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

// Same attribute = OR logic
productList.catalog.query.addCondition({
  attribute: 'category',
  operator: 'eq',
  value: 'Electronics',
});

productList.catalog.query.addCondition({
  attribute: 'category',
  operator: 'eq',
  value: 'Computers',
});

// Result: Electronics OR Computers
const results = productList.catalog.query.items();
```

**Explicit OR group**

Use addGroup with groupType: "or" for OR logic across different attributes.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

// Conditions on different attributes are combined with AND,
// so put them in one group with groupType 'or'
productList.catalog.query.addGroup({
  groupKey: 'special',
  groupType: 'or',
  queries: [
    { attribute: 'featured', operator: 'eq', value: true },
    { attribute: 'onSale', operator: 'eq', value: true },
  ],
});

// Result: featured OR on sale
const specialProducts = productList.catalog.query.items();
```

**Multiple OR groups (AND between groups)**

Combine multiple OR groups with AND logic between them.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

// Category group (OR): Electronics OR Computers
productList.catalog.query.addGroup({
  groupKey: 'category-group',
  groupType: 'or',
  queries: [
    { attribute: 'category', operator: 'eq', value: 'Electronics' },
    { attribute: 'category', operator: 'eq', value: 'Computers' },
  ],
});

// Brand group (OR): Apple OR Samsung
productList.catalog.query.addGroup({
  groupKey: 'brand-group',
  groupType: 'or',
  queries: [
    { attribute: 'brand', operator: 'eq', value: 'Apple' },
    { attribute: 'brand', operator: 'eq', value: 'Samsung' },
  ],
});

// Result: (Electronics OR Computers) AND (Apple OR Samsung)
const results = productList.catalog.query.items();
```

**Faceted search pattern**

Build a typical e-commerce filter with multiple facets.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

function applyFacetedFilters(filters: {
  categories?: string[];
  brands?: string[];
  priceMin?: number;
  priceMax?: number;
}) {
  const query = productList.catalog.query;
  query.clear();

  // Categories (OR within group). Key the group by its attribute:
  // fn.facets({ attribute: 'category' }) leaves the group with that key out when counting
  if (filters.categories?.length) {
    query.addGroup({
      groupKey: 'category',
      groupType: 'or',
      queries: filters.categories.map((cat) => ({
        attribute: 'category',
        operator: 'eq' as const,
        value: cat,
      })),
    });
  }

  // Brands: the 'in' operator matches any of the listed values
  if (filters.brands?.length) {
    query.addCondition({ attribute: 'brand', operator: 'in', value: filters.brands });
  }

  // Price range (AND within group)
  const price = [];
  if (filters.priceMin !== undefined) {
    price.push({ attribute: 'price', operator: 'gte' as const, value: filters.priceMin });
  }
  if (filters.priceMax !== undefined) {
    price.push({ attribute: 'price', operator: 'lte' as const, value: filters.priceMax });
  }
  if (price.length) {
    query.addGroup({ groupKey: 'price', groupType: 'and', queries: price });
  }

  // Result: (cat1 OR cat2) AND (brand1 OR brand2) AND (price range)
  return query.items();
}
```

**Replace vs. add**

Control whether a new condition replaces or adds to the existing ones on the same attribute.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

// addCondition adds: Electronics
productList.catalog.query.addCondition({
  attribute: 'category',
  operator: 'eq',
  value: 'Electronics',
});

// setCondition replaces the conditions on 'category'
productList.catalog.query.setCondition({
  attribute: 'category',
  operator: 'eq',
  value: 'Books',
});

// Now only Books products match
// (addCondition here would give Electronics OR Books)
const results = productList.catalog.query.items();
```

**Remove specific condition**

Remove a specific condition while keeping others.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

// Add multiple category filters
productList.catalog.query.addCondition({
  attribute: 'category',
  operator: 'eq',
  value: 'Electronics',
});

productList.catalog.query.addCondition({
  attribute: 'category',
  operator: 'eq',
  value: 'Books',
});

// Remove just the Electronics filter (pass the full condition)
productList.catalog.query.removeCondition({
  attribute: 'category',
  operator: 'eq',
  value: 'Electronics',
});

// Now only Books products match
const results = productList.catalog.query.items();

// Remove all conditions on 'category'
productList.catalog.query.removeGroup('category');
```

**Query state inspection**

Check current query state and reset.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

productList.catalog.query.addCondition({
  attribute: 'category',
  operator: 'eq',
  value: 'Electronics',
});

// Check current query state
const currentQuery = productList.catalog.query.current();
console.log('Current query:', currentQuery);

// Check if query has any conditions
const isEmpty = productList.catalog.query.isEmpty();
console.log('Is empty:', isEmpty); // false

// Check for a specific group (addCondition groups are keyed by attribute)
const hasCategory = productList.catalog.query.has({ groupKey: 'category' });

// Clear all conditions
productList.catalog.query.clear();

// Now returns all products
const allProducts = productList.catalog.query.items();
```

**Conditional UI based on query state**

Use isEmpty() and has() to drive UI decisions.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

// Check if query has any conditions
if (productList.catalog.query.isEmpty()) {
  // Show all products view
  showAllProductsMessage();
}

// Check if a specific filter is active
if (productList.catalog.query.has({ groupKey: 'category' })) {
  // Show reset button for category filter
  showCategoryResetButton();
}

// Combine with faceted search
function updateFilterUI() {
  const query = productList.catalog.query;

  // Show "clear all" only when filters are active
  clearAllButton.hidden = query.isEmpty();

  // Show individual reset buttons per filter
  categoryReset.hidden = !query.has({ groupKey: 'category' });
  brandReset.hidden = !query.has({ groupKey: 'brand' });
  priceReset.hidden = !query.has({ groupKey: 'price' });
}
```

**SDK**

```typescript
// Faceted search: (category1 OR category2) AND (brand1)
const list = dcupl.lists.create({ modelKey: 'Article' });

// Conditions on the same attribute are combined with OR
list.catalog.query.addCondition([
  { attribute: 'category', operator: 'eq', value: 'Running' },
  { attribute: 'category', operator: 'eq', value: 'Training' },
]);

// Conditions on different attributes are combined with AND
list.catalog.query.addCondition({
  attribute: 'brand',
  operator: 'eq',
  value: 'Nike',
});

const results = list.catalog.query.items();
```
