<?php
/**
 * Contract Language — Multi-document PDF.js Viewer + Gemini AI Chat
 */

// Build file lists for Operations and Procedures manuals
$opsFiles = [];
foreach (glob(__DIR__ . '/../../assets/data/Operations 08-09/*.pdf') as $f) {
    $name = basename($f, '.pdf');
    $opsFiles[] = ['name' => $name, 'path' => 'assets/data/Operations%2008-09/' . rawurlencode(basename($f))];
}
sort($opsFiles);

$procFiles = [];
foreach (glob(__DIR__ . '/../../assets/data/Procedures Manual/*.pdf') as $f) {
    $name = basename($f, '.pdf');
    $procFiles[] = ['name' => $name, 'path' => 'assets/data/Procedures%20Manual/' . rawurlencode(basename($f))];
}
sort($procFiles);
?>

<style>
.policy-page { display:flex; gap:0; height:calc(100vh - var(--top-offset) - var(--footer-height)); margin:-24px; }

/* ─── Left: PDF Viewer ─── */
.policy-pdf {
    flex:1; min-width:0; display:flex; flex-direction:column; background:#525659;
    border-right:1px solid rgba(0,0,0,0.3);
}
.policy-pdf-toolbar {
    display:flex; align-items:center; gap:8px; padding:6px 12px;
    background:#323639; color:#fff; flex-shrink:0; border-bottom:1px solid rgba(0,0,0,0.3);
}
.policy-pdf-toolbar button {
    background:rgba(255,255,255,0.08); border:1px solid rgba(255,255,255,0.1);
    color:rgba(255,255,255,0.7); padding:5px 10px; border-radius:4px; cursor:pointer;
    font-size:12px; transition:all 0.15s;
}
.policy-pdf-toolbar button:hover { background:rgba(255,255,255,0.15); color:#fff; }
.policy-pdf-toolbar input[type="text"] {
    width:180px; padding:5px 10px; border-radius:4px; border:1px solid rgba(255,255,255,0.15);
    background:rgba(255,255,255,0.08); color:#fff; font-size:12px; outline:none;
}
.policy-pdf-toolbar input:focus { border-color:#4A90D9; background:rgba(255,255,255,0.12); }
.policy-pdf-toolbar .pdf-page-info { font-size:11px; color:rgba(255,255,255,0.5); white-space:nowrap; }
.policy-pdf-toolbar .pdf-spacer { flex:1; }

.policy-pdf-container {
    flex:1; overflow-y:auto; display:flex; flex-direction:column; align-items:center;
    padding:16px; gap:8px; scroll-behavior:smooth;
}
.pdf-page-wrapper {
    position:relative; background:#fff; box-shadow:0 2px 8px rgba(0,0,0,0.3);
}
.pdf-page-wrapper canvas { display:block; }
/* Text layer for selection + highlighting */
/* Document Tabs */
.doc-tabs {
    display:flex; background:#2A2D30; flex-shrink:0;
    border-bottom:1px solid rgba(0,0,0,0.3);
}
.doc-tab {
    flex:1; padding:10px 16px; font-size:12px; font-weight:600;
    background:transparent; border:none; color:rgba(255,255,255,0.45);
    cursor:pointer; transition:all 0.2s; border-bottom:2px solid transparent;
    white-space:nowrap;
}
.doc-tab:hover { color:rgba(255,255,255,0.7); background:rgba(255,255,255,0.03); }
.doc-tab.active { color:#fff; border-bottom-color:#1B6CB0; background:rgba(27,108,176,0.08); }
.doc-tab .compare-check { margin-right:6px; accent-color:#1B6CB0; }

.doc-compare-btn {
    padding:8px 14px; font-size:11px; font-weight:600; background:transparent;
    border:none; color:rgba(255,255,255,0.4); cursor:pointer; transition:all 0.2s;
    display:flex; align-items:center; gap:5px; white-space:nowrap; border-left:1px solid rgba(255,255,255,0.06);
}
.doc-compare-btn:hover { color:rgba(255,255,255,0.7); }
.doc-compare-btn.active { color:#5BA4D9; background:rgba(27,108,176,0.1); }
.doc-compare-btn.active svg { stroke:#5BA4D9; }

.doc-file-list {
    max-height:200px; overflow-y:auto; background:#323639;
    border-bottom:1px solid rgba(0,0,0,0.3);
}
.doc-file-item {
    padding:7px 16px; font-size:11px; color:rgba(255,255,255,0.6);
    cursor:pointer; transition:all 0.15s; border-bottom:1px solid rgba(255,255,255,0.03);
}
.doc-file-item:hover { background:rgba(27,108,176,0.15); color:#fff; }
.doc-file-item.active { background:rgba(27,108,176,0.2); color:#5BA4D9; font-weight:600; }

/* Split PDF viewer */
.pdf-split-wrap { flex:1; display:flex; flex-direction:column; overflow:hidden; }
.pdf-split-wrap.split-mode { flex-direction:row; }
.pdf-viewer-pane { flex:1; display:flex; flex-direction:column; overflow:hidden; min-height:0; }
.pdf-split-wrap.split-mode .pdf-viewer-pane { border-right:2px solid rgba(27,108,176,0.3); }
.pdf-split-wrap.split-mode .pdf-viewer-pane:last-child { border-right:none; }

.pdf-pane-toolbar {
    display:flex; align-items:center; gap:6px; padding:4px 10px;
    background:#323639; border-bottom:1px solid rgba(0,0,0,0.3); flex-shrink:0;
}
.pdf-pane-label { font-size:11px; font-weight:700; color:#5BA4D9; white-space:nowrap; }
.pdf-pane-search { width:120px; padding:4px 8px; border-radius:4px; border:1px solid rgba(255,255,255,0.12); background:rgba(255,255,255,0.06); color:#fff; font-size:11px; outline:none; }
.pdf-pane-search:focus { border-color:#4A90D9; }
.pdf-pane-toolbar button { background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.08); color:rgba(255,255,255,0.6); padding:3px 8px; border-radius:3px; cursor:pointer; font-size:11px; }
.pdf-pane-toolbar button:hover { background:rgba(255,255,255,0.12); color:#fff; }

/* PDF.js TextLayer overrides */
.textLayer { position:absolute !important; top:0; left:0; }
.textLayer span.highlight { background:rgba(27,108,176,0.4) !important; border-radius:2px; }
.textLayer span.highlight-active { background:rgba(27,108,176,0.6) !important; outline:2px solid #1B6CB0; }

/* Ref navigator */
.pdf-ref-nav {
    display:none; padding:6px 12px; background:#1B6CB0; color:#fff;
    font-size:11px; font-weight:600; align-items:center; justify-content:space-between;
}
.pdf-ref-nav.visible { display:flex; }
.pdf-ref-nav button {
    background:rgba(255,255,255,0.15); border:none; color:#fff;
    padding:3px 10px; border-radius:4px; cursor:pointer; font-size:11px;
}
.pdf-ref-nav button:hover { background:rgba(255,255,255,0.25); }

/* ─── Right: Chat ─── */
.policy-chat {
    width:420px; flex-shrink:0; display:flex; flex-direction:column;
    background:linear-gradient(180deg, #070F1B 0%, #0B2240 100%); color:#fff;
}
.policy-chat-header {
    padding:14px 20px; border-bottom:1px solid rgba(255,255,255,0.06); flex-shrink:0;
    display:flex; align-items:center; justify-content:space-between;
}
.policy-chat-header-left h2 { font-size:14px; font-weight:700; margin-bottom:2px; }
.policy-chat-header-left p { font-size:10px; color:rgba(255,255,255,0.35); }
.policy-chat-clear { background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.1); color:rgba(255,255,255,0.4); padding:4px 10px; border-radius:6px; font-size:10px; cursor:pointer; }
.policy-chat-clear:hover { color:#fff; background:rgba(255,255,255,0.1); }

.policy-chat-messages { flex:1; overflow-y:auto; padding:16px 20px; display:flex; flex-direction:column; gap:12px; }
.chat-msg { max-width:92%; padding:12px 16px; border-radius:14px; font-size:13px; line-height:1.6; animation:msgIn 0.3s ease; }
@keyframes msgIn { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }
.chat-msg-user { align-self:flex-end; background:var(--accent); color:#fff; border-bottom-right-radius:4px; }
.chat-msg-ai { align-self:flex-start; background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.06); color:rgba(255,255,255,0.85); border-bottom-left-radius:4px; }
.chat-msg-ai strong { color:#fff; }
.chat-msg-ai code { background:rgba(255,255,255,0.08); padding:1px 5px; border-radius:4px; font-size:12px; color:#5BA4D9; }
.chat-msg-ai h3,.chat-msg-ai h4 { color:#5BA4D9; margin:8px 0 4px; font-size:13px; }
.chat-msg-ai ul,.chat-msg-ai ol { padding-left:18px; margin:4px 0; }
.chat-msg-ai blockquote { border-left:3px solid var(--accent); padding-left:10px; margin:8px 0; color:rgba(255,255,255,0.6); font-style:italic; }
.chat-msg-system { align-self:center; color:rgba(255,255,255,0.25); font-size:11px; text-align:center; padding:4px; }
.chat-ref { display:inline-block; padding:1px 8px; margin:1px 2px; background:rgba(27,108,176,0.2); border:1px solid rgba(27,108,176,0.3); border-radius:4px; color:#5BA4D9; font-size:11px; font-weight:700; cursor:pointer; }
.chat-ref:hover { background:rgba(27,108,176,0.4); color:#fff; }
.chat-typing { display:flex; gap:4px; padding:12px 16px; align-self:flex-start; }
.chat-typing span { width:6px; height:6px; background:rgba(255,255,255,0.3); border-radius:50%; animation:tb 1.4s infinite; }
.chat-typing span:nth-child(2){animation-delay:.2s} .chat-typing span:nth-child(3){animation-delay:.4s}
@keyframes tb{0%,60%,100%{transform:translateY(0)}30%{transform:translateY(-6px)}}

.chat-suggestions { display:flex; flex-wrap:wrap; gap:6px; padding:0 20px 12px; }
.chat-suggestion { padding:6px 12px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); border-radius:20px; color:rgba(255,255,255,0.5); font-size:11px; cursor:pointer; }
.chat-suggestion:hover { background:rgba(27,108,176,0.2); color:#5BA4D9; border-color:rgba(27,108,176,0.3); }

.policy-chat-input { padding:12px 16px; border-top:1px solid rgba(255,255,255,0.06); flex-shrink:0; display:flex; gap:8px; }
.policy-chat-input textarea { flex:1; padding:10px 14px; border-radius:12px; border:1px solid rgba(255,255,255,0.1); background:rgba(255,255,255,0.06); color:#fff; font-size:13px; font-family:inherit; resize:none; outline:none; min-height:42px; max-height:120px; }
.policy-chat-input textarea::placeholder { color:rgba(255,255,255,0.25); }
.policy-chat-input textarea:focus { border-color:var(--accent); }
.policy-chat-send { width:42px; height:42px; border-radius:50%; border:none; background:linear-gradient(135deg,var(--accent),var(--highlight)); color:#fff; cursor:pointer; display:flex; align-items:center; justify-content:center; flex-shrink:0; align-self:flex-end; }
.policy-chat-send:hover { transform:scale(1.05); }
.policy-chat-send:disabled { opacity:0.4; cursor:not-allowed; }

@media(max-width:900px){.policy-page{flex-direction:column;height:auto}.policy-pdf{height:50vh}.policy-chat{width:100%;height:50vh}}
</style>

<!-- PDF.js CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.9.155/pdf.min.mjs" type="module"></script>

<div class="policy-page">
    <!-- LEFT: PDF Viewer -->
    <div class="policy-pdf">
        <!-- Document Tabs -->
        <div class="doc-tabs">
            <button class="doc-tab active" data-doc="l48-contract" data-pdf="assets/data/l48-contract.pdf" onclick="switchDocument(this)">
                <input type="checkbox" class="compare-check" data-doc="l48-contract" style="display:none;" onclick="event.stopPropagation()">
                L48 Contract
            </button>
            <button class="doc-tab" data-doc="operations" onclick="switchDocument(this)">
                <input type="checkbox" class="compare-check" data-doc="operations" style="display:none;" onclick="event.stopPropagation()">
                Operations Manual
            </button>
            <button class="doc-tab" data-doc="procedures" onclick="switchDocument(this)">
                <input type="checkbox" class="compare-check" data-doc="procedures" style="display:none;" onclick="event.stopPropagation()">
                Procedures Manual
            </button>
            <button class="doc-compare-btn" id="compareBtn" onclick="toggleCompareMode()">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 3h5v5"/><path d="M8 3H3v5"/><path d="M21 3l-7 7"/><path d="M3 3l7 7"/><path d="M16 21h5v-5"/><path d="M8 21H3v-5"/><path d="M21 21l-7-7"/><path d="M3 21l7-7"/></svg>
                Compare
            </button>
        </div>

        <!-- File list for multi-file manuals (hidden by default) -->
        <div class="doc-file-list" id="docFileList" style="display:none;">
            <div id="opsFileList" style="display:none;">
                <?php foreach ($opsFiles as $f): ?>
                <div class="doc-file-item" onclick="loadSubPdf('<?= $f['path'] ?>', this)"><?= htmlspecialchars($f['name']) ?></div>
                <?php endforeach; ?>
            </div>
            <div id="procFileList" style="display:none;">
                <?php foreach ($procFiles as $f): ?>
                <div class="doc-file-item" onclick="loadSubPdf('<?= $f['path'] ?>', this)"><?= htmlspecialchars($f['name']) ?></div>
                <?php endforeach; ?>
            </div>
        </div>

        <!-- Split PDF viewer container -->
        <div class="pdf-split-wrap" id="pdfSplitWrap">
            <!-- Viewer A (always visible) -->
            <div class="pdf-viewer-pane" id="pdfPaneA">
                <div class="pdf-pane-toolbar">
                    <span class="pdf-pane-label" id="pdfLabelA">L48 Contract</span>
                    <div class="pdf-spacer"></div>
                    <input type="text" class="pdf-pane-search" id="pdfSearchA" placeholder="Search..." onkeydown="if(event.key==='Enter')pdfSearchIn(0)">
                    <button onclick="pdfSearchIn(0)">Find</button>
                    <span class="pdf-page-info" id="pdfPageA">--</span>
                </div>
                <div class="pdf-ref-nav" id="pdfRefNavA">
                    <span id="pdfRefLabelA"></span>
                    <div><button onclick="pdfRefPrevIn(0)">&#8249;</button><button onclick="pdfRefNextIn(0)">&#8250;</button><button onclick="pdfClearIn(0)">&times;</button></div>
                </div>
                <div class="policy-pdf-container" id="pdfContainerA"></div>
            </div>
            <!-- Viewer B (only in compare mode) -->
            <div class="pdf-viewer-pane" id="pdfPaneB" style="display:none;">
                <div class="pdf-pane-toolbar">
                    <span class="pdf-pane-label" id="pdfLabelB">--</span>
                    <div class="pdf-spacer"></div>
                    <input type="text" class="pdf-pane-search" id="pdfSearchB" placeholder="Search..." onkeydown="if(event.key==='Enter')pdfSearchIn(1)">
                    <button onclick="pdfSearchIn(1)">Find</button>
                    <span class="pdf-page-info" id="pdfPageB">--</span>
                </div>
                <div class="pdf-ref-nav" id="pdfRefNavB">
                    <span id="pdfRefLabelB"></span>
                    <div><button onclick="pdfRefPrevIn(1)">&#8249;</button><button onclick="pdfRefNextIn(1)">&#8250;</button><button onclick="pdfClearIn(1)">&times;</button></div>
                </div>
                <div class="policy-pdf-container" id="pdfContainerB"></div>
            </div>
        </div>
    </div>

    <!-- RIGHT: Chat -->
    <div class="policy-chat">
        <div class="policy-chat-header">
            <div class="policy-chat-header-left">
                <h2>Contract Assistant</h2>
                <p>Gemini 3.1 Pro &middot; L48 2023-2026</p>
            </div>
            <button class="policy-chat-clear" onclick="clearChat()">Clear</button>
        </div>
        <div class="policy-chat-messages" id="chatMessages">
            <div class="chat-msg chat-msg-ai">
                I've loaded the complete Local 48 contract. Ask me anything — I'll cite specific articles and you can <strong>click references</strong> to jump to that page in the PDF.
            </div>
        </div>
        <div class="chat-suggestions" id="chatSuggestions">
            <span class="chat-suggestion" onclick="askQ(this)">What is the salary for a Captain?</span>
            <span class="chat-suggestion" onclick="askQ(this)">Explain the grievance procedure</span>
            <span class="chat-suggestion" onclick="askQ(this)">Overtime and comp time rules</span>
            <span class="chat-suggestion" onclick="askQ(this)">What are the shift schedules?</span>
        </div>
        <div class="policy-chat-input">
            <textarea id="chatInput" placeholder="Ask about the contract..." rows="1" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendChat();}"></textarea>
            <button class="voice-mic-btn" id="voiceMicBtn" onclick="toggleVoiceChat()" title="Voice Chat">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>
            </button>
            <button class="policy-chat-send" id="chatSendBtn" onclick="sendChat()">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
            </button>
        </div>
    </div>
</div>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.9.155/pdf_viewer.min.css">
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<script type="module">
import * as pdfjsLib from 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.9.155/pdf.min.mjs';
import { TextLayer } from 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.9.155/pdf.min.mjs';
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.9.155/pdf.worker.min.mjs';

// ─── PDF Viewer Class ───────────────────────────────
class PdfViewer {
    constructor(containerEl, pageInfoEl, refNavEl, refLabelEl) {
        this.container = containerEl;
        this.pageInfoEl = pageInfoEl;
        this.refNavEl = refNavEl;
        this.refLabelEl = refLabelEl;
        this.pdfDoc = null;
        this.scale = 1.0;
        this.texts = [];
        this.highlights = [];
        this.refIdx = 0;
        this.ready = Promise.resolve();
        this._resolveReady = null;
        this.docId = '';
    }
    async load(url, docId) {
        this.ready = new Promise(r => { this._resolveReady = r; });
        this.docId = docId || '';
        this.pdfDoc = await pdfjsLib.getDocument(url).promise;
        if (this.pageInfoEl) this.pageInfoEl.textContent = this.pdfDoc.numPages + ' pg';
        await this.render();
        if (this._resolveReady) this._resolveReady();
    }
    async render() {
        this.container.innerHTML = '';
        this.texts = [];
        for (let i = 1; i <= this.pdfDoc.numPages; i++) {
            const page = await this.pdfDoc.getPage(i);
            const vp = page.getViewport({ scale: this.scale });
            const wrap = document.createElement('div');
            wrap.className = 'pdf-page-wrapper';
            wrap.dataset.page = i;
            wrap.style.width = vp.width+'px'; wrap.style.height = vp.height+'px';
            const cvs = document.createElement('canvas');
            cvs.width = vp.width; cvs.height = vp.height;
            wrap.appendChild(cvs);
            await page.render({ canvasContext: cvs.getContext('2d'), viewport: vp }).promise;
            const tc = await page.getTextContent();
            const tld = document.createElement('div');
            tld.className = 'textLayer';
            tld.style.cssText = `width:${vp.width}px;height:${vp.height}px;position:absolute;top:0;left:0;`;
            wrap.appendChild(tld);
            const tl = new TextLayer({ textContentSource: tc, container: tld, viewport: vp });
            await tl.render();
            const items = [];
            tld.querySelectorAll('span').forEach(s => { if (s.textContent.trim()) items.push({str:s.textContent, span:s, page:i}); });
            this.container.appendChild(wrap);
            this.texts.push({ page: i, items });
        }
        const self = this;
        this.container.onscroll = () => {
            const mid = self.container.scrollTop + self.container.clientHeight/2;
            for (const w of self.container.querySelectorAll('.pdf-page-wrapper')) {
                if (w.offsetTop + w.clientHeight > mid) { if (self.pageInfoEl) self.pageInfoEl.textContent = w.dataset.page+'/'+self.pdfDoc.numPages; break; }
            }
        };
    }
    search(term) {
        this.clearHL();
        const results = [];
        const re = new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'), 'gi');
        this.texts.forEach(p => p.items.forEach(it => { re.lastIndex=0; if (re.test(it.str)) { it.span.classList.add('highlight'); results.push({page:p.page,span:it.span,term}); } }));
        if (results.length) { this.highlights=results; this.refIdx=0; results[0].span.classList.add('highlight-active'); this.updateNav(); results[0].span.scrollIntoView({behavior:'smooth',block:'center'}); }
    }
    highlightTerms(terms) {
        this.clearHL();
        const results = [];
        terms.forEach(t => { const re = new RegExp(t.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'),'gi'); this.texts.forEach(p => p.items.forEach(it => { re.lastIndex=0; if(re.test(it.str)){it.span.classList.add('highlight');results.push({page:p.page,span:it.span,term:t});} })); });
        if (results.length) { this.highlights=results; this.refIdx=0; results[0].span.classList.add('highlight-active'); this.updateNav(); results[0].span.scrollIntoView({behavior:'smooth',block:'center'}); }
    }
    navNext() { if(!this.highlights.length) return; this.highlights[this.refIdx]?.span.classList.remove('highlight-active'); this.refIdx=(this.refIdx+1)%this.highlights.length; this.highlights[this.refIdx].span.classList.add('highlight-active'); this.updateNav(); this.highlights[this.refIdx].span.scrollIntoView({behavior:'smooth',block:'center'}); }
    navPrev() { if(!this.highlights.length) return; this.highlights[this.refIdx]?.span.classList.remove('highlight-active'); this.refIdx=(this.refIdx-1+this.highlights.length)%this.highlights.length; this.highlights[this.refIdx].span.classList.add('highlight-active'); this.updateNav(); this.highlights[this.refIdx].span.scrollIntoView({behavior:'smooth',block:'center'}); }
    updateNav() { if(!this.highlights.length){this.refNavEl?.classList.remove('visible');return;} this.refNavEl?.classList.add('visible'); const r=this.highlights[this.refIdx]; if(this.refLabelEl) this.refLabelEl.textContent=`${r.term||'Match'} p${r.page} (${this.refIdx+1}/${this.highlights.length})`; }
    clearHL() { this.container.querySelectorAll('.highlight,.highlight-active').forEach(s=>s.classList.remove('highlight','highlight-active')); this.highlights=[]; this.refNavEl?.classList.remove('visible'); }
}

// ─── Create two viewer instances ────────────────────
const viewers = [
    new PdfViewer(document.getElementById('pdfContainerA'), document.getElementById('pdfPageA'), document.getElementById('pdfRefNavA'), document.getElementById('pdfRefLabelA')),
    new PdfViewer(document.getElementById('pdfContainerB'), document.getElementById('pdfPageB'), document.getElementById('pdfRefNavB'), document.getElementById('pdfRefLabelB'))
];

// ─── Cross-viewer unified highlight navigation ──────
const docNames = { 'l48-contract': 'L48 Contract', 'operations': 'Operations Manual', 'procedures': 'Procedures Manual' };
let crossHighlights = [];
let crossIdx = 0;

function buildCrossViewerNav(readyViewers) {
    crossHighlights = [];
    readyViewers.forEach(v => {
        const label = docNames[v.docId] || v.docId || 'Document';
        v.highlights.forEach(h => {
            crossHighlights.push({ ...h, viewer: v, docLabel: label });
        });
    });
    crossIdx = 0;
    updateCrossNav();
}

function crossNavNext() {
    if (!crossHighlights.length) return;
    crossHighlights[crossIdx]?.span.classList.remove('highlight-active');
    crossIdx = (crossIdx + 1) % crossHighlights.length;
    const h = crossHighlights[crossIdx];
    h.span.classList.add('highlight-active');
    h.span.scrollIntoView({ behavior: 'smooth', block: 'center' });
    updateCrossNav();
}

function crossNavPrev() {
    if (!crossHighlights.length) return;
    crossHighlights[crossIdx]?.span.classList.remove('highlight-active');
    crossIdx = (crossIdx - 1 + crossHighlights.length) % crossHighlights.length;
    const h = crossHighlights[crossIdx];
    h.span.classList.add('highlight-active');
    h.span.scrollIntoView({ behavior: 'smooth', block: 'center' });
    updateCrossNav();
}

function updateCrossNav() {
    // Update both viewer nav bars to show unified count
    viewers.forEach(v => {
        if (v.refNavEl && crossHighlights.length) {
            v.refNavEl.classList.add('visible');
            const h = crossHighlights[crossIdx];
            if (v.refLabelEl) v.refLabelEl.textContent = `${h?.term || 'Match'} — ${h?.docLabel || ''} p${h?.page} (${crossIdx + 1}/${crossHighlights.length})`;
        }
    });
}

// Override viewer nav buttons to use cross-viewer navigation
window.pdfHighlightText = function(terms) {
    const readyViewers = viewers.filter(v => v.pdfDoc);
    Promise.all(readyViewers.map(v => v.ready)).then(() => {
        readyViewers.forEach(v => v.highlightTerms(terms));
        buildCrossViewerNav(readyViewers);
    });
};

const docPdfs = {
    'l48-contract': 'assets/data/l48-contract.pdf',
    'operations': 'assets/data/Operations%2008-09/201.01%20Emergency%20Response%20Updated%2010-2016.pdf',
    'procedures': 'assets/data/Procedures%20Manual/101%20Administration.pdf'
};

// ─── PDF Lookup Maps (policy number → file path) ─────
const opsBase = 'assets/data/Operations%2008-09/';
const opsPdfMap = {
    '201.01':'201.01%20Emergency%20Response%20Updated%2010-2016.pdf','201.02':'201.02%20Persons%20Riding%20on%20Fire%20Apparatus.pdf','201.04':'201.04%20PUCO%20Notification.pdf',
    '202.01':'202.01%20Incident%20Command%20System%20Update%2010-14%20Completed.pdf','202.02':'202.02%20Pre%20Planning.pdf','202.04':'202.04%20Fire%20Investigation%20Rev%207-05.pdf',
    '202.05':'202.05%20%20Rehabilitation%20EMS%20Standby%20Updated%2010-14%20Completed.pdf','202.06':'202.06%20Fire%20Zones%20Perimeter%20Updated%2010-14%20Completed.pdf','202.07':'202.07%20Accountability%208-14-19.pdf',
    '203.01':'203.01%20Structure%20Fire%20Operations.pdf','203.02':'203.02%20Suppression%20Systems.pdf','203.03':'203.03%20Outdoor%20Fires.pdf',
    '203.04':'203.04%20Mailbox%20Fires.pdf','203.05':'203.05%20Elevator%20Escalator%20Emergencies.pdf','203.06':'203.06%20Vehicular%20fires.pdf',
    '203.07':'203.07%20Vehicular%20accidents.pdf','203.08':'203.08%20Aircraft%20Emergencies.pdf','203.09':'203.09%20Structural%20Collapse.pdf',
    '203.12':'203.12%20Handling%20Fatalities%20rev%207-05.pdf','203.13':'203.13%20Natural%20Disasters.pdf','203.14':'203.14%20%20Police%20Fire%20Operations.pdf',
    '203.15':'203.15%20Cave%20In%20and%20Manhole%20Rescues.pdf','203.16':'203.16%20Electrical%20Emergencies.pdf','203.17':'203.17%20Water%20Emergencies%2011-2011%20revision.pdf',
    '203.18':'203.18%20Highway%20emergencies.pdf','203.19':'203.19%20Railroad%20Emergencies.pdf','203.20':'203.20%20Hazardous%20Materials.pdf',
    '203.21':'203.21%20Hazardous%20Device%20Response.pdf','203.24':'203.24%20Bulk%20Power%20Emergencies.pdf','203.26':'203.26%20Chemical%20Suicide.pdf','203.27':'203.27%20Streetcar%20Emergencies.pdf',
    '204.01':'204.01%20Emergency%20Medical%20Responses%20Updated%2012.15.pdf','204.02':'204.02%20Mass%20Casualty%20Incident%20Rev%2011-2012.pdf',
    '204.03':'204.03%20Carbon%20Monoxide%20Incident%20Rev%208-05.pdf','204.04':'204.04%20Air%20Care%20Helicopter%20rev%20-8-05.pdf','204.05':'204.05%20Infection%20Control%20SOP.pdf',
    '204.10':'204.10%20Trauma%20Triage%20Protocols12-06.pdf','204.11':'204.11%20Clinical%20Practice%20Standards4-07.pdf','204.13':'204.13%20Continuous%20Quality%20Improvement.pdf',
    '204.17':'204.17%20DUI%20Blood%20draws.pdf','204.19':'204.19%20Combative%20Patients.pdf','204.21':'204.21%20Drug%20License.pdf',
    '205.01':'205.01%20Rev%202-21.pdf','205.15':'205.15%20EMS%20Reporting.pdf','206.01':'206.01%20CBRNE%20Response%20Rev%209-05.pdf','207.01':'207.01%20Squad%20Company%20Responses.pdf',
};
// Sub-SOPs like 203.01.01 through 203.01.20
for (let i = 1; i <= 20; i++) {
    const k = '203.01.' + String(i).padStart(2,'0');
    if (!opsPdfMap[k]) opsPdfMap[k] = opsPdfMap['203.01']; // fall back to parent
}

const procBase = 'assets/data/Procedures%20Manual/';
const procPdfMap = {
    '101':'101%20Administration.pdf','102':'102%20Organizational%20Structure%20Updated%205-8-2020.pdf','103':'103%20Prep%20of%20Policies.pdf','104':'104%20Terminology.pdf',
    '301':'301%20Vehicles%20Intro.pdf','302':'302%20Driving%20and%20Operation%20Updated%205-8-2020.pdf','303':'303%20Maint%20And%20Repair.pdf','304':'304%20Reserve%20App.pdf',
    '305':'305%20Vehicles%20other%20than%20Apparatus.pdf','306':'306%20CAD%20Equipment%20File.pdf','307':'307%20Vehicle%20Policy.pdf',
    '401':'401%20Uniform%20Clothing%208-21-19.pdf','402':'402%20Personal%20Protective%20Equipment%20Updated%20Dec-2017.pdf','403':'403%20Investigation%20of%20PPE%20Failure.pdf','404':'404%20Apparatus%20Equipment.pdf',
    '501':'501%20Personal%20Behavior.pdf','502':'502%20Professional%20Appearance%20REV%2003-2021.pdf','503':'503%20Discipline%20Updated%2012-8-2021.pdf',
    '504':'504%20Disciplinary%20Investigation.pdf','505':'505%20LODD%20and%20Injury%20Investigation.pdf','506':'506%20Social%20Media.pdf','507':'507%20Awards%20Program%20Procedure_rev3.pdf',
    '601':'601.08%20Work%20Schedule%20and%20Duties%20Rev%202-22.pdf','602':'602%20Company%20Status.pdf','604':'604%20Excessive%20heat.pdf','605':'605%20Death%20ofan%20Active%20or%20Retired%20Member%20Updated%206-2017.pdf',
    '701':'701-Radio%20Procedure.pdf','702':'702%20Telephone%20Procedure.pdf','703':'703%20Dispatch%20Procedures%20Updated%208-13-19.pdf','704':'704%20Drug%20Accountability.pdf','706':'706%20Language%20Translation%20Service.pdf',
    '801':'801%20Training%20and%20Education.pdf','802':'802%20Education.pdf','803':'803%20Reference%20Lists%20Rev%205-16.pdf','804':'804%20Travel%20for%20Education%20%26%20Training.pdf',
    '805':'805%20EMT%20Continuing%20Education%20Certification.pdf',
    '901':'901%20Leaves%20of%20Absence%2C%20General.pdf','902':'902.01%20Sick%20Leave.pdf','903':'903%20IWP.pdf','905':'905%20-%20Other%20Leaves%20of%20Absence%20Updated%205-8-2020.pdf','906':'906%20EHS.pdf','907':'907%20PEAP.pdf',
    '1001':'1001%20Conducting%20Fire%20Prevention%20Inspections.pdf','1002':'1002%20Inspection%20Categories.pdf','1003':'1003%20Violations%20and%20Referrals.pdf',
    '1004':'1004%20Unsafe%20Vacant%20and%20Abandoned%20Bldgs%20Marking%20System%2020.06.20%20(1).pdf','1005':'1005%20Establishment%20of%20Firewatch.pdf',
    '1101':'1101%20Station%20Repair%20and%20Maintenance%20Updated%209-2017.pdf','1102':'1102%20Americans%20with%20Disabilities%20Act.pdf',
    '1201':'1201%20Central%20Stores%20(2).pdf','1301':'1301%20Computer%20Procedures%20(2).pdf','1302':'1302%20Software%20Use%20(2).pdf','1303':'1303%20Computer%20Virus%20(2).pdf','1304':'1304%20Use%20of%20Internet%20and%20Email.pdf',
    '1402':'1402%20Right%20to%20Know.pdf','1501':'1501%20House%20Fund.pdf',
};

// Auto-load correct PDF based on policy references in AI response
async function autoLoadReferencedPdf(answer, viewerIdx, currentDocId) {
    if (currentDocId === 'l48-contract') return; // L48 is a single PDF, no switching needed

    // Extract policy/SOP numbers from the AI answer
    const refs = [];
    const patterns = [
        /(?:Section|SOP|Policy|Procedure)\s+(\d{3,4}(?:\.\d{1,2})?)/gi,
        /\b(\d{3,4}\.\d{2})\b/g, // bare numbers like 203.01, 302.01
    ];
    for (const p of patterns) {
        let m;
        while ((m = p.exec(answer)) !== null) refs.push(m[1]);
    }
    if (!refs.length) return;

    const map = currentDocId === 'operations' ? opsPdfMap : procPdfMap;
    const base = currentDocId === 'operations' ? opsBase : procBase;

    // Find the first matching PDF
    for (const ref of refs) {
        // Try exact match, then try truncating sub-sections
        let file = map[ref];
        if (!file) {
            const parts = ref.split('.');
            if (parts.length >= 2) file = map[parts[0] + '.' + parts[1]];
            if (!file) file = map[parts[0]];
        }
        if (file) {
            const newPath = base + file;
            const currentPath = docPdfs[currentDocId];
            if (newPath !== currentPath) {
                docPdfs[currentDocId] = newPath;
                await viewers[viewerIdx].load(newPath, currentDocId);
            }
            return;
        }
    }
}

// Global functions for toolbar buttons
window.pdfSearchIn = (idx) => { const term = document.getElementById(idx===0?'pdfSearchA':'pdfSearchB').value.trim(); if(term) viewers[idx].search(term); };
window.pdfRefNextIn = (idx) => {
    if (crossHighlights.length) {
        // Jump to next highlight in this viewer's document, or cross-nav if at end
        const viewerHL = crossHighlights.filter(h => h.viewer === viewers[idx]);
        if (viewerHL.length) {
            // Find current position within this viewer's highlights
            const currentInViewer = viewerHL.findIndex((h, i) => crossHighlights.indexOf(h) === crossIdx);
            if (currentInViewer >= 0 && currentInViewer < viewerHL.length - 1) {
                // Next within same viewer
                crossHighlights[crossIdx]?.span.classList.remove('highlight-active');
                crossIdx = crossHighlights.indexOf(viewerHL[currentInViewer + 1]);
            } else {
                // Jump to first highlight in this viewer
                crossHighlights[crossIdx]?.span.classList.remove('highlight-active');
                crossIdx = crossHighlights.indexOf(viewerHL[0]);
            }
            crossHighlights[crossIdx].span.classList.add('highlight-active');
            crossHighlights[crossIdx].span.scrollIntoView({ behavior: 'smooth', block: 'center' });
            updateCrossNav();
        } else {
            crossNavNext();
        }
    } else {
        viewers[idx].navNext();
    }
};
window.pdfRefPrevIn = (idx) => {
    if (crossHighlights.length) {
        const viewerHL = crossHighlights.filter(h => h.viewer === viewers[idx]);
        if (viewerHL.length) {
            const currentInViewer = viewerHL.findIndex((h, i) => crossHighlights.indexOf(h) === crossIdx);
            if (currentInViewer > 0) {
                crossHighlights[crossIdx]?.span.classList.remove('highlight-active');
                crossIdx = crossHighlights.indexOf(viewerHL[currentInViewer - 1]);
            } else {
                crossHighlights[crossIdx]?.span.classList.remove('highlight-active');
                crossIdx = crossHighlights.indexOf(viewerHL[viewerHL.length - 1]);
            }
            crossHighlights[crossIdx].span.classList.add('highlight-active');
            crossHighlights[crossIdx].span.scrollIntoView({ behavior: 'smooth', block: 'center' });
            updateCrossNav();
        } else {
            crossNavPrev();
        }
    } else {
        viewers[idx].navPrev();
    }
};
window.pdfClearIn = (idx) => viewers[idx].clearHL();
window.pdfHighlightText = async function(terms) {
    const readyViewers = viewers.filter(v => v.pdfDoc);
    await Promise.all(readyViewers.map(v => v.ready));
    readyViewers.forEach(v => v.highlightTerms(terms));
    buildCrossViewerNav(readyViewers);
};

let currentDocument = 'l48-contract';
let compareActive = false;
let selectedCompare = [];
const splitWrap = document.getElementById('pdfSplitWrap');
const paneA = document.getElementById('pdfPaneA');
const paneB = document.getElementById('pdfPaneB');

// Load initial PDF
viewers[0].load('assets/data/l48-contract.pdf');
document.getElementById('pdfLabelA').textContent = 'L48 Contract';

// ─── Compare Mode ───────────────────────────────────
window.toggleCompareMode = function() {
    compareActive = !compareActive;
    const btn = document.getElementById('compareBtn');
    const checks = document.querySelectorAll('.compare-check');
    btn.classList.toggle('active', compareActive);

    if (compareActive) {
        checks.forEach(c => { c.style.display = 'inline'; c.checked = false; });
        selectedCompare = [];
        paneB.style.display = 'none';
        splitWrap.classList.remove('split-mode');
        history = [];
        messagesEl.innerHTML = '<div class="chat-msg chat-msg-ai">Compare mode activated. Select 2 or 3 documents, then ask a question.</div>';
    } else {
        checks.forEach(c => { c.style.display = 'none'; c.checked = false; });
        selectedCompare = [];
        paneB.style.display = 'none';
        splitWrap.classList.remove('split-mode');
        history = [];
        messagesEl.innerHTML = '<div class="chat-msg chat-msg-ai">Compare mode off. Back to single document.</div>';
        suggestionsEl.style.display = 'flex';
    }
};

document.addEventListener('change', function(e) {
    if (!e.target.classList.contains('compare-check')) return;
    selectedCompare = [];
    document.querySelectorAll('.compare-check:checked').forEach(c => selectedCompare.push(c.dataset.doc));

    if (selectedCompare.length >= 2) {
        // Show split view with first two selected docs
        splitWrap.classList.add('split-mode');
        paneB.style.display = 'flex';

        // Load PDFs into each pane
        const docA = selectedCompare[0];
        const docB = selectedCompare[1];
        const pdfA = docPdfs[docA] || 'assets/data/l48-contract.pdf';
        const pdfB = docPdfs[docB] || 'assets/data/l48-contract.pdf';

        document.getElementById('pdfLabelA').textContent = docNames[docA];
        document.getElementById('pdfLabelB').textContent = docNames[docB];

        if (pdfA) viewers[0].load(pdfA, docA);
        if (pdfB) viewers[1].load(pdfB, docB);

        const sel = selectedCompare.map(d => docNames[d]).join(' + ');
        history = [];
        messagesEl.innerHTML = `<div class="chat-msg chat-msg-ai">Comparing <strong>${sel}</strong>. Both documents are shown side by side. Ask me to find conflicts or differences.</div>`;
        suggestionsEl.innerHTML = '<span class="chat-suggestion" onclick="askQ(this)">Compare overtime policies</span><span class="chat-suggestion" onclick="askQ(this)">Are there conflicts between these?</span><span class="chat-suggestion" onclick="askQ(this)">What does each say about discipline?</span>';
        suggestionsEl.style.display = 'flex';
    } else {
        paneB.style.display = 'none';
        splitWrap.classList.remove('split-mode');
    }
});

// ─── Document Switching (single mode) ───────────────
window.switchDocument = function(btn) {
    if (compareActive) return; // in compare mode, tabs act as checkboxes only
    const docId = btn.dataset.doc;
    document.querySelectorAll('.doc-tab').forEach(t => t.classList.remove('active'));
    btn.classList.add('active');

    const fileList = document.getElementById('docFileList');
    const opsFL = document.getElementById('opsFileList');
    const procFL = document.getElementById('procFileList');

    if (docId === 'l48-contract') {
        fileList.style.display = 'none';
        currentDocument = 'l48-contract';
        viewers[0].load('assets/data/l48-contract.pdf');
        document.getElementById('pdfLabelA').textContent = 'L48 Contract';
    } else if (docId === 'operations') {
        fileList.style.display = 'block'; opsFL.style.display = 'block'; procFL.style.display = 'none';
        currentDocument = 'operations';
        document.getElementById('pdfLabelA').textContent = 'Operations Manual';
        const first = opsFL.querySelector('.doc-file-item');
        if (first) loadSubPdf(first.getAttribute('onclick').match(/'([^']+)'/)[1], first);
    } else if (docId === 'procedures') {
        fileList.style.display = 'block'; opsFL.style.display = 'none'; procFL.style.display = 'block';
        currentDocument = 'procedures';
        document.getElementById('pdfLabelA').textContent = 'Procedures Manual';
        const first = procFL.querySelector('.doc-file-item');
        if (first) loadSubPdf(first.getAttribute('onclick').match(/'([^']+)'/)[1], first);
    }
    history = [];
    messagesEl.innerHTML = `<div class="chat-msg chat-msg-ai">Switched to <strong>${btn.textContent.trim()}</strong>.</div>`;
    suggestionsEl.style.display = 'flex';
};

window.loadSubPdf = function(path, item) {
    document.querySelectorAll('.doc-file-item').forEach(i => i.classList.remove('active'));
    if (item) item.classList.add('active');
    viewers[0].load(path);
};

// ─── Chat ──────────────────────────────────────────
const messagesEl = document.getElementById('chatMessages');
const inputEl = document.getElementById('chatInput');
const sendBtn = document.getElementById('chatSendBtn');
const suggestionsEl = document.getElementById('chatSuggestions');
let history = [];
let sending = false;

inputEl.addEventListener('input', () => { inputEl.style.height='auto'; inputEl.style.height=Math.min(inputEl.scrollHeight,120)+'px'; });
window.askQ = (el) => { inputEl.value=el.textContent; sendChat(); };
window.clearChat = () => { history=[]; messagesEl.innerHTML='<div class="chat-msg chat-msg-ai">Chat cleared.</div>'; suggestionsEl.style.display='flex'; viewers.forEach(v=>v.clearHL()); };

// Detect if a question is an image generation request
function isImageRequest(q) {
    const triggers = ['generate an image', 'generate a diagram', 'create an image', 'create a diagram',
        'draw me', 'draw a', 'show me an image', 'show me a diagram', 'visualize', 'illustration of',
        'create a visual', 'generate a visual', 'make an image', 'make a diagram', 'design a',
        'create an infographic', 'generate an infographic', 'create a poster', 'generate a flowchart'];
    const ql = q.toLowerCase();
    return triggers.some(t => ql.includes(t));
}

window.sendChat = async function() {
    const question = inputEl.value.trim();
    if (!question || sending) return;
    sending=true; sendBtn.disabled=true; suggestionsEl.style.display='none';
    addMsg('user', question);
    inputEl.value=''; inputEl.style.height='auto';
    const typ = document.createElement('div');
    typ.className='chat-typing'; typ.innerHTML='<span></span><span></span><span></span>';
    messagesEl.appendChild(typ); messagesEl.scrollTop=messagesEl.scrollHeight;

    try {
        if (isImageRequest(question)) {
            // Route to Nano Banana Pro for image generation
            typ.querySelector('span:first-child').style.background = '#9333EA';
            const resp = await fetch(window.CFD_PORTAL.apiPath+'/ai-image.php', {
                method:'POST',
                headers:{'Content-Type':'application/json','X-Requested-With':'XMLHttpRequest','X-CSRF-TOKEN':window.CFD_PORTAL.csrfToken},
                body:JSON.stringify({ prompt: question, context: currentDocument, csrf_token: window.CFD_PORTAL.csrfToken })
            });
            const data = await resp.json(); typ.remove();
            if (data.error) { addMsg('system', 'Image error: ' + data.error); }
            else if (data.image) {
                const imgId = 'aiImg_' + (++aiChartCounter);
                addMsg('ai', (data.text || 'Here is your generated image:') +
                    `\n\n__IMG_${imgId}__`);
                // Inject the image after the message is in DOM
                setTimeout(() => {
                    const placeholder = document.querySelector('.chat-msg-ai:last-child');
                    if (placeholder) {
                        placeholder.innerHTML = placeholder.innerHTML.replace(
                            `__IMG_${imgId}__`,
                            `<div class="ai-chart-wrap" onclick="window.expandImage('${imgId}')" style="margin-top:10px;">
                                <img id="${imgId}" src="data:${data.mimeType};base64,${data.image}" style="width:100%;border-radius:8px;cursor:pointer;">
                                <div class="ai-chart-hint">Click to expand &middot; Download</div>
                            </div>`
                        );
                    }
                }, 50);
                history.push({role:'user',text:question}, {role:'model',text:'[Generated image]'});
            }
        } else {
            // Standard text chat via Gemini 3.1 Pro
            const resp = await fetch(window.CFD_PORTAL.apiPath+'/contract-chat.php', {
                method:'POST',
                headers:{'Content-Type':'application/json','X-Requested-With':'XMLHttpRequest','X-CSRF-TOKEN':window.CFD_PORTAL.csrfToken},
                body:JSON.stringify({ question, history, document:currentDocument, compare:compareActive&&selectedCompare.length>=2?selectedCompare:null, csrf_token:window.CFD_PORTAL.csrfToken })
            });
            const data = await resp.json(); typ.remove();
            if (data.error) { addMsg('system','Error: '+data.error); }
            else {
                addMsg('ai', data.answer);
                history.push({role:'user',text:question}); history.push({role:'model',text:data.answer});
                if (history.length>20) history=history.slice(-20);
                // Document-aware highlighting: parse [L48], [OPS], [PROC] tags from AI response
                const docTagMap = { 'L48': 'l48-contract', 'OPS': 'operations', 'PROC': 'procedures' };
                const l48Terms = [];
                const opsTerms = [];
                const procTerms = [];
                const genericTerms = [];

                // Parse tagged references: [L48] Article 37... or [OPS] SOP 204.01...
                const answer = data.answer;

                // Extract document-tagged sections and their quotes
                const taggedSections = answer.split(/(?=\[(?:L48|OPS|PROC)\])/g);
                for (const section of taggedSections) {
                    let targetBucket = genericTerms;
                    if (section.startsWith('[L48]')) targetBucket = l48Terms;
                    else if (section.startsWith('[OPS]')) targetBucket = opsTerms;
                    else if (section.startsWith('[PROC]')) targetBucket = procTerms;

                    // Extract Article/Section refs from this section
                    [...section.matchAll(/Article\s+(\d+)/gi)].forEach(m => targetBucket.push('ARTICLE ' + m[1]));
                    [...section.matchAll(/Section\s+(\d+\.\d+)/gi)].forEach(m => targetBucket.push('Section ' + m[1]));
                    [...section.matchAll(/SOP\s+(\d{3}\.\d{2})/gi)].forEach(m => targetBucket.push(m[1]));
                    // Extract quoted text
                    [...section.matchAll(/"([^"]{15,120})"/g)].forEach(m => {
                        const q = m[1].trim();
                        if (q && !q.match(/^\d+$/) && q.length >= 15) targetBucket.push(q);
                    });
                }

                // Also catch untagged Article/Section refs as L48 (default)
                [...answer.matchAll(/(?<!\[(?:L48|OPS|PROC)\][^]*?)Article\s+(\d+)/gi)].forEach(m => {
                    if (!l48Terms.includes('ARTICLE ' + m[1])) l48Terms.push('ARTICLE ' + m[1]);
                });

                const allTerms = [...new Set([...l48Terms, ...opsTerms, ...procTerms, ...genericTerms])];

                if (allTerms.length) {
                    // Auto-load correct PDF for ops/procedures
                    if (compareActive && selectedCompare.length >= 2) {
                        await autoLoadReferencedPdf(answer, 0, selectedCompare[0]);
                        await autoLoadReferencedPdf(answer, 1, selectedCompare[1]);
                    } else {
                        await autoLoadReferencedPdf(answer, 0, currentDocument);
                    }

                    const readyViewers = viewers.filter(v => v.pdfDoc);
                    await Promise.all(readyViewers.map(v => v.ready));

                    // Route terms to correct viewer based on document tags
                    for (const v of readyViewers) {
                        let viewerTerms = [...genericTerms]; // all viewers get generic terms
                        if (v.docId === 'l48-contract') viewerTerms = [...viewerTerms, ...l48Terms];
                        else if (v.docId === 'operations') viewerTerms = [...viewerTerms, ...opsTerms];
                        else if (v.docId === 'procedures') viewerTerms = [...viewerTerms, ...procTerms];
                        if (viewerTerms.length) v.highlightTerms([...new Set(viewerTerms)]);
                    }
                    buildCrossViewerNav(readyViewers);
                }
            }
        }
    } catch(e) { typ.remove(); addMsg('system','Failed to reach AI.'); }
    sending=false; sendBtn.disabled=false; inputEl.focus();
};

let aiChartCounter = 0;
let pendingCharts = [];
const aiChartInstances = {};

function addMsg(role, text) {
    pendingCharts = [];
    const div = document.createElement('div');
    if (role==='user') { div.className='chat-msg chat-msg-user'; div.textContent=text; }
    else if (role==='ai') { div.className='chat-msg chat-msg-ai'; div.innerHTML=fmtRefs(text); }
    else { div.className='chat-msg chat-msg-system'; div.textContent=text; }
    messagesEl.appendChild(div); messagesEl.scrollTop=messagesEl.scrollHeight;

    // Render any pending charts after DOM insertion
    if (pendingCharts.length) {
        setTimeout(() => {
            pendingCharts.forEach(p => renderAiChart(p.id, p.type, p.config));
            pendingCharts = [];
        }, 100);
    }

    // Auto-detect numerical data in AI responses and generate chart
    if (role === 'ai' && !pendingCharts.length) {
        const autoChart = autoDetectChartData(text);
        if (autoChart) {
            const chartId = 'aiChart_' + (++aiChartCounter);
            const chartDiv = document.createElement('div');
            chartDiv.className = 'ai-chart-wrap';
            chartDiv.onclick = () => window.expandChart(chartId);
            chartDiv.innerHTML = `<div id="${chartId}" style="width:100%;height:200px;"></div><div class="ai-chart-hint">Click to expand &middot; Download</div>`;
            div.appendChild(chartDiv);
            setTimeout(() => {
                renderAiChart(chartId, autoChart.type, autoChart.config);
            }, 100);
        }
    }
}

function autoDetectChartData(text) {
    const pairs = [];
    const lines = text.split('\n');

    for (const line of lines) {
        // Pattern 1: **Bold label** ... $amount
        let m = line.match(/\*{2}(.+?)\*{2}.*?\$\s*([\d,]+\.?\d*)/);
        if (m) {
            const label = m[1].replace(/\([^)]*\)/g, '').replace(/:/g, '').trim();
            const value = parseFloat(m[2].replace(/,/g, ''));
            if (label.length > 1 && label.length < 40 && !isNaN(value) && value > 0) {
                pairs.push({ label, value });
                continue;
            }
        }
        // Pattern 2: - Label: $amount (no bold)
        m = line.match(/[-*•]\s*(.+?)\s*:\s*\$\s*([\d,]+\.?\d*)/);
        if (m) {
            const label = m[1].replace(/\*+/g, '').replace(/\([^)]*\)/g, '').trim();
            const value = parseFloat(m[2].replace(/,/g, ''));
            if (label.length > 1 && label.length < 40 && !isNaN(value) && value > 0) {
                pairs.push({ label, value });
                continue;
            }
        }
        // Pattern 3: Label — X% or Label: X%
        m = line.match(/\*{0,2}(.+?)\*{0,2}\s*[:—–-]+\s*([\d.]+)\s*%/);
        if (m) {
            const label = m[1].replace(/\*+/g, '').replace(/\([^)]*\)/g, '').trim();
            const value = parseFloat(m[2]);
            if (label.length > 1 && label.length < 40 && !isNaN(value)) {
                pairs.push({ label, value });
            }
        }
    }

    if (pairs.length < 3) return null;

    // Deduplicate
    const seen = new Set();
    const unique = pairs.filter(p => { if (seen.has(p.label)) return false; seen.add(p.label); return true; });
    if (unique.length < 3) return null;

    // Detect if values are dollar amounts for title
    const isDollar = unique[0].value > 1000;
    return {
        type: 'bar',
        config: {
            title: isDollar ? 'Salary Comparison' : 'Data Comparison',
            labels: unique.map(p => p.label),
            datasets: [{ name: isDollar ? 'Annual Salary' : 'Value', values: unique.map(p => p.value) }],
        }
    };
}

function fmtRefs(text) {
    // Extract chart directives BEFORE escaping HTML
    const chartPlaceholders = [];
    text = text.replace(/\[CHART:(bar|line|pie|radar|gauge)\s+(\{[\s\S]*?\})\]/gi, (match, type, jsonStr) => {
        const placeholder = `__CHART_${chartPlaceholders.length}__`;
        const chartId = 'aiChart_' + (++aiChartCounter);
        try {
            // Clean JSON — Gemini sometimes wraps in backticks or adds trailing commas
            let cleanJson = jsonStr.replace(/,\s*}/g, '}').replace(/,\s*]/g, ']');
            const config = JSON.parse(cleanJson);
            pendingCharts.push({ id: chartId, type: type.toLowerCase(), config });
            chartPlaceholders.push(`<div class="ai-chart-wrap" onclick="window.expandChart('${chartId}')">
                <div id="${chartId}" style="width:100%;height:200px;"></div>
                <div class="ai-chart-hint">Click to expand &middot; Download</div>
            </div>`);
        } catch(e) {
            chartPlaceholders.push(`<div style="padding:8px;color:rgba(255,255,255,0.3);font-size:11px;">[Chart data error]</div>`);
        }
        return placeholder;
    });

    // Clean up markdown artifacts
    let h = text
        .replace(/^>\s?(.+)$/gm, '  "$1"')
        .replace(/^#{1,6}\s+(.+)$/gm, '**$1**');

    // Escape HTML
    h = h.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');

    // Markdown formatting
    h = h.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>')
        .replace(/\*(.+?)\*/g,'<em>$1</em>')
        .replace(/`([^`]+)`/g,'<code>$1</code>')
        .replace(/\|([^\n]+)\|/g, function(match) {
            const cells = match.split('|').filter(c => c.trim() && !c.match(/^[\s:-]+$/));
            if (cells.length > 1) return '<div style="display:flex;gap:12px;padding:4px 0;border-bottom:1px solid rgba(255,255,255,0.06);">' +
                cells.map(c => `<span style="flex:1;font-size:12px;">${c.trim()}</span>`).join('') + '</div>';
            return match;
        })
        .replace(/^[\s]*[-*]\s+(.+)$/gm,'<li>$1</li>')
        .replace(/(<li>[\s\S]*?<\/li>)/g, function(match) {
            if (!match.startsWith('<ul>')) return '<ul style="margin:6px 0;">' + match + '</ul>';
            return match;
        })
        .replace(/\n{2,}/g,'<br><br>')
        .replace(/\n/g,'<br>');

    // Clickable references
    h = h.replace(/Article\s+(\d+)/gi, (m,n) => `<span class="chat-ref" onclick="window.pdfHighlightText(['ARTICLE ${n}'])">${m}</span>`);
    h = h.replace(/Section\s+(\d+)\.(\d+)/gi, (m,a,s) => `<span class="chat-ref" onclick="window.pdfHighlightText(['Section ${a}.${s}'])">${m}</span>`);

    // Restore chart placeholders
    chartPlaceholders.forEach((html, i) => {
        h = h.replace(`__CHART_${i}__`, html);
    });

    return h;
}

// ─── Chart Rendering ─────────────────────────────────
const chartColors = ['#5BA4D9','#00A6A0','#8CC63F','#FCD34D','#FF9800','#DC2626','#9333EA','#EC4899'];

function renderAiChart(id, type, config) {
    const el = document.getElementById(id);
    if (!el || !window.echarts) return;
    const chart = echarts.init(el, 'dark');
    const opt = buildChartOption(type, config);
    chart.setOption(opt);
    aiChartInstances[id] = { chart, type, config };
    window.addEventListener('resize', () => chart.resize());
}

function buildChartOption(type, config) {
    const labels = config.labels || [];
    const datasets = config.datasets || [];
    const title = config.title || '';

    const base = {
        backgroundColor: 'transparent',
        title: title ? { text: title, textStyle: { color: '#fff', fontSize: 13, fontWeight: 700 }, left: 'center', top: 4 } : undefined,
        tooltip: { trigger: type === 'pie' ? 'item' : 'axis' },
        animationDuration: 1500,
        animationEasing: 'elasticOut',
    };

    if (type === 'bar') {
        return { ...base,
            grid: { top: title ? 32 : 8, right: 16, bottom: 24, left: 40 },
            xAxis: { type: 'category', data: labels, axisLabel: { color: '#888', fontSize: 9, rotate: labels.length > 6 ? 30 : 0 }, axisLine: { lineStyle: { color: '#333' } } },
            yAxis: { type: 'value', axisLabel: { color: '#888', fontSize: 9 }, splitLine: { lineStyle: { color: 'rgba(255,255,255,0.05)' } } },
            series: datasets.map((ds, i) => ({
                name: ds.name || '', type: 'bar', data: ds.values,
                itemStyle: { color: new echarts.graphic.LinearGradient(0,0,0,1,[{offset:0,color:chartColors[i%chartColors.length]},{offset:1,color:chartColors[i%chartColors.length]+'80'}]), borderRadius: [4,4,0,0] },
                barWidth: datasets.length > 1 ? '35%' : '50%',
            })),
            legend: datasets.length > 1 ? { bottom: 0, textStyle: { color: '#888', fontSize: 10 } } : undefined,
        };
    }
    if (type === 'line') {
        return { ...base,
            grid: { top: title ? 32 : 8, right: 16, bottom: 24, left: 40 },
            xAxis: { type: 'category', data: labels, axisLabel: { color: '#888', fontSize: 9 }, axisLine: { lineStyle: { color: '#333' } } },
            yAxis: { type: 'value', axisLabel: { color: '#888', fontSize: 9 }, splitLine: { lineStyle: { color: 'rgba(255,255,255,0.05)' } } },
            series: datasets.map((ds, i) => ({
                name: ds.name || '', type: 'line', data: ds.values, smooth: 0.4, symbol: 'circle', symbolSize: 6,
                lineStyle: { color: chartColors[i%chartColors.length], width: 2 },
                areaStyle: { color: new echarts.graphic.LinearGradient(0,0,0,1,[{offset:0,color:chartColors[i%chartColors.length]+'40'},{offset:1,color:'transparent'}]) },
                itemStyle: { color: chartColors[i%chartColors.length] },
            })),
        };
    }
    if (type === 'pie') {
        const values = datasets[0]?.values || [];
        return { ...base,
            series: [{ type: 'pie', radius: ['35%', '65%'], center: ['50%', '55%'],
                data: labels.map((l, i) => ({ name: l, value: values[i] || 0, itemStyle: { color: chartColors[i%chartColors.length] } })),
                label: { color: '#ccc', fontSize: 10 },
                itemStyle: { borderRadius: 4, borderColor: '#1a1a2e', borderWidth: 2 },
            }],
        };
    }
    if (type === 'radar') {
        const values = datasets[0]?.values || [];
        const max = Math.max(...values, 1) * 1.3;
        return { ...base,
            radar: { indicator: labels.map(l => ({ name: l, max })), shape: 'polygon',
                axisName: { color: '#aaa', fontSize: 9 }, splitLine: { lineStyle: { color: 'rgba(255,255,255,0.08)' } },
                splitArea: { areaStyle: { color: ['rgba(91,164,217,0.02)','rgba(91,164,217,0.05)'] } },
            },
            series: [{ type: 'radar', data: [{ value: values, areaStyle: { color: 'rgba(91,164,217,0.3)' }, lineStyle: { color: '#5BA4D9' }, itemStyle: { color: '#5BA4D9' } }] }],
        };
    }
    return base;
}

// ─── Chart Modal (expand + download) ─────────────────
let modalChart = null;
let modalChartId = null;

window.expandChart = function(id) {
    const stored = aiChartInstances[id];
    if (!stored) return;
    modalChartId = id;
    const modal = document.getElementById('aiChartModal');
    const body = document.getElementById('aiChartModalBody');
    const titleEl = document.getElementById('aiChartModalTitle');
    titleEl.textContent = stored.config.title || 'Chart';
    modal.style.display = 'flex';
    requestAnimationFrame(() => modal.classList.add('active'));
    body.innerHTML = '';
    modalChart = echarts.init(body, 'dark');
    const opt = buildChartOption(stored.type, stored.config);
    if (opt.title) opt.title.textStyle.fontSize = 18;
    modalChart.setOption(opt);
};

window.closeChartModal = function() {
    const modal = document.getElementById('aiChartModal');
    modal.classList.remove('active');
    setTimeout(() => { modal.style.display = 'none'; if (modalChart) { modalChart.dispose(); modalChart = null; } }, 300);
};

window.expandImage = function(imgId) {
    const img = document.getElementById(imgId);
    if (!img) return;
    const modal = document.getElementById('aiChartModal');
    const body = document.getElementById('aiChartModalBody');
    const titleEl = document.getElementById('aiChartModalTitle');
    titleEl.textContent = 'Generated Image';
    modal.style.display = 'flex';
    requestAnimationFrame(() => modal.classList.add('active'));
    body.innerHTML = `<img src="${img.src}" style="max-width:100%;max-height:100%;object-fit:contain;border-radius:8px;">`;
    modalChartId = imgId;
};

window.downloadChart = function() {
    // Check if it's an image or a chart
    const modalBody = document.getElementById('aiChartModalBody');
    const img = modalBody.querySelector('img');
    if (img) {
        const a = document.createElement('a');
        a.href = img.src;
        a.download = 'ai-generated-image.png';
        a.click();
        return;
    }
    if (!modalChart) return;
    const url = modalChart.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: '#0B2240' });
    const a = document.createElement('a');
    a.href = url;
    a.download = (aiChartInstances[modalChartId]?.config?.title || 'chart').replace(/\s+/g,'_') + '.png';
    a.click();
};
</script>

<!-- Chart Fullscreen Modal -->
<div id="aiChartModal" class="ai-chart-modal" style="display:none;" onclick="if(event.target===this)closeChartModal()">
    <div class="ai-chart-modal-content">
        <div class="ai-chart-modal-header">
            <h3 id="aiChartModalTitle"></h3>
            <div style="display:flex;gap:8px;">
                <button onclick="downloadChart()" style="display:flex;align-items:center;gap:5px;padding:6px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.15);color:rgba(255,255,255,0.7);border-radius:8px;font-size:12px;cursor:pointer;">
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
                    Download PNG
                </button>
                <button onclick="closeChartModal()" style="display:flex;align-items:center;gap:5px;padding:6px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.15);color:rgba(255,255,255,0.7);border-radius:8px;font-size:12px;cursor:pointer;">
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
                    Close
                </button>
            </div>
        </div>
        <div id="aiChartModalBody" style="width:100%;height:450px;"></div>
    </div>
</div>

<style>
.ai-chart-wrap {
    margin:10px -8px; padding:8px; background:rgba(0,0,0,0.3); border-radius:10px;
    border:1px solid rgba(255,255,255,0.06); cursor:pointer; transition:all 0.2s; position:relative;
}
.ai-chart-wrap:hover { border-color:rgba(91,164,217,0.3); background:rgba(0,0,0,0.4); }
.ai-chart-hint {
    text-align:center; font-size:9px; color:rgba(255,255,255,0.2); margin-top:4px;
    letter-spacing:0.5px; text-transform:uppercase;
}
.ai-chart-modal {
    position:fixed; inset:0; z-index:9999; background:rgba(0,0,0,0.7); backdrop-filter:blur(8px);
    display:flex; align-items:center; justify-content:center;
    opacity:0; transition:opacity 0.3s;
}
.ai-chart-modal.active { opacity:1; }
.ai-chart-modal-content {
    background:linear-gradient(135deg, #0B2240 0%, #070F1B 100%); border-radius:16px;
    border:1px solid rgba(255,255,255,0.1); width:800px; max-width:90vw;
    box-shadow:0 24px 80px rgba(0,0,0,0.5); transform:scale(0.95); transition:transform 0.3s;
}
.ai-chart-modal.active .ai-chart-modal-content { transform:scale(1); }
.ai-chart-modal-header {
    display:flex; align-items:center; justify-content:space-between;
    padding:16px 24px; border-bottom:1px solid rgba(255,255,255,0.06);
}
.ai-chart-modal-header h3 { font-size:16px; font-weight:700; color:#fff; }

/* Voice Mic Button */
.voice-mic-btn {
    width:42px; height:42px; border-radius:50%; border:none; cursor:pointer;
    display:flex; align-items:center; justify-content:center; flex-shrink:0; align-self:flex-end;
    background:rgba(255,255,255,0.06); color:rgba(255,255,255,0.5); transition:all 0.2s;
}
.voice-mic-btn:hover { background:rgba(255,255,255,0.1); color:#fff; }
.voice-mic-btn.active {
    background:linear-gradient(135deg, #DC2626, #FF6B6B); color:#fff;
    animation:voicePulse 1.5s ease-in-out infinite;
}
@keyframes voicePulse { 0%,100%{box-shadow:0 0 0 0 rgba(220,38,38,0.4)} 50%{box-shadow:0 0 0 12px rgba(220,38,38,0)} }

.voice-status {
    text-align:center; font-size:10px; color:rgba(255,255,255,0.3);
    padding:4px 0; letter-spacing:0.5px; text-transform:uppercase;
}
</style>

<script src="assets/js/voice-ai.js"></script>
<script>
// Voice Chat for Contract Page
(function() {
    const micBtn = document.getElementById('voiceMicBtn');
    if (!micBtn || !window.VoiceAI) return;

    const apiKey = window.CFD_PORTAL?.geminiKey || '<?= env('GOOGLE_GEMINI_API_KEY', '') ?>';
    const docName = '<?= $documents['l48-contract']['name'] ?? 'Local 48 Contract' ?>';

    VoiceAI.init({
        apiKey: apiKey,
        systemPrompt: `You are a voice assistant for the Cincinnati Fire Department. You help answer questions about the ${docName}. Keep answers brief and conversational — you are speaking out loud, not writing. 2-3 sentences max. Be professional but approachable.`,
        onTranscript: (text) => {
            // Show what user said
        },
        onResponse: (text) => {
            // Show AI response in chat
            if (typeof addMsg === 'function') addMsg('ai', text);
        },
        onStateChange: (state) => {
            micBtn.classList.toggle('active', state === 'listening' || state === 'speaking');
            micBtn.title = state === 'listening' ? 'Listening... (click to stop)' : state === 'speaking' ? 'Speaking...' : 'Voice Chat';
        },
        onError: (msg) => {
            if (typeof addMsg === 'function') addMsg('system', 'Voice: ' + msg);
            micBtn.classList.remove('active');
        }
    });

    window.toggleVoiceChat = function() {
        VoiceAI.toggle();
    };
})();
</script>
