# Hàm tiện ích (Utilities)

## Tổng quan

Thư mục `src/app/lib/` chứa các hàm tiện ích dùng chung trong toàn bộ dự án AutoShopee Kit. Các hàm được tổ chức theo nhóm chức năng:

```
src/app/lib/
├── content.js              # Xử lý URL trang web
├── get.js                  # Lấy thông tin trình duyệt/thiết bị
├── match.js                # Trích xuất thông tin từ URL các nền tảng
├── utils/
│   ├── utils.js            # Hàm xử lý chung (mảng, object, chuỗi)
│   ├── str.js              # Định dạng số và tính toán phần trăm
│   ├── crypt.js            # Mã hóa/giải mã dữ liệu
│   ├── moment.js           # Xử lý ngày giờ
│   ├── excel.js            # Xuất file Excel (popup/options)
│   ├── sheet.js            # Xuất file Excel (content script)
│   └── save.js             # Nén file ZIP và chuyển đổi base64
└── pdf/
    ├── api.js              # Tạo, gộp, watermark PDF
    ├── service.js          # PdfService class - chỉnh sửa PDF nâng cao
    ├── parser.js           # Trích xuất text từ PDF (pdfjs-dist)
    └── worker.js           # Trích xuất text PDF trong content script
```

---

## Xử lý chung (`lib/utils/utils.js`)

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `sortArrayRand(array)` | `array: Array` | `Array` | Xáo trộn ngẫu nhiên thứ tự các phần tử trong mảng |
| `contentPhoneReplace(str, phoneNumber)` | `str: string`, `phoneNumber: string` | `string` | Thay thế tất cả số điện thoại (9-11 chữ số) trong chuỗi bằng số điện thoại mới |
| `filterObject(obj, listKey)` | `obj: Object`, `listKey: string[]` | `Object` | Lọc object chỉ giữ lại các key nằm trong danh sách cho phép |
| `ignoreObject(obj, listIgnore)` | `obj: Object`, `listIgnore: string[]` | `Object` | Lọc object bỏ đi các key nằm trong danh sách loại trừ |
| `parseBody(body)` | `body: Object` (webRequest body) | `Object \| null` | Parse body từ webRequest (ArrayBuffer) thành JSON object |
| `genUuid()` | -- | `string` | Tạo UUID v4 duy nhất |
| `cloneObject(obj)` | `obj: Object` | `Object` | Deep clone object bằng JSON serialize/deserialize |
| `extractKeywordLine(input)` | `input: string` | `Array<{keyword, price}>` | Trích xuất danh sách keyword-price từ chuỗi nhiều dòng. Hỗ trợ phân tách bằng tab, dấu phẩy, chấm phẩy, pipe, hai chấm |

---

## Định dạng số (`lib/utils/str.js`)

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `addCommas(num, unit)` | `num: number`, `unit: string` (mặc định `''`) | `string` | Định dạng số với dấu phẩy phân cách hàng nghìn, kèm đơn vị. Ví dụ: `addCommas(1000000, 'đ')` => `"1,000,000 đ"` |
| `calcPercentage(value, total, decimal)` | `value: number`, `total: number`, `decimal: number` (mặc định `0`) | `number` | Tính phần trăm `(value / total) * 100`, làm tròn theo số thập phân chỉ định |
| `calcDiscount(price, promotion, decimal)` | `price: number`, `promotion: number`, `decimal: number` (mặc định `0`) | `number` | Tính phần trăm giảm giá `((price - promotion) / price) * 100` |

---

## Mã hóa dữ liệu (`lib/utils/crypt.js`)

Sử dụng thư viện `crypto-js` (AES) và `sqids` (mã hóa số).

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `enCryptApiData(data)` | `data: string` | `string` | Mã hóa chuỗi bằng AES với khóa API nội bộ |
| `deCryptApiData(data)` | `data: string` | `string` | Giải mã chuỗi AES với khóa API nội bộ |
| `encryptNumber(number)` | `number: number` | `string` | Mã hóa số thành chuỗi Sqids (độ dài tối thiểu 36 ký tự) |
| `decryptNumber(str)` | `str: string` | `number` | Giải mã chuỗi Sqids thành số gốc |

---

## Xử lý ngày giờ (`lib/utils/moment.js`)

