import {Meta} from '@storybook/addon-docs/blocks'

<Meta title='@dxtmisha/ru/functional-basic/# Api'/>

# Api

Класс `Api` — это основной инструмент для работы с HTTP-запросами. Он предоставляет простой интерфейс для стандартных методов и управляет глобальными настройками, такими как базовый URL, заголовки по умолчанию и хуки жизненного цикла.

### Основные возможности

Класс предоставляет полный набор инструментов для управления сетевым взаимодействием:
- **CRUD методы** — выполнение `GET`, `POST`, `PUT`, `PATCH`, `DELETE` запросов.
- **Глобальная конфигурация** — настройка базовых URL, источников (origin), заголовков и параметров.
- **Хуки жизненного цикла** — обработка событий до и после запроса (например, для Refresh Token).
- **Гидратация (SSR)** — передача данных от сервера к клиенту для предотвращения дублирования запросов.
- **Кэширование** — сохранение ответов в памяти или внешнем хранилище (LocalStorage).
- **Эмуляция ответов** — создание мок-данных для разработки и тестирования.
- **Автоматическая локализация** — замена меток `{locale}`, `{country}` и `{language}` в путях запросов.
- **Управление загрузкой и ошибками** — интеграция с глобальными компонентами индикации и центрами ошибок.

## Базовое использование

Самый простой способ выполнить запрос — использовать статические методы `get`, `post`, `put`, или `delete`. Все методы поддерживают типизацию ответа через Generic.

```javascript
import { Api } from '@dxtmisha/functional-basic'

// Простой GET запрос с типизацией
const data = await Api.get<User[]>({ path: 'users' })

// POST запрос с отправкой данных в теле
const newUser = await Api.post<User>({
  path: 'users',
  request: { name: 'Иван Иванов', job: 'Разработчик' }
})
```

## Глобальная конфигурация

Вы можете настроить поведение API глобально, например, при инициализации приложения. Это избавит от необходимости указывать полные пути и общие заголовки в каждом вызове.

```javascript
// Установка базового URL для всех относительных путей
Api.setUrl('/api/')

// Установка источника (протокола и домена) для базового URL
// Если urlRoot начинается с '/', к нему будет добавлен origin
Api.setOrigin('https://api.example.com')

// Добавление общих заголовков (например, токен авторизации)
Api.setHeaders({
  'Authorization': 'Bearer YOUR_TOKEN'
})

// Установка общих параметров, которые будут добавлены к каждому запросу
Api.setRequestDefault({
  app_version: '1.0.0'
})

// Проверка, работает ли сервер на localhost
if (Api.isLocalhost()) {
  console.log('Работаем локально')
}
```

### Метод setConfig

Позволяет установить все основные настройки одним объектом.

```javascript
Api.setConfig({
  urlRoot: '/api/',
  origin: 'https://api.example.com',
  headers: { 'Authorization': 'Bearer ...' },
  timeout: 5000,
  devMode: true
})
```

## Работа с URL

При формировании итогового URL класс автоматически заменяет специальные метки на текущие значения из настроек локализации (`Geo`):
- `{locale}` — текущая локаль (например, `ru-RU`).
- `{country}` — текущая страна (например, `ru`).
- `{language}` — текущий язык (например, `ru`).

```javascript
// Если текущая локаль 'ru-RU', итоговый путь будет '/api/ru-RU/users'
const data = await Api.get({ path: '{locale}/users' })
```

## Статус и мета-информация

Класс `Api` автоматически извлекает метаданные из ответа сервера (например, `success`, `status`, `message`, `code`) и объединяет их с вашим объектом данных. Это позволяет получать и полезную нагрузку, и результат выполнения в одном объекте.

Вы можете получить полный статус последнего запроса через метод `getStatus()`, который возвращает экземпляр `ApiStatus`.

```javascript
const data = await Api.get({ path: 'users' })
const status = Api.getStatus()

console.log(status.getStatus())     // 200 (HTTP статус)
console.log(status.getStatusType()) // 'success' | 'error' | 'warning' | 'info'
console.log(status.getMessage())    // Сообщение от сервера или текст статуса
console.log(status.getResponse())   // Исходное тело ответа после парсинга
```

## Идентификация ошибок

Библиотека предоставляет продвинутую систему управления ошибками через `ApiError`. Вы можете заранее зарегистрировать известные паттерны ошибок API, и система автоматически идентифицирует их на основе статуса, кодов или кастомных функций валидации.

```javascript
import { ApiError, ApiMethodItem } from '@dxtmisha/functional-basic'

// Регистрация конкретного паттерна ошибки
ApiError.add({
  url: 'users/login',
  method: ApiMethodItem.post,
  code: 'INVALID_CREDENTIALS',
  message: 'Пожалуйста, проверьте логин и пароль.'
})

// При выполнении запроса
const status = Api.getStatus()
const errorItem = status.get()?.errorObject // Экземпляр ApiErrorItem

if (errorItem && errorItem.getCode() === 'INVALID_CREDENTIALS') {
  // Обработка конкретного случая ошибки
}
```

## Хуки жизненного цикла

