<?php

namespace App\Components\Document;

use App\Models\Document\Document;
use App\Services\DocumentReviewWipService;
use App\Trait\SupportsWip;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Livewire\Component;

class DocumentPreview extends Component
{
    use AuthorizesRequests, SupportsWip;

    public Document $document;

    /** pdf|image|docx|legacy_doc|zip|audio|text|other */
    public string $kind = 'other';

    /** Unique per page load; guards the WIP booking against double-counting. */
    public string $reviewToken = '';

    /** Flat, depth-tagged list of entries when the document is a zip archive. */
    public array $zipEntries = [];

    public bool $finished = false;
    public ?int $bookedUnits = null;

    public function mount(Document $document): void
    {
        $path = $document->owner->getDocumentStoragePath() . $document->file_name;
        abort_unless(Storage::disk('public')->exists($path), 404, 'Document file not found.');

        $this->document    = $document;
        $this->kind        = $this->detectKind($document);
        $this->reviewToken = (string) Str::uuid();

        if ($this->kind === 'zip') {
            $this->zipEntries = $this->buildZipEntries($document);
        }

        $this->startWipTimer();
    }

    /** Explicit "Finish review" — books the elapsed review time, then shows a done state. */
    public function finish(DocumentReviewWipService $wip): void
    {
        if ($this->finished) {
            return;
        }
    
        $this->stopWipTimer();
    
        $entry = $wip->book(
            $this->document,
            $this->wipSeconds,
            $this->wipUnits,
            $this->wipDescription,
            $this->reviewToken,
            $this->wipStartedAt,
        );
    
        $this->bookedUnits = $entry?->units ?? $this->wipUnits;
        $this->finished = true;
    
        $this->resetWip();
    }

    private function detectKind(Document $document): string
    {
        $type = strtolower($document->file_type ?? '');
        $ext  = strtolower(pathinfo($document->file_name, PATHINFO_EXTENSION));

        return match (true) {
            $type === 'application/pdf' || $ext === 'pdf' => 'pdf',
            Str::startsWith($type, 'image/') || in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg']) => 'image',
            Str::contains($type, 'wordprocessingml') || $ext === 'docx' => 'docx',
            $type === 'application/msword' || $ext === 'doc' => 'legacy_doc',
            in_array($type, ['application/zip', 'application/x-zip-compressed', 'multipart/x-zip']) || $ext === 'zip' => 'zip',
            Str::startsWith($type, 'audio/') || in_array($ext, ['mp3', 'wav', 'ogg', 'm4a']) => 'audio',
            Str::startsWith($type, 'text/') || in_array($ext, ['txt', 'csv', 'log', 'md', 'json', 'xml', 'eml']) => 'text',
            default => 'other',
        };
    }

    /**
     * Read the archive's table of contents (no extraction) into a flat list with a
     * depth for indentation — rendered as a tree, streamed per-entry on click.
     *
     * @return array<int, array{path: string, name: string, depth: int, is_dir: bool, size: int}>
     */
    private function buildZipEntries(Document $document): array
    {
        $abs = Storage::disk('public')->path(
            $document->owner->getDocumentStoragePath() . $document->file_name
        );

        $zip = new \ZipArchive();
        if ($zip->open($abs) !== true) {
            return [];
        }

        // Keyed by display path so synthesized folders aren't duplicated by an
        // archive that also carries an explicit directory entry. Files key on
        // their real entry name (needed to stream them); folders key on "name/".
        $map = [];
        $put = function (string $entryName, bool $isDir, int $size) use (&$map) {
            $clean = rtrim($entryName, '/');
            if ($clean === '') {
                return;
            }
            $key = $isDir ? $clean . '/' : $clean;
            $map[$key] ??= [
                'path'   => $isDir ? $clean . '/' : $entryName,
                'name'   => basename($clean),
                'depth'  => substr_count($clean, '/'),
                'is_dir' => $isDir,
                'size'   => $size,
            ];
        };

        for ($i = 0; $i < $zip->numFiles; $i++) {
            $stat = $zip->statIndex($i);
            $name = $stat['name'] ?? '';

            if ($name === '' || Str::startsWith($name, '__MACOSX/')) {
                continue;
            }

            $clean = rtrim($name, '/');
            if ($clean === '') {
                continue;
            }

            // Synthesize every ancestor folder so the tree shows them even when
            // the archive omits explicit directory entries.
            $segments = explode('/', $clean);
            array_pop($segments);
            $ancestor = '';
            foreach ($segments as $segment) {
                $ancestor = $ancestor === '' ? $segment : $ancestor . '/' . $segment;
                $put($ancestor, true, 0);
            }

            $put($name, Str::endsWith($name, '/'), $stat['size'] ?? 0);
        }
        $zip->close();

        $entries = array_values($map);
        usort($entries, fn ($a, $b) => strcmp($a['path'], $b['path']));

        return $entries;
    }

    public function render()
    {
        return view('components.document.document-preview');
    }
}
