# Hooks — Xertica UI

Xertica UI exposes a set of React hooks via the `xertica-ui/hooks` subpath. These hooks provide access to global contexts (theme, language, layout, assistant, API keys, brand colors) and utility behaviors (audio player, keyboard shortcuts, mobile detection).

---

## Import

```tsx
import {
  useTheme,
  useLanguage,
  useBrandColors,
  useAssistente,
  useApiKey,
  useLayout,
  useAudioPlayer,
  useLayoutShortcuts,
  useIsMobile,
} from 'xertica-ui/hooks';
```

All hooks are also available from the root barrel:

```tsx
import { useTheme, useLayout } from 'xertica-ui';
```

---

## `useTheme`

Accesses and controls the current color theme (light/dark).

**Source**: `contexts/ThemeContext.tsx`

### Return Value

| Property      | Type                            | Description                    |
| ------------- | ------------------------------- | ------------------------------ |
| `theme`       | `'light' \| 'dark' \| 'system'` | Current active theme           |
| `setTheme`    | `(theme: Theme) => void`        | Sets the theme explicitly      |
| `toggleTheme` | `() => void`                    | Toggles between light and dark |

### Usage

```tsx
import { useTheme } from 'xertica-ui/hooks';

function ThemeButton() {
  const { theme, toggleTheme } = useTheme();
  return <button onClick={toggleTheme}>Current: {theme}</button>;
}
```

> **Note**: `useTheme` must be used inside `<XerticaProvider>` (which includes `ThemeProvider`). For a self-contained toggle that does not require context, use the `<ThemeToggle>` component instead.

---

## `useLanguage`

Accesses and controls the current UI language preference, plus the list of available languages.

**Source**: `contexts/LanguageContext.tsx`

### Return Value

| Property             | Type                           | Description                                                                                                    |
| -------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------- |
| `language`           | `string`                       | Current language code (e.g. `'pt-BR'`, `'en'`, `'fr'`)                                                         |
| `setLanguage`        | `(language: Language) => void` | Changes the language: persists to localStorage, calls `i18n.changeLanguage`, and invalidates React Query cache |
| `availableLanguages` | `LanguageDefinition[]`         | The full list of languages configured via `availableLanguages` prop on `<XerticaProvider>`                     |
| `isMonolingual`      | `boolean`                      | `true` when only one language is configured. The `LanguageSelector` uses this to auto-hide                     |

### Usage

```tsx
import { useLanguage } from 'xertica-ui/hooks';

function LanguageDisplay() {
  const { language, setLanguage, availableLanguages } = useLanguage();
  // The dropdown is fully data-driven — works with any number of languages
  return (
    <select value={language} onChange={e => setLanguage(e.target.value)}>
      {availableLanguages.map(lang => (
        <option key={lang.code} value={lang.code}>
          {lang.label}
        </option>
      ))}
    </select>
  );
}
```

> **Note**: The language preference is persisted in `localStorage` under the key `xertica_language`. Calling `setLanguage(lang)` also (1) calls `i18n.changeLanguage(lang)` from `i18next` — all components that use `useTranslation()` re-render immediately, and (2) invalidates the React Query cache — data-layer strings (like mock API responses) are refetched in the new language. The `LanguageSelector` component wires this automatically — you only need to call `setLanguage` directly when changing the language programmatically (e.g., from a settings page).

> **Type note**: `Language` is `string`, not a strict union, to support runtime-added locales. Use `availableLanguages` from this hook to discover valid codes.

---

## `useBrandColors`

Accesses and modifies the brand color palette applied as CSS custom properties.

**Source**: `contexts/BrandColorsContext.tsx`

### Return Value

| Property        | Type                                              | Description                           |
| --------------- | ------------------------------------------------- | ------------------------------------- |
| `colors`        | `BrandColors`                                     | Current brand color values            |
| `setBrandColor` | `(key: keyof BrandColors, value: string) => void` | Updates a single brand color          |
| `currentTheme`  | `string`                                          | Name of the active color theme preset |

### Usage

