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

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

# Api

Lớp `Api` là công cụ trung tâm để thực hiện các yêu cầu HTTP. Nó cung cấp một giao diện đơn giản cho các phương thức phổ biến và quản lý các cài đặt toàn cục như URL cơ sở, tiêu đề mặc định và các hook vòng đời.

### Các khả năng chính

Lớp cung cấp một bộ công cụ toàn diện để quản lý tương tác mạng:
- **Các phương thức CRUD** — thực hiện các yêu cầu `GET`, `POST`, `PUT`, `PATCH`, `DELETE`.
- **Cấu hình toàn cục** — thiết lập URL cơ sở, tiêu đề và tham số.
- **Hook vòng đời** — xử lý các sự kiện trước và sau yêu cầu (ví dụ: cho Refresh Token).
- **Hydration (SSR)** — chuyển dữ liệu từ máy chủ sang máy khách để ngăn chặn các yêu cầu trùng lặp.
- **Bộ nhớ đệm (Caching)** — lưu các phản hồi trong bộ nhớ hoặc bộ nhớ ngoài (LocalStorage).
- **Giả lập phản hồi** — tạo dữ liệu giả để phát triển và kiểm thử.
- **Quản lý tải và lỗi** — tích hợp với các thành phần chỉ báo toàn cục và trung tâm lỗi.

## Cách dùng cơ bản

Cách đơn giản nhất để thực hiện yêu cầu là sử dụng các phương thức tĩnh `get`, `post`, `put`, hoặc `delete`. Tất cả các phương thức đều hỗ trợ định nghĩa kiểu dữ liệu (typing) thông qua Generic.

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

// Yêu cầu GET đơn giản với định nghĩa kiểu
const data = await Api.get<User[]>({ path: 'users' })

// Yêu cầu POST kèm dữ liệu trong body
const newUser = await Api.post<User>({
  path: 'users',
  request: { name: 'Nguyễn Văn A', job: 'Lập trình viên' }
})
```

## Cấu hình toàn cục

Bạn có thể cấu hình hành vi của API trên toàn hệ thống, ví dụ như khi khởi tạo ứng dụng. Việc này giúp loại bỏ sự cần thiết phải chỉ định các đường dẫn đầy đủ và các tiêu đề chung trong mỗi lần gọi.

```javascript
// Đặt URL cơ sở cho tất cả các đường dẫn tương đối
Api.setUrl('https://api.example.com/v1/')

// Thêm các tiêu đề chung (ví dụ: token xác thực)
Api.setHeaders({
  'Authorization': 'Bearer YOUR_TOKEN'
})

// Đặt các tham số chung sẽ được thêm vào mọi yêu cầu
Api.setRequestDefault({
  app_version: '1.0.0'
})
```

## Hook vòng đời

Thư viện cho phép bạn chèn logic toàn cục sẽ được kích hoạt cho mọi yêu cầu. Việc này rất hữu ích để thêm token động, ghi log hoặc xử lý tập trung các lỗi máy chủ.

### Hook chuẩn bị (setPreparation)

Được thực thi ngay trước khi yêu cầu được gửi đến máy chủ.

- **Tham số**: Nhận đối tượng `apiFetch`, chứa tất cả các tham số của yêu cầu hiện tại (đường dẫn, phương thức, dữ liệu, v.v.).
- **Sử dụng**: Kiểm tra quyền truy cập, thêm các tham số động vào `apiFetch.request`, hoặc đơn giản là ghi log bắt đầu một hoạt động.

```javascript
Api.setPreparation(async (apiFetch) => {
  // Bạn có thể sửa đổi apiFetch trước khi gửi
  console.log(`Bắt đầu yêu cầu đến: ${apiFetch.path}`)
})
```

### Hook kết thúc (setEnd)

Được thực thi sau khi máy chủ trả về phản hồi, nhưng trước khi dữ liệu đến mã của bạn.

- **Tham số**: Nhận đối tượng `Response` gốc và đối tượng cài đặt `apiFetch`.
- **Trả về**: Một Promise với đối tượng `{ reset?: boolean, data?: any }`.
- **Cờ reset**: Nếu trả về `true`, thư viện sẽ hủy chuỗi hiện tại và thực hiện lại yêu cầu. Việc này rất hữu ích để tự động xử lý token hết hạn (Refresh Token).
- **Trường data**: Cho phép bạn thay thế hoặc bổ sung dữ liệu được trả về bởi phương thức `Api.request`.

```javascript
Api.setEnd(async (response, apiFetch) => {
  // Ví dụ: xử lý lỗi 401 Unauthorized
  if (response.status === 401) {
    const isRefreshed = await myAuthService.refreshToken()
    
    if (isRefreshed) {
      return { reset: true } // Thử lại yêu cầu với token mới
    }
  }
  
  return {} // Tiếp tục xử lý tiêu chuẩn
})
```

## Hydration

Hydration cho phép chuyển dữ liệu thu được trong quá trình server-side rendering (SSR) sang phía máy khách. Điều này ngăn chặn việc thực hiện lại các yêu cầu tương tự ngay sau khi trang tải xong.

1. **Trên máy chủ**: Thực hiện các yêu cầu cần thiết và lấy script qua `Api.getHydrationScript()`.
2. **Trong mẫu (Template)**: Chèn chuỗi kết quả vào HTML.
3. **Trên máy khách**: Khi khởi tạo `Api`, dữ liệu sẽ tự động được tải vào trình quản lý phản hồi.

```javascript
// Ví dụ SSR
await Api.get({ path: 'settings' });
const hydrationScript = Api.getHydrationScript(); // <script>...</script>
```

## Bộ nhớ đệm (Caching)

Lớp `Api` hỗ trợ tự động lưu bộ nhớ đệm cho các phản hồi. Bạn có thể sử dụng bộ nhớ trong tích hợp hoặc kết nối bộ nhớ ngoài (ví dụ: `LocalStorage`).

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

// Kết nối bộ nhớ ngoài (tùy chọn)
ApiCache.init(
  async (key) => DataStorage.get(key),
  async (key, value) => DataStorage.set(key, value)
);

// Sử dụng trong một yêu cầu
const data = await Api.get({
  path: 'catalog/list',
  cache: 3600 // Lưu bộ nhớ đệm trong 1 giờ (tính bằng giây)
});
```

## Giả lập phản hồi

Trình quản lý `ApiResponse` tích hợp sẵn (có thể truy cập qua `Api.getResponse()`) cho phép bạn chặn các yêu cầu và trả về dữ liệu giả (mock). Điều này cực kỳ hữu ích cho việc phát triển giao diện khi backend chưa sẵn sàng hoặc để viết các bài kiểm thử.

### Đăng ký Mock

Bạn có thể thêm cả phản hồi tĩnh và các hàm động.

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

// 1. Phản hồi tĩnh cho yêu cầu GET
responseManager.add({
  path: 'profile',
  method: 'GET',
  response: { id: 1, name: 'Admin' },
  lag: true // Mô phỏng độ trễ mạng (0-2 giây)
})

// 2. Phản hồi động dựa trên các tham số yêu cầu
responseManager.add({
  path: 'users/search',
  method: 'GET',
  response: (request) => {
    return {
      results: [`Kết quả cho: ${request.query}`],
      count: 1
    }
  }
})

// 3. Chặn yêu cầu POST với bất kỳ body nào
responseManager.add({
  path: 'users/update',
  method: 'POST',
  request: '*any', // Bỏ qua việc kiểm tra body yêu cầu
  response: { success: true }
})
```

### Chế độ phát triển

Để gỡ lỗi dễ dàng hơn, bạn có thể bật tính năng ghi nhận (logging) giả lập trong console. Trong chế độ này, trình quản lý sẽ cảnh báo trong console về mỗi yêu cầu bị chặn.

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

Trình giả lập sẽ kích hoạt **trước khi** lệnh `fetch` thực sự được thực hiện. Nếu không tìm thấy mock phù hợp, `Api` sẽ thực hiện một yêu cầu mạng thông thường.
