# Bongo Image Package - Cursor Rules

## Project Overview

This is a Laravel package that provides comprehensive image management functionality including:
- Image uploads and storage with multiple types (avatar, cover_image, upload, wysiwyg)
- On-the-fly image manipulation (resize, crop, fit) using Intervention Image
- Cached image generation with presets
- Polymorphic relationships via `HasImages` trait
- Base64 image extraction from WYSIWYG content
- RESTful APIs for frontend, backend, and API routes

**Key Dependencies:**
- `bongo/framework` (^3.0) - Provides `AbstractServiceProvider` and base classes
- `intervention/image` (^2.6) - Image manipulation library
- PHP 8.2+ and Laravel 10+

## Package Structure

```
src/
├── Commands/
│   └── UpdateDimensionsCommand.php    # Updates missing width/height on images
├── Config/
│   └── image.php                      # Configuration (paths, presets, quality)
├── Exceptions/                        # Custom exceptions
├── Http/
│   ├── Controllers/
│   │   ├── Api/ImageController.php
│   │   ├── Backend/ImageController.php
│   │   └── Frontend/ImageController.php
│   └── Requests/
├── Interfaces/
│   └── Imageable.php                  # Interface for models with images
├── Migrations/                        # images & imageables tables
├── Models/
│   ├── Image.php                      # Main image model
│   └── Imageable.php                  # Pivot model
├── Routes/
│   ├── api.php                        # API routes (auth:sanctum)
│   ├── backend.php                    # Admin routes (auth + employee)
│   └── frontend.php                   # Public image delivery
├── Services/
│   ├── AvatarImage.php               # Single avatar upload (replaces existing)
│   ├── BuilderService.php            # Extracts base64 images from HTML
│   ├── CoverImage.php                # Single cover image upload (replaces existing)
│   ├── ImageManipulator.php          # Intervention Image wrapper
│   ├── ImagePlaceholder.php          # Generates default placeholder images
│   └── WysiwygImage.php              # Wysiwyg editor uploads
├── Traits/
│   └── HasImages.php                 # Attach to models for image relationships
├── Translations/
└── Views/
```

## Architecture Patterns

### Service Provider Bootstrap

`ImageServiceProvider` extends `Bongo\Framework\Providers\AbstractServiceProvider`:

```php
class ImageServiceProvider extends AbstractServiceProvider
{
    protected string $module = 'image';

    protected array $commands = [
        UpdateDimensionsCommand::class,
    ];
}
```

The parent class automatically:
- Loads config from `src/Config/image.php`
- Registers routes from `src/Routes/` with appropriate middleware
- Registers views under the `image::` namespace
- Loads migrations and translations
- Registers commands

### Polymorphic Image Relationships

Models use the `HasImages` trait to establish polymorphic relationships:

```php
use Bongo\Image\Traits\HasImages;

class Post extends Model
{
    use HasImages;
}

// Usage:
$post->images();                          // MorphToMany relationship
$post->primaryImage();                     // First image
$post->getPrimaryImage(['preset' => 'large']); // URL with preset
```

### Image Types

The `Image` model defines four types:
- `AVATAR` - Single user avatar (replaces on upload)
- `COVER_IMAGE` - Single cover image (replaces on upload)
- `UPLOAD` - Standard multi-file uploads
- `WYSIWYG` - Images from WYSIWYG editors

### Image Manipulation Flow

1. **Request**: `GET /photos/{image_name}?preset=large&q=80`
2. **Controller**: `Frontend\ImageController@show` validates params
3. **Cached Check**: Looks for cached version at `cache_path/preset_q80_image_name.jpg`
4. **Generate**: If not cached, uses `ImageManipulator` to resize/crop
5. **Store**: Saves to cache with naming: `{preset}_{mode}_q{quality}_{filename}`
6. **Stream**: Returns with cache headers (86400s default)

### Service Classes

All service classes follow constructor dependency injection:

```php
// Single-image replacement pattern
$avatar = new AvatarImage($user, $uploadedFile);
$avatar->save(); // Deletes old, saves new

// Multi-image pattern
$wysiwyg = new WysiwygImage($post, $uploadedFile);
$wysiwyg->save(); // Appends to collection

// Base64 extraction from HTML
$builder = new BuilderService($post, 'posts/images');
$builder->process(); // Extracts and stores base64 images
```

## Coding Conventions

### Naming Conventions

- **Models**: Singular PascalCase (`Image`, `Imageable`)
- **Controllers**: PascalCase with suffix (`ImageController`)
- **Services**: PascalCase with suffix (`BuilderService`, `ImageManipulator`)
- **Traits**: PascalCase with "Has" prefix (`HasImages`)
- **Methods**: camelCase with descriptive names
- **Scopes**: camelCase with "scope" prefix (`scopeOfType`)

### Return Types

Always use strict return types on public methods:

```php
public function getFileName(): string
public function hasImages(): bool
public function primaryImage(): ?Image
public function images(): MorphToMany
```