```tsx
import { useBrandColors } from 'xertica-ui/hooks';

function BrandCustomizer() {
  const { colors, setBrandColor } = useBrandColors();
  return (
    <input
      type="color"
      value={colors.primary}
      onChange={e => setBrandColor('primary', e.target.value)}
    />
  );
}
```

> **Note**: Color changes are applied immediately as CSS variables on `:root`. They persist across re-renders but are not saved to `localStorage` by default.

---

## `useAssistente`

Accesses the global AI Assistant context — conversation list, current conversation, and navigation helpers.

**Source**: `contexts/AssistenteContext.tsx`

### Return Value

| Property           | Type                           | Description                                    |
| ------------------ | ------------------------------ | ---------------------------------------------- |
| `conversas`        | `Conversation[]`               | List of all conversations                      |
| `conversaAtual`    | `string \| null`               | ID of the currently active conversation        |
| `setConversaAtual` | `(id: string \| null) => void` | Selects a conversation                         |
| `sugestoes`        | `Suggestion[]`                 | Quick-reply suggestions for the welcome screen |
| `setSugestoes`     | `(s: Suggestion[]) => void`    | Replaces the suggestion list                   |

### Usage

```tsx
import { useAssistente } from 'xertica-ui/hooks';

function ConversationCount() {
  const { conversas } = useAssistente();
  return <span>{conversas.length} conversations</span>;
}
```

> **Note**: This hook must be used inside `<XerticaProvider>` (which includes `AssistenteProvider`). It is primarily used internally by `XerticaAssistant`.

---

## `useApiKey`

Accesses and manages API keys for Xertica services, Gemini AI, and Google Maps.

**Source**: `contexts/ApiKeyContext.tsx`

### Return Value

| Property               | Type                    | Description                              |
| ---------------------- | ----------------------- | ---------------------------------------- |
| `apiKey`               | `string`                | Xertica platform API key                 |
| `setApiKey`            | `(key: string) => void` | Updates the Xertica API key              |
| `geminiApiKey`         | `string`                | Google Gemini API key                    |
| `setGeminiApiKey`      | `(key: string) => void` | Updates the Gemini API key               |
| `isApiKeyValid`        | `boolean`               | Whether the Xertica API key is non-empty |
| `googleMapsApiKey`     | `string`                | Google Maps API key                      |
| `setGoogleMapsApiKey`  | `(key: string) => void` | Updates the Maps API key                 |
| `isGoogleMapsKeyValid` | `boolean`               | Whether the Maps key is non-empty        |
| `reloadMapsApi`        | `() => Promise<void>`   | Reloads the Maps script after key change |

### Usage

```tsx
import { useApiKey } from 'xertica-ui/hooks';

function ApiKeySettings() {
  const { geminiApiKey, setGeminiApiKey, isApiKeyValid } = useApiKey();
  return (
    <input
      type="password"
      value={geminiApiKey}
      onChange={e => setGeminiApiKey(e.target.value)}
      placeholder="Enter Gemini API key"
    />
  );
}
```

> **Note**: API keys are persisted in `localStorage`. The `XerticaAssistant` component reads `geminiApiKey` automatically when `demoMode={false}` (real AI mode). When `demoMode={true}` (default), no API key is required.

---

## `useLayout`

Accesses and controls the global layout state — sidebar expansion, assistant panel, and width values.

**Source**: `contexts/LayoutContext.tsx`

### Return Value

| Property                  | Type                      | Description                            |
| ------------------------- | ------------------------- | -------------------------------------- |
| `sidebarExpanded`         | `boolean`                 | Whether the sidebar is expanded        |
| `sidebarWidth`            | `number`                  | Current sidebar width in pixels        |
| `setSidebarWidth`         | `(width: number) => void` | Updates the sidebar width              |
| `toggleSidebar`           | `() => void`              | Toggles sidebar expanded state         |
| `assistenteExpanded`      | `boolean`                 | Whether the AI assistant panel is open |
| `toggleAssistente`        | `() => void`              | Toggles the assistant panel            |
| `toggleAssistenteWithTab` | `(tab: string) => void`   | Opens the assistant on a specific tab  |

