# Phase 2 — Advanced Post Composer
## Weeks 3–4 | Complete in order. Commit after each section.

---

## STEP 2.1 — Multi-Platform Composer (Main Livewire Component)

**Livewire Component: `Composer\PostComposer`** (matches Hootsuite New Post modal)

### UI Layout:
```
┌─────────────────────────────────────────────────┬──────────────────────┐
│ [New Post] [Drafts] [Presets]         (tabs)     │   Post Preview       │
├─────────────────────────────────────────────────┤                      │
│ [Select Accounts ▼] — multi-select dropdown      │  [Platform tabs]     │
├─────────────────────────────────────────────────┤  Twitter / Facebook  │
│ Caption textarea (expandable)                    │  Instagram / etc.    │
│ 0/280 character counter (updates per platform)   │                      │
├─────────────────────────────────────────────────┤  [Live preview card] │
│ [+Add Media] [🔗 URL] [😀] [🏷️] [📍] [...]      │                      │
├─────────────────────────────────────────────────┤                      │
│ Hashtags: [input + AI suggest]                   │                      │
├─────────────────────────────────────────────────┤                      │
│ ○ Post now                                       │                      │
│ ○ Schedule for later  [date/time picker]         │                      │
│   [⚡ Suggested Times ▼]                         │                      │
├─────────────────────────────────────────────────┤                      │
│ [🗑️]              [Save as Draft] [📅 Schedule]  │                      │
└─────────────────────────────────────────────────┴──────────────────────┘
```

### Component Properties:
```php
public array $selectedAccounts = [];
public string $globalCaption = '';
public array $platformOverrides = []; // per-platform caption overrides
public array $mediaFiles = [];
public ?Carbon $scheduledAt = null;
public string $postMode = 'now'; // 'now' | 'schedule' | 'draft' | 'queue'
public string $activePreviewPlatform = 'twitter';
public array $hashtags = [];
public ?string $linkUrl = null;
public string $timezone = 'UTC';
public bool $showEmojiPicker = false;
public bool $showHashtagSuggestions = false;
```

### Key Methods:
```php
public function selectAccount(int $accountId): void
public function updateCaption(string $text): void  // triggers live preview
public function getCharacterCount(string $platform): int
public function isOverLimit(string $platform): bool
public function suggestHashtags(): void   // calls Gemini API via Job
public function addHashtag(string $tag): void
public function removeHashtag(string $tag): void
public function attachMedia(int $mediaFileId): void
public function removeMedia(int $mediaFileId): void
public function switchPreview(string $platform): void
public function getSuggestedTimes(): array  // from analytics
public function saveAsDraft(): void
public function schedule(): void
public function postNow(): void
private function createContent(): Content
private function createPosts(Content $content): void
```

### Platform override panel (per platform tab in composer):
When user selects multiple accounts → show "Customize per platform" toggle.
When enabled: render a sub-tab per selected platform with its own textarea, showing character count for that platform.

---

## STEP 2.2 — Live Platform Previews

Create a Blade component for each platform preview.

### `resources/views/components/previews/twitter.blade.php`
```html
<!-- White card, twitter blue header -->
<div class="bg-white rounded-xl border border-gray-200 p-4 max-w-sm">
  <div class="flex items-start gap-3">
    <img src="{{ $avatar }}" class="w-10 h-10 rounded-full">
    <div class="flex-1">
      <div class="flex items-center gap-1">
        <span class="font-bold text-sm">{{ $name }}</span>
        <span class="text-gray-500 text-sm">@{{ $handle }}</span>
      </div>
      <p class="text-sm mt-1 whitespace-pre-wrap">{{ $caption }}</p>
      @if($media)
        <img src="{{ $media }}" class="mt-2 rounded-xl w-full object-cover max-h-48">
      @endif
      <p class="text-xs text-gray-400 mt-2">{{ $scheduledAt }}</p>
    </div>
  </div>
</div>
```

Create similar components for:
- `instagram-feed.blade.php` — square/portrait image prominent, caption below, hashtags in lighter color
- `instagram-story.blade.php` — 9:16 ratio portrait card, caption overlay at bottom
- `linkedin.blade.php` — profile name + title visible, longer caption, document-style look
- `facebook.blade.php` — page name + like/comment/share buttons
- `tiktok.blade.php` — portrait video thumbnail with caption + hashtags
- `youtube.blade.php` — thumbnail + title + channel name

Livewire component `Composer\PlatformPreview`:
```php
// Receives: platform, caption, media, account, scheduledAt
// Renders the appropriate preview blade component
// Updates reactively via $wire.entangle or Livewire events
```

---

## STEP 2.3 — Media Attachment System

### Livewire Component: `Composer\MediaUploader`

Use FilePond for drag-and-drop (Alpine.js integration):
```javascript
// Alpine component
x-data="mediaUploader()"
// FilePond options: maxFileSize, acceptedFileTypes, imagePreviewHeight
// On file process: call $wire.attachFile(response.serverId)
```

PHP upload endpoint: `POST /media/upload` → `MediaController@upload`
- Validates MIME + size
- Stores temp file
- Returns `{id, url, type, thumbnail}` JSON

### In-browser Image Cropper (Cropper.js):
When user selects an image:
1. Show modal with Cropper.js initialized on the image
2. Ratio preset buttons: 1:1, 4:5, 9:16, 16:9
3. "Apply Crop" → send cropped canvas to server via `POST /media/crop`
4. Returns new MediaFile with cropped version