Библиотека позволяет внедрить глобальную логику, которая будет срабатывать для каждого запроса. Это удобно для динамического добавления токенов, логирования или централизованной обработки ошибок сервера.

### Хук подготовки (setPreparation)

Выполняется непосредственно перед отправкой запроса на сервер.

- **Параметры**: получает объект `apiFetch`, содержащий все настройки текущего запроса (путь, метод, данные и т.д.).
- **Применение**: проверка прав доступа, добавление динамических параметров в `apiFetch.request` или простое логирование начала операции.

```javascript
Api.setPreparation(async (apiFetch) => {
  // Можно изменить apiFetch перед отправкой
  console.log(`Запуск запроса: ${apiFetch.path}`)
})
```

### Хук завершения (setEnd)

Выполняется после того, как сервер вернул ответ, но до того, как данные попадут в ваш код.

- **Параметры**: получает нативный объект `Response` и объект настроек `apiFetch`.
- **Возвращает**: Promise с объектом `{ reset?: boolean, data?: any }`.
- **Флаг reset**: если вернуть `true`, библиотека прервет текущую цепочку и выполнит запрос заново. Это полезно для автоматического обновления истекшего токена (Refresh Token).
- **Поле data**: позволяет подменить или дополнить данные, которые вернет метод `Api.request`.

```javascript
Api.setEnd(async (response, apiFetch) => {
  // Например, обработка ошибки 401 (Unauthorized)
  if (response.status === 401) {
    const isRefreshed = await myAuthService.refreshToken()
    
    if (isRefreshed) {
      return { reset: true } // Повторить запрос с новым токеном
    }
  }
  
  return {} // Продолжить стандартную обработку
})
```

## Гидратация

Гидратация позволяет передавать данные, полученные при серверном рендеринге (SSR), на клиентскую сторону. Это предотвращает повторное выполнение одних и тех же запросов сразу после загрузки страницы.

1. **На сервере**: выполните нужные запросы и получите скрипт через `Api.getHydrationScript()`.
2. **В шаблоне**: вставьте полученную строку в HTML.
3. **На клиенте**: при инициализации `Api` данные будут автоматически загружены в менеджер ответов.

```javascript
// Пример на сервере (SSR)
await Api.get({ path: 'settings' });
const hydrationScript = Api.getHydrationScript(); // <script>...</script>
```

## Кэширование

Класс `Api` поддерживает автоматическое кэширование ответов. Вы можете использовать как встроенное хранилище в памяти, так и подключить внешнее (например, `LocalStorage`).

```javascript
import { ApiCache, DataStorage } from '@dxtmisha/functional-basic';

// Подключение внешнего хранилища (необязательно)
ApiCache.init(
  async (key) => DataStorage.get(key),
  async (key, value) => DataStorage.set(key, value)
);

// Использование в запросе
const data = await Api.get({
  path: 'catalog/list',
  cache: 3600 // Кэшировать на 1 час (в секундах)
});
```

## Эмуляция ответов

Встроенный менеджер `ApiResponse` (доступен через `Api.getResponse()`) позволяет перехватывать запросы и возвращать мок-данные. Это крайне полезно при разработке интерфейса, когда бэкенд еще не готов, или для написания тестов.

### Регистрация моков

Вы можете добавлять как статические ответы, так и динамические функции.

```javascript
const responseManager = Api.getResponse()

// 1. Статический ответ на GET запрос
responseManager.add({
  path: 'profile',
  method: 'GET',
  response: { id: 1, name: 'Admin' },
  lag: true // Имитация задержки сети (0-2 сек)
})

// 2. Динамический ответ на основе параметров запроса
responseManager.add({
  path: 'users/search',
  method: 'GET',
  response: (request) => {
    return {
      results: [`Результат для: ${request.query}`],
      count: 1
    }
  }
})

// 3. Перехват POST запроса с любым телом
responseManager.add({
  path: 'users/update',
  method: 'POST',
  request: '*any', // Игнорировать проверку тела запроса
  response: { success: true }
})
```

### Режим разработки

Для удобства отладки можно включить логирование эмуляции в консоль. В этом режиме менеджер будет предупреждать в консоли о каждом перехваченном запросе.

```javascript
Api.getResponse().setDevMode(true)
```

Эмулятор срабатывает **до** выполнения реального `fetch`. Если подходящий мок не найден, `Api` выполнит обычный сетевой запрос.

## Продвинутое использование: ApiInstance

Класс `Api` является **статическим прокси** для глобального экземпляра `ApiInstance`. Этот экземпляр хранится в `ServerStorage`, что обеспечивает изоляцию между запросами при SSR и поведение синглтона на клиенте.

Для продвинутых сценариев — например, при необходимости работы с несколькими независимыми API одновременно или создания временных изолированных окружений — вы можете создавать экземпляры `ApiInstance` напрямую.

```javascript
import { ApiInstance } from '@dxtmisha/functional-basic'

const secondaryApi = new ApiInstance()
secondaryApi.setUrl('https://another-api.com/')
secondaryApi.setHeaders({ 'X-Client-Id': '...' })

const data = await secondaryApi.get({ path: 'resource' })
```
