<?php

namespace App\Services;

use Illuminate\Http\UploadedFile;
use PhpOffice\PhpWord\IOFactory as WordIOFactory;
use Smalot\PdfParser\Parser as PdfParser;

/**
 * Extracts plain text from uploaded PDF or Word documents.
 *
 * Pipeline:
 *   1. Digital PDF → smalot/pdfparser (fast, no API cost)
 *   2. Word (.docx) → phpoffice/phpword
 *   3. Scanned PDF / image → returns raw bytes for GPT-4o Vision (OCR handled in RegulationExtractorService)
 *
 * Chunks the text into overlapping windows for LLM context limits.
 */
class DocumentTextExtractorService
{
    private const CHUNK_WORDS  = 800;   // words per chunk
    private const OVERLAP_WORDS = 80;   // words of overlap between chunks

    // ── Public entry point ────────────────────────────────────────────────────

    /**
     * Extract text from a file at the given storage path.
     *
     * @param  string $storagePath  Absolute file system path
     * @param  string $fileType     'pdf' | 'docx' | 'doc'
     * @return array{
     *   method: string,
     *   full_text: string,
     *   chunks: string[],
     *   page_count: int,
     *   needs_vision: bool,
     *   error: string|null
     * }
     */
    public function extract(string $storagePath, string $fileType): array
    {
        try {
            return match (strtolower($fileType)) {
                'pdf'         => $this->extractPdf($storagePath),
                'docx', 'doc' => $this->extractWord($storagePath),
                default       => $this->unsupportedType($fileType),
            };
        } catch (\Throwable $e) {
            return [
                'method'       => 'error',
                'full_text'    => '',
                'chunks'       => [],
                'page_count'   => 0,
                'needs_vision' => false,
                'error'        => $e->getMessage(),
            ];
        }
    }

    // ── PDF extraction ────────────────────────────────────────────────────────

    private function extractPdf(string $path): array
    {
        $parser = new PdfParser();
        $pdf    = $parser->parseFile($path);
        $pages  = $pdf->getPages();
        $pageCount = count($pages);

        $fullText = '';
        foreach ($pages as $page) {
            $fullText .= $page->getText() . "\n\n";
        }

        $fullText = $this->normaliseText($fullText);

        // If very little text extracted — likely a scanned PDF, flag for Vision
        $wordCount = str_word_count($fullText);
        if ($wordCount < 50 && $pageCount > 0) {
            return [
                'method'       => 'pdf_scanned',
                'full_text'    => $fullText,
                'chunks'       => [],
                'page_count'   => $pageCount,
                'needs_vision' => true,
                'error'        => null,
            ];
        }

        return [
            'method'       => 'pdf_digital',
            'full_text'    => $fullText,
            'chunks'       => $this->chunkText($fullText),
            'page_count'   => $pageCount,
            'needs_vision' => false,
            'error'        => null,
        ];
    }

    // ── Word extraction ───────────────────────────────────────────────────────

    private function extractWord(string $path): array
    {
        $phpWord  = WordIOFactory::load($path);
        $sections = $phpWord->getSections();

        $fullText = '';
        foreach ($sections as $section) {
            foreach ($section->getElements() as $element) {
                $fullText .= $this->extractWordElement($element) . "\n";
            }
        }

        $fullText = $this->normaliseText($fullText);

        return [
            'method'       => 'word',
            'full_text'    => $fullText,
            'chunks'       => $this->chunkText($fullText),
            'page_count'   => 0,
            'needs_vision' => false,
            'error'        => null,
        ];
    }

    private function extractWordElement(mixed $element): string
    {
        $text = '';
        if (method_exists($element, 'getText')) {
            $text = (string) $element->getText();
        } elseif (method_exists($element, 'getElements')) {
            foreach ($element->getElements() as $child) {
                $text .= $this->extractWordElement($child) . ' ';
            }
        }
        return trim($text);
    }

    // ── Chunking ──────────────────────────────────────────────────────────────

    /**
     * Split text into overlapping word-window chunks safe for GPT-4o context.
     *
     * @return string[]
     */
    public function chunkText(string $text): array
    {
        $words  = preg_split('/\s+/', $text, -1, PREG_SPLIT_NO_EMPTY);
        $total  = count($words);
        $chunks = [];
        $i      = 0;

        while ($i < $total) {
            $slice    = array_slice($words, $i, self::CHUNK_WORDS);
            $chunks[] = implode(' ', $slice);
            $i       += self::CHUNK_WORDS - self::OVERLAP_WORDS;
        }

        return array_values(array_filter($chunks));
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    private function normaliseText(string $text): string
    {
        // Collapse multiple blank lines, normalise whitespace
        $text = preg_replace('/\r\n?/', "\n", $text);
        $text = preg_replace('/[ \t]+/', ' ', $text);
        $text = preg_replace('/\n{3,}/', "\n\n", $text);
        return trim($text);
    }

    private function unsupportedType(string $fileType): array
    {
        return [
            'method'       => 'unsupported',
            'full_text'    => '',
            'chunks'       => [],
            'page_count'   => 0,
            'needs_vision' => false,
            'error'        => "Unsupported file type: {$fileType}",
        ];
    }

    /** Derive file type from extension */
    public static function typeFromExtension(string $filename): string
    {
        return strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    }
}
