{{--
  Layout: single fixed page.

  The PDF area is a flex container (align-items:center justify-content:center).
  Inside it, [pageWrapper] and [annotation zone] are BOTH flex children — so they
  are centred by the same amount and their tops are always level with each other.
  Annotation cards are position:absolute at (bbox.y × canvasH)px within the zone,
  which is exactly level with the corresponding highlight on the canvas.
--}}
<div
    x-data="evidenceViewer()"
    x-init="init()"
    class="d-flex flex-column"
    style="min-height:80vh;"
>
    {{-- Toolbar --}}
    <div class="d-flex align-items-center justify-content-between mb-2 gap-2 flex-wrap">
        <div class="d-flex align-items-center gap-2">
            <select wire:model.live="filterState" class="form-select form-select-sm" style="width:auto;">
                <option value="">All items</option>
                <option value="unreviewed">Unreviewed</option>
                <option value="confirmed">Confirmed</option>
                <option value="dismissed">Dismissed</option>
            </select>
            <span class="badge bg-secondary-subtle text-secondary"
                  x-text="($wire.itemsData || []).length + ' items'"></span>
        </div>
        <div class="d-flex align-items-center gap-2">
            <button @click="toggleDrawMode()"
                    :class="drawMode ? 'btn-warning' : 'btn-outline-secondary'"
                    class="btn btn-sm">
                <i class="mdi mdi-selection-drag me-1"></i>
                <span x-text="drawMode ? 'Click &amp; drag on the page…' : 'Draw box'"></span>
            </button>
            <div class="d-flex align-items-center gap-1">
                <button @click="zoomOut()" class="btn btn-sm btn-outline-secondary"><i class="mdi mdi-minus"></i></button>
                <span class="small" x-text="Math.round(scale * 100) + '%'" style="min-width:3rem;text-align:center;"></span>
                <button @click="zoomIn()" class="btn btn-sm btn-outline-secondary"><i class="mdi mdi-plus"></i></button>
            </div>
        </div>
    </div>

    {{--
      PDF area — one big flex row.
      Both pageWrapper and the annotation zone are centred together by align-items:center,
      so their tops are always at exactly the same Y position.
    --}}
    <div class="flex-grow-1 bg-secondary overflow-auto d-flex align-items-center justify-content-center gap-0"
         style="min-height:0;"
         x-ref="pdfArea"
         @wheel.prevent="onWheel($event)">

        {{-- ── Canvas side ── --}}
        <div class="position-relative flex-shrink-0" x-ref="pageWrapper">

            {{-- Floating page indicator — sits at top of canvas wrapper --}}
            <div class="position-absolute top-0 start-0 w-100 d-flex justify-content-center py-1"
                 style="z-index:20;pointer-events:none;">
                <div x-show="totalPages > 0"
                     class="d-flex align-items-center gap-1 rounded px-2 py-1"
                     style="background:rgba(0,0,0,.55);pointer-events:auto;">
                    <button @click="prevPage()" :disabled="currentPage <= 1"
                            class="btn btn-sm p-0 border-0 text-white" style="width:1.4rem;line-height:1;">
                        <i class="mdi mdi-chevron-left"></i>
                    </button>
                    <span class="text-white" style="font-size:.72rem;white-space:nowrap;">
                        Page <span x-text="currentPage"></span> / <span x-text="totalPages"></span>
                    </span>
                    <button @click="nextPage()" :disabled="currentPage >= totalPages"
                            class="btn btn-sm p-0 border-0 text-white" style="width:1.4rem;line-height:1;">
                        <i class="mdi mdi-chevron-right"></i>
                    </button>
                </div>
            </div>

            <canvas x-ref="pdfCanvas"
                    style="display:block;box-shadow:0 4px 20px rgba(0,0,0,.45);"></canvas>

            {{-- Text layer: invisible — used by computeTextHighlights() --}}
            <div x-ref="textLayer"
                 class="position-absolute top-0 start-0 w-100 h-100"
                 style="overflow:hidden;opacity:0;pointer-events:none;"></div>

            {{-- Highlight overlay --}}
            <div class="position-absolute top-0 start-0 w-100 h-100" style="pointer-events:none;">
                <template x-for="item in currentPageItems()" :key="item.id">
                    <div
                        x-show="item.bbox || textBboxes[item.id]"
                        :style="highlightStyle(item)"
                        class="position-absolute synapse-highlight"
                        style="pointer-events:auto;cursor:pointer;border-radius:2px;"
                        @mouseenter="pulsingItemId = item.id"
                        @mouseleave="pulsingItemId = null"
                        @click="scrollAnnotationToItem(item.id)"
                        :title="item.comment"
                    ></div>
                </template>
            </div>

            {{-- Draw capture layer --}}
            <div x-show="drawMode"
                 class="position-absolute top-0 start-0 w-100 h-100"
                 style="cursor:crosshair;z-index:10;"
                 @mousedown.prevent="onDrawStart($event)"
                 @mousemove="onDrawMove($event)"
                 @mouseup="onDrawEnd($event)">
            </div>

            {{-- Ghost rect while drawing --}}
            <div x-show="drawing"
                 :style="ghostStyle()"
                 class="position-absolute"
                 style="border:2px dashed #7c3aed;background:rgba(124,58,237,.1);pointer-events:none;z-index:12;"></div>

            {{-- Loading spinner --}}
            <div x-show="loading"
                 class="position-absolute top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center"
                 style="background:rgba(0,0,0,.3);">
                <span class="spinner-border text-white"></span>
            </div>
        </div>

        {{--
          Annotation zone — flex sibling of pageWrapper.
          Height is set to canvasH so it is exactly the same size as the canvas.
          Since both are centred by the same align-items:center, top edges align.
          Cards inside are position:absolute at top = bbox.y × canvasH.
        --}}
        <div class="position-relative flex-shrink-0 bg-light border-start"
             style="width:280px; overflow-y:auto;"
             :style="'height:' + (canvasH || 600) + 'px'"
             x-ref="annotationZone">

            <template x-for="item in currentPageItems()" :key="item.id">
                <div :id="'annotation-item-' + item.id"
                     class="position-absolute card border-0 shadow-sm evidence-annotation"
                     style="width:272px;left:4px;"
                     :style="annotationStyle(item)"
                     @mouseenter="pulsingItemId = item.id"
                     @mouseleave="pulsingItemId = null">
                    <div class="card-body p-2">
                        <div class="d-flex align-items-center gap-1 flex-wrap mb-1">
                            <span class="badge rounded-pill"
                                  :style="'background:'+item.colour+'20;color:'+item.colour+';border:1px solid '+item.colour+'50;font-size:.6rem;'"
                                  x-text="categoryLabel(item.category)"></span>
                            <span class="badge rounded-pill"
                                  :class="item.severity==='high'?'bg-danger-subtle text-danger':(item.severity==='medium'?'bg-warning-subtle text-warning':'bg-secondary-subtle text-secondary')"
                                  style="font-size:.6rem;"
                                  x-text="item.severity.charAt(0).toUpperCase()+item.severity.slice(1)"></span>
                            <span x-show="item.source==='human'" class="badge bg-info-subtle text-info" style="font-size:.6rem;">Human</span>
                            <span x-show="item.source!=='human'" class="badge text-white" style="background:#7c3aed;font-size:.55rem;">
                                <i class="mdi mdi-brain me-1"></i>Synapse
                            </span>
                            <i x-show="item.state==='confirmed'" class="mdi mdi-check-circle text-success ms-auto flex-shrink-0"></i>
                        </div>
                        <blockquote x-show="item.quote" x-text="item.quote"
                                    class="small fst-italic text-muted mb-1 ps-2 border-start"
                                    style="font-size:.72rem;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;"></blockquote>
                        <p class="small mb-2" style="font-size:.74rem;" x-text="item.comment"></p>
                        <div class="d-flex gap-1 flex-wrap" @click.stop>
                            <button x-show="item.state!=='confirmed'"
                                    @click="$wire.call('confirm',item.id)"
                                    class="btn btn-xs btn-outline-success"
                                    style="font-size:.65rem;padding:.15rem .4rem;">
                                <i class="mdi mdi-check"></i> Confirm
                            </button>
                            <button x-show="item.state!=='dismissed'"
                                    @click="$wire.call('dismiss',item.id)"
                                    class="btn btn-xs btn-outline-secondary"
                                    style="font-size:.65rem;padding:.15rem .4rem;">
                                <i class="mdi mdi-close"></i> Dismiss
                            </button>
                            <button x-show="item.state==='confirmed'||item.state==='dismissed'"
                                    @click="$wire.call('confirm',item.id)"
                                    class="btn btn-xs btn-outline-secondary"
                                    style="font-size:.65rem;padding:.15rem .4rem;" title="Undo">
                                <i class="mdi mdi-undo"></i>
                            </button>
                            <button x-show="item.source==='human'"
                                    @click="if(confirm('Delete this item?')) $wire.call('deleteItem',item.id)"
                                    class="btn btn-xs btn-outline-danger ms-auto"
                                    style="font-size:.65rem;padding:.15rem .4rem;">
                                <i class="mdi mdi-trash-can-outline"></i>
                            </button>
                        </div>
                    </div>
                </div>
            </template>
        </div>
    </div>

    {{-- Add Evidence form — fixed overlay when drawing a box --}}
    @if($showAddForm)
        <div class="position-fixed card border-0 shadow-lg"
             style="right:296px;top:50%;transform:translateY(-50%);width:280px;z-index:1060;border-top:3px solid #7c3aed !important;">
            <div class="card-header d-flex align-items-center gap-2 py-2">
                <span class="badge text-white" style="background:#7c3aed;font-size:.6rem;">
                    <i class="mdi mdi-brain"></i> Synapse
                </span>
                <span class="small fw-semibold">Add Evidence</span>
                @if($addPage)
                    <span class="badge bg-light text-secondary border ms-1" style="font-size:.6rem;">p.{{ $addPage }}</span>
                @endif
                <button wire:click="cancelAdd" class="btn-close ms-auto" style="font-size:.6rem;"></button>
            </div>
            <div class="card-body p-2">
                @if($addQuote)
                    <div class="small bg-light rounded p-2 mb-2 fst-italic" style="max-height:3rem;overflow:hidden;" title="{{ $addQuote }}">
                        "{{ $addQuote }}"
                    </div>
                @endif
                <div class="mb-2">
                    <label class="form-label small mb-1">Category</label>
                    <select wire:model="addCategory" class="form-select form-select-sm">
                        @foreach($categories as $value => $label)
                            <option value="{{ $value }}">{{ $label }}</option>
                        @endforeach
                    </select>
                </div>
                <div class="mb-2">
                    <label class="form-label small mb-1">Severity</label>
                    <select wire:model="addSeverity" class="form-select form-select-sm">
                        <option value="high">High</option>
                        <option value="medium">Medium</option>
                        <option value="low">Low</option>
                    </select>
                </div>
                <div class="mb-2">
                    <label class="form-label small mb-1">Comment</label>
                    <textarea wire:model="addComment" class="form-control form-control-sm" rows="2"
                              placeholder="Why is this significant?"></textarea>
                </div>
                <button wire:click="saveEvidence" class="btn btn-sm text-white w-100" style="background:#7c3aed;">
                    <i class="mdi mdi-check me-1"></i>Save
                </button>
            </div>
        </div>
    @endif