Wrapper trên thư viện `moment.js` với locale tiếng Việt (`vi`). Export default là instance `moment` đã cấu hình locale.

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `getDateOnly(input)` | `input: any` (mặc định `null` = hiện tại) | `string` | Trả về ngày theo format `YYYY-MM-DD` |
| `getYearOnly(input)` | `input: any` | `string` | Trả về năm theo format `YYYY` |
| `getMonthOnly(input)` | `input: any` | `string` | Trả về tháng theo format `YYYY-MM` |
| `getTimeOnly(input)` | `input: any` | `string` | Trả về giờ theo format `HH:mm:ss` |
| `getDateTime(input)` | `input: any` | `string` | Trả về ngày giờ đầy đủ theo format `YYYY-MM-DD HH:mm:ss` |

---

## Xuất file Excel

### Phiên bản Popup/Options (`lib/utils/excel.js`)

Sử dụng thư viện `xlsx` và `file-saver`.

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `exportSheet(rows, sheetName, fileName)` | `rows: Array`, `sheetName: string` (mặc định `'Data'`), `fileName: string` (mặc định `'Data.xlsx'`) | `void` | Xuất mảng dữ liệu thành file Excel (.xlsx) và tải xuống qua trình duyệt |

### Phiên bản Content Script (`lib/utils/sheet.js`)

Phiên bản dành cho content script, tạo link download thay vì dùng `file-saver`.

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `exportSheet(listData, fileName)` | `listData: Array`, `fileName: string` (mặc định `'sheet'`) | `void` | Xuất mảng dữ liệu thành file Excel (.xlsx) và tải xuống bằng cách tạo thẻ `<a>` ẩn |

---

## Lưu file và nén ZIP (`lib/utils/save.js`)

Sử dụng thư viện `jszip`, `file-saver`, và `fetch-base64-in-browser`.

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `zipFileBase64(listFile, fileName)` | `listFile: Array<{fileName, base64}>`, `fileName: string` | `Promise<void>` | Nén danh sách file (dạng base64) thành file ZIP và tải xuống |
| `getBase64Url(url)` | `url: string` | `Promise<string>` | Tải hình ảnh từ URL và trả về dạng base64 (không bao gồm prefix data URI) |
| `saveAs` | -- | -- | Re-export từ `file-saver` |

---

## Xử lý URL trang web (`lib/content.js`)

Sử dụng thư viện `query-string`.

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `getCurrentUrl()` | -- | `string` | Trả về URL hiện tại của trang (`window.location.href`) |
| `parseCurrentUrl()` | -- | `Object` | Parse URL hiện tại thành object chứa `url` và `query` parameters |

---

## Thông tin trình duyệt và thiết bị (`lib/get.js`)

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `getBrowserAgent()` | -- | `string` | Trả về chuỗi User-Agent của trình duyệt |
| `getBrowserName()` | -- | `string` | Nhận diện tên trình duyệt: `Chrome`, `Firefox`, `Edge`, `Opera`, `Safari`, hoặc `Unknown` |
| `getPlatformInfo()` | -- | `Promise<Object>` | Lấy thông tin nền tảng hệ điều hành qua API extension |
| `getIpAddress()` | -- | `Promise<string>` | Lấy địa chỉ IP công cộng từ server AutoShopee |
| `getHashProfileId(input)` | `input: string` | `Promise<string>` | Tạo hash SHA-256 rút gọn (6 cặp hex viết hoa, phân cách bằng dấu gạch ngang). Ví dụ: `"AB-CD-EF-12-34-56"` |
| `getFingerprintId()` | -- | `Promise<string>` | Tạo fingerprint ID duy nhất cho trình duyệt bằng FingerprintJS |

---

## Khớp URL nền tảng (`lib/match.js`)

Các hàm trích xuất thông tin (product ID, order ID, job ID) từ URL của các nền tảng thương mại điện tử. Trả về `null` nếu URL không khớp pattern.

### Shopee

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `shopeeUrlMatchProductId(url)` | `url: string` | `{shopId, itemId, productId} \| null` | Trích xuất shop ID và product ID từ URL sản phẩm Shopee (hỗ trợ cả URL dạng slug và dạng ID) |
| `shopeeUrlMatchOrderId(url)` | `url: string` | `{orderId} \| null` | Trích xuất order ID từ URL trang chi tiết đơn hàng Shopee seller |
| `shopeeUrlMatchSellerPrint(url)` | `url: string` | `{jobUrl, from, jobId, shopId} \| null` | Trích xuất thông tin in phiếu giao hàng từ URL trang in Shopee (`banhang.shopee.vn/awbprint`) |

