# PDF Merger Service Usage Guide

## Overview

The `PdfMergerService` is designed to efficiently merge large PDF files using the `setasign/fpdi` library. This service is optimized for handling large files with minimal memory usage.

## Why setasign/fpdi?

After evaluating the available options:

1. **spatie/pdf-to-text**: Primarily for text extraction, not merging
2. **barryvdh/laravel-dompdf**: For HTML-to-PDF generation, not optimal for merging large existing PDFs
3. **setasign/fpdi**: ✅ **BEST CHOICE** - Specifically designed for PDF manipulation and merging

### Advantages of setasign/fpdi:
- Memory efficient for large files
- Page-by-page processing
- Designed specifically for PDF manipulation
- Can handle complex PDF structures
- Low memory footprint
- Already installed in your project

## Basic Usage

### 1. Simple PDF Merging

```php
use App\Services\PdfMergerService;

$pdfMerger = new PdfMergerService();

// Array of PDF file paths to merge
$pdfPaths = [
    'storage/documents/cv.pdf',
    'storage/documents/certificates.pdf',
    'storage/documents/research_papers.pdf'
];

// Merge PDFs
$mergedPdfPath = $pdfMerger->mergePdfs($pdfPaths, 'storage/merged/final_application.pdf');

echo "Merged PDF created at: " . $mergedPdfPath;
```

### 2. Advanced Options

```php
$options = [
    'add_cover_page' => true,
    'cover_page_title' => 'Complete Application Package',
    'add_bookmarks' => true,
    'add_page_numbers' => true,
    'memory_limit_mb' => 1024, // Increase for very large files
    'chunk_size' => 5 // Process 5 files at a time
];

$mergedPdfPath = $pdfMerger->mergePdfs($pdfPaths, null, $options);
```

### 3. Application-Specific Merging

```php
// For a specific application
$application = Application::find(1);
$mergedPdfPath = $pdfMerger->mergeApplicationDocuments($application);
```

## Controller Integration

### In ApplicationController

```php
public function generateMergedPDF(Application $application)
{
    try {
        $pdfMergerService = new PdfMergerService();
        
        $mergedPdfPath = $pdfMergerService->mergeApplicationDocuments($application, [
            'add_cover_page' => true,
            'cover_page_title' => 'Complete Application Package - ' . $application->application_number,
            'add_bookmarks' => true,
            'add_page_numbers' => true,
            'memory_limit_mb' => 1024
        ]);
        
        $filename = $application->application_number . '_Complete_Package.pdf';
        
        return response()->download($mergedPdfPath, $filename);
        
    } catch (\Exception $e) {
        return redirect()->back()->with('error', 'Error generating merged PDF: ' . $e->getMessage());
    }
}
```

## Frontend Integration

### JavaScript/AJAX Example

```javascript
// Get merge preview
fetch(`/applicant/applications/${applicationId}/merge-preview`)
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            console.log(`Found ${data.data.pdf_count} PDFs`);
            console.log(`Total pages: ${data.data.total_pages}`);
            console.log(`Total size: ${data.data.total_size_formatted}`);
        }
    });

// Generate merged PDF
function generateMergedPDF(applicationId) {
    const form = document.createElement('form');
    form.method = 'POST';
    form.action = `/applicant/applications/${applicationId}/generate-merged-pdf`;
    
    // Add CSRF token
    const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
    const csrfInput = document.createElement('input');
    csrfInput.type = 'hidden';
    csrfInput.name = '_token';
    csrfInput.value = csrfToken;
    form.appendChild(csrfInput);
    
    document.body.appendChild(form);
    form.submit();
}
```

### Blade Template Example

```blade
@if($application->supportingDocuments->count() > 0 || $application->offlineFormDocument)
<div class="card">
    <div class="card-header">
        <h5>Merge All Documents</h5>
    </div>
    <div class="card-body">
        <p>Combine all your uploaded PDF documents into a single file.</p>
        
        <button type="button" class="btn btn-info" onclick="getMergePreview({{ $application->id }})">
            Preview Merge
        </button>
        
        <form method="POST" action="{{ route('applicant.applications.generate-merged-pdf', $application) }}" style="display: inline;">
            @csrf
            <button type="submit" class="btn btn-primary">
                <i class="fas fa-file-pdf"></i> Generate Merged PDF
            </button>
        </form>
    </div>
</div>

<script>
function getMergePreview(applicationId) {
    fetch(`/applicant/applications/${applicationId}/merge-preview`)
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                alert(`Preview:\n- ${data.data.pdf_count} PDF files\n- ${data.data.total_pages} total pages\n- ${data.data.total_size_formatted} total size`);
            } else {
                alert('Error: ' + data.message);
            }
        })
        .catch(error => {
            alert('Error getting preview: ' + error.message);
        });
}
</script>
@endif
```

## Memory Management for Large Files

### Configuration Options

```php
$options = [
    'memory_limit_mb' => 2048,  // Set higher for very large files
    'chunk_size' => 3,          // Process fewer files at once
];
```

### PHP Configuration

For very large files, you may need to adjust PHP settings:

```ini
; In php.ini or .htaccess
memory_limit = 2048M
max_execution_time = 300
upload_max_filesize = 100M
post_max_size = 100M
```

## Error Handling

```php
try {
    $mergedPdfPath = $pdfMerger->mergePdfs($pdfPaths);
} catch (\Exception $e) {
    Log::error('PDF merge failed: ' . $e->getMessage());
    
    // Handle specific error types
    if (str_contains($e->getMessage(), 'memory')) {
        // Memory limit exceeded
        return response()->json(['error' => 'Files too large. Please try with smaller files.']);
    } elseif (str_contains($e->getMessage(), 'not found')) {
        // File not found
        return response()->json(['error' => 'One or more PDF files could not be found.']);
    } else {
        // Generic error
        return response()->json(['error' => 'Failed to merge PDFs. Please try again.']);
    }
}
```

## Performance Tips

1. **Use chunking** for many files: Set `chunk_size` to 5-10 files
2. **Increase memory limit** for large files: Set `memory_limit_mb` to 1024-2048
3. **Process in background** for very large operations using Laravel queues
4. **Clean up temporary files** automatically handled by the service
5. **Monitor memory usage** with the built-in memory tracking

## File Path Handling

The service automatically handles different storage paths:

```php
// These all work automatically:
$paths = [
    'storage/documents/file1.pdf',           // Public storage
    'private/documents/file2.pdf',           // Private storage
    '/full/path/to/file3.pdf',              // Absolute path
    'documents/user123/file4.pdf'           // Relative to storage/app
];
```

## Routes Available

- `GET /applicant/applications/{application}/merge-preview` - Get merge preview info
- `POST /applicant/applications/{application}/generate-merged-pdf` - Generate merged PDF

## Testing

```php
// Test with sample files
$testPaths = [
    storage_path('app/test/sample1.pdf'),
    storage_path('app/test/sample2.pdf')
];

$merger = new PdfMergerService();
$result = $merger->mergePdfs($testPaths, storage_path('app/test/merged.pdf'));

echo "Test merge completed: " . $result;
```

## Troubleshooting

### Common Issues

1. **Memory limit exceeded**: Increase `memory_limit_mb` option
2. **File not found**: Check file paths and permissions
3. **Invalid PDF**: Ensure all input files are valid PDFs
4. **Permission denied**: Check storage directory permissions

### Debug Information

```php
// Get PDF information
$info = $pdfMerger->getPdfInfo('path/to/file.pdf');
print_r($info);
```

This will help you identify issues with specific PDF files before merging.