</div>

@push('scripts')
<script>
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';

function evidenceViewer() {
    // PDF.js uses private class fields — keep outside Alpine's Proxy.
    let _pdfDoc     = null;
    let _renderTask = null;

    return {
        loading:       true,
        currentPage:   1,
        totalPages:    1,
        scale:         1.2,
        canvasW:       1,
        canvasH:       1,
        textBboxes:    {},
        pulsingItemId: null,
        drawMode:      false,
        drawing:       false,
        drawStart:     null,
        drawCurrent:   null,

        async init() {
            await this.loadPdf();
        },

        // ── PDF ───────────────────────────────────────────────────────────────

        async loadPdf() {
            this.loading = true;
            try {
                @if($this->attachment)
                const url = @js(route('inbox.attachment.preview', $this->attachment->id));
                @else
                const url = @js(route('documents.view', $this->document->id));
                @endif
                _pdfDoc         = await pdfjsLib.getDocument(url).promise;
                this.totalPages = _pdfDoc.numPages;
                this.scale      = this._fitScale(await _pdfDoc.getPage(1));
                await this.renderPage(1);
            } catch (e) {
                console.error('PDF load error', e);
            }
            this.loading = false;
        },

        _fitScale(page) {
            const area = this.$refs.pdfArea;
            if (!area || !area.clientHeight) return 1.2;
            const vp = page.getViewport({ scale: 1.0 });
            return Math.min(Math.max((area.clientHeight - 56) / vp.height, 0.4), 2.5);
        },

        async renderPage(pageNum) {
            if (_renderTask) _renderTask.cancel();
            this.loading = true;

            const page   = await _pdfDoc.getPage(pageNum);
            const vp     = page.getViewport({ scale: this.scale });
            const canvas = this.$refs.pdfCanvas;
            const ctx    = canvas.getContext('2d');

            canvas.width  = vp.width;
            canvas.height = vp.height;
            this.canvasW  = vp.width;
            this.canvasH  = vp.height;

            _renderTask = page.render({ canvasContext: ctx, viewport: vp });
            try { await _renderTask.promise; }
            catch (e) { if (e.name !== 'RenderingCancelledException') console.error(e); }

            const tl = this.$refs.textLayer;
            tl.innerHTML = '';
            const content = await page.getTextContent();
            pdfjsLib.renderTextLayer({ textContentSource: content, container: tl, viewport: vp, textDivs: [] });
            this.computeTextHighlights(content, vp, pageNum);

            this.loading = false;
        },

        // ── Navigation ────────────────────────────────────────────────────────

        async prevPage() {
            if (this.currentPage > 1) { this.currentPage--; await this.renderPage(this.currentPage); }
        },
        async nextPage() {
            if (this.currentPage < this.totalPages) { this.currentPage++; await this.renderPage(this.currentPage); }
        },
        onWheel(event) {
            if (event.deltaY > 0) this.nextPage(); else this.prevPage();
        },
        async scrollPdfToPage(page) {
            if (!page || page === this.currentPage) return;
            this.currentPage = page;
            await this.renderPage(page);
        },

        // ── Zoom ─────────────────────────────────────────────────────────────

        async zoomIn()  { this.scale = Math.min(this.scale + 0.2, 3.0); await this.renderPage(this.currentPage); },
        async zoomOut() { this.scale = Math.max(this.scale - 0.2, 0.4); await this.renderPage(this.currentPage); },

        // ── Items ─────────────────────────────────────────────────────────────

        currentPageItems() {
            return (this.$wire.itemsData || []).filter(i => i.page === this.currentPage || i.page === null);
        },

        // ── Highlight on canvas ───────────────────────────────────────────────

        highlightStyle(item) {
            const bbox  = item.bbox || this.textBboxes[item.id];
            if (!bbox) return 'display:none;';
            const alpha = item.state === 'dismissed' ? '18'
                        : this.pulsingItemId === item.id ? '70' : '40';
            return `left:${bbox.x*this.canvasW}px;top:${bbox.y*this.canvasH}px;`
                 + `width:${bbox.width*this.canvasW}px;height:${bbox.height*this.canvasH}px;`
                 + `background:${item.colour}${alpha};border:2px solid ${item.colour};`
                 + `box-shadow:0 0 0 1px ${item.colour}60;`;
        },

        // ── Annotation card positioning ───────────────────────────────────────
        // top = bbox.y × canvasH, measured from the annotation zone top.
        // The zone is the same height as the canvas and a flex sibling of pageWrapper,
        // so both are centred by the same amount — tops are always level.

        annotationStyle(item) {
            const bbox    = item.bbox || this.textBboxes[item.id];
            const located = !!bbox;
            let y;
            if (located) {
                y = Math.round(bbox.y * this.canvasH);
            } else {
                // Text not found on this page — spread evenly so cards don't pile at top:0.
                // Dashed border (set below) signals the position is approximate.
                const pageItems = this.currentPageItems();
                const idx       = pageItems.findIndex(i => i.id === item.id);
                const count     = pageItems.length;
                y = count > 1 ? Math.round((idx / count) * this.canvasH * 0.75) : 0;
            }
            const alpha  = item.state === 'dismissed' ? '.45' : '1';
            const border = located
                ? `border-left:4px solid ${item.colour} !important;`
                : `border-left:4px dashed ${item.colour} !important;`;
            return `top:${y}px;opacity:${alpha};${border}`;
        },

        scrollAnnotationToItem(id) {
            document.getElementById('annotation-item-' + id)
                ?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
        },

        // ── AI text highlight positioning ─────────────────────────────────────
        //
        // Two separate PDF text-extraction libraries are in play:
        //   • smalot/pdfparser  — used by the extraction job; the AI saw THIS text and
        //                         attributed page numbers from it.
        //   • PDF.js            — used here in the viewer; different stream ordering,
        //                         character encoding and spacing.
        //
        // Because the two libs can produce different strings for the same page, a simple
        // prefix-only match frequently fails.  We use two strategies in sequence:
        //
        //   Strategy 1 — Sliding-window substring match
        //     Try substrings of the normalised quote starting at offsets 0, 4, 8 … 60,
        //     with lengths shrinking from 20 down to 6.  This handles cases where the
        //     beginning of the quote differs between libraries but the middle matches.
        //
        //   Strategy 2 — Distinctive-word lookup
        //     Extract every word ≥ 6 chars from the RAW quote (preserving medical
        //     terminology), then look each up in a pre-built word→textItem map.
        //     Uncommon medical words (e.g. "dyspareunia", "diazepam", "haematoma") are
        //     almost always unique on a page and survive encoding differences.

        computeTextHighlights(content, viewport, pageNum) {
            const pending = (this.$wire.itemsData || []).filter(i => i.page === pageNum && i.quote && !i.bbox);
            if (!pending.length) return;

            const textItems = content.items.filter(i => i.str && i.str.trim().length > 0);

            // ── Build char→item map for contiguous substring matching ─────────
            let normText = '';
            const charToItem = [];
            for (const ti of textItems) {
                const norm = ti.str.toLowerCase().replace(/[^a-z0-9]/g, '');
                for (let j = 0; j < norm.length; j++) charToItem.push(ti);
                normText += norm;
            }

            // ── Build word→textItem map (first occurrence) for word lookup ────
            const wordMap = new Map();
            for (const ti of textItems) {
                for (const w of (ti.str.match(/[a-zA-Z0-9]{5,}/g) || [])) {
                    const key = w.toLowerCase();
                    if (!wordMap.has(key)) wordMap.set(key, ti);
                }
            }

            const bboxes = { ...this.textBboxes };

            for (const ev of pending) {
                const rawQuote  = ev.quote || '';
                const normQuote = rawQuote.toLowerCase().replace(/[^a-z0-9]/g, '');
                if (normQuote.length < 5) continue;

                let foundTi = null;

                // ── Strategy 1: sliding-window substring ──────────────────────
                outer:
                for (let start = 0; start <= Math.min(normQuote.length - 6, 60); start += 4) {
                    for (let len = Math.min(normQuote.length - start, 20); len >= 6; len -= 2) {
                        const pos = normText.indexOf(normQuote.substring(start, start + len));
                        if (pos >= 0 && pos < charToItem.length) {
                            foundTi = charToItem[pos];
                            break outer;
                        }
                    }
                }

                // ── Strategy 2: distinctive-word lookup ───────────────────────
                if (!foundTi) {
                    const quoteWords = rawQuote.match(/[a-zA-Z]{6,}/g) || [];
                    wordSearch:
                    for (const qw of quoteWords) {
                        const key = qw.toLowerCase();
                        // Exact match first
                        if (wordMap.has(key)) { foundTi = wordMap.get(key); break; }
                        // Prefix match (first 6 chars) — handles plural/tense differences
                        const prefix = key.substring(0, 6);
                        for (const [k, ti] of wordMap) {
                            if (k.startsWith(prefix)) { foundTi = ti; break wordSearch; }
                        }
                    }
                }

                if (!foundTi) continue;

                const [vpX, vpY] = viewport.convertToViewportPoint(foundTi.transform[4], foundTi.transform[5]);
                const fontH = Math.max(
                    Math.abs(foundTi.transform[3]) * viewport.scale,
                    foundTi.height * viewport.scale,
                    10
                );

                bboxes[ev.id] = {
                    x:      Math.max(0, (vpX - 4) / viewport.width),
                    y:      Math.max(0, (vpY - fontH * 1.1) / viewport.height),
                    width:  Math.min(0.96, Math.max(0.5, (viewport.width * 0.88 - vpX + 4) / viewport.width)),
                    height: Math.min(0.08, fontH * 1.4 / viewport.height),
                };
            }

            this.textBboxes = bboxes;
        },

        categoryLabel(cat) {
            const m = {
                physical_abuse:'Physical Abuse', emotional_abuse:'Emotional Abuse',
                financial_abuse:'Financial Abuse', neglect:'Neglect',
                drug_alcohol:'Drug / Alcohol', self_harm_suicide:'Self-Harm / Suicide',
                care_history:'Care History', medication_error:'Medication Error',
                documentation_failure:'Documentation Failure', safeguarding:'Safeguarding',
            };
            return m[cat] ?? 'Other';
        },

        // ── Draw box ──────────────────────────────────────────────────────────

        toggleDrawMode() { this.drawMode = !this.drawMode; this.drawing = false; },

        ghostStyle() {
            if (!this.drawStart || !this.drawCurrent) return '';
            return `left:${Math.min(this.drawStart.x, this.drawCurrent.x)}px;`
                 + `top:${Math.min(this.drawStart.y, this.drawCurrent.y)}px;`
                 + `width:${Math.abs(this.drawCurrent.x - this.drawStart.x)}px;`
                 + `height:${Math.abs(this.drawCurrent.y - this.drawStart.y)}px;`;
        },

        onDrawStart(event) {
            this.drawing     = true;
            this.drawStart   = { x: event.offsetX, y: event.offsetY };
            this.drawCurrent = { ...this.drawStart };
        },

        onDrawMove(event) {
            if (!this.drawing) return;
            const rect = this.$refs.pageWrapper.getBoundingClientRect();
            this.drawCurrent = {
                x: Math.max(0, Math.min(event.clientX - rect.left, this.canvasW)),
                y: Math.max(0, Math.min(event.clientY - rect.top,  this.canvasH)),
            };
        },

        onDrawEnd(event) {
            if (!this.drawing) return;
            this.drawing  = false;
            this.drawMode = false;

            const rect = this.$refs.pageWrapper.getBoundingClientRect();
            const end  = {
                x: Math.max(0, Math.min(event.clientX - rect.left, this.canvasW)),
                y: Math.max(0, Math.min(event.clientY - rect.top,  this.canvasH)),
            };
            const x = Math.min(this.drawStart.x, end.x) / this.canvasW;
            const y = Math.min(this.drawStart.y, end.y) / this.canvasH;
            const w = Math.abs(end.x - this.drawStart.x) / this.canvasW;
            const h = Math.abs(end.y - this.drawStart.y) / this.canvasH;

            if (w < 0.01 || h < 0.01) return;

            @this.call('openAddForm', this.currentPage, null, { x, y, width: w, height: h });
        },
    };
}
</script>
<style>
.synapse-highlight        { transition: background .12s; }
.synapse-highlight:hover  { filter: brightness(0.75); z-index: 10; }
.evidence-annotation      { transition: box-shadow .15s; }
.evidence-annotation:hover { box-shadow: 0 3px 14px rgba(0,0,0,.18) !important; z-index: 5; }
</style>
@endpush