### Lazada

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `lazadaUrlMatchSellerPrint(url)` | `url: string` | `{jobUrl, from, jobId} \| null` | Trích xuất thông tin in phiếu giao hàng từ URL trang in Lazada (`sellercenter.lazada.vn/apps/order/print`) |

### TikTok

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `tiktokUrlMatchSellerPrint(url)` | `url: string` | `{jobUrl, jobId, from} \| null` | Trích xuất thông tin in phiếu giao hàng từ URL trang in TikTok Shop |
| `tiktokUrlMatchProductId(url)` | `url: string` | `{productId} \| null` | Trích xuất product ID từ URL sản phẩm TikTok Shop (`tiktok.com/pdp/{id}`) |

---

## Xử lý PDF

### Các hàm API cơ bản (`lib/pdf/api.js`)

Sử dụng thư viện `pdf-lib` và `downloadjs`.

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `createPdf(text)` | `text: string` | `Promise<Buffer>` | Tạo PDF mới với nội dung text, font Times Roman cỡ 10 |
| `saveAsPdf(fileBuffer, fileName)` | `fileBuffer: Buffer`, `fileName: string` | `Promise` | Tải file PDF xuống máy người dùng |
| `getPdfBuffer(fileUrl)` | `fileUrl: string` | `Promise<Blob>` | Tải PDF từ URL và trả về dạng Blob |
| `getUrlBlobPdf(pdfBytes)` | `pdfBytes: Buffer` | `string` | Tạo Blob URL từ buffer PDF để hiển thị preview |
| `addTextPdf(pdfBuffer, text)` | `pdfBuffer: Buffer`, `text: string` | `Promise<Buffer>` | Thêm text vào góc dưới trái mỗi trang PDF, font Helvetica cỡ 20 |
| `mergeListPdf(listPdfBuffer)` | `listPdfBuffer: Buffer[]` | `Promise<Buffer>` | Gộp nhiều file PDF thành một file duy nhất (theo thứ tự) |
| `wartermarkPdf(pdfBuffer, imageBuffer, options)` | `pdfBuffer: Buffer`, `imageBuffer: Buffer` (PNG), `options: Object` | `Promise<Buffer>` | Thêm watermark hình ảnh PNG vào mỗi trang PDF |
| `getMetaPdf(pdfBuffer)` | `pdfBuffer: Buffer` | `Promise<{pageCount}>` | Lấy metadata cơ bản của PDF (số trang) |
| `reRenderPdf(pdfBuffer, options)` | `pdfBuffer: Buffer`, `options: {text, size, marginX, marginY, rotation}` | `Promise<Buffer>` | Thêm text tùy chỉnh vào mỗi trang PDF kèm số trang, hỗ trợ xoay chữ |

### PdfService class (`lib/pdf/service.js`)

Class quản lý PDF với khả năng thêm nội dung overlay (text và hình ảnh) và footer.

| Phương thức | Tham số | Trả về | Mô tả |
|-------------|---------|--------|-------|
| `constructor(pdfBytes, pdfFile)` | `pdfBytes: ArrayBuffer`, `pdfFile: File` | -- | Khởi tạo service với buffer và file PDF |
| `loadPdfFromFile(file)` | `file: File` | `Promise<boolean>` | Tải PDF từ File object |
| `loadPdfFromUrl(url)` | `url: string` | `Promise<boolean>` | Tải PDF từ URL |
| `getPositionForText(position, pageWidth, pageHeight, fontSize, textLength)` | `position: string`, `pageWidth: number`, `pageHeight: number`, `fontSize: number`, `textLength: number` | `{x, y} \| null` | Tính tọa độ vẽ text theo vị trí đặt tên (9 vị trí: `top-left`, `top-center`, `top-right`, `middle-left`, `middle-center`, `middle-right`, `bottom-left`, `bottom-center`, `bottom-right`) |
| `getPositionForImage(position, pageWidth, pageHeight, imageWidth, imageHeight)` | Tương tự trên + kích thước ảnh | `{x, y} \| null` | Tính tọa độ vẽ hình ảnh theo vị trí đặt tên |
| `addConfigContent(configContent)` | `configContent: PrintPdfConfigOverlay` | `Promise<Buffer>` | Thêm text và/hoặc hình ảnh overlay vào tất cả trang PDF theo cấu hình |
| `addFooterContent(sessionPrint)` | `sessionPrint: PrintSession` | `Promise<Buffer>` | Thêm footer text vào mỗi trang PDF, hỗ trợ đánh số trang và timestamp |
| `getPdfPreviewUrl(pdfInput)` | `pdfInput: Buffer` (tùy chọn) | `string` | Tạo Blob URL để preview PDF |
| `getPdfFile()` | -- | `File` | Trả về file PDF hiện tại |
| `getPdfBytes()` | -- | `ArrayBuffer` | Trả về buffer PDF hiện tại |
| `setPdfBytes(pdfBuffer)` | `pdfBuffer: Buffer` | `Buffer` | Cập nhật buffer PDF |
| `resetOriginal()` | -- | `void` | Khôi phục về PDF gốc ban đầu |
| `base64ToFile(base64, filename)` | `base64: string`, `filename: string` | `File` | Chuyển đổi chuỗi base64 (data URI) thành File object |

