# Media Service v2 (l-min-components)

This hook is part of the `l-min-components` package. It assumes the consuming
app uses the shared axios config and cookies set by `AppMainLayout` for account
context.

## API Reference

Base URL: `<API_BASE>/media/v2`

### Authentication
- `Authorization: Bearer <token>` (handled by axios config)
- `X-Account: <account_id>` (or `_account` query param)

### Endpoint Prefixes
- Personal: `/files/`
- Instructor: `/instructor/files/`
- Enterprise: `/enterprise/files/`
- Instructor (enterprise context): `/instructor/{enterprise_id}/files/`

## Import (consuming apps)

```js
import { useMediaServiceV2 } from "l-min-components/hooks";
```

## Context Rules

Default context comes from cookies:
- `defaultAccountType` → personal/instructor/enterprise
- `defaultAccountID` → `_account` query param
- `affiliateAccount` → enterprise id for instructor affiliates

Instructor variants like `instructor-affiliate` normalize to `instructor`.

Override context at hook init or per call:

```js
const media = useMediaServiceV2({
  accountType: "instructor",
  enterpriseAccountId: affiliateAccount?.id,
});

// Or per-call override
await media.listFiles({
  context: { accountType: "enterprise", accountId: "<account_id>" },
});
```

## Upload Flow (Presigned)

### 1. Create record(s) + get presigned upload URL(s)

```js
const { data } = await media.createFiles({
  tag: "lesson",
  caption: "Lesson 1",
  duration: "00:01:30",
  lecture_id: "<lecture_id>",
  files: [
    {
      name: file.name,
      mimetype: file.type,
      size: file.size,
      upload_type: "put", // or "multipart" for large files
    },
  ],
});

// Response shape:
// {
//   results: [{
//     file_id: "123",
//     object_key: "media/media/videos/.../intro.mp4",
//     upload_type: "put",
//     upload_url: "https://...presigned...",
//     upload_urls: null,
//     upload_id: null,
//     required_headers: { "Content-Type": "video/mp4" },
//     expires_at: "2025-01-01T12:00:00Z",
//     upload_complete_url: "<API_BASE>/media/v2/files/123/confirm_upload/"
//   }]
// }
```

### 2. Upload to presigned URL

```js
const upload = data.results[0];

// Single PUT upload
await media.uploadToPresignedUrl({
  url: upload.upload_url,
  file,
  headers: upload.required_headers,
});
```

### 3. Confirm upload (triggers processing)

```js
await media.confirmUpload(upload.file_id, { upload_type: "put" });

// Response: { uploaded: true }
```

## Multipart Upload Flow

For large files, the server returns `upload_urls` array instead of single `upload_url`:

```js
const upload = createRes.data.results[0];

// Upload all parts
const parts = await media.uploadMultipartParts({
  file,
  uploadUrls: upload.upload_urls,
  partSize: 5 * 1024 * 1024, // must match server-minted part size
  headers: upload.required_headers,
});

// Confirm with parts info
await media.confirmUpload(upload.file_id, {
  upload_type: "multipart",
  upload_id: upload.upload_id,
  parts, // [{ ETag: "\"etag1\"", PartNumber: 1 }, ...]
});
```

## Refresh Presigned URLs

URLs expire after 15 minutes (check `expires_at`). Refresh before expiry or after 403:

```js
const { data } = await media.presignUploadUrls(upload.file_id, {
  upload_type: "multipart",
  upload_id: upload.upload_id,
  part_numbers: [1, 2, 3],
});

// Response shape (same as create):
// {
//   file_id: "123",
//   object_key: "...",
//   upload_type: "multipart",
//   upload_url: null,
//   upload_urls: [{ part_number: 1, url: "https://..." }, ...],
//   upload_id: "<upload_id>",
//   required_headers: {},
//   expires_at: "2025-01-01T12:00:00Z",
//   upload_complete_url: "..."
// }
```

## Playback (Streaming)

### Start playback

```js
const streamUrl = media.buildStreamUrl(fileId);
// GET /files/{id}/stream/
// - If processing NOT complete: 302 → presigned original file URL
// - If HLS ready: 302 → index.m3u8
```

### HLS manifest

The `index.m3u8` response contains:
- 200 with HLS playlist text
- Segment URLs are absolute presigned URLs
- Nested playlists point back to `/stream/` URLs

### Segment or nested playlist

```js
const segmentUrl = media.buildStreamPathUrl(fileId, "segment.ts");
// GET /files/{id}/stream/{path}
// - If segment: 302 → presigned segment URL
// - If .m3u8: rewritten manifest
```

## Download Original

```js
const downloadUrl = media.buildDownloadUrl(fileId);
// GET /files/{id}/download/
// Returns 302 → presigned URL for original upload
```

## Standard CRUD

```js
// List files
const { data } = await media.listFiles({ params: { tag: "lesson" } });

// Get single file
const { data } = await media.getFile(fileId);

// Update file
await media.updateFile(fileId, { caption: "Updated caption" });

// Delete file
await media.deleteFile(fileId);
```

## Refreshing Presigned URLs (Summary)

### Upload URLs
- Watch `expires_at` from create response
- If near expiry or 403 from S3: call `presignUploadUrls()`

### Progressive playback (non-HLS)
- Re-hit `GET /files/{id}/stream/` for fresh presigned URL

### HLS playback
- Re-request `index.m3u8` for manifest with fresh segment URLs
- If segment returns 403, refresh manifest and continue

### Images
- Use `resolvePresignedUrl(fileId)` to follow the stream redirect and get the presigned URL
- Or use the `usePresignedImage` hook for automatic caching and refresh

## Resolving Presigned URLs for Images

Images always return a 302 redirect from the stream endpoint to the presigned URL.
Use `resolvePresignedUrl()` to follow the redirect and get the actual URL:

```js
// In useMediaServiceV2
const presignedUrl = await media.resolvePresignedUrl(fileId);
```

Or use the dedicated `usePresignedImage` hook:

```jsx
import { usePresignedImage } from "l-min-components/hooks";

const MyImageComponent = ({ fileId }) => {
  const { imageUrl, loading, error, refresh } = usePresignedImage(fileId, {
    accountType: "instructor",
    enterpriseAccountId: affiliateId,
    fallbackUrl: "/placeholder.png",
    autoRefresh: true, // Auto-refresh before expiry
  });

  if (loading) return <Spinner />;
  if (error) return <ErrorMessage />;
  
  return <img src={imageUrl} alt="..." />;
};
```

## Type Definitions

All types are documented via JSDoc in the source file. Key types:

- `CreateFilesPayload` - Request for creating files
- `CreateFilesResponse` - Response with upload results
- `UploadResult` - Single file upload info with presigned URLs
- `ConfirmUploadPayload` - Request for confirming upload
- `MediaFile` - File record from list/retrieve
- `ListFilesResponse` - Paginated file list

## Notes

- `uploadToPresignedUrl` and `uploadMultipartParts` talk directly to S3/Spaces.
  Do not attach auth headers; only send `required_headers`.
- The hook does not manage UI state. Keep progress/error state in your component.
- Presigned URLs default to 15 minute expiry.