### Carousel Builder (Instagram):
When platform = Instagram and multiple images attached:
- Show "Build Carousel" toggle
- Drag to reorder images (Sortable.js)
- Shows "Slide 1 of N" indicator
- Preview updates to show carousel navigation dots

---

## STEP 2.4 — Post Scheduling Engine

### Service: `app/Services/Post/SchedulingService.php`
```php
public function schedule(Content $content, array $accountIds, Carbon $scheduledAt): array
// Creates Post record(s) with status = scheduled
// Dispatches ScheduledPostJob delayed to $scheduledAt

public function addToQueue(Content $content, array $accountIds): array
// Fetches next best-time slot for each platform
// Calls schedule() with that time

public function getBestTimeSuggestions(string $platform): array
// Queries analytics_snapshots for engagement by hour
// Returns top 3 time slots as [Carbon, score]% array

public function reschedule(Post $post, Carbon $newTime): void
public function cancel(Post $post): void
public function duplicate(Post $post): Post
```

### Job: `app/Jobs/PublishPostJob.php`
```php
public function handle(): void
// 1. Load post + content + social account
// 2. Verify account is still active (token not expired)
// 3. Dispatch platform-specific publishing job:
//    - InstagramPublishJob, FacebookPublishJob, TwitterPublishJob, etc.
// 4. Update post status to 'publishing'

// Each platform job:
// 1. Build API payload from content
// 2. Call platform API
// 3. On success: update post status='published', store platform_post_id
// 4. On failure: update status='failed', store error_message, log to post_logs
// 5. Dispatch PostPublishedNotification or PostFailedNotification
```

### Platform Publishing Services: `app/Services/Social/Publishing/`
Create one service per platform:
- `InstagramPublishingService.php` — supports image, video, carousel, reels
- `FacebookPublishingService.php` — page posts
- `TwitterPublishingService.php` — tweets with media
- `LinkedInPublishingService.php` — text + image posts
- `TikTokPublishingService.php` — video upload
- `YouTubePublishingService.php` — video upload
- `PinterestPublishingService.php` — pins
- `ThreadsPublishingService.php` — text + media

Each service implements `PublishingServiceInterface`:
```php
interface PublishingServiceInterface {
    public function publish(Post $post, Content $content, SocialAccount $account): string; // returns platform_post_id
    public function delete(string $platformPostId, SocialAccount $account): bool;
}
```

### Scheduled Command: `app/Console/Commands/PublishScheduledPosts.php`
```bash
# Runs every minute via Task Scheduler
# Queries posts where status='scheduled' AND scheduled_at <= now()
# Dispatches PublishPostJob for each (with unique job key to prevent duplicates)
```

---

## STEP 2.5 — Post Status Workflow

### Livewire Component: `Posts\PostsList`

Tabbed list view:
- Tabs: All | Drafts | Scheduled | Needs Review | Published | Failed
- Each row: platform icon, caption snippet, scheduled time, status badge, actions (Edit | Cancel | Retry | Duplicate)

### "Needs Review" queue:
- Posts created by AI auto-distribution land here with status = `needs_review`
- "Approve" → changes status to `scheduled`, dispatches job
- "Edit" → opens composer pre-filled with post data
- "Reject" → deletes the post

### Retry failed posts:
```php
// PostController@retry
// Resets status to 'scheduled', re-dispatches PublishPostJob immediately
```

---

## STEP 2.6 — Routes

```php
// Composer
GET  /compose                   → ComposerController@index
POST /posts                     → PostController@store
GET  /posts/{post}/edit         → ComposerController@edit
PUT  /posts/{post}              → PostController@update
DELETE /posts/{post}            → PostController@destroy
POST /posts/{post}/retry        → PostController@retry
POST /posts/{post}/duplicate    → PostController@duplicate
POST /posts/{post}/approve      → PostController@approve

// Media
POST /media/upload              → MediaController@upload
POST /media/crop                → MediaController@crop
DELETE /media/{media}           → MediaController@destroy
GET  /media/library             → MediaController@index (JSON for composer)

// Drafts & Presets
GET  /drafts                    → PostController@drafts
GET  /presets                   → CaptionTemplateController@index
POST /presets                   → CaptionTemplateController@store
```

---

## STEP 2.7 — UTM Link Builder

Alpine.js modal component within the composer:
```html
<!-- Opens when clicking 🔗 link button -->
<div x-show="showLinkBuilder">
  <input placeholder="URL" x-model="url">
  <input placeholder="UTM Source" x-model="utm.source">
  <input placeholder="UTM Medium" x-model="utm.medium">
  <input placeholder="UTM Campaign" x-model="utm.campaign">
  <span x-text="builtUrl"><!-- live preview of full UTM URL --></span>
  <button @click="insertLink()">Insert Link</button>
</div>
```

---

## COMPLETION CHECKLIST
- [ ] PostComposer Livewire component fully working
- [ ] Multi-account selection working
- [ ] Per-platform caption overrides working
- [ ] Character counter per platform (correct limits)
- [ ] All 8 platform preview components built
- [ ] FilePond drag-and-drop media upload working
- [ ] Cropper.js image cropper working
- [ ] Instagram carousel builder working
- [ ] Scheduling (now / schedule / queue / draft) working
- [ ] PublishPostJob dispatching correctly
- [ ] All 8 platform publishing services scaffolded
- [ ] Scheduled command publishes posts on time
- [ ] Post status workflow (draft → scheduled → published/failed)
- [ ] Needs Review queue working
- [ ] Retry + duplicate actions working
- [ ] UTM link builder working

When done: update `docs/current_phase.txt` to `3`, commit `feat(phase2): complete advanced post composer`, stop.