### Usage

```tsx
import { useLayout } from 'xertica-ui/hooks';

function ContentArea() {
  const { sidebarExpanded, sidebarWidth } = useLayout();
  return <main style={{ marginLeft: sidebarWidth }}>{/* content */}</main>;
}
```

> **See also**: `docs/layout.md` for the complete layout system reference.

---

## `useAudioPlayer`

Headless hook that encapsulates all state and side effects for audio playback. Use this to build a fully custom audio player UI while reusing the library's playback logic.

**Source**: `components/media/audio-player/use-audio-player.ts`

### Props (`UseAudioPlayerProps`)

| Prop              | Type              | Default  | Description                                          |
| ----------------- | ----------------- | -------- | ---------------------------------------------------- |
| `src`             | `string`          | —        | Audio file URL                                       |
| `autoPlay`        | `boolean`         | `false`  | Start playing on mount                               |
| `initialTime`     | `number`          | `0`      | Initial seek position in seconds                     |
| `initialDuration` | `number`          | —        | Pre-known duration (loaded from metadata if omitted) |
| `variant`         | `'card' \| 'bar'` | `'card'` | Layout variant (affects floating behavior)           |
| `isOpen`          | `boolean`         | `true`   | Whether the bar variant is visible                   |
| `enableAutoFloat` | `boolean`         | `true`   | Auto-float when scrolled out of view (card only)     |
| `onCloseFloating` | `() => void`      | —        | Called when user closes the floating player          |

### Return Value (`UseAudioPlayerReturn`)

**Refs**

| Property       | Type                          | Description                                         |
| -------------- | ----------------------------- | --------------------------------------------------- |
| `audioRef`     | `RefObject<HTMLAudioElement>` | Attach to `<audio>` element                         |
| `containerRef` | `RefObject<HTMLDivElement>`   | Attach to the player container for scroll detection |

**Playback State**

| Property        | Type      | Description                             |
| --------------- | --------- | --------------------------------------- |
| `isPlaying`     | `boolean` | Whether audio is currently playing      |
| `currentTime`   | `number`  | Current playback position in seconds    |
| `duration`      | `number`  | Total duration in seconds               |
| `volume`        | `number`  | Volume level (0–1)                      |
| `isMuted`       | `boolean` | Whether audio is muted                  |
| `playbackSpeed` | `number`  | Current playback speed (0.5, 1, 1.5, 2) |

**UI State**

| Property               | Type      | Description                              |
| ---------------------- | --------- | ---------------------------------------- |
| `isFloating`           | `boolean` | Whether the player is in floating mode   |
| `isManualFloating`     | `boolean` | Whether floating was triggered manually  |
| `isVisible`            | `boolean` | Whether the container is in the viewport |
| `isMobile`             | `boolean` | Whether the viewport is mobile-sized     |
| `enableAutoFloatLocal` | `boolean` | Current auto-float setting               |

**Layout Context**

| Property             | Type      | Description                         |
| -------------------- | --------- | ----------------------------------- |
| `sidebarExpanded`    | `boolean` | Sidebar state (for bar positioning) |
| `sidebarWidth`       | `number`  | Sidebar width in pixels             |
| `assistenteExpanded` | `boolean` | Assistant panel state               |

**Handlers**

| Property                  | Type                          | Description                          |
| ------------------------- | ----------------------------- | ------------------------------------ |
| `togglePlay`              | `() => void`                  | Play/pause toggle                    |
| `toggleMute`              | `() => void`                  | Mute/unmute toggle                   |
| `handleSeek`              | `(value: number[]) => void`   | Seek to position (Slider-compatible) |
| `handleVolumeChange`      | `(value: number[]) => void`   | Change volume (Slider-compatible)    |
| `handleSetFloating`       | `(floating: boolean) => void` | Set floating state                   |
| `handleEnableManualFloat` | `() => void`                  | Trigger manual float                 |
| `setPlaybackSpeed`        | `(speed: number) => void`     | Change playback speed                |
| `resetAudio`              | `() => void`                  | Reset to beginning                   |