### Path Handling

Use configuration for all paths:

```php
// Good
config('image.public_path') . $relativePath
config('image.cache_path') . $filename

// Bad - hardcoded paths
storage_path('app/public/images')
```

Clean paths consistently:

```php
$path = ltrim($path, '/');
$path = rtrim($path, '/');
```

### File Storage

Use Laravel's `Storage` facade exclusively:

```php
Storage::exists($path)
Storage::put($path, $content)
Storage::get($path)
Storage::url($path)
Storage::path($path) // For absolute paths
```

### Image Dimensions

Always set width, height, and orientation when creating images:

```php
[$width, $height] = getimagesize($dbImage->getFileSrc());
$dbImage->width = $width;
$dbImage->height = $height;
$dbImage->orientation = ($width > $height) ? Image::LANDSCAPE : Image::PORTRAIT;
```

### Error Handling

Services use exceptions, controllers catch and return fallbacks:

```php
try {
    $manipulator->resize()->crop();
    Storage::put($cachedFile, $manipulator->stream());
} catch (Exception $e) {
    Log::info($e->getMessage());
    return (new ImagePlaceholder($args))->get();
}
```

## Common Tasks

### Adding a New Image Type

1. Add constant to `Image` model:
```php
public const NEW_TYPE = 'new_type';
```

2. Create service class following `AvatarImage` pattern:
```php
class NewTypeImage
{
    protected UploadedFile $file;
    protected Imageable $entity;

    public function save(): Imageable { }
    protected function delete(): void { }
}
```

3. Add controller method for upload endpoint

### Adding a New Manipulation Mode

1. Add method to `ImageManipulator`:
```php
public function newMode(): ImageManipulator
{
    $this->image->someOperation($this->width, $this->height);
    return $this;
}
```

2. Update `Frontend\ImageController@show` mode handling:
```php
elseif ($this->has('mode', $args) && $args['mode'] === 'newMode') {
    $imageManipulator->newMode();
}
```

3. Update `Image::getCachedFileName()` mode abbreviation

### Adding a New Image Preset

Update `config/image.php`:

```php
'presets' => [
    'thumb' => 150,
    'small' => 480,
    'xlarge' => 2400, // New preset
],
```

Use in views:

```php
{{ $model->getPrimaryImage(['preset' => 'xlarge']) }}
```

### Extending HasImages Trait

The trait is designed for extension via model methods:

```php
class Post extends Model
{
    use HasImages;

    public function featuredImage(): ?Image
    {
        return $this->images()
            ->where('type', Image::UPLOAD)
            ->orderBy('sort_order')
            ->first();
    }
}
```

## Testing

Run tests from package root:

```bash
vendor/bin/phpunit
```

Key test scenarios:
- Image upload and storage
- Polymorphic relationship attachment
- Cached file generation
- Placeholder fallback behaviour
- Path cleaning and validation
- Dimension updates

## Commands

### Update Image Dimensions

```bash
php artisan image:update_dimensions
```

Updates width/height/orientation for images missing dimensions.

### Code Quality

```bash
# Check code style
vendor/bin/pint --test

# Fix code style
vendor/bin/pint
```

## Configuration

Key config options in `config/image.php`:

- `prefix` - URL prefix for image routes (default: 'photos')
- `public_path` - Storage path for uploaded images
- `tmp_path` - Temporary upload path
- `cache_path` - Cached/manipulated images path
- `quality` - Default JPEG quality (1-100)
- `presets` - Named size presets
- `browser_cache` - Cache-Control max-age in seconds

## Route Patterns

### Frontend Routes (Public)
- Prefix: `config('image.prefix')` (default: `/photos`)
- Named: `frontend.image.*`
- Example: `GET /photos/{image_name}?preset=large`

### Backend Routes (Admin)
- Prefix: `/admin/images`
- Named: `backend.image.*`
- Middleware: `auth`, `employee`

### API Routes
- Prefix: `/api/images`
- Named: `api.image.*`
- Middleware: `auth:sanctum`

## Key Files Reference

| File | Purpose |
|------|---------|
| `Models/Image.php` | Core image model with file operations |
| `Traits/HasImages.php` | Add to models for polymorphic image relationships |
| `Services/ImageManipulator.php` | Intervention Image wrapper for manipulation |
| `Controllers/Frontend/ImageController.php` | Public image delivery with caching |
| `Services/BuilderService.php` | Extract base64 images from HTML content |
| `Config/image.php` | All configuration options |

## Common Pitfalls

1. **Don't forget orientation**: Always set orientation when creating images
2. **Clean paths**: Use `ltrim()` and `rtrim()` when handling user paths
3. **Check cache first**: Frontend controller checks cache before manipulating
4. **Use Storage facade**: Never use direct file operations
5. **Type constants**: Use `Image::AVATAR` not string 'avatar'
6. **Eager load images**: Use `->with('images')` to avoid N+1 queries
7. **Placeholder fallbacks**: Always provide placeholder when no image exists
