<?php
// ocr_utils.php
declare(strict_types=1);

use Google\Cloud\Vision\V1\Client\ImageAnnotatorClient;
use Google\Cloud\Vision\V1\Feature;
use Google\Cloud\Vision\V1\Image;
use Google\Cloud\Vision\V1\AnnotateImageRequest;
use Google\Cloud\Vision\V1\BatchAnnotateImagesRequest;

use Google\Cloud\Storage\StorageClient;
use Google\Cloud\Vision\V1\AsyncAnnotateFileRequest;
use Google\Cloud\Vision\V1\InputConfig;
use Google\Cloud\Vision\V1\OutputConfig;
use Google\Cloud\Vision\V1\GcsSource;
use Google\Cloud\Vision\V1\GcsDestination;

/**
 * Ensure the Vision client class is available.
 */
function make_vision_client(): ImageAnnotatorClient
{
    if (!class_exists(ImageAnnotatorClient::class)) {
        $autoload = __DIR__ . '/vendor/autoload.php';
        if (file_exists($autoload)) {
            require_once $autoload;
        }
    }

    if (!class_exists(ImageAnnotatorClient::class)) {
        throw new RuntimeException(
            'Google Cloud Vision SDK not found. Ensure vendor/autoload.php is loaded and google/cloud-vision is installed.'
        );
    }

    return new ImageAnnotatorClient();
}

/**
 * OCR an image using Google Cloud Vision (DOCUMENT_TEXT_DETECTION).
 */
function ocr_image_vision(string $path): string
{
    if (!file_exists($path)) {
        throw new RuntimeException("Image not found: $path");
    }

    $imageData = file_get_contents($path);
    if ($imageData === false) {
        throw new RuntimeException("Failed to read file: $path");
    }

    $client = make_vision_client();

    try {
        $image = new Image();
        $image->setContent($imageData);

        $feature = new Feature();
        $feature->setType(Feature\Type::DOCUMENT_TEXT_DETECTION);

        $annotateReq = new AnnotateImageRequest();
        $annotateReq->setImage($image);
        $annotateReq->setFeatures([$feature]);

        $batchReq = new BatchAnnotateImagesRequest();
        $batchReq->setRequests([$annotateReq]);

        $batchRes = $client->batchAnnotateImages($batchReq);

        $responses = $batchRes->getResponses();
        if (count($responses) === 0) return '';

        $res = $responses[0];

        $annotation = $res->getFullTextAnnotation();
        if ($annotation && $annotation->getText() !== null) {
            return $annotation->getText();
        }

        $texts = $res->getTextAnnotations();
        if (count($texts) > 0 && $texts[0]->getDescription() !== null) {
            return $texts[0]->getDescription();
        }

        return '';
    } finally {
        $client->close();
    }
}

/**
 * OCR a PDF using Google Cloud Vision Async Batch (DOCUMENT_TEXT_DETECTION).
 *
 * Requires GCS bucket:
 *   - env OCR_GCS_BUCKET (recommended) or hardcode in code.
 *
 * Flow:
 *  - Upload PDF to GCS
 *  - asyncBatchAnnotateFiles
 *  - Read output JSON files from GCS prefix and concatenate fullTextAnnotation.text
 */
function ocr_pdf_vision(string $path): string
{
    if (!file_exists($path)) {
        throw new RuntimeException("PDF not found: $path");
    }

    $bucketName = getenv('OCR_GCS_BUCKET') ?: '';
    if ($bucketName === '') {
        throw new RuntimeException("Missing OCR_GCS_BUCKET env var. Set it to your Google Cloud Storage bucket name.");
    }

    // Upload PDF to GCS
    $storage = new StorageClient();
    $bucket = $storage->bucket($bucketName);

    $inputObjectName = 'ocr-input/' . date('Ymd') . '/' . uniqid('file_', true) . '.pdf';
    $bucket->upload(fopen($path, 'r'), ['name' => $inputObjectName]);

    $gcsInputUri = "gs://{$bucketName}/{$inputObjectName}";

    // Output prefix (Vision will write JSON(s) here)
    $outputPrefix = 'ocr-output/' . date('Ymd') . '/' . uniqid('job_', true) . '/';
    $gcsOutputUri = "gs://{$bucketName}/{$outputPrefix}";

    $client = make_vision_client();

    try {
        $feature = new Feature();
        $feature->setType(Feature\Type::DOCUMENT_TEXT_DETECTION);

        $inputConfig = new InputConfig();
        $inputConfig->setMimeType("application/pdf");
        $inputConfig->setGcsSource((new GcsSource())->setUri($gcsInputUri));

        $outputConfig = new OutputConfig();
        $outputConfig->setBatchSize(2); // pages per output JSON file; tune if needed
        $outputConfig->setGcsDestination((new GcsDestination())->setUri($gcsOutputUri));

        $req = new AsyncAnnotateFileRequest();
        $req->setFeatures([$feature]);
        $req->setInputConfig($inputConfig);
        $req->setOutputConfig($outputConfig);

        // Async call
        $operation = $client->asyncBatchAnnotateFiles([$req]);
        $operation->pollUntilComplete();

        if (!$operation->operationSucceeded()) {
            throw new RuntimeException("Vision async PDF OCR failed (operation not succeeded).");
        }

        // Read results JSON files from GCS
        $text = '';
        $foundAny = false;

        foreach ($bucket->objects(['prefix' => $outputPrefix]) as $obj) {
            // Vision writes JSON files like output-1-to-2.json
            $name = $obj->name();
            if (!str_ends_with($name, '.json')) continue;

            $foundAny = true;
            $jsonStr = $obj->downloadAsString();
            $json = json_decode($jsonStr, true);

            if (!is_array($json) || !isset($json['responses']) || !is_array($json['responses'])) {
                continue;
            }

            foreach ($json['responses'] as $resp) {
                $t = $resp['fullTextAnnotation']['text'] ?? '';
                if (is_string($t) && trim($t) !== '') {
                    $text .= $t . "\n";
                }
            }
        }

        if (!$foundAny) {
            throw new RuntimeException("Vision produced no output JSON files in GCS prefix: {$outputPrefix}");
        }

        return trim($text);
    } finally {
        $client->close();
        // Optional: delete input/output to avoid storage buildup (uncomment if desired).
        // NOTE: Be careful in production (auditing/debugging).
        //
        // try {
        //     $bucket->object($inputObjectName)->delete();
        //     foreach ($bucket->objects(['prefix' => $outputPrefix]) as $obj) $obj->delete();
        // } catch (Throwable $e) { /* ignore cleanup errors */ }
    }
}

/**
 * Standard wrapper:
 *   ocr_file($tmpPath, 'vision', $isPdf)
 */
function ocr_file(string $tmpPath, string $engine = 'vision', bool $isPdf = false): string
{
    if ($engine !== 'vision') {
        throw new RuntimeException("Unsupported OCR engine: $engine (only 'vision' is supported)");
    }

    if ($isPdf) {
        return ocr_pdf_vision($tmpPath);
    }

    return ocr_image_vision($tmpPath);
}