**Audio Element Event Handlers**

| Property           | Type          | Description                          |
| ------------------ | ------------- | ------------------------------------ |
| `onPlay`           | `() => void`  | Attach to `<audio onPlay>`           |
| `onPause`          | `() => void`  | Attach to `<audio onPause>`          |
| `onEnded`          | `() => void`  | Attach to `<audio onEnded>`          |
| `onTimeUpdate`     | `(e) => void` | Attach to `<audio onTimeUpdate>`     |
| `onLoadedMetadata` | `(e) => void` | Attach to `<audio onLoadedMetadata>` |

**Utilities**

| Property     | Type                       | Description                |
| ------------ | -------------------------- | -------------------------- |
| `formatTime` | `(time: number) => string` | Formats seconds as `MM:SS` |

### Usage

```tsx
import { useAudioPlayer } from 'xertica-ui/hooks';

function MyCustomPlayer({ src }: { src: string }) {
  const {
    audioRef,
    containerRef,
    isPlaying,
    togglePlay,
    currentTime,
    duration,
    formatTime,
    onPlay,
    onPause,
    onEnded,
    onTimeUpdate,
    onLoadedMetadata,
  } = useAudioPlayer({ src });

  return (
    <div ref={containerRef}>
      <audio
        ref={audioRef}
        src={src}
        onPlay={onPlay}
        onPause={onPause}
        onEnded={onEnded}
        onTimeUpdate={onTimeUpdate}
        onLoadedMetadata={onLoadedMetadata}
      />
      <button onClick={togglePlay}>{isPlaying ? 'Pause' : 'Play'}</button>
      <span>
        {formatTime(currentTime)} / {formatTime(duration)}
      </span>
    </div>
  );
}
```

> **Note**: For most use cases, use the `<AudioPlayer>` component directly. Use `useAudioPlayer` only when you need a fully custom player UI.

---

## `useLayoutShortcuts`

Registers global keyboard shortcuts for layout control. Must be called **once** in the root layout component.

**Source**: `components/hooks/use-layout-shortcuts.ts`

### Registered Shortcuts

| Shortcut           | Action         |
| ------------------ | -------------- |
| `Ctrl+B` / `Cmd+B` | Toggle sidebar |

### Usage

```tsx
import { useLayoutShortcuts } from 'xertica-ui/hooks';

function AppLayout({ children }: { children: React.ReactNode }) {
  useLayoutShortcuts(); // registers Ctrl+B for sidebar toggle
  return <div>{children}</div>;
}
```

> **Important**: Call this hook only once in your application. Calling it in multiple components will register duplicate event listeners.

---

## `useIsMobile` / `useMobile`

Detects whether the current viewport is mobile-sized (< 768px).

**Source**: `components/shared/use-mobile.ts`

### Return Value

Returns `boolean` — `true` if the viewport width is below the mobile breakpoint.

### Usage

```tsx
import { useIsMobile } from 'xertica-ui/hooks';

function ResponsiveComponent() {
  const isMobile = useIsMobile();
  return isMobile ? <MobileView /> : <DesktopView />;
}
```

> **Note**: Both `useIsMobile` and `useMobile` are exported and are identical. `useIsMobile` is the preferred name. See [`use-mobile.md`](./use-mobile.md) for the full reference.

---

## AI Rules

> [!IMPORTANT]
>
> - **Always use hooks inside providers**: All context hooks (`useTheme`, `useLanguage`, `useBrandColors`, `useAssistente`, `useApiKey`, `useLayout`) require `<XerticaProvider>` in the component tree. Calling them outside a provider throws an error.
> - **`useLayoutShortcuts` is singleton**: Call it exactly once in your root layout component, never in page-level or feature components.
> - **Prefer `<AudioPlayer>` over `useAudioPlayer`**: The headless hook is for advanced custom UIs only. For standard playback, use the component.
> - **`useLayout` for sidebar state**: Never manage sidebar width or expansion state manually. Always read from `useLayout()`.