### Trích xuất text từ PDF (`lib/pdf/parser.js`)

Sử dụng thư viện `pdfjs-dist` (PDF.js).

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `parsePdfBuffer(pdfBuffer, options)` | `pdfBuffer: ArrayBuffer\|Uint8Array`, `options: {maxPages, includeMetadata}` | `Promise<{text, pages, totalPages, parsedPages, metadata}>` | Trích xuất text đầy đủ từ PDF buffer, hỗ trợ giới hạn số trang và lấy metadata (title, author, subject, creator, producer, ngày tạo/sửa) |
| `parsePdfToText(pdfBuffer, maxPages)` | `pdfBuffer: ArrayBuffer\|Uint8Array`, `maxPages: number` | `Promise<string>` | Trích xuất toàn bộ text từ PDF thành một chuỗi |
| `parsePdfPageToText(pdfBuffer, pageNumber)` | `pdfBuffer: ArrayBuffer\|Uint8Array`, `pageNumber: number` | `Promise<string>` | Trích xuất text từ một trang cụ thể của PDF |

### Worker PDF cho Content Script (`lib/pdf/worker.js`)

Phiên bản trích xuất text PDF dành riêng cho content script, sử dụng PDF.js được bundle sẵn trong extension.

| Hàm | Tham số | Trả về | Mô tả |
|-----|---------|--------|-------|
| `parsePdfBufferToText(pdfBuffer)` | `pdfBuffer: ArrayBuffer\|Uint8Array` | `Promise<string>` | Trích xuất toàn bộ text từ PDF buffer (chạy trong content script, không dùng web worker) |

---

## Cách sử dụng

### Import hàm tiện ích

```js
// Hàm xử lý chung
import { cloneObject, genUuid, filterObject } from '@/lib/utils/utils';

// Định dạng số
import { addCommas, calcPercentage } from '@/lib/utils/str';

// Mã hóa
import { enCryptApiData, deCryptApiData } from '@/lib/utils/crypt';

// Ngày giờ
import moment, { getDateOnly, getDateTime } from '@/lib/utils/moment';

// Xuất Excel (trong popup/options)
import { exportSheet } from '@/lib/utils/excel';

// Xuất Excel (trong content script)
import { exportSheet } from '@/lib/utils/sheet';

// Lưu file
import { zipFileBase64, getBase64Url, saveAs } from '@/lib/utils/save';

// Thông tin trình duyệt
import { getBrowserName, getFingerprintId } from '@/lib/get';

// Khớp URL
import { shopeeUrlMatchProductId, lazadaUrlMatchSellerPrint } from '@/lib/match';

// PDF
import { mergeListPdf, createPdf } from '@/lib/pdf/api';
import { PdfService } from '@/lib/pdf/service';
import { parsePdfToText } from '@/lib/pdf/parser';
```

### Ví dụ thường gặp

```js
// Định dạng giá tiền
addCommas(1500000, 'đ');  // "1,500,000 đ"

// Tính giảm giá
calcDiscount(200000, 150000);  // 25

// Clone object an toàn
const copy = cloneObject(originalData);

// Trích xuất product ID từ URL Shopee
const result = shopeeUrlMatchProductId('https://shopee.vn/product/123/456');
// { shopId: '123', itemId: '456', productId: '456' }

// Gộp nhiều PDF
const merged = await mergeListPdf([pdfBuffer1, pdfBuffer2]);

// Trích xuất text từ PDF
const text = await parsePdfToText(pdfBuffer);
```
