@extends('layouts.app')
@section('title', $appointment->ref_no . ' - Appointment')

@section('css')
<style>
/* ── Appointment Hero ─────────────────────────────────── */
.apt-hero {
    border-radius: 18px;
    padding: 28px 30px 0;
    margin-bottom: 24px;
    color: #fff;
    position: relative;
    overflow: hidden;
    box-shadow: 0 12px 40px rgba(0,0,0,.22), 0 2px 8px rgba(0,0,0,.12);
}
/* Dot-grid noise texture */
.apt-hero::before {
    content: '';
    position: absolute; inset: 0;
    background-image: radial-gradient(circle, rgba(255,255,255,.12) 1px, transparent 1px);
    background-size: 22px 22px;
    pointer-events: none;
    z-index: 0;
}
/* Large blurred orbs */
.apt-hero-orb {
    position: absolute; border-radius: 50%; pointer-events: none; z-index: 0;
    filter: blur(40px);
}
.apt-hero-orb-1 { width:260px; height:260px; top:-80px; right:-60px; background:rgba(255,255,255,.13); }
.apt-hero-orb-2 { width:180px; height:180px; bottom:-60px; left:-40px; background:rgba(255,255,255,.09); }
.apt-hero-orb-3 { width:120px; height:120px; top:20px; right:200px; background:rgba(255,255,255,.07); }

/* Main content zone sits above texture */
.apt-hero-top {
    position: relative; z-index: 1;
    display: flex; align-items: flex-start; gap: 20px; margin-bottom: 22px;
}
.apt-type-icon {
    width: 64px; height: 64px; border-radius: 16px; flex-shrink: 0;
    background: rgba(255,255,255,.15);
    border: 1.5px solid rgba(255,255,255,.3);
    display: flex; align-items: center; justify-content: center;
    font-size: 26px;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 6px 16px rgba(0,0,0,.15);
}
.apt-ref-chip {
    display: inline-block;
    background: rgba(255,255,255,.18);
    border: 1px solid rgba(255,255,255,.28);
    border-radius: 20px;
    padding: 2px 10px;
    font-size: 11px; font-weight: 700; letter-spacing: .5px;
    margin-bottom: 6px;
}
.apt-hero-info h2 {
    margin: 0 0 6px; font-size: 22px; font-weight: 800;
    letter-spacing: -.4px; text-shadow: 0 1px 4px rgba(0,0,0,.15);
}
.apt-hero-info .meta {
    font-size: 13px; line-height: 2;
    color: rgba(255,255,255,.88);
}
.apt-hero-info .meta .meta-pill {
    display: inline-flex; align-items: center; gap: 4px;
    background: rgba(255,255,255,.12);
    border-radius: 20px; padding: 1px 9px;
    margin-right: 5px; font-size: 12px;
    border: 1px solid rgba(255,255,255,.18);
}
.apt-hero-actions { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; align-items: flex-start; }
.apt-hero-actions .btn {
    border: 1.5px solid rgba(255,255,255,.3); color: #fff;
    background: rgba(255,255,255,.12);
    font-size: 12px; font-weight: 600; border-radius: 8px;
    transition: all .18s;
}
.apt-hero-actions .btn:hover {
    background: rgba(255,255,255,.24);
    border-color: rgba(255,255,255,.55);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0,0,0,.15);
}

/* SVG Wave between content and status bar */
.apt-hero-wave { position: relative; z-index: 1; margin: 0 -30px; line-height: 0; }
.apt-hero-wave svg { display: block; width: 100%; }

/* ── Status Bar ───────────────────────────────────────── */
.apt-status-bar {
    position: relative; z-index: 1;
    display: flex; align-items: center;
    background: rgba(0,0,0,.18);
    padding: 14px 30px;
    margin: 0 -30px;
    border-radius: 0 0 18px 18px;
}
.status-step {
    display: flex; align-items: center; gap: 7px;
    font-size: 11.5px; font-weight: 600; letter-spacing: .2px;
    color: rgba(255,255,255,.4);
    cursor: pointer; white-space: nowrap;
    transition: color .2s;
}
.status-step:not(.terminal):hover { color: rgba(255,255,255,.82); }
.status-step .s-dot {
    width: 24px; height: 24px; border-radius: 50%; flex-shrink: 0;
    background: rgba(255,255,255,.15);
    border: 2px solid rgba(255,255,255,.2);
    display: flex; align-items: center; justify-content: center;
    font-size: 9px;
    transition: all .2s;
}
.status-step.s-done { color: rgba(255,255,255,.8); }
.status-step.s-done .s-dot {
    background: #27ae60; border-color: #2ecc71;
    box-shadow: 0 0 0 3px rgba(46,204,113,.25);
}
.status-step[data-status="waiting"].s-active .s-dot {
    background: #0d9488; border-color: #14b8a6;
    box-shadow: 0 0 0 4px rgba(13,148,136,.3);
}
.status-step[data-status="waiting"].s-active { color: #fff; }
.status-step.s-active { color: #fff; }
.status-step.s-active .s-dot {
    background: #fff; border-color: #fff; color: #333;
    font-size: 10px; font-weight: 700;
    box-shadow: 0 0 0 4px rgba(255,255,255,.25);
}
.s-connector {
    flex: 1; height: 2px; margin: 0 6px; min-width: 18px;
    background: rgba(255,255,255,.18); border-radius: 2px;
    position: relative; overflow: hidden;
}
.s-connector.s-done { background: #2ecc71; }
.apt-terminal-badge {
    font-size: 13px; font-weight: 700; letter-spacing: .3px;
    padding: 4px 12px; background: rgba(255,255,255,.15);
    border-radius: 20px; border: 1px solid rgba(255,255,255,.25);
}

/* ── Sidebar Cards ────────────────────────────────────── */
.vc-card { background:#fff; border-radius:8px; box-shadow:0 1px 4px rgba(0,0,0,.08); margin-bottom:14px; }
.vc-card-header { padding:10px 14px; border-bottom:1px solid #f3f3f3; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.5px; color:#888; }
.vc-card-body   { padding:14px; }

.pet-mini { display:flex; align-items:center; gap:12px; margin-bottom:12px; }
.pet-mini-avatar {
    width:52px; height:52px; border-radius:50%; flex-shrink:0;
    background:#f0f0f5; display:flex; align-items:center; justify-content:center;
    font-size:20px; color:#ccc; overflow:hidden;
}
.pet-mini-avatar img { width:52px; height:52px; border-radius:50%; object-fit:cover; }
.pet-name-link { font-weight:700; font-size:15px; color:#2c3e50; text-decoration:none; }
.pet-name-link:hover { text-decoration:underline; color:#2980b9; }
.owner-name-link { font-weight:600; color:#2c3e50; text-decoration:none; border-bottom:1px dotted #94a3b8; }
.owner-name-link:hover { color:#2980b9; border-bottom-color:#2980b9; text-decoration:none; }
.pet-sub { font-size:12px; color:#999; margin-top:2px; }

.info-row { display:flex; justify-content:space-between; align-items:flex-start; padding:5px 0; border-bottom:1px solid #f9f9f9; font-size:12px; }
.info-row:last-child { border-bottom:none; }
.info-row .ik { color:#777; font-weight:600; }
.info-row .iv { color:#333; text-align:right; max-width:55%; }

.qa-btn {
    display:flex; align-items:center; gap:8px; padding:8px 10px;
    border-radius:6px; border:1px solid #e0e0e0; background:#fff;
    color:#333; font-size:12px; font-weight:600; width:100%;
    cursor:pointer; margin-bottom:6px; text-decoration:none;
    transition:background .15s, border-color .15s;
}
.qa-btn:hover { background:#f5f5f5; text-decoration:none; color:#333; border-color:#ccc; }
.qa-icon { width:26px; height:26px; border-radius:5px; display:flex; align-items:center; justify-content:center; flex-shrink:0; font-size:12px; color:#fff; }

.cond-chip {
    display:inline-block; border-radius:12px; padding:2px 9px;
    font-size:11px; margin:2px; border:1px solid transparent;
}
.cond-chip.active  { background:#fdecea; border-color:#f5c6cb; color:#721c24; }
.cond-chip.chronic { background:#fff3cd; border-color:#ffc107; color:#856404; }
.cond-chip.resolved{ background:#d4edda; border-color:#c3e6cb; color:#155724; }

.status-select-row { display:flex; gap:6px; }
.status-select-row select { flex:1; font-size:12px; }

/* ── Vitals Grid ──────────────────────────────────────── */
.vitals-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:10px; margin-bottom:0; }
.vital-box {
    background:#f8fafc; border:1px solid #e2e8f0; border-radius:8px;
    padding:12px 8px; text-align:center;
}
.vital-box .vb-label { font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.5px; color:#64748b; margin-bottom:6px; }
.vital-box input {
    width:100%; border:none; background:transparent; text-align:center;
    font-size:20px; font-weight:700; color:#1e293b;
    outline:none; padding:0;
}
.vital-box input::placeholder { color:#cbd5e1; font-weight:400; font-size:16px; }
.vital-box .vb-unit { font-size:11px; color:#94a3b8; margin-top:3px; }
.vital-box .vb-range { font-size:9.5px; color:#94a3b8; margin-top:3px; min-height:12px; line-height:1.2; font-weight:600; }
.vital-box.is-normal   input { border-color:#86efac !important; }
.vital-box.is-low      input,
.vital-box.is-high     input { border-color:#f59e0b !important; background:#fffbeb; }
.vital-box.is-critical input { border-color:#dc2626 !important; background:#fef2f2; box-shadow:0 0 0 2px rgba(220,38,38,.15); }
.vital-box.is-low      .vb-range,
.vital-box.is-high     .vb-range { color:#d97706; }
.vital-box.is-critical .vb-range { color:#dc2626; }
.vital-box:focus-within { border-color:#3498db; background:#fff; }

/* ── Smart Consultation ───────────────────────────────── */
.soap-card {
    border-radius:10px; border:1px solid #e8ecf0;
    margin-bottom:10px; overflow:hidden;
    box-shadow:0 1px 3px rgba(0,0,0,.04);
    transition: box-shadow .15s;
}
.soap-card:focus-within { box-shadow:0 2px 8px rgba(0,0,0,.1); }
.soap-card[data-soap="s"] { border-left:4px solid #3498db; }
.soap-card[data-soap="o"] { border-left:4px solid #27ae60; }
.soap-card[data-soap="a"] { border-left:4px solid #e67e22; }
.soap-card[data-soap="i"] { border-left:4px solid #16a085; }
.soap-card[data-soap="p"] { border-left:4px solid #8e44ad; }
.soap-card-hd {
    display:flex; align-items:center; gap:10px;
    padding:11px 16px; background:#f8fafc;
    border-bottom:1px solid #e8ecf0;
    cursor:pointer; user-select:none;
}
.soap-card-hd:hover { background:#f1f5f9; }
.soap-badge-lg {
    width:28px; height:28px; border-radius:7px; flex-shrink:0;
    display:flex; align-items:center; justify-content:center;
    font-size:13px; font-weight:800; color:#fff;
}
.soap-card-title { font-weight:700; font-size:13px; color:#1e293b; }
.soap-card-sub   { font-size:11px; color:#94a3b8; margin-top:1px; }
.soap-card-chevron { margin-left:auto; color:#94a3b8; transition:transform .2s; font-size:11px; }
.soap-card.collapsed .soap-card-chevron { transform:rotate(-90deg); }
.soap-card-body { padding:14px 16px; }
.soap-card.collapsed .soap-card-body { display:none; }

/* Complaint chips */
.chip-zone { display:flex; flex-wrap:wrap; gap:5px; margin-bottom:10px; }
.complaint-chip {
    padding:3px 11px; border-radius:14px; font-size:11.5px; font-weight:600;
    border:1.5px solid #cbd5e1; background:#fff; color:#475569;
    cursor:pointer; transition:all .13s; user-select:none;
}
.complaint-chip:hover { background:#ebf5fb; border-color:#3498db; color:#2980b9; }
.complaint-chip.active { background:#3498db; border-color:#2980b9; color:#fff; }
/* Brief glow when the AI synonym matcher auto-lights a chip, so the doctor
   sees "the AI just picked these up" instead of wondering what changed. */
.complaint-chip.vc-chip-ai-flash {
    animation: drmChipAiFlash 1.4s ease-out;
    box-shadow: 0 0 0 3px rgba(14,165,233,.35);
}
@keyframes drmChipAiFlash {
    0%   { background:#0ea5e9; border-color:#0284c7; box-shadow:0 0 0 6px rgba(14,165,233,.5); }
    100% { background:#3498db; border-color:#2980b9; box-shadow:0 0 0 0   rgba(14,165,233,0);  }
}
.soap-search-result:hover { background:#f1f5f9; }
.soap-search-result:last-child { border-bottom:none !important; }

/* ── Item #4b — AI suggest-from-Subjective banner ─────────── */
.vc-ai-suggest {
    border:1px solid #bae6fd; background:#f0f9ff; border-left:4px solid #0ea5e9;
    border-radius:6px; padding:8px 12px; margin-bottom:10px;
    animation: drmAiSuggestIn .25s ease-out;
}
@keyframes drmAiSuggestIn { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:none; } }
.vc-ai-suggest-hd { display:flex; align-items:center; gap:6px; font-size:12px; color:#075985; margin-bottom:6px; flex-wrap:wrap; }
.vc-ai-suggest-reason { color:#0c4a6e; font-weight:500; font-size:11.5px; font-style:italic; }
.vc-ai-suggest-x {
    margin-left:auto; background:none; border:none; font-size:16px; color:#94a3b8;
    cursor:pointer; padding:0 4px; line-height:1;
}
.vc-ai-suggest-x:hover { color:#dc2626; }
.vc-ai-suggest-chips { display:flex; gap:6px; flex-wrap:wrap; }
.vc-ai-suggest-chip {
    background:#fff; color:#0c4a6e; border:1px solid #7dd3fc; border-radius:14px;
    padding:3px 12px; font-size:12px; font-weight:600; cursor:pointer; user-select:none;
    transition:all .15s;
}
.vc-ai-suggest-chip:hover { background:#0ea5e9; color:#fff; border-color:#0284c7; }
.vc-ai-suggest-chip.applied { background:#d1fae5; color:#065f46; border-color:#6ee7b7; cursor:default; }

/* ── Item #1 - locked clinical sections ─────────────────────
   Disables all inputs and obscures the section body when the
   appointment hasn't reached in_progress yet. Pointer-events:none
   makes the section unclickable; opacity dims it; user-select:none
   stops accidental highlight. Save buttons + AI/mic triggers
   stay disabled too. */
.vc-section-locked .soap-card-body,
.vc-section-locked .vac-section,
.vc-section-locked .lab-section,
.vc-section-locked #btn_save_consultation,
.vc-section-locked .vc-ai-trigger,
.vc-section-locked .vc-mic-trigger,
.vc-section-locked .vital-box input,
.vc-section-locked .vitals-grid {
    pointer-events: none;
    opacity: 0.55;
    user-select: none;
    filter: grayscale(0.2);
}
.vc-section-locked .soap-card-hd::after {
    content: ' 🔒'; font-size: 13px; opacity: .7;
}
.vc-section-lock-banner { animation: drmAiSuggestIn .25s ease-out; }

/* ── Key clinical findings panel (extractSymptoms) ─────────── */
.vc-ai-symptoms {
    margin-top:8px; padding:8px 12px;
    border:1px solid #e0e7ff; background:#eef2ff; border-left:4px solid #6366f1;
    border-radius:6px;
    animation: drmAiSuggestIn .25s ease-out;
}
.vc-ai-symptoms-hd { display:flex; align-items:center; gap:6px; margin-bottom:6px; font-size:12px; color:#3730a3; flex-wrap:wrap; }
.vc-ai-concern { font-size:10px; font-weight:800; text-transform:uppercase; letter-spacing:.5px; padding:1px 8px; border-radius:9px; }
.vc-ai-concern.high     { background:#fef2f2; color:#dc2626; border:1px solid #fecaca; }
.vc-ai-concern.moderate { background:#fffbeb; color:#b45309; border:1px solid #fde68a; }
.vc-ai-concern.low      { background:#f0fdf4; color:#15803d; border:1px solid #bbf7d0; }
.vc-ai-symptoms-list { display:flex; flex-wrap:wrap; gap:5px; }
.vc-ai-symptom-pill {
    display:inline-flex; align-items:center; gap:4px;
    padding:2px 9px; border-radius:11px; font-size:11.5px; font-weight:600;
    cursor:default; user-select:none; border:1px solid; transition:all .12s;
}
.vc-ai-symptom-pill.critical { background:#fef2f2; color:#991b1b; border-color:#fecaca; }
.vc-ai-symptom-pill.moderate { background:#fffbeb; color:#92400e; border-color:#fde68a; }
.vc-ai-symptom-pill.mild     { background:#f8fafc; color:#475569; border-color:#e2e8f0; }
.vc-ai-symptom-pill[title]:hover { transform:translateY(-1px); }

/* ── Item 1.4 Phase A — Structured Objective Exam ─────────── */
.vc-exam-toolbar { display:flex; align-items:center; gap:8px; padding:8px 10px;
    background:#f1f5f9; border:1px solid #e2e8f0; border-radius:8px 8px 0 0;
    border-bottom:none; flex-wrap:wrap; }
.vc-exam-toolbar-label { font-size:11px; font-weight:700; color:#475569; text-transform:uppercase; letter-spacing:.4px; }
.vc-exam-add-menu-item { padding:8px 12px; cursor:pointer; font-size:12.5px; display:flex; align-items:center; gap:8px; border-bottom:1px solid #f0f0f0; }
.vc-exam-add-menu-item:hover { background:#f1f5f9; }
.vc-exam-add-menu-item.disabled { opacity:.5; cursor:not-allowed; }
.vc-exam-add-menu-item i { width:14px; text-align:center; }
.vc-exam-critical-badge { font-size:11px; font-weight:700; color:#dc2626; background:#fef2f2;
    border:1px solid #fecaca; padding:3px 9px; border-radius:10px; }
#clc_exam_panels { border:1px solid #e2e8f0; border-top:none; border-radius:0 0 8px 8px;
    background:#fff; padding:6px; min-height:60px; }
.vc-exam-panel { border:1px solid #e5e9f0; border-left:4px solid var(--c, #2980b9);
    border-radius:6px; margin-bottom:8px; background:#fff; overflow:hidden; }
.vc-exam-panel-hd { display:flex; align-items:center; gap:8px; padding:8px 12px;
    background:#fafafa; border-bottom:1px solid #f1f5f9; font-weight:700; font-size:12.5px; color:#0f172a; }
.vc-exam-panel-hd .vc-exam-panel-close { margin-left:auto; cursor:pointer; color:#94a3b8;
    background:none; border:none; padding:2px 6px; font-size:14px; }
.vc-exam-panel-hd .vc-exam-panel-close:hover { color:#dc2626; }
.vc-exam-panel-body { padding:6px 10px 10px; }
.vc-exam-region { border:1px solid #f1f5f9; border-radius:5px; margin-top:6px; }
.vc-exam-region-hd { padding:7px 10px; cursor:pointer; background:#f8fafc;
    display:flex; align-items:center; gap:8px; font-weight:600; font-size:11.5px; color:#334155;
    user-select:none; border-radius:5px; }
.vc-exam-region.open .vc-exam-region-hd { border-radius:5px 5px 0 0; border-bottom:1px solid #e2e8f0; }
.vc-exam-region-hd .vc-exam-region-chev { font-size:9px; color:#94a3b8; transition:transform .15s; }
.vc-exam-region.open .vc-exam-region-chev { transform:rotate(90deg); }
.vc-exam-region-hd .vc-exam-region-meta { margin-left:auto; font-size:10px; color:#94a3b8; font-weight:500; }
.vc-exam-region-hd .vc-exam-region-meta.crit { color:#dc2626; font-weight:700; }
.vc-exam-region-body { display:none; padding:6px 10px 10px; }
.vc-exam-region.open .vc-exam-region-body { display:block; }
.vc-exam-item { display:grid; grid-template-columns:200px 1fr; gap:10px; align-items:center;
    padding:5px 0; font-size:12px; border-bottom:1px dotted #f1f5f9; }
.vc-exam-item:last-child { border-bottom:none; }
.vc-exam-item-label { color:#475569; font-weight:600; }
.vc-exam-item-label.req::after { content:" *"; color:#dc2626; }
.vc-exam-item-input { display:flex; gap:6px; align-items:center; flex-wrap:wrap; }
.vc-exam-item-input select, .vc-exam-item-input input[type=text], .vc-exam-item-input input[type=number],
.vc-exam-item-input textarea { font-size:12px; padding:3px 6px; border:1px solid #cbd5e1;
    border-radius:4px; }
.vc-exam-item-input textarea { width:100%; min-height:32px; resize:vertical; }
.vc-exam-item-input.has-critical { box-shadow: inset 3px 0 0 #dc2626; padding-left:6px; }
.vc-exam-item-input.has-abnormal { box-shadow: inset 3px 0 0 #d97706; padding-left:6px; }
.vc-exam-side-label { font-size:9px; font-weight:700; color:#94a3b8; padding:1px 5px;
    background:#f1f5f9; border-radius:3px; text-transform:uppercase; }
.vc-exam-multi-chip { font-size:11px; padding:2px 8px; border:1px solid #cbd5e1; border-radius:10px;
    cursor:pointer; user-select:none; background:#fff; }
.vc-exam-multi-chip.on { background:#2980b9; color:#fff; border-color:#2980b9; }
.vc-exam-saving { font-size:10px; color:#94a3b8; margin-left:6px; }
.vc-exam-saving.ok  { color:#16a34a; }
.vc-exam-saving.err { color:#dc2626; }

/* ── Body Systems - Side-nav layout ───────────────────────── */
.systems-grid { margin-bottom:10px; }

.sys-nav-shell {
    display:flex; border:1px solid #e2e8f0; border-radius:10px; overflow:hidden;
    box-shadow:0 2px 10px rgba(0,0,0,.06); min-height:190px;
}

/* Left rail */
.sys-nav-rail {
    width:185px; flex-shrink:0;
    background:linear-gradient(180deg,#f8fafc 0%,#f1f5f9 100%);
    border-right:1px solid #e2e8f0;
    display:flex; flex-direction:column;
}
.sys-nav-item {
    display:flex; flex-direction:column; gap:4px;
    padding:10px 11px 10px 13px; cursor:pointer;
    border-left:3px solid transparent;
    border-bottom:1px solid rgba(226,232,240,.7);
    transition:background .13s, border-color .13s;
    position:relative;
}
.sys-nav-item:last-child { border-bottom:none; }
.sys-nav-item:hover { background:rgba(255,255,255,.7); }
.sys-nav-item.active {
    background:#fff;
    border-left-color:var(--gc);
    box-shadow:inset -1px 0 0 #e2e8f0;
}
.sys-nav-item.active::after {
    content:'';
    position:absolute; right:-1px; top:0; bottom:0;
    width:1px; background:#fff; z-index:1;
}
.sni-top { display:flex; align-items:center; gap:5px; }
.sni-icon { font-size:12px; color:var(--gc); flex-shrink:0; }
.sni-label {
    font-size:11px; font-weight:700; line-height:1.25;
    color:#475569; flex:1; white-space:nowrap;
    overflow:hidden; text-overflow:ellipsis;
}
.sys-nav-item.active .sni-label { color:var(--gc); }
.sni-badges { display:flex; gap:3px; flex-wrap:wrap; }
.sys-grp-badge {
    font-size:9.5px; padding:1px 6px; border-radius:10px; font-weight:700;
    line-height:1.4;
}
.sys-grp-badge.gnormal { background:#d4edda; color:#155724; }
.sys-grp-badge.gabn    { background:#fdecea; color:#721c24; }
.sys-grp-badge.gne     { background:#e2e8f0; color:#475569; }

/* All-normal quick indicator dot on the rail item */
.sni-dot {
    width:7px; height:7px; border-radius:50%; flex-shrink:0;
    background:var(--gc); opacity:.25;
}
.sys-nav-item.has-normal .sni-dot  { opacity:.55; background:#22c55e; }
.sys-nav-item.has-abn    .sni-dot  { background:#ef4444; opacity:1; }

/* Right panel */
.sys-nav-panel {
    flex:1; padding:10px; background:#fff;
    display:grid; grid-template-columns:repeat(2,1fr); gap:7px;
    align-content:start; min-width:0;
}
.sys-panel-empty {
    grid-column:1/-1; color:#94a3b8; font-size:12px;
    display:flex; align-items:center; justify-content:center;
    min-height:100px;
}

/* Quick-action bar at top of panel */
.sys-panel-bar {
    grid-column:1/-1;
    display:flex; align-items:center; justify-content:space-between;
    padding-bottom:7px; border-bottom:1px solid #f0f4f8; margin-bottom:2px;
}
.sys-panel-title {
    font-size:11px; font-weight:800; letter-spacing:.3px;
    color:var(--gc);
    display:flex; align-items:center; gap:6px;
}
.sys-panel-actions { display:flex; gap:5px; }
.sys-panel-btn {
    font-size:10px; padding:3px 10px; border-radius:7px; cursor:pointer;
    border:1px solid #e2e8f0; background:#fff; color:#64748b;
    font-weight:700; letter-spacing:.2px; transition:all .14s;
    display:inline-flex; align-items:center; gap:4px; line-height:1.6;
}
.sys-panel-btn:hover {
    border-color:#cbd5e1; color:#1e293b;
    transform:translateY(-1px); box-shadow:0 3px 7px rgba(15,23,42,.1);
}
.sys-panel-btn:active { transform:translateY(0); box-shadow:none; }
.sys-panel-btn.mark-all-n   { border-color:#bbf7d0; color:#15803d; background:#f0fdf4; }
.sys-panel-btn.mark-all-n:hover   { background:#22c55e; color:#fff; border-color:#22c55e; box-shadow:0 3px 8px rgba(34,197,94,.32); }
.sys-panel-btn.mark-all-ne   { border-color:#bfdbfe; color:#1d4ed8; background:#eff6ff; }
.sys-panel-btn.mark-all-ne:hover  { background:#3b82f6; color:#fff; border-color:#3b82f6; box-shadow:0 3px 8px rgba(59,130,246,.32); }
.sys-panel-btn.mark-all-abn { border-color:#fecaca; color:#b91c1c; background:#fff5f5; }
.sys-panel-btn.mark-all-abn:hover { background:#ef4444; color:#fff; border-color:#ef4444; box-shadow:0 3px 8px rgba(239,68,68,.32); }

/* Individual system row */
.system-row {
    background:#f8fafc; border:1px solid #e2e8f0; border-radius:8px;
    padding:8px 10px; transition:border-color .15s, background .15s;
}
.system-row-top { display:flex; align-items:center; justify-content:space-between; margin-bottom:4px; }
.system-label   { font-size:10.5px; font-weight:700; color:#475569; }
.sys-btns { display:flex; gap:2px; }
.sys-btn {
    padding:2px 7px; border-radius:5px; font-size:10px; font-weight:600;
    border:1px solid #e2e8f0; background:#fff; color:#94a3b8;
    cursor:pointer; transition:all .12s;
}
.sys-btn:hover    { border-color:#94a3b8; color:#475569; }
.sys-btn.snormal  { background:#d4edda; border-color:#c3e6cb; color:#155724; }
.sys-btn.sabn     { background:#fdecea; border-color:#f5c6cb; color:#721c24; }
.sys-btn.sne      { background:#e2e8f0; border-color:#cbd5e1; color:#475569; }
.sys-finding {
    width:100%; font-size:11px; border:1px solid #e2e8f0; border-radius:4px;
    padding:3px 6px; margin-top:5px; display:none; background:#fff;
}
.sys-finding.show { display:block; }
.system-row.is-abn {
    border-color:#f5c6cb; background:#fff8f8;
}
.system-row.is-abn .system-label { color:#721c24; }
.system-row.is-ne {
    border-color:#cbd5e1; background:#f8fafc;
}

/* ── Enhanced finding-checklist panel (revealed when Abn or N/E) ── */
.sys-findings-panel {
    display:none; margin-top:6px; padding:8px 10px; background:#fff;
    border:1px solid #e2e8f0; border-radius:6px;
}
.sys-findings-panel.show { display:block; }
.sys-findings-hint {
    font-size:10.5px; font-weight:700; color:#475569; text-transform:uppercase;
    letter-spacing:.4px; margin-bottom:6px;
}
.sys-finding-list { display:flex; flex-direction:column; gap:4px; }
.sys-finding-item {
    display:flex; align-items:center; gap:8px; padding:4px 6px;
    border-radius:4px; cursor:pointer; font-size:12px; color:#334155;
    transition:background .12s;
}
.sys-finding-item:hover { background:#f1f5f9; }
.sys-finding-item.on    { background:#fef2f2; }
.sys-finding-item input[type=checkbox] { margin:0; flex-shrink:0; }
.sys-finding-label { flex:1; min-width:0; }
.sys-finding-sev, .sys-finding-side { display:flex; gap:3px; align-items:center; }
.sev-pill, .side-pill {
    font-size:10px; padding:1px 7px; border-radius:9px;
    border:1px solid #e2e8f0; background:#fff; color:#64748b;
    cursor:pointer; user-select:none; font-weight:600;
}
.sev-pill input, .side-pill input { display:none; }
.sev-pill.on  { background:#fef3c7; border-color:#fde68a; color:#92400e; }
.side-pill.on { background:#dbeafe; border-color:#bfdbfe; color:#1e40af; }
.sys-notes {
    width:100%; margin-top:6px; font-size:11.5px; padding:5px 7px;
    border:1px solid #e2e8f0; border-radius:4px; resize:vertical;
    min-height:36px; background:#fff;
}

/* Diagnosis tags */
.diag-tags { display:flex; flex-wrap:wrap; gap:5px; margin-top:8px; min-height:4px; }
.diag-tag {
    display:inline-flex; align-items:center; gap:5px;
    background:#fff7ed; border:1px solid #fed7aa; color:#9a3412;
    border-radius:12px; padding:2px 10px; font-size:11.5px; font-weight:600;
}
.diag-tag-rm { cursor:pointer; opacity:.6; font-size:10px; line-height:1; }
.diag-tag-rm:hover { opacity:1; }

/* Plan med rows */
.rx-row {
    display:flex; align-items:center; gap:8px;
    background:#f8fafc; border:1px solid #e2e8f0; border-radius:8px;
    padding:8px 12px; margin-bottom:6px;
}
.rx-row-name   { flex:3; font-size:12px; font-weight:700; color:#1e293b; }
.rx-row-detail { flex:3; font-size:11px; color:#64748b; }
.rx-row-rm { color:#e74c3c; cursor:pointer; font-size:11px; flex-shrink:0; }
.rx-row-rm:hover { color:#c0392b; }

/* Auto-grow SOAP textareas */
.soap-card textarea {
    overflow: hidden;
    resize: none;
    min-height: 64px;
    box-sizing: border-box;
}

/* Draft / save indicator */
.draft-bar { display:flex; justify-content:space-between; align-items:center; margin-top:10px; }
.draft-indicator { font-size:11px; color:#94a3b8; display:flex; align-items:center; gap:5px; }
.draft-indicator.dirty    { color:#e67e22; }
.draft-indicator.saved    { color:#27ae60; }
.draft-indicator.restored { color:#e67e22; font-weight:600; }

/* ── Document Cards ───────────────────────────────────── */
.doc-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:14px; }
.doc-card {
    background:#fff; border:1px solid #e8ecf0; border-radius:12px;
    overflow:hidden; display:flex; flex-direction:column;
    transition:box-shadow .2s, transform .18s;
}
.doc-card:hover { box-shadow:0 6px 24px rgba(0,0,0,.1); transform:translateY(-2px); }
.doc-card-preview {
    height:116px; display:flex; align-items:center;
    justify-content:center; overflow:hidden; position:relative;
    flex-shrink:0;
}
.doc-card-preview img { width:100%; height:100%; object-fit:cover; }
.doc-preview-overlay {
    position:absolute; inset:0; background:rgba(0,0,0,.0);
    display:flex; align-items:center; justify-content:center;
    transition:background .2s;
}
.doc-card:hover .doc-preview-overlay { background:rgba(0,0,0,.18); }
.doc-preview-overlay .doc-eye { color:#fff; font-size:22px; opacity:0; transition:opacity .2s; }
.doc-card:hover .doc-preview-overlay .doc-eye { opacity:1; }
.doc-card-body { padding:12px 14px; display:flex; flex-direction:column; flex:1; }
.doc-card-name {
    font-weight:700; font-size:13px; color:#1e293b;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
    margin-bottom:5px;
}
.doc-card-badges { display:flex; flex-wrap:wrap; gap:4px; margin-bottom:5px; }
.doc-card-meta { font-size:11px; color:#94a3b8; margin-bottom:6px; }
.doc-card-desc {
    font-size:11px; color:#64748b; font-style:italic;
    margin-bottom:8px; flex:1;
    overflow:hidden; display:-webkit-box;
    -webkit-line-clamp:2; -webkit-box-orient:vertical;
}
.doc-card-actions { display:flex; gap:4px; margin-top:auto; }
.doc-card-actions .btn { flex:1; font-size:11px; padding:4px 6px; }

/* Medicine row action buttons */
.rx-actions { display:flex; gap:5px; flex-shrink:0; }
.rx-btn {
    width:26px; height:26px; border-radius:5px; border:1px solid;
    display:flex; align-items:center; justify-content:center;
    cursor:pointer; font-size:12px; flex-shrink:0; transition:all .13s;
}
.rx-btn-edit { color:#2980b9; border-color:#bde0f7; background:#ebf5fb; }
.rx-btn-edit:hover { background:#2980b9; color:#fff; border-color:#2980b9; }
.rx-btn-rm   { color:#e74c3c; border-color:#f9ccc9; background:#fdecea; }
.rx-btn-rm:hover   { background:#e74c3c; color:#fff; border-color:#e74c3c; }
.rx-row-note { font-size:10.5px; color:#64748b; font-style:italic; margin-top:2px; }

/* ── Timeline ─────────────────────────────────────────── */
.vc-timeline { position:relative; padding:0; list-style:none; margin:0; }
.vc-timeline::before { content:''; position:absolute; top:0; bottom:0; left:20px; width:2px; background:#e9ecef; }
.tl-item { position:relative; padding-left:48px; margin-bottom:16px; }
.tl-dot {
    position:absolute; left:14px; top:6px; width:12px; height:12px;
    border-radius:50%; background:#3498db; border:2px solid #fff;
    box-shadow:0 0 0 2px #3498db;
}
.tl-card { background:#f8f9fa; border-radius:6px; padding:10px 14px; }
.tl-head { display:flex; justify-content:space-between; align-items:center; margin-bottom:4px; }
.tl-head strong { font-size:13px; color:#2c3e50; }
.tl-head .tl-meta { font-size:11px; color:#aaa; }
.tl-body { font-size:13px; color:#555; white-space:pre-wrap; }

/* ── Treatment Card Grid ────────────────────────────── */
.vax-tab-header {
    display:flex; align-items:center; justify-content:space-between;
    margin-bottom:16px; padding-bottom:12px;
    border-bottom:1px solid #f1f5f9;
}
.vax-tab-header-title {
    font-size:13px; font-weight:800; color:#1e293b;
    display:flex; align-items:center; gap:7px;
}
.vax-count-badge {
    background:#ecfdf5; color:#059669; font-size:11px; font-weight:700;
    padding:2px 9px; border-radius:10px; border:1px solid #a7f3d0;
}
.vax-card-grid {
    display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:14px;
}
.vax-card {
    background:#fff; border-radius:12px; border:1px solid #e8ecf1;
    box-shadow:0 2px 8px rgba(0,0,0,.06); overflow:hidden;
    transition:box-shadow .18s, transform .18s;
}
.vax-card:hover { box-shadow:0 6px 20px rgba(0,0,0,.1); transform:translateY(-2px); }
.vax-card-top {
    display:flex; align-items:flex-start; justify-content:space-between;
    padding:14px 16px 10px; gap:8px;
}
.vax-card-name { font-size:14px; font-weight:800; color:#1e293b; line-height:1.3; flex:1; min-width:0; }
.vax-where-badge {
    flex-shrink:0; font-size:10.5px; font-weight:700; padding:3px 9px;
    border-radius:20px; display:flex; align-items:center; gap:4px;
    white-space:nowrap;
}
.vax-where-clinic   { background:#dcfce7; color:#166534; border:1px solid #bbf7d0; }
.vax-where-external { background:#dbeafe; color:#1e40af; border:1px solid #bfdbfe; }
.vax-where-scheduled{ background:#fef3c7; color:#92400e; border:1px solid #fde68a; }

.vax-card-dates {
    display:flex; align-items:stretch; margin:0 16px 10px;
    background:#f8fafc; border-radius:8px; overflow:hidden;
    border:1px solid #e8ecf1;
}
.vax-date-block {
    flex:1; padding:8px 10px; text-align:center;
}
.vax-date-block:first-child { border-right:1px solid #e8ecf1; }
.vax-date-label { font-size:9.5px; font-weight:700; text-transform:uppercase; letter-spacing:.5px; color:#94a3b8; margin-bottom:4px; }
.vax-date-value { font-size:12px; font-weight:700; color:#1e293b; }
.vax-date-arrow {
    display:flex; align-items:center; padding:0 8px;
    color:#cbd5e1; font-size:14px;
}
.vax-due-overdue  { color:#dc2626 !important; }
.vax-due-soon     { color:#d97706 !important; }
.vax-due-ok       { color:#16a34a !important; }

.vax-card-details {
    display:flex; flex-wrap:wrap; gap:6px;
    padding:0 16px 10px; font-size:11.5px;
}
.vax-detail-chip {
    display:inline-flex; align-items:center; gap:4px;
    background:#f1f5f9; border-radius:20px; padding:2px 9px;
    color:#475569; font-weight:600;
}
.vax-detail-chip i { font-size:10px; color:#94a3b8; }

.vax-card-notes {
    margin:0 16px 10px; font-size:11.5px; color:#64748b;
    background:#fafafa; border-radius:6px; padding:6px 10px;
    border-left:3px solid #e2e8f0; font-style:italic;
    white-space:pre-wrap; word-break:break-word;
}

.vax-card-footer {
    padding:8px 12px; background:#f8fafc; border-top:1px solid #f1f5f9;
    display:flex; align-items:center; justify-content:flex-end; gap:6px;
}

/* ── Treatment Modal - Status Option Cards ────────────── */
.vax-status-options {
    display:grid; grid-template-columns:repeat(3,1fr); gap:8px; margin-bottom:2px;
}
.vax-status-opt {
    border:2px solid #e2e8f0; border-radius:10px; padding:10px 8px;
    text-align:center; cursor:pointer; transition:all .15s;
    background:#fff; user-select:none;
}
.vax-status-opt:hover { border-color:#3b82f6; background:#eff6ff; }
.vax-status-opt.selected-clinic   { border-color:#22c55e; background:#f0fdf4; }
.vax-status-opt.selected-external { border-color:#3b82f6; background:#eff6ff; }
.vax-status-opt.selected-scheduled{ border-color:#f59e0b; background:#fffbeb; }
.vax-status-opt .so-icon { font-size:22px; margin-bottom:4px; }
.vax-status-opt .so-label { font-size:11.5px; font-weight:700; color:#1e293b; margin-bottom:2px; }
.vax-status-opt .so-sub   { font-size:10px; color:#94a3b8; }

/* ── Treatment Modal - section dividers ───────────────── */
.vax-modal-section {
    border-bottom:1px solid #f1f5f9; padding-bottom:14px; margin-bottom:14px;
}
.vax-modal-section:last-child { border-bottom:none; margin-bottom:0; padding-bottom:0; }
.vax-modal-section-title {
    font-size:10px; font-weight:800; text-transform:uppercase; letter-spacing:.6px;
    color:#94a3b8; margin-bottom:10px; display:flex; align-items:center; gap:6px;
}

/* Empty vax state */
.vax-empty-state {
    text-align:center; padding:40px 20px;
    background:#fafbfc; border-radius:12px; border:2px dashed #e2e8f0;
}
.vax-empty-state .vax-es-icon { font-size:42px; margin-bottom:12px; opacity:.4; }
.vax-empty-state p { color:#94a3b8; font-size:13px; margin-bottom:14px; }

/* Next-visit slot picker (same look as calendar quick-create modal) */
.qc-slot-grid { display:flex; flex-wrap:wrap; gap:5px; margin-top:4px; }
.qc-slot-btn { padding:5px 12px; border-radius:5px; border:1.5px solid #cbd5e1; background:#fff; font-size:11px; font-weight:600; cursor:pointer; transition:all .15s; color:#334155; min-width:90px; text-align:center; user-select:none; }
.qc-slot-btn:hover:not(.unavailable):not(.selected) { border-color:#3498db; color:#3498db; background:#eff6ff; }
.qc-slot-btn.selected { background:#3498db; color:#fff; border-color:#2471a3; }
.qc-slot-btn.booked-warn { background:#fff7ed; color:#c2410c; border-color:#fed7aa; }
.qc-slot-btn.unavailable { background:#fee2e2; color:#ef4444; border-color:#fca5a5; cursor:not-allowed; opacity:.7; text-decoration:line-through; }
.qc-cascade-hint { font-size:10.5px; color:#94a3b8; }
.qc-overlap-alert { padding:6px 10px; background:#fff7ed; border:1px solid #fed7aa; border-radius:5px; color:#c2410c; font-size:11px; margin-top:6px; }
.qc-selection-summary { padding:6px 10px; background:#eff6ff; border:1px solid #bfdbfe; border-radius:5px; font-size:12px; margin-top:6px; }
/* Disabled (out-of-schedule) days in the Next-Visit datepicker */
.datepicker td.nv-day-off { color:#cbd5e1 !important; background:repeating-linear-gradient(45deg,#f8fafc,#f8fafc 4px,#f1f5f9 4px,#f1f5f9 8px) !important; cursor:not-allowed !important; text-decoration:line-through; }
.datepicker td.nv-day-off:hover { background:repeating-linear-gradient(45deg,#f8fafc,#f8fafc 4px,#f1f5f9 4px,#f1f5f9 8px) !important; }

/* ── Evaluation modal shell ──────────────────────────── */
.vc-eval-modal .modal-content {
    border:none; border-radius:14px; overflow:hidden;
    box-shadow:0 24px 60px rgba(15,23,42,.32);
}
.vc-eval-modal .modal-body { max-height:74vh; overflow-y:auto; }
.vc-eval-x {
    position:absolute; top:13px; right:15px; z-index:10;
    width:30px; height:30px; border-radius:50%;
    border:none; background:rgba(255,255,255,.22); color:#fff;
    font-size:15px; line-height:1; cursor:pointer; opacity:.92;
    transition:all .18s;
}
.vc-eval-x:hover { background:rgba(255,255,255,.42); opacity:1; transform:rotate(90deg); }
.vc-eval-loading {
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    gap:13px; padding:64px 20px; color:#94a3b8; font-size:13px; font-weight:600;
}
.vc-eval-loading i { font-size:30px; color:#5b8def; }
.vc-eval-foot { border-top:1px solid #eef1f5; background:#fafbfc; padding:12px 18px; }
.vc-eval-foot .btn { border-radius:8px; font-weight:600; padding:7px 18px; }
.vc-eval-foot .btn-success {
    background:linear-gradient(135deg,#22c55e,#16a34a); border:none;
    box-shadow:0 4px 12px rgba(34,197,94,.32);
}
.vc-eval-foot .btn-success:hover { background:linear-gradient(135deg,#16a34a,#15803d); }

/* ── Visit Time Tracker card ──────────────────────────── */
.vc-tt-grid { display:flex; gap:10px; margin-bottom:12px; }
.vc-tt-metric {
    flex:1; border-radius:9px; padding:12px 10px; text-align:center;
    border:1px solid #e8ecf0;
}
.vc-tt-wait { background:linear-gradient(135deg,#fff7ed,#ffedd5); border-color:#fed7aa; }
.vc-tt-cons { background:linear-gradient(135deg,#eff6ff,#dbeafe); border-color:#bfdbfe; }
.vc-tt-val  { font-size:20px; font-weight:800; line-height:1; }
.vc-tt-wait .vc-tt-val { color:#d97706; }
.vc-tt-cons .vc-tt-val { color:#2563eb; }
.vc-tt-lbl  { font-size:10px; text-transform:uppercase; letter-spacing:.4px; color:#94a3b8; margin-top:5px; font-weight:700; }
.vc-tt-list { list-style:none; margin:0 0 4px; padding:0; }
.vc-tt-list li {
    display:flex; justify-content:space-between; align-items:center;
    font-size:11.5px; padding:5px 0; border-bottom:1px dashed #eef1f5;
}
.vc-tt-list li:last-child { border-bottom:none; }
.vc-tt-list li span { color:#64748b; }
.vc-tt-list li span i { width:14px; color:#94a3b8; }
.vc-tt-list li b { color:#1e293b; font-weight:700; }
.vc-tt-log-title {
    font-size:10px; font-weight:800; text-transform:uppercase; letter-spacing:.4px;
    color:#94a3b8; margin:10px 0 6px;
}
.vc-tt-log { list-style:none; margin:0; padding:0 0 0 4px; }
.vc-tt-log li {
    position:relative; padding:3px 0 3px 16px; font-size:11.5px; color:#475569;
    border-left:2px solid #e8ecf0;
}
.vc-tt-log li:last-child { border-left-color:transparent; }
.vc-tt-dot {
    position:absolute; left:-5px; top:7px; width:8px; height:8px;
    border-radius:50%; background:#6366f1; border:2px solid #fff; box-shadow:0 0 0 1px #c7d2fe;
}
.vc-tt-log-status { font-weight:700; color:#1e293b; }
.vc-tt-log small { color:#94a3b8; margin-left:5px; }

/* ── Edit-request modal ───────────────────────────────── */
.vc-er-modal .modal-content { border:none; border-radius:13px; overflow:hidden; box-shadow:0 22px 55px rgba(15,23,42,.3); }
.vc-er-head {
    background:linear-gradient(135deg,#f59e0b,#d97706); color:#fff; padding:16px 20px;
}
.vc-er-head h4 { margin:0; font-weight:700; font-size:16px; }
.vc-er-head p  { margin:5px 0 0; font-size:12px; opacity:.92; }
</style>
@include('clinicCore::partials._mobile_responsive')
@endsection

@section('content')
@php
$typeIcons  = ['checkup'=>'fa-stethoscope','vaccination'=>'fa-syringe','surgery'=>'fa-procedures','grooming'=>'fa-cut','emergency'=>'fa-ambulance','other'=>'fa-notes-medical'];
$typeColors = ['checkup'=>'#2980b9','vaccination'=>'#27ae60','surgery'=>'#c0392b','grooming'=>'#8e44ad','emergency'=>'#e67e22','other'=>'#7f8c8d'];
$typeIcon   = $typeIcons[$appointment->type]  ?? 'fa-calendar';
$typeColor  = $typeColors[$appointment->type] ?? '#2c3e50';

$statusOrder = ['pending','confirmed','waiting','in_progress','completed'];
$currentIdx  = array_search($appointment->status, $statusOrder);
$isTerminal  = in_array($appointment->status, ['cancelled','no_show']);

$stepLabels  = ['pending'=>'Scheduled','confirmed'=>'Confirmed','waiting'=>'Waiting','in_progress'=>'In Consultation','completed'=>'Completed'];
@endphp

<section class="content-header" style="display:none"></section>
<section class="content">

{{-- Appointment Hero --}}
<div class="apt-hero" style="background: linear-gradient(130deg, {{ $typeColor }} 0%, {{ $typeColor }}bb 40%, {{ $typeColor }}88 100%);">
    {{-- Decorative orbs --}}
    <div class="apt-hero-orb apt-hero-orb-1"></div>
    <div class="apt-hero-orb apt-hero-orb-2"></div>
    <div class="apt-hero-orb apt-hero-orb-3"></div>

    {{-- Top row --}}
    <div class="apt-hero-top">
        <div class="apt-type-icon"><i class="fas {{ $typeIcon }}"></i></div>
        <div class="apt-hero-info">
            <div class="apt-ref-chip">{{ $appointment->ref_no }}</div>
            <h2>{{ ucfirst(str_replace('_',' ',$appointment->type)) }} Appointment</h2>
            <div class="meta">
                <span class="meta-pill">
                    <i class="fa fa-calendar"></i>
                    {{ optional($appointment->appointment_date)->format('l, d M Y') }}
                </span>
                <span class="meta-pill">
                    <i class="fa fa-clock"></i>
                    @php $aptTimeFormat = session('business.time_format', 24) == 12 ? 'h:i A' : 'H:i'; @endphp
                    {{ \Carbon\Carbon::parse($appointment->appointment_time)->format($aptTimeFormat) }}
                </span>
                @if(optional($appointment->doctor)->name)
                <span class="meta-pill">
                    <i class="fa fa-user-md"></i> {{ $appointment->doctor->name }}
                </span>
                @endif
                @if(optional($appointment->department)->name)
                <span class="meta-pill">
                    <i class="fa fa-hospital"></i> {{ $appointment->department->name }}
                </span>
                @endif
            </div>
        </div>
        <div class="apt-hero-actions">
            @can('clinic.manage_appointments')
            @if($editGate['allowed'])
            <a href="{{ route('clinic.appointments.edit', $appointment->id) }}" class="btn btn-sm">
                <i class="fa fa-edit"></i> Edit
                @if($editGate['request'])
                <span class="label label-success" style="margin-left:5px;font-size:9px;">Approved - 1 edit</span>
                @endif
            </a>
            @elseif($editGate['pending'])
            <button type="button" class="btn btn-sm" disabled title="Your edit request is awaiting approval">
                <i class="fa fa-hourglass-half"></i> Edit Pending Approval
            </button>
            @else
            <button type="button" class="btn btn-sm" data-toggle="modal" data-target="#editRequestModal"
                    title="This appointment is completed and locked">
                <i class="fa fa-unlock-alt"></i> Request Edit
            </button>
            @endif

            {{-- Doctor-change exception request (users without the change permission) --}}
            @unless($canChangeDoctor)
                @if($pendingDoctorChange)
                <button type="button" class="btn btn-sm" disabled
                        title="A doctor-change request is awaiting approval">
                    <i class="fa fa-hourglass-half"></i> Doctor Change Pending
                </button>
                @else
                <button type="button" class="btn btn-sm" data-toggle="modal" data-target="#doctorChangeModal"
                        title="Request approval to change this appointment's doctor">
                    <i class="fa fa-user-md"></i> Request Doctor Change
                </button>
                @endif
            @endunless
            @endcan
            <a href="{{ route('clinic.appointments.print', $appointment->id) }}" target="_blank" class="btn btn-sm">
                <i class="fa fa-print"></i> Print
            </a>
            @if($appointment->erp_sell_id)
            <a href="{{ url('/sells/'.$appointment->erp_sell_id) }}" target="_blank" class="btn btn-sm">
                <i class="fas fa-eye"></i> View Invoice
            </a>
            <a href="{{ url('/sells/'.$appointment->erp_sell_id.'/edit') }}" target="_blank" class="btn btn-sm">
                <i class="fas fa-edit"></i> Edit Invoice
            </a>
            <a href="{{ url('/sells/'.$appointment->erp_sell_id.'/print') }}" target="_blank" class="btn btn-sm">
                <i class="fa fa-print"></i> Print Invoice
            </a>
            @else
            <a href="{{ route('clinic.appointments.launch-pos', $appointment->id) }}" target="_blank" class="btn btn-sm" id="btn_create_invoice">
                <i class="fas fa-file-invoice-dollar"></i> Create Invoice
            </a>
            <button type="button" class="btn btn-sm" data-toggle="modal" data-target="#linkInvoiceModal">
                <i class="fa fa-link"></i> Link Invoice
            </button>
            @endif
            {{-- Estimated visit cost label --}}
            <div id="estimated_cost_badge"
                 style="display:none;align-items:center;gap:6px;padding:5px 12px;border-radius:18px;background:rgba(255,255,255,.18);color:#fff;font-size:12px;font-weight:700;backdrop-filter:blur(6px);">
                <i class="fa fa-calculator"></i>
                <span style="opacity:.75;font-weight:500;">Est. cost:</span>
                <span id="estimated_cost_value">0.00</span>
                <span style="opacity:.65;font-size:10px;font-weight:500;" id="estimated_cost_breakdown" title="Breakdown"></span>
            </div>
        </div>
    </div>

    {{-- Wave separator --}}
    <div class="apt-hero-wave">
        <svg viewBox="0 0 1200 40" preserveAspectRatio="none" style="height:38px;">
            <path d="M0,30 C200,5 400,38 600,20 C800,2 1000,35 1200,18 L1200,40 L0,40 Z"
                  fill="rgba(0,0,0,.18)"/>
            <path d="M0,34 C300,14 600,38 900,22 C1050,14 1150,30 1200,26 L1200,40 L0,40 Z"
                  fill="rgba(0,0,0,.12)"/>
        </svg>
    </div>

    {{-- Status Bar --}}
    <div class="apt-status-bar">
        @if($isTerminal)
            <span class="apt-terminal-badge">
                <i class="fa fa-{{ $appointment->status === 'cancelled' ? 'times-circle' : 'user-slash' }}"></i>
                {{ ucfirst(str_replace('_',' ',$appointment->status)) }}
            </span>
        @else
            @foreach($stepLabels as $key => $label)
                @php
                    $stepIdx   = array_search($key, $statusOrder);
                    $stepClass = $stepIdx < $currentIdx ? 's-done' : ($stepIdx == $currentIdx ? 's-active' : '');
                @endphp
                @if(!$loop->first)
                    <div class="s-connector {{ $stepIdx <= $currentIdx ? 's-done' : '' }}"></div>
                @endif
                <div class="status-step {{ $stepClass }}" data-status="{{ $key }}" title="Click to set: {{ $label }}">
                    <div class="s-dot">
                        @if($stepIdx < $currentIdx)
                            <i class="fa fa-check" style="color:#fff;font-size:9px;"></i>
                        @elseif($stepIdx == $currentIdx)
                            <span style="font-size:9px;font-weight:800;color:#333;">{{ $stepIdx + 1 }}</span>
                        @else
                            <span style="font-size:9px;color:rgba(255,255,255,.5);">{{ $stepIdx + 1 }}</span>
                        @endif
                    </div>
                    {{ $label }}
                </div>
            @endforeach
        @endif
    </div>
</div>

{{-- Flash --}}
@if(session('status'))
    @php $st = session('status'); @endphp
    <div class="alert alert-{{ $st['success'] ? 'success' : 'danger' }}">{{ $st['msg'] }}</div>
@endif

<div class="row">

    {{-- ──────────────── LEFT SIDEBAR ──────────────── --}}
    <div class="col-md-3">

        {{-- Patient Summary Card --}}
        <div class="vc-card">
            <div class="vc-card-header"><i class="fas fa-notes-medical fa-fw"></i> Patient</div>
            <div class="vc-card-body">
                <div class="pet-mini">
                    <div class="pet-mini-avatar">
                        @if(optional($appointment->pet)->photo_url)
                            <img src="{{ $appointment->pet->photo_url }}" alt="">
                        @else
                            <i class="fas fa-notes-medical"></i>
                        @endif
                    </div>
                    <div>
                        <a href="{{ route('clinic.patients.show', $appointment->patient_id) }}" class="pet-name-link">
                            {{ optional($appointment->pet)->patient_name ?? '-' }}
                        </a>
                        @if(config('cliniccore.show_animal_fields', false))
                        <div class="pet-sub">
                            {{ optional($appointment->pet)->species }}
                            {{ optional($appointment->pet)->breed ? '- '.optional($appointment->pet)->breed : '' }}
                        </div>
                        @endif
                    </div>
                </div>

                @php
                    $patient = $appointment->pet;
                    $ownerName = trim(optional($appointment->owner)->name ?? '') ?: (optional($appointment->owner)->supplier_business_name ?? '-');
                    // Resolve the patient_contact_details.id from contact_id - that's the
                    // primary key the vc.patient-contacts.show route expects (NOT contacts.id).
                    $ownerDetailId = optional($appointment->owner)->id
                        ? \Modules\ClinicCore\Entities\ClcPatientContactDetail::where('contact_id', $appointment->owner->id)->value('id')
                        : null;
                @endphp

                <div class="info-row">
                    <span class="ik"><i class="fa fa-user fa-fw"></i> Contact</span>
                    <span class="iv">
                        @if($ownerDetailId)
                            <a href="{{ route('clinic.patient-contacts.show', $ownerDetailId) }}"
                               class="owner-name-link" title="Open contact profile">{{ $ownerName }}</a>
                        @else
                            {{ $ownerName }}
                        @endif
                        <br>
                        <small class="text-muted">{{ optional($appointment->owner)->mobile }}</small>
                    </span>
                </div>
                @if(optional($patient)->gender)
                <div class="info-row">
                    <span class="ik"><i class="fa fa-venus-mars fa-fw"></i> Gender</span>
                    <span class="iv">{{ ucfirst($patient->gender) }}{{ (config('cliniccore.show_animal_fields', false) && $patient->neutered) ? ' - Neutered' : '' }}</span>
                </div>
                @endif
                @if(optional($patient)->dob)
                <div class="info-row">
                    <span class="ik"><i class="fa fa-birthday-cake fa-fw"></i> Age</span>
                    <span class="iv">{{ $patient->age }} yrs</span>
                </div>
                @endif
                @if(config('cliniccore.show_animal_fields', false) && optional($patient)->microchip_no)
                <div class="info-row">
                    <span class="ik"><i class="fas fa-microchip fa-fw"></i> Chip</span>
                    <span class="iv">{{ $patient->microchip_no }}</span>
                </div>
                @endif

                {{-- Active conditions --}}
                @if($patient && ($patient->conditions ?? collect())->isNotEmpty())
                <div style="margin-top:10px;padding-top:10px;border-top:1px solid #f3f3f3;">
                    <div style="font-size:10px;text-transform:uppercase;letter-spacing:.5px;color:#aaa;margin-bottom:5px;">Active Conditions</div>
                    @foreach(($patient->conditions ?? collect())->take(5) as $pc)
                    <span class="cond-chip {{ $pc->status }}">{{ optional($pc->condition)->name }}</span>
                    @endforeach
                </div>
                @endif
            </div>
        </div>

        {{-- Specialty panel (tooth chart, skin profile, …) contributed by the
             appointment's specialty plugin via the SpecialtyDriverRegistry. --}}
        @php $clcApptDriver = optional($appointment->specialty)->driver(); @endphp
        @if($clcApptDriver && $clcApptDriver->appointmentFragment() && $patient)
        <div class="vc-card">
            <div class="vc-card-header">
                <i class="fas {{ optional($appointment->specialty)->icon ?: 'fa-stethoscope' }} fa-fw"></i>
                {{ optional($appointment->specialty)->name }}
            </div>
            <div class="vc-card-body">
                @includeIf($clcApptDriver->appointmentFragment(), ['patient' => $patient, 'appointment' => $appointment])
            </div>
        </div>
        @endif

        {{-- Quick Actions --}}
        @can('clinic.manage_appointments')
        <div class="vc-card">
            <div class="vc-card-header"><i class="fa fa-bolt fa-fw"></i> Quick Actions</div>
            <div class="vc-card-body" style="padding:10px 14px;">
                @if(in_array($appointment->status, ['pending','confirmed']))
                <button class="qa-btn" id="btn_patient_arrived"
                        style="border-color:#0d9488;background:#f0fdfa;color:#0d9488;">
                    <span class="qa-icon" style="background:#0d9488;"><i class="fas fa-door-open"></i></span>
                    Patient Arrived (→ Waiting)
                </button>
                @endif
                @if($appointment->status === 'waiting')
                <button class="qa-btn" id="btn_start_consultation"
                        style="border-color:#6366f1;background:#f5f3ff;color:#4f46e5;">
                    <span class="qa-icon" style="background:#6366f1;"><i class="fas fa-stethoscope"></i></span>
                    Start Consultation
                </button>
                @endif
                @can('clinic.manage_vaccinations')
                <button class="qa-btn" data-toggle="modal" data-target="#vaccinationModal">
                    <span class="qa-icon" style="background:#27ae60;"><i class="fas fa-syringe"></i></span>
                    Record Treatment
                </button>
                @endcan
                @can('clinic.manage_prescriptions')
                @php $aptRx = ($appointment->prescriptions ?? collect())->first(); @endphp
                <div id="qa_rx_zone">
                @if($aptRx)
                    <a class="qa-btn" href="{{ route('clinic.prescriptions.show', $aptRx->id) }}">
                        <span class="qa-icon" style="background:#2980b9;"><i class="fas fa-prescription-bottle-alt"></i></span>
                        View Prescription
                    </a>
                    <a class="qa-btn" href="{{ route('clinic.prescriptions.print', $aptRx->id) }}" target="_blank">
                        <span class="qa-icon" style="background:#34495e;"><i class="fa fa-print"></i></span>
                        Print Prescription
                    </a>
                @else
                    <a class="qa-btn text-muted" style="opacity:.55;cursor:default;pointer-events:none;">
                        <span class="qa-icon" style="background:#bdc3c7;"><i class="fas fa-prescription-bottle-alt"></i></span>
                        Prescription (auto on save)
                    </a>
                @endif
                </div>
                @endcan
                <a class="qa-btn" href="{{ route('clinic.patients.show', $appointment->patient_id) }}">
                    <span class="qa-icon" style="background:#8e44ad;"><i class="fas fa-notes-medical"></i></span>
                    Patient Profile
                </a>
                @if(auth()->user()->can('clinic.referral.manage'))
                <a class="qa-btn" href="{{ route('clinic.referrals.index', ['refer_appointment' => $appointment->id]) }}">
                    <span class="qa-icon" style="background:#0ea5e9;"><i class="fas fa-share-square"></i></span>
                    {{ __('clinicCore::lang.refer') }}
                </a>
                @endif
                @if(auth()->user()->can('clinic.view_imaging'))
                <a class="qa-btn" href="{{ route('clinic.imaging.worklist') }}">
                    <span class="qa-icon" style="background:#6366f1;"><i class="fas fa-x-ray"></i></span>
                    {{ __('clinicCore::lang.imaging_worklist') }}
                </a>
                @endif
                @if($appointment->erp_sell_id)
                <a class="qa-btn" href="{{ url('/sells/'.$appointment->erp_sell_id) }}" target="_blank">
                    <span class="qa-icon" style="background:#27ae60;"><i class="fas fa-eye"></i></span>
                    View Invoice
                </a>
                <a class="qa-btn" href="{{ url('/sells/'.$appointment->erp_sell_id.'/print') }}" target="_blank">
                    <span class="qa-icon" style="background:#34495e;"><i class="fa fa-print"></i></span>
                    Print Invoice
                </a>
                @else
                <a class="qa-btn" href="{{ route('clinic.appointments.launch-pos', $appointment->id) }}" target="_blank">
                    <span class="qa-icon" style="background:#e67e22;"><i class="fas fa-file-invoice-dollar"></i></span>
                    Create Invoice
                </a>
                <button type="button" class="qa-btn" data-toggle="modal" data-target="#linkInvoiceModal" style="text-align:left;">
                    <span class="qa-icon" style="background:#8e44ad;"><i class="fa fa-link"></i></span>
                    Link Existing Invoice
                </button>
                @endif
            </div>
        </div>
        @endcan

        {{-- Visit Time Tracker --}}
        @php
            $drmFmtDur = function ($mins) {
                if ($mins === null) return '-';
                if ($mins < 60)    return $mins . ' min';
                return intdiv($mins, 60) . 'h ' . ($mins % 60) . 'm';
            };
        @endphp
        <div class="vc-card">
            <div class="vc-card-header"><i class="fa fa-stopwatch fa-fw"></i> Visit Time Tracker</div>
            <div class="vc-card-body">
                <div class="vc-tt-grid">
                    <div class="vc-tt-metric vc-tt-wait">
                        <div class="vc-tt-val">{{ $drmFmtDur($appointment->waiting_minutes) }}</div>
                        <div class="vc-tt-lbl">Waiting area</div>
                    </div>
                    <div class="vc-tt-metric vc-tt-cons">
                        <div class="vc-tt-val">{{ $drmFmtDur($appointment->consultation_minutes) }}</div>
                        <div class="vc-tt-lbl">Consultation</div>
                    </div>
                </div>
                <ul class="vc-tt-list">
                    <li><span><i class="fa fa-door-open"></i> Arrived</span>
                        <b>{{ optional($appointment->arrived_at)->format('d M, H:i') ?? '-' }}</b></li>
                    <li><span><i class="fa fa-stethoscope"></i> Consultation start</span>
                        <b>{{ optional($appointment->consultation_started_at)->format('d M, H:i') ?? '-' }}</b></li>
                    <li><span><i class="fa fa-flag-checkered"></i> Consultation end</span>
                        <b>{{ optional($appointment->consultation_ended_at)->format('d M, H:i') ?? '-' }}</b></li>
                </ul>
                @if(($appointment->statusLogs ?? collect())->count())
                <div class="vc-tt-log-title">Status history</div>
                <ul class="vc-tt-log">
                    @foreach($appointment->statusLogs as $log)
                    <li>
                        <span class="vc-tt-dot"></span>
                        <span class="vc-tt-log-status">{{ ucfirst(str_replace('_', ' ', $log->to_status)) }}</span>
                        <small>{{ optional($log->changed_at)->format('d M, H:i') }}
                               - {{ optional($log->changedBy)->first_name ?? 'System' }}</small>
                    </li>
                    @endforeach
                </ul>
                @else
                <p class="text-muted" style="font-size:11px;margin:6px 0 0;">No status changes recorded yet.</p>
                @endif
            </div>
        </div>

        {{-- Evaluations --}}
        @if(auth()->user()->can('clinic.evaluate_pet_owner') || auth()->user()->can('clinic.evaluate_doctor'))
        @php $isCompleted = $appointment->status === 'completed'; @endphp
        <div class="vc-card" id="clc_eval_card">
            <div class="vc-card-header"><i class="fa fa-star fa-fw"></i> Evaluations
                <small id="clc_eval_locked_note" class="text-muted"
                       style="font-weight:400;{{ $isCompleted ? 'display:none;' : '' }}">(available after completion)</small>
            </div>
            <div class="vc-card-body" style="display:flex;flex-direction:column;gap:8px;">
                @can('clinic.evaluate_pet_owner')
                <button type="button"
                        class="qa-btn vc-eval-trigger {{ $isCompleted ? '' : 'vc-eval-locked' }}"
                        data-category="pet_owner"
                        style="text-align:left;">
                    <span class="qa-icon" style="background:#5b8def;"><i class="fa fa-user"></i></span>
                    Evaluate Patient
                    <span id="clc_eval_status_pet_owner" class="text-muted" style="margin-left:auto;font-size:11px;"></span>
                </button>
                @endcan
                @can('clinic.evaluate_doctor')
                <button type="button"
                        class="qa-btn vc-eval-trigger {{ $isCompleted ? '' : 'vc-eval-locked' }}"
                        data-category="clinic"
                        style="text-align:left;">
                    <span class="qa-icon" style="background:#f39c12;"><i class="fa fa-hospital"></i></span>
                    Patient Feedback (Clinic / Doctor)
                    <span id="clc_eval_status_clinic" class="text-muted" style="margin-left:auto;font-size:11px;"></span>
                </button>
                @endcan
            </div>
        </div>
        <style>
            /* Lock class - toggled live by JS when status becomes 'completed'
               so the rate buttons unlock without a page refresh. */
            .vc-eval-trigger.vc-eval-locked { opacity: .55; pointer-events: none; cursor: not-allowed; }
        </style>

        {{-- Evaluation Modal --}}
        <div class="modal fade vc-eval-modal" id="drmEvaluationModal" tabindex="-1">
            <div class="modal-dialog modal-lg">
                <div class="modal-content">
                    <button type="button" class="vc-eval-x" data-dismiss="modal" aria-label="Close">&times;</button>
                    <div class="modal-body" id="clc_eval_modal_body">
                        <div class="vc-eval-loading">
                            <i class="fa fa-spinner fa-spin"></i>
                            <span>Loading evaluation form-</span>
                        </div>
                    </div>
                    <div class="modal-footer vc-eval-foot">
                        <button class="btn btn-default" data-dismiss="modal"><i class="fa fa-times"></i> Cancel</button>
                        <button class="btn btn-success" id="btn_clc_eval_save"><i class="fa fa-save"></i> Save Evaluation</button>
                    </div>
                </div>
            </div>
        </div>
        @endif

        {{-- Change Status --}}
        @can('clinic.manage_appointments')
        <div class="vc-card">
            <div class="vc-card-header"><i class="fa fa-toggle-on fa-fw"></i> Change Status</div>
            <div class="vc-card-body">
                <div class="status-select-row">
                    <select id="apt_status_select" class="form-control">
                        @foreach(['pending'=>'Scheduled','waiting'=>'Waiting (Arrived)','confirmed'=>'Confirmed','in_progress'=>'In Consultation','completed'=>'Completed','cancelled'=>'Cancelled','no_show'=>'No Show'] as $s => $sl)
                        <option value="{{ $s }}" {{ $appointment->status == $s ? 'selected' : '' }}>{{ $sl }}</option>
                        @endforeach
                    </select>
                    <button id="btn_update_status" class="btn btn-primary" style="font-size:12px;">
                        <i class="fa fa-check"></i>
                    </button>
                </div>
            </div>
        </div>
        @endcan

    </div>{{-- end sidebar --}}

    {{-- ──────────────── MAIN PANEL ──────────────── --}}
    <div class="col-md-9">
        <ul class="nav nav-tabs" id="aptTabs" style="margin-bottom:0;">
            <li class="active">
                <a data-toggle="tab" href="#tab-consultation">
                    <i class="fas fa-stethoscope"></i> Consultation
                </a>
            </li>
            <li>
                <a data-toggle="tab" href="#tab-notes">
                    <i class="fa fa-sticky-note"></i> Notes
                    <span class="badge">{{ ($appointment->appointmentNotes ?? collect())->count() }}</span>
                </a>
            </li>
            <li>
                <a data-toggle="tab" href="#tab-vaccinations">
                    <i class="fas fa-syringe"></i> Treatments
                    <span class="badge">{{ ($appointment->vaccinations ?? collect())->count() }}</span>
                </a>
            </li>
            <li>
                <a data-toggle="tab" href="#tab-documents">
                    <i class="fa fa-paperclip"></i> Documents
                    <span class="badge" id="doc_count_badge">{{ ($appointment->documents ?? collect())->count() }}</span>
                </a>
            </li>
            {{-- NX600 Lab module integration (shown when the module is installed) --}}
            @if($appointment->pet && false /* NX600Lab analyzer is vet-only; disabled in ClinicCore */)
            @php
                // Count distinct lab samples for this patient - orders raised (worklist)
                // plus any device results - so the badge reflects activity even
                // before the analyzer sends results back.
                $nxLabCount = \Modules\NX600Lab\Entities\NxWorklist::where('clc_patient_id', $appointment->patient_id)->pluck('sample_no')
                    ->merge(\Modules\NX600Lab\Entities\NxTestSession::where('clc_patient_id', $appointment->patient_id)->pluck('sample_no'))
                    ->filter()->unique()->count();
            @endphp
            <li>
                <a data-toggle="tab" href="#tab-lab">
                    <i class="fas fa-vials"></i> Lab Tests
                    <span class="badge">{{ $nxLabCount }}</span>
                </a>
            </li>
            @endif
            {{-- Consents tab --}}
            @php
                $drmConsentList = \Modules\ClinicCore\Entities\ClcConsent::with('issuedBy')
                    ->where('appointment_id', $appointment->id)
                    ->orderByDesc('issued_at')->get();
            @endphp
            <li>
                <a data-toggle="tab" href="#tab-consents">
                    <i class="fas fa-file-signature"></i> Consents
                    <span class="badge">{{ $drmConsentList->count() }}</span>
                </a>
            </li>
            @php
                $drmQuotationCount = \Schema::hasTable('clc_quotation_meta')
                    ? \Modules\ClinicCore\Entities\ClcQuotationMeta::where('appointment_id', $appointment->id)->count()
                    : 0;
            @endphp
            @if(\Schema::hasTable('clc_quotation_meta'))
            <li>
                <a data-toggle="tab" href="#tab-quotations">
                    <i class="fa fa-file-invoice-dollar"></i> Quotations
                    <span class="badge">{{ $drmQuotationCount }}</span>
                </a>
            </li>
            @endif
        </ul>

        <div class="tab-content" style="background:#fff;border:1px solid #ddd;border-top:none;border-radius:0 0 8px 8px;padding:20px;">

            {{-- ── Consultation Tab ── --}}
            <div id="tab-consultation" class="tab-pane fade in active vc-section-host
                 {{ in_array($appointment->status, ['in_progress', 'completed'], true) ? '' : 'vc-section-locked' }}">

                @php
                    // Item #1 - the consultation, vaccinations, lab and prescriptions
                    // sections are locked for editing until the appointment is
                    // actively in_progress (or already completed). Statuses pending,
                    // confirmed and waiting must NOT allow clinical edits to avoid
                    // recording findings before the doctor has actually seen the pet.
                    $consultActionable = in_array($appointment->status, ['in_progress', 'completed'], true);

                    // SIFA P3-T3 - once the encounter note is signed & locked the
                    // legacy SOAP S/O/A/P textareas render read-only; further edits
                    // go through the amendment workflow (encounter sign-off bar).
                    // saveConsultation already drops these columns server-side when
                    // locked; this mirrors that in the UI so the fields are visibly
                    // frozen. method_exists guard keeps older installs safe.
                    $clcSoapLocked = method_exists($appointment, 'isSoapLocked')
                        ? $appointment->isSoapLocked() : false;

                    // The AI assistant is available when the VetAI module is installed
                    // and the user may use it. Each SOAP card then gets a small robot
                    // icon that opens that section's AI checkpoint in a popup.
                    $drmShowAi = \Illuminate\Support\Facades\View::exists('vetAI::partials.appointment_card')
                        && optional(auth()->user())->can('vet_ai.use_assistant');
                    // SOAP voice dictation - same VetAI module, separate permission.
                    $drmShowMic = optional(auth()->user())->can('vet_ai.use_transcription');
                    $drmMicProvider = null; $drmMicConsentWarn = false; $drmMicAutoStructure = false;
                    $drmMicSilenceSec = 30;
                    if ($drmShowMic && class_exists(\Modules\VetAI\Entities\VaiSetting::class)) {
                        $__vai = \Modules\VetAI\Entities\VaiSetting::forBusiness(session('user.business_id'));
                        $drmMicProvider      = $__vai->transcriptionConfig()['provider'];
                        $drmMicConsentWarn   = (bool) $__vai->consent_warning;
                        $drmMicAutoStructure = (bool) $__vai->auto_structure_soap;
                        $drmMicSilenceSec    = max(5, min(300, (int) ($__vai->t_silence_limit_sec ?: 30)));
                    }
                @endphp

                @if($drmShowAi || $drmShowMic)
                <style>
                    .vc-ai-trigger{
                        margin-left:8px;border:1px solid #c7d2fe;background:#eef2ff;color:#4f46e5;
                        border-radius:6px;padding:3px 9px;font-size:11px;font-weight:700;cursor:pointer;
                        display:inline-flex;align-items:center;gap:5px;line-height:1.4;
                    }
                    .vc-ai-trigger:hover{background:#4f46e5;color:#fff;border-color:#4f46e5;}
                    .vc-mic-trigger{
                        margin-left:6px;border:1px solid #fecaca;background:#fef2f2;color:#dc2626;
                        border-radius:6px;padding:3px 9px;font-size:11px;font-weight:700;cursor:pointer;
                        display:inline-flex;align-items:center;gap:5px;line-height:1.4;
                    }
                    .vc-mic-trigger:hover{background:#dc2626;color:#fff;border-color:#dc2626;}
                    .vc-mic-trigger.recording{background:#dc2626;color:#fff;border-color:#dc2626;animation:drmMicPulse 1.2s infinite;}
                    @keyframes drmMicPulse { 0%,100%{box-shadow:0 0 0 0 rgba(220,38,38,.5);} 50%{box-shadow:0 0 0 6px rgba(220,38,38,0);} }
                </style>
                @endif

                {{-- SIFA P3-T2/T3/T4 — alert banner + encounter panel (encounter
                     meta + check-in + follow-up, diagnoses, extended vitals,
                     sign/lock/amend, typed notes, voice dictation). Layered on
                     top of the existing SOAP/vitals blocks below. --}}
                @include('clinicCore::partials.encounter_panel', ['appointment' => $appointment])

                {{-- Item #1 - section-lock banner. Visible while the
                     appointment is pending/confirmed/waiting. Once the
                     doctor clicks "Start Consultation" (status -> in_progress)
                     the banner is hidden and the inputs unlock.
                     Server-side $consultActionable drives the initial
                     state; client-side onStatusChanged() toggles it live. --}}
                <div id="clc_section_lock_banner" class="vc-section-lock-banner"
                     style="{{ $consultActionable ? 'display:none;' : '' }}margin-bottom:12px;padding:11px 16px;background:#fef3c7;border:1px solid #fde68a;border-radius:8px;font-size:12.5px;color:#78350f;display:flex;align-items:center;gap:10px;">
                    <i class="fa fa-lock" style="font-size:18px;color:#b45309;"></i>
                    <div style="flex:1;">
                        <strong>This consultation is locked.</strong>
                        Clinical sections (consultation, vaccinations, lab tests, prescriptions)
                        cannot be edited until the appointment status is
                        <strong>In Consultation</strong>.
                        Click the <em>Start Consultation</em> quick-action button or change the
                        status from the sidebar to begin.
                    </div>
                </div>

                {{-- Draft restored banner --}}
                <div id="draft_banner" style="display:none;margin-bottom:12px;padding:9px 14px;background:#fff7ed;border:1px solid #fed7aa;border-radius:8px;font-size:12px;color:#92400e;">
                    <i class="fa fa-history"></i> <strong>Draft restored</strong> - unsaved changes from your last session were recovered.
                    <a href="#" id="draft_discard" style="margin-left:10px;color:#e67e22;font-weight:600;">Discard draft</a>
                </div>

                @if($drmShowMic)
                <div style="margin-bottom:12px;display:flex;align-items:center;gap:10px;padding:8px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:8px;">
                    <i class="fa fa-microphone" style="color:#dc2626;font-size:14px;"></i>
                    <span style="font-size:12px;color:#7f1d1d;">
                        Dictate the whole consultation in one go - the transcript is
                        {{ $drmMicAutoStructure ? 'auto-split into S/O/A/P fields' : 'added to the Subjective field' }}.
                        Or use the small <i class="fa fa-microphone" style="color:#dc2626;"></i> button on each SOAP section to dictate that section alone.
                    </span>
                    <button type="button" class="btn btn-sm btn-default" style="margin-left:auto;font-weight:700;"
                            onclick="drmVoiceConsult(this)">
                        <i class="fa fa-microphone"></i> Voice consultation
                    </button>
                </div>
                @endif

                @if($appointment->reason)
                <div style="margin-bottom:12px;padding:9px 14px;background:#f0f7ff;border-radius:8px;font-size:12px;border-left:3px solid #3498db;">
                    <i class="fa fa-info-circle" style="color:#3498db;"></i>
                    <strong>Reason for Visit:</strong> {{ $appointment->reason }}
                </div>
                @endif

                {{-- Vitals — Item 1.3: live range warning per metric.
                     Each input has a .vb-range slot below the unit; the
                     JS module fetches the species/age-aware reference
                     range and colour-codes the input border + hint when
                     the doctor types a value outside it. --}}
                <div style="margin-bottom:14px;">
                    <div style="font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:#64748b;margin-bottom:8px;">
                        <i class="fas fa-heartbeat fa-fw" style="color:#e74c3c;"></i> Vitals
                        <small id="v_range_label" class="text-muted" style="font-weight:500;text-transform:none;font-size:10px;margin-left:6px;"></small>
                        {{-- SIFA P3-T4 — vitals trend link (moved here from the removed
                             encounter-panel vitals card). Feeds off the clc_vitals rows
                             that saveConsultation now upserts on each save. --}}
                        @if(Route::has('clinic.patients.vitals-trends') && auth()->user() && (auth()->user()->can('clinic.vitals.view_trends') || auth()->user()->can('clinic.view_appointments')))
                            <a href="{{ route('clinic.patients.vitals-trends', $appointment->patient_id) }}" target="_blank"
                               style="float:right;font-weight:600;text-transform:none;font-size:11px;">
                                <i class="fa fa-line-chart"></i> {{ __('clinicCore::lang.vitals_trend') }}
                            </a>
                        @endif
                    </div>
                    <div class="vitals-grid">
                        <div class="vital-box" data-vital="weight_kg">
                            <div class="vb-label">Weight</div>
                            <input type="number" id="v_weight" step="0.01" placeholder="-" value="{{ $appointment->weight_kg }}">
                            <div class="vb-unit">kg</div>
                            <div class="vb-range" data-vital-range></div>
                        </div>
                        <div class="vital-box" data-vital="temperature">
                            <div class="vb-label">Temperature</div>
                            <input type="number" id="v_temp" step="0.1" placeholder="-" value="{{ $appointment->temperature }}">
                            <div class="vb-unit">-C</div>
                            <div class="vb-range" data-vital-range></div>
                        </div>
                        <div class="vital-box" data-vital="heart_rate">
                            <div class="vb-label">Heart Rate</div>
                            <input type="number" id="v_hr" step="1" placeholder="-" value="{{ $appointment->heart_rate }}">
                            <div class="vb-unit">bpm</div>
                            <div class="vb-range" data-vital-range></div>
                        </div>
                        <div class="vital-box" data-vital="respiratory_rate">
                            <div class="vb-label">Resp. Rate</div>
                            <input type="number" id="v_rr" step="1" placeholder="-" value="{{ $appointment->respiratory_rate }}">
                            <div class="vb-unit">brpm</div>
                            <div class="vb-range" data-vital-range></div>
                        </div>
                        @php
                            $clcVitalsCap = \Modules\ClinicCore\Entities\Services\ClinicCapabilities::enabled(
                                (int) session('user.business_id'), $appointment->specialty_id ?? null, 'vitals');
                            $clcExtraFields = [];
                            if ($appointment->specialty_id) {
                                $clcSpec = \Modules\ClinicCore\Entities\ClcSpecialty::find($appointment->specialty_id);
                                $clcExtraFields = $clcSpec ? $clcSpec->extraVitalFields() : [];
                            }
                            $clcExtraVals = (array) ($appointment->extra_vitals ?? []);
                        @endphp
                        @if($clcVitalsCap && Schema::hasColumn('clc_appointments', 'systolic_bp'))
                        <div class="vital-box" data-vital="systolic_bp">
                            <div class="vb-label">@lang('clinicCore::lang.systolic')</div>
                            <input type="number" id="v_sys" step="1" placeholder="-" value="{{ $appointment->systolic_bp }}">
                            <div class="vb-unit">mmHg</div>
                            <div class="vb-range" data-vital-range></div>
                        </div>
                        <div class="vital-box" data-vital="diastolic_bp">
                            <div class="vb-label">@lang('clinicCore::lang.diastolic')</div>
                            <input type="number" id="v_dia" step="1" placeholder="-" value="{{ $appointment->diastolic_bp }}">
                            <div class="vb-unit">mmHg</div>
                            <div class="vb-range" data-vital-range></div>
                        </div>
                        <div class="vital-box" data-vital="spo2">
                            <div class="vb-label">@lang('clinicCore::lang.spo2')</div>
                            <input type="number" id="v_spo2" step="1" placeholder="-" value="{{ $appointment->spo2 }}">
                            <div class="vb-unit">%</div>
                            <div class="vb-range" data-vital-range></div>
                        </div>
                        <div class="vital-box" data-vital="blood_glucose">
                            <div class="vb-label">@lang('clinicCore::lang.blood_glucose')</div>
                            <input type="number" id="v_glucose" step="0.1" placeholder="-" value="{{ $appointment->blood_glucose }}">
                            <div class="vb-unit">mg/dL</div>
                            <div class="vb-range" data-vital-range></div>
                        </div>
                        <div class="vital-box" data-vital="bmi">
                            <div class="vb-label">@lang('clinicCore::lang.bmi')</div>
                            <input type="number" id="v_bmi" step="0.1" placeholder="-" value="{{ $appointment->bmi }}"
                                   data-height="{{ optional($appointment->pet)->height_cm }}">
                            <div class="vb-unit">kg/m²</div>
                            <div class="vb-range" data-vital-range></div>
                        </div>
                        @foreach($clcExtraFields as $f)
                        <div class="vital-box" data-vital="extra:{{ $f['key'] }}">
                            <div class="vb-label">{{ $f['label'] ?? $f['key'] }}</div>
                            <input type="{{ ($f['type'] ?? 'number') === 'text' ? 'text' : 'number' }}" step="0.1"
                                   class="clc-extra-vital" data-extra-key="{{ $f['key'] }}" placeholder="-"
                                   value="{{ $clcExtraVals[$f['key']] ?? '' }}">
                            <div class="vb-unit">{{ $f['unit'] ?? '' }}</div>
                            <div class="vb-range"></div>
                        </div>
                        @endforeach
                        @endif
                    </div>
                </div>

                {{-- S - Subjective --}}
                <div class="soap-card" data-soap="s">
                    <div class="soap-card-hd" onclick="toggleSoapCard(this)">
                        <span class="soap-badge-lg" style="background:#3498db;">S</span>
                        <div>
                            <div class="soap-card-title">Subjective - Chief Complaint</div>
                            <div class="soap-card-sub">Tap a chip or type freely</div>
                        </div>
                        @if($drmShowAi)<button type="button" class="vc-ai-trigger" onclick="event.stopPropagation();drmOpenAi('exam_review')" title="AI - Examination Review"><i class="fas fa-robot"></i> AI</button>@endif
                        @if($drmShowMic)<button type="button" class="vc-mic-trigger" onclick="event.stopPropagation();drmMicDictate(this,'soap_s','subjective')" title="Dictate this section"><i class="fa fa-microphone"></i></button>@endif
                        <i class="fa fa-chevron-down soap-card-chevron"></i>
                    </div>
                    <div class="soap-card-body">
                        <div class="chip-zone" id="complaint_chips"></div>
                        <div style="position:relative;margin-bottom:8px;">
                            <input type="text" id="complaint_search" class="form-control" placeholder="+ Search more complaints-" style="height:28px;font-size:11.5px;padding:0 10px;">
                            <div id="complaint_search_results" style="display:none;position:absolute;z-index:9999;left:0;right:0;top:30px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,.12);max-height:160px;overflow-y:auto;"></div>
                        </div>
                        <textarea id="soap_s" class="form-control" rows="3" {{ $clcSoapLocked ? 'readonly' : '' }}
                            placeholder="Chief complaint, history, contact observations-">{{ $appointment->soap_subjective }}</textarea>
                        {{-- Key clinical findings extracted from Subjective by AI.
                             Pills are colour-coded by severity (red=critical,
                             amber=moderate, grey=mild). Click a pill to copy the
                             label to clipboard so it can be searched in chips
                             or pasted elsewhere. --}}
                        <div id="clc_ai_symptoms" class="vc-ai-symptoms" style="display:none;">
                            <div class="vc-ai-symptoms-hd">
                                <i class="fa fa-stethoscope" style="color:#0ea5e9;"></i>
                                <strong>Key clinical findings:</strong>
                                <span id="clc_ai_symptoms_concern" class="vc-ai-concern"></span>
                                <button type="button" id="clc_ai_symptoms_close" class="vc-ai-suggest-x"
                                        title="Dismiss for this session">&times;</button>
                            </div>
                            <div id="clc_ai_symptoms_list" class="vc-ai-symptoms-list"></div>
                        </div>
                    </div>
                </div>

                {{-- O - Objective --}}
                <div class="soap-card" data-soap="o">
                    <div class="soap-card-hd" onclick="toggleSoapCard(this)">
                        <span class="soap-badge-lg" style="background:#27ae60;">O</span>
                        <div>
                            <div class="soap-card-title">Objective - Physical Examination</div>
                            <div class="soap-card-sub">Structured head-to-tail exam &middot; click a region to expand</div>
                        </div>
                        @if($drmShowAi)<button type="button" class="vc-ai-trigger" onclick="event.stopPropagation();drmOpenAi('diagnosis')" title="AI - Expected Diagnoses"><i class="fas fa-robot"></i> AI</button>@endif
                        @if($drmShowMic)<button type="button" class="vc-mic-trigger" onclick="event.stopPropagation();drmMicDictate(this,'soap_o','objective')" title="Dictate this section"><i class="fa fa-microphone"></i></button>@endif
                        <i class="fa fa-chevron-down soap-card-chevron"></i>
                    </div>
                    <div class="soap-card-body">
                        {{-- Enhanced body-systems exam.
                             Each system has Normal / Abnormal / N/E toggle.
                             When Abnormal or N/E -> reveal a checklist of
                             common findings for that system (multi-select),
                             plus optional severity (mild/mod/severe) +
                             lateralisation (L/R) per finding, plus a
                             multi-line free-text notes box.
                             The Phase A structured-exam schema is still in
                             place for Phases B-D to consume; the on-screen
                             UI here intentionally stays close to the paper
                             exam-report layout doctors already know. --}}
                        {{-- Item #4b - AI suggest-from-Subjective banner.
                             Populated when the LLM reads the Subjective text
                             and recommends specific specialist exams to open.
                             Each chip is clickable: it auto-expands the matching
                             body-system row and marks it Abnormal so the
                             checklist is immediately ready for input. --}}
                        <div id="clc_ai_exam_suggest" class="vc-ai-suggest" style="display:none;">
                            <div class="vc-ai-suggest-hd">
                                <i class="fa fa-robot" style="color:#0ea5e9;"></i>
                                <strong>AI suggests focusing on:</strong>
                                <span id="clc_ai_suggest_reason" class="vc-ai-suggest-reason"></span>
                                <button type="button" id="clc_ai_suggest_close" class="vc-ai-suggest-x"
                                        title="Dismiss for this session">&times;</button>
                            </div>
                            <div id="clc_ai_suggest_chips" class="vc-ai-suggest-chips"></div>
                        </div>
                        <div class="systems-grid" id="systems_grid"></div>
                        <textarea id="soap_o" class="form-control" rows="3" {{ $clcSoapLocked ? 'readonly' : '' }}
                            placeholder="Overall Objective summary - auto-builds from system findings, free-text welcome-">{{ $appointment->soap_objective }}</textarea>
                    </div>
                </div>

                {{-- A - Assessment --}}
                <div class="soap-card" data-soap="a">
                    <div class="soap-card-hd" onclick="toggleSoapCard(this)">
                        <span class="soap-badge-lg" style="background:#e67e22;">A</span>
                        <div>
                            <div class="soap-card-title">Assessment - Diagnosis</div>
                            <div class="soap-card-sub">Search the conditions catalogue or type freely</div>
                        </div>
                        @if($drmShowAi)<button type="button" class="vc-ai-trigger" onclick="event.stopPropagation();drmOpenAi('treatment')" title="AI - Treatment Plan"><i class="fas fa-robot"></i> AI</button>@endif
                        @if($drmShowMic)<button type="button" class="vc-mic-trigger" onclick="event.stopPropagation();drmMicDictate(this,'soap_a','assessment')" title="Dictate this section"><i class="fa fa-microphone"></i></button>@endif
                        <i class="fa fa-chevron-down soap-card-chevron"></i>
                    </div>
                    <div class="soap-card-body">
                        <select id="diag_search" style="width:100%;"></select>
                        <div class="diag-tags" id="diag_tags"></div>
                        <label style="font-size:11px;color:#64748b;margin-top:8px;font-weight:normal;cursor:pointer;">
                            <input type="checkbox" id="chk_add_conditions">
                            Save selected conditions to pet's active conditions on save
                        </label>
                        <textarea id="soap_a" class="form-control" rows="2" style="margin-top:8px;" {{ $clcSoapLocked ? 'readonly' : '' }}
                            placeholder="Additional assessment notes-">{{ $appointment->soap_assessment }}</textarea>
                    </div>
                </div>

                {{-- I - Investigations --}}
                <div class="soap-card" data-soap="i">
                    <div class="soap-card-hd" onclick="toggleSoapCard(this)">
                        <span class="soap-badge-lg" style="background:#16a085;">I</span>
                        <div>
                            <div class="soap-card-title">Investigations &amp; Exams Required</div>
                            <div class="soap-card-sub">Tap to select tests, or type custom</div>
                        </div>
                        <i class="fa fa-chevron-down soap-card-chevron"></i>
                    </div>
                    <div class="soap-card-body">
                        <div style="font-size:10px;font-weight:700;letter-spacing:.5px;color:#16a085;margin-bottom:5px;display:flex;align-items:center;gap:6px;">
                            <span><i class="fa fa-flask fa-fw"></i> LAB TESTS</span>
                            @if($drmShowMic)<button type="button" class="vc-mic-trigger" style="padding:1px 7px;font-size:10px;" onclick="event.stopPropagation();drmMicDictate(this,'soap_labs','investigations_lab')" title="Dictate lab tests"><i class="fa fa-microphone"></i></button>@endif
                        </div>
                        <div class="chip-zone" id="lab_chips" style="margin-bottom:4px;"></div>
                        <div style="position:relative;margin-bottom:8px;">
                            <input type="text" id="lab_search" class="form-control" placeholder="+ Search more lab tests-" style="height:26px;font-size:11px;padding:0 8px;">
                            <div id="lab_search_results" style="display:none;position:absolute;z-index:9999;left:0;right:0;top:28px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,.12);max-height:160px;overflow-y:auto;"></div>
                        </div>
                        <textarea id="soap_labs" class="form-control" rows="2" style="margin-bottom:12px;" {{ $clcSoapLocked ? 'readonly' : '' }}
                            placeholder="Selected labs listed above, or add custom-">{{ $appointment->soap_lab_investigations ?? '' }}</textarea>
                        <div style="font-size:10px;font-weight:700;letter-spacing:.5px;color:#16a085;margin-bottom:5px;display:flex;align-items:center;gap:6px;">
                            <span><i class="fas fa-x-ray fa-fw"></i> PHYSICAL EXAMS</span>
                            @if($drmShowMic)<button type="button" class="vc-mic-trigger" style="padding:1px 7px;font-size:10px;" onclick="event.stopPropagation();drmMicDictate(this,'soap_exams','investigations_exam')" title="Dictate physical exams"><i class="fa fa-microphone"></i></button>@endif
                        </div>
                        <div class="chip-zone" id="exam_chips" style="margin-bottom:4px;"></div>
                        <div style="position:relative;margin-bottom:8px;">
                            <input type="text" id="exam_search" class="form-control" placeholder="+ Search more exams-" style="height:26px;font-size:11px;padding:0 8px;">
                            <div id="exam_search_results" style="display:none;position:absolute;z-index:9999;left:0;right:0;top:28px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,.12);max-height:160px;overflow-y:auto;"></div>
                        </div>
                        <textarea id="soap_exams" class="form-control" rows="2" {{ $clcSoapLocked ? 'readonly' : '' }}
                            placeholder="Selected exams listed above, or add custom-">{{ $appointment->soap_exam_investigations ?? '' }}</textarea>
                    </div>
                </div>

                {{-- Investigation Status & Results ─────────────────── --}}
                <div class="soap-card" data-soap="ir" id="exam_status_card" style="display:none;">
                    <div class="soap-card-hd" onclick="toggleSoapCard(this)">
                        <span class="soap-badge-lg" style="background:#0ea5e9;"><i class="fa fa-tasks"></i></span>
                        <div>
                            <div class="soap-card-title">Investigation Status &amp; Results</div>
                            <div class="soap-card-sub">For each selected lab/exam: mark Done (record result + optional upload) or Not Done (give reason)</div>
                        </div>
                        <span id="exam_status_summary" style="margin-left:auto;font-size:11px;font-weight:700;color:#64748b;"></span>
                        <i class="fa fa-chevron-down soap-card-chevron"></i>
                    </div>
                    <div class="soap-card-body">
                        <div id="exam_results_list"></div>
                        <p class="text-muted" id="exam_results_empty" style="font-size:11.5px;margin:6px 0 0;display:none;">
                            <i class="fa fa-info-circle"></i> Select lab tests or exams above (click chips) to start tracking their status here.
                        </p>
                    </div>
                </div>

                {{-- P - Plan --}}
                <div class="soap-card" data-soap="p">
                    <div class="soap-card-hd" onclick="toggleSoapCard(this)">
                        <span class="soap-badge-lg" style="background:#8e44ad;">P</span>
                        <div>
                            <div class="soap-card-title">Plan - Treatment &amp; Follow-up</div>
                            <div class="soap-card-sub">Add medications above - textarea below is for general plan notes only</div>
                        </div>
                        @if($drmShowAi)<button type="button" class="vc-ai-trigger" onclick="event.stopPropagation();drmOpenAi('interaction')" title="AI - Interaction &amp; Dose Check"><i class="fas fa-robot"></i> AI</button>@endif
                        @if($drmShowMic)<button type="button" class="vc-mic-trigger" onclick="event.stopPropagation();drmMicDictate(this,'soap_p','plan')" title="Dictate this section"><i class="fa fa-microphone"></i></button>@endif
                        <i class="fa fa-chevron-down soap-card-chevron"></i>
                    </div>
                    <div class="soap-card-body">
                        {{-- Quick med row --}}
                        <div style="padding:10px;background:#f8fafc;border-radius:8px;border:1px solid #e8ecf0;margin-bottom:10px;">
                            <div style="display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end;margin-bottom:6px;">
                                <div style="flex:3;min-width:140px;">
                                    <div style="font-size:10px;font-weight:700;color:#94a3b8;margin-bottom:3px;letter-spacing:.5px;">MEDICINE</div>
                                    <select id="plan_med_search" style="width:100%;"></select>
                                </div>
                                <div style="flex:1;min-width:75px;">
                                    <div style="font-size:10px;font-weight:700;color:#94a3b8;margin-bottom:3px;letter-spacing:.5px;">DOSAGE</div>
                                    <input type="text" id="plan_dosage" class="form-control" placeholder="1 tab" style="height:32px;font-size:12px;">
                                </div>
                                <div style="flex:1;min-width:120px;">
                                    <div style="font-size:10px;font-weight:700;color:#94a3b8;margin-bottom:3px;letter-spacing:.5px;">FREQUENCY</div>
                                    <select id="plan_freq" class="form-control" style="height:32px;font-size:12px;"></select>
                                </div>
                                <div style="flex:0 0 60px;">
                                    <div style="font-size:10px;font-weight:700;color:#94a3b8;margin-bottom:3px;letter-spacing:.5px;">DAYS</div>
                                    <input type="number" id="plan_days" class="form-control" placeholder="7" min="1" style="height:32px;font-size:12px;">
                                </div>
                                <div style="flex-shrink:0;">
                                    <div style="font-size:10px;color:transparent;margin-bottom:3px;">-</div>
                                    <div style="display:flex;gap:4px;">
                                        <button id="btn_add_plan_med" class="btn btn-success" style="height:32px;padding:0 12px;font-size:12px;">
                                            <i class="fa fa-plus"></i> Add
                                        </button>
                                        <button id="btn_cancel_edit_med" class="btn btn-default" style="height:32px;padding:0 9px;font-size:12px;display:none;" title="Cancel edit">
                                            <i class="fa fa-times"></i>
                                        </button>
                                    </div>
                                </div>
                            </div>
                            <div>
                                <div style="font-size:10px;font-weight:700;color:#94a3b8;margin-bottom:3px;letter-spacing:.5px;">NOTE / INSTRUCTIONS</div>
                                <input type="text" id="plan_note" class="form-control" placeholder="e.g. With food, avoid sunlight-" style="height:32px;font-size:12px;">
                            </div>
                        </div>
                        <div id="plan_med_list"></div>
                        <textarea id="soap_p" class="form-control" rows="2" style="margin-top:8px;" {{ $clcSoapLocked ? 'readonly' : '' }}
                            placeholder="Additional plan notes, follow-up instructions-">{{ $appointment->soap_plan }}</textarea>
                    </div>
                </div>

                {{-- N - Next Visit (task 1.3) ──────────────────────────── --}}
                <div class="soap-card" data-soap="n">
                    <div class="soap-card-hd" onclick="toggleSoapCard(this)">
                        <span class="soap-badge-lg" style="background:linear-gradient(135deg,#818cf8,#6366f1 55%,#4f46e5);box-shadow:0 4px 10px rgba(99,102,241,.4);"><i class="fa fa-calendar-plus"></i></span>
                        <div>
                            <div class="soap-card-title">Next Visit</div>
                            <div class="soap-card-sub">Set when you save the consultation, the next appointment will be auto-created &amp; printed on the prescription</div>
                        </div>
                        @if($appointment->next_appointment_id ?? null)
                            <span style="margin-left:auto;font-size:11px;font-weight:700;color:#22c55e;">
                                <i class="fa fa-check-circle"></i> Scheduled
                            </span>
                        @endif
                        <i class="fa fa-chevron-down soap-card-chevron"></i>
                    </div>
                    <div class="soap-card-body">
                        @php
                            $nvDoctorDefault = $appointment->next_visit_doctor_id ?? $appointment->doctor_id;
                            $nvDeptDefault   = $appointment->next_visit_department_id ?? $appointment->department_id;
                            $nvTypeIds       = $appointment->next_visit_type_ids ?? $appointment->type_ids ?? [];
                            $nvReasonIds     = $appointment->next_visit_reason_ids ?? $appointment->reason_ids ?? [];
                            $activeDoctors   = \Modules\ClinicCore\Entities\ClcDoctor::where('business_id', $appointment->business_id)
                                ->where('status', 'active')->orderBy('name')->get();
                            $activeDepts     = \Modules\ClinicCore\Entities\ClcDepartment::where('business_id', $appointment->business_id)
                                ->where('status', 'active')->orderBy('name')->get();
                            // Item #3 - default: schedule a follow-up only when the
                            // doctor explicitly opts in OR when a next-visit date is
                            // already on record from a prior save.
                            $nvOptedIn = !empty($appointment->next_visit_date)
                                      || !empty($appointment->next_appointment_id);
                        @endphp

                        {{-- Item #3 - opt-in toggle. When OFF the next-visit form
                             is hidden + all fields cleared on save so the
                             controller treats this visit as a one-off. --}}
                        <div style="margin-bottom:12px;padding:8px 12px;background:#eef2ff;border:1px solid #c7d2fe;border-radius:6px;display:flex;align-items:center;gap:10px;">
                            <label style="font-weight:600;cursor:pointer;color:#3730a3;margin:0;display:flex;align-items:center;gap:8px;flex:1;">
                                <input type="checkbox" id="nv_schedule_followup" {{ $nvOptedIn ? 'checked' : '' }}>
                                <span><i class="fa fa-calendar-plus"></i> Schedule a follow-up visit</span>
                            </label>
                            <small id="nv_skip_note" class="text-muted" style="font-size:11px;display:{{ $nvOptedIn ? 'none' : 'inline' }};">
                                Off &mdash; no future visit will be auto-created when the consultation is saved.
                            </small>
                        </div>

                        <div id="nv_form_body" style="{{ $nvOptedIn ? '' : 'display:none;' }}">
                        <div class="row" style="margin-bottom:10px;">
                            <div class="col-md-6">
                                <label style="font-size:11px;font-weight:700;color:#475569;">Doctor</label>
                                <select id="next_visit_doctor_id" class="form-control input-sm">
                                    <option value="">- Select doctor -</option>
                                    @foreach($activeDoctors as $d)
                                    <option value="{{ $d->id }}" {{ $nvDoctorDefault == $d->id ? 'selected' : '' }}>{{ $d->name }}</option>
                                    @endforeach
                                </select>
                            </div>
                            <div class="col-md-6">
                                <label style="font-size:11px;font-weight:700;color:#475569;">Department</label>
                                <select id="next_visit_department_id" class="form-control input-sm">
                                    <option value="">- Select department -</option>
                                    @foreach($activeDepts as $dep)
                                    <option value="{{ $dep->id }}" {{ $nvDeptDefault == $dep->id ? 'selected' : '' }}>{{ $dep->name }}</option>
                                    @endforeach
                                </select>
                            </div>
                        </div>

                        <div class="row" style="margin-bottom:10px;">
                            <div class="col-md-4">
                                <label style="font-size:11px;font-weight:700;color:#475569;">Date</label>
                                <input type="text" id="next_visit_date_display" class="form-control input-sm" autocomplete="off" placeholder="Pick a date">
                                <input type="hidden" id="next_visit_date_iso" value="{{ optional($appointment->next_visit_date)->format('Y-m-d') ?? '' }}">
                            </div>
                            <div class="col-md-8">
                                <label style="font-size:11px;font-weight:700;color:#475569;">Time</label>
                                {{-- Slot picker (visible once doctor + date known) --}}
                                <div id="nv_slot_picker" style="display:none;">
                                    <div id="nv_slot_grid" class="qc-slot-grid"></div>
                                    <div class="qc-overlap-alert" id="nv_overlap_alert" style="display:none;">
                                        <i class="fa fa-exclamation-triangle"></i> Selected slot overlaps an existing appointment for this doctor.
                                    </div>
                                    <div class="qc-selection-summary" id="nv_selection_summary" style="display:none;">
                                        <i class="fa fa-calendar-check text-primary"></i> <span id="nv_selection_detail"></span>
                                    </div>
                                </div>
                                {{-- Manual fallback when no schedule exists --}}
                                <div id="nv_manual_time_group" style="display:none;">
                                    <input type="time" id="nv_time_manual" class="form-control input-sm" style="width:160px;display:inline-block;"
                                           value="{{ $appointment->next_visit_time ? \Carbon\Carbon::parse($appointment->next_visit_time)->format('H:i') : '09:00' }}">
                                    <small class="text-muted" style="margin-left:8px;">No schedule found - enter time manually.</small>
                                </div>
                                {{-- Pre-doctor hint --}}
                                <div id="nv_pre_doctor_hint" class="qc-cascade-hint" style="margin-top:6px;">
                                    Pick doctor + date to see available slots.
                                </div>
                                <input type="hidden" id="next_visit_time" value="{{ $appointment->next_visit_time ? \Carbon\Carbon::parse($appointment->next_visit_time)->format('H:i:s') : '' }}">
                                <input type="hidden" id="next_visit_schedule_id" value="">
                                <input type="hidden" id="next_visit_duration" value="30">
                            </div>
                        </div>

                        <div class="row" style="margin-bottom:10px;">
                            <div class="col-md-6">
                                <label style="font-size:11px;font-weight:700;color:#475569;">Type</label>
                                @if(isset($aptTypes) && $aptTypes->isNotEmpty())
                                    <select id="next_visit_type_ids" class="form-control" style="width:100%;" multiple>
                                        @foreach($aptTypes as $at)
                                        <option value="{{ $at->id }}" {{ in_array($at->id, (array)$nvTypeIds) ? 'selected' : '' }}>{{ $at->name }}</option>
                                        @endforeach
                                    </select>
                                @else
                                    <input type="text" id="next_visit_type_text" class="form-control input-sm" placeholder="Type (no catalogue)" value="">
                                @endif
                            </div>
                            <div class="col-md-6">
                                <label style="font-size:11px;font-weight:700;color:#475569;">Reason / Chief Complaint</label>
                                @if(isset($aptReasons) && $aptReasons->isNotEmpty())
                                    <select id="next_visit_reason_ids" class="form-control" style="width:100%;" multiple>
                                        @foreach($aptReasons as $ar)
                                        <option value="{{ $ar->id }}" {{ in_array($ar->id, (array)$nvReasonIds) ? 'selected' : '' }}>{{ $ar->name }}</option>
                                        @endforeach
                                    </select>
                                @else
                                    <input type="text" id="next_visit_reason" class="form-control input-sm"
                                           value="{{ $appointment->next_visit_reason ?? '' }}" placeholder="e.g. Follow-up, suture removal-">
                                @endif
                            </div>
                        </div>

                        <div class="row">
                            <div class="col-md-12">
                                <label style="font-size:11px;font-weight:700;color:#475569;">Notes</label>
                                <textarea id="next_visit_notes" class="form-control input-sm" rows="3" placeholder="Additional notes for the next visit-" style="resize:vertical;">{{ $appointment->next_visit_notes ?? '' }}</textarea>
                            </div>
                        </div>

                        @if($appointment->next_appointment_id ?? null)
                            <div style="margin-top:10px;padding:8px 12px;background:#ecfdf5;border:1px solid #a7f3d0;border-radius:6px;font-size:12px;">
                                <i class="fa fa-link text-success"></i>
                                Next appointment linked &middot;
                                <a href="{{ route('clinic.appointments.show', $appointment->next_appointment_id) }}" target="_blank" style="font-weight:700;">
                                    View appointment #{{ $appointment->next_appointment_id }}
                                </a>
                            </div>
                        @endif
                        </div>{{-- /nv_form_body --}}
                    </div>
                </div>

                @can('clinic.manage_appointments')
                @if(\Modules\ClinicCore\Http\Controllers\ClcAiController::available() && \Modules\ClinicCore\Entities\Services\ClinicCapabilities::businessHas((int)session('user.business_id'), 'ai_assist'))
                @can('clinic.use_ai_assist')
                <div class="ai-toolbar" style="margin:10px 0;display:flex;gap:6px;flex-wrap:wrap;">
                    <span class="text-muted" style="font-size:11px;align-self:center;"><i class="fa fa-robot"></i> @lang('clinicCore::lang.ai_assistant'):</span>
                    <button type="button" class="btn btn-xs btn-info clc-ai-btn" data-mode="draft_soap" data-patient="{{ $appointment->patient_id }}" data-appointment="{{ $appointment->id }}">@lang('clinicCore::lang.ai_draft_soap')</button>
                    <button type="button" class="btn btn-xs btn-info clc-ai-btn" data-mode="structure_soap" data-from="#soap_s" data-patient="{{ $appointment->patient_id }}" data-appointment="{{ $appointment->id }}">@lang('clinicCore::lang.ai_structure_soap')</button>
                    <button type="button" class="btn btn-xs btn-info clc-ai-btn" data-mode="differential" data-patient="{{ $appointment->patient_id }}" data-appointment="{{ $appointment->id }}">@lang('clinicCore::lang.ai_differential')</button>
                    <button type="button" class="btn btn-xs btn-info clc-ai-btn" data-mode="narrate_exam" data-patient="{{ $appointment->patient_id }}" data-appointment="{{ $appointment->id }}">@lang('clinicCore::lang.ai_narrate_exam')</button>
                    <button type="button" class="btn btn-xs btn-info clc-ai-btn" data-mode="summarize_history" data-patient="{{ $appointment->patient_id }}" data-appointment="{{ $appointment->id }}">@lang('clinicCore::lang.ai_summarize_history')</button>
                    <button type="button" class="btn btn-xs btn-info clc-ai-btn" data-mode="explain_to_patient" data-patient="{{ $appointment->patient_id }}" data-appointment="{{ $appointment->id }}">@lang('clinicCore::lang.ai_explain_patient')</button>
                </div>
                @endcan
                @endif
                <div class="draft-bar">
                    <div class="draft-indicator" id="draft_status">
                        <i class="fa fa-circle" style="font-size:6px;"></i> No unsaved changes
                    </div>
                    <button id="btn_save_consultation" class="btn btn-primary">
                        <i class="fa fa-save"></i> Save Consultation
                    </button>
                </div>
                @endcan
            </div>

            {{-- ── Notes Tab ── --}}
            <div id="tab-notes" class="tab-pane fade">
                @can('clinic.manage_appointments')
                <div style="margin-bottom:16px;">
                    <input type="text" id="note_title" class="form-control" placeholder="Note title (optional)" style="margin-bottom:6px;">
                    <textarea id="note_text" class="form-control" rows="2" placeholder="Add a note-"></textarea>
                    <div style="text-align:right;margin-top:6px;">
                        <button id="btn_add_note" class="btn btn-sm btn-primary">
                            <i class="fa fa-plus"></i> Add Note
                        </button>
                    </div>
                </div>
                <hr>
                @endcan

                @if(($appointment->appointmentNotes ?? collect())->isEmpty())
                    <p class="text-muted"><i class="fa fa-info-circle"></i> No notes yet.</p>
                @else
                <ul class="vc-timeline" id="notes_list">
                    @foreach(($appointment->appointmentNotes ?? collect()) as $note)
                    <li class="tl-item">
                        <div class="tl-dot"></div>
                        <div class="tl-card">
                            <div class="tl-head">
                                <strong>{{ $note->title ?: 'Note' }}</strong>
                                <span class="tl-meta">
                                    {{ optional($note->user)->first_name }} {{ optional($note->user)->last_name }}
                                    &mdash; {{ \Carbon\Carbon::parse($note->created_at)->format(session('business.date_format', 'd M Y') . ' ' . (session('business.time_format', 24) == 12 ? 'h:i A' : 'H:i')) }}
                                </span>
                            </div>
                            <div class="tl-body">{{ $note->notes }}</div>
                        </div>
                    </li>
                    @endforeach
                </ul>
                @endif
            </div>

            {{-- ── Treatments Tab ── --}}
            <div id="tab-vaccinations" class="tab-pane fade vc-section-host
                 {{ in_array($appointment->status, ['in_progress', 'completed'], true) ? '' : 'vc-section-locked' }}">

                {{-- Header --}}
                <div class="vax-tab-header">
                    <div class="vax-tab-header-title">
                        <i class="fas fa-syringe" style="color:#22c55e;"></i>
                        Vaccination Records
                        @php $vaxCount = ($appointment->vaccinations ?? collect())->count(); @endphp
                        @if($vaxCount)
                        <span class="vax-count-badge">{{ $vaxCount }} recorded</span>
                        @endif
                    </div>
                    @can('clinic.manage_vaccinations')
                    <button class="btn btn-sm btn-success" data-toggle="modal" data-target="#vaccinationModal"
                            style="border-radius:8px;font-weight:700;padding:5px 14px;">
                        <i class="fas fa-plus"></i> Record Treatment
                    </button>
                    @endcan
                </div>

                {{-- Empty state --}}
                <div id="no_vax_msg" @if($vaxCount) style="display:none;" @endif>
                    <div class="vax-empty-state">
                        <div class="vax-es-icon">💉</div>
                        <p>No vaccinations recorded for this appointment yet.</p>
                        @can('clinic.manage_vaccinations')
                        <button class="btn btn-sm btn-success" data-toggle="modal" data-target="#vaccinationModal"
                                style="border-radius:8px;font-weight:700;">
                            <i class="fas fa-plus"></i> Record First Treatment
                        </button>
                        @endcan
                    </div>
                </div>

                {{-- Card grid --}}
                <div class="vax-card-grid" id="vax_card_grid" @if(!$vaxCount) style="display:none;" @endif>
                @foreach(($appointment->vaccinations ?? collect()) as $vax)
                @php
                    $whereClass = match($vax->taken_status ?? '') {
                        'taken_in'  => 'vax-where-clinic',
                        'taken_out' => 'vax-where-external',
                        default     => 'vax-where-scheduled',
                    };
                    $whereIcon  = match($vax->taken_status ?? '') {
                        'taken_in'  => '🏥',
                        'taken_out' => '🏠',
                        default     => '⏳',
                    };
                    $whereLabel = match($vax->taken_status ?? '') {
                        'taken_in'  => 'At Clinic',
                        'taken_out' => 'External',
                        default     => 'Scheduled',
                    };
                    $dueClass = '';
                    if ($vax->next_due_date) {
                        $daysUntil = now()->diffInDays($vax->next_due_date, false);
                        $dueClass  = $daysUntil < 0 ? 'vax-due-overdue' : ($daysUntil <= 30 ? 'vax-due-soon' : 'vax-due-ok');
                    }
                @endphp
                <div class="vax-card" id="vax_row_{{ $vax->id }}">
                    <div class="vax-card-top">
                        <div class="vax-card-name">{{ optional($vax->vaccineType)->name ?? '-' }}</div>
                        <span class="vax-where-badge {{ $whereClass }}">{{ $whereIcon }} {{ $whereLabel }}</span>
                    </div>

                    <div class="vax-card-dates">
                        <div class="vax-date-block">
                            <div class="vax-date-label">💉 Administered</div>
                            <div class="vax-date-value">{{ $vax->administered_date ? \Carbon\Carbon::parse($vax->administered_date)->format(session('business.date_format', 'd M Y')) : '-' }}</div>
                        </div>
                        <div class="vax-date-arrow"><i class="fa fa-arrow-right"></i></div>
                        <div class="vax-date-block">
                            <div class="vax-date-label">📅 Next Due</div>
                            <div class="vax-date-value {{ $dueClass }}">
                                @if($vax->next_due_date)
                                    {{ \Carbon\Carbon::parse($vax->next_due_date)->format(session('business.date_format', 'd M Y')) }}
                                    @if($daysUntil < 0)
                                        <small style="display:block;font-size:9.5px;font-weight:600;">{{ abs((int)$daysUntil) }}d overdue</small>
                                    @elseif($daysUntil <= 30)
                                        <small style="display:block;font-size:9.5px;font-weight:600;">in {{ (int)$daysUntil }}d</small>
                                    @endif
                                @else
                                    <span style="color:#94a3b8;">-</span>
                                @endif
                            </div>
                        </div>
                    </div>

                    @php
                        $chips = [];
                        if ($vax->dose)               $chips[] = ['fa-tint',       'Dose: '.$vax->dose];
                        if ($vax->batch_no)            $chips[] = ['fa-barcode',    'Batch: '.$vax->batch_no];
                        if ($vax->weight_at_time)      $chips[] = ['fa-balance-scale', $vax->weight_at_time.' kg'];
                        if ($vax->temperature_at_time) $chips[] = ['fa-thermometer-half', $vax->temperature_at_time.'-C'];
                    @endphp
                    @if(count($chips))
                    <div class="vax-card-details">
                        @foreach($chips as $chip)
                        <span class="vax-detail-chip"><i class="fa {{ $chip[0] }}"></i> {{ $chip[1] }}</span>
                        @endforeach
                    </div>
                    @endif

                    @if($vax->notes)
                    <div class="vax-card-notes">{{ $vax->notes }}</div>
                    @endif

                    <div class="vax-card-footer">
                        @can('clinic.manage_vaccinations')
                        <button class="btn btn-xs btn-warning btn-edit-vax" data-id="{{ $vax->id }}" title="Edit"
                                style="border-radius:6px;font-size:11px;padding:3px 9px;">
                            <i class="fa fa-pencil-alt"></i> Edit
                        </button>
                        <button class="btn btn-xs btn-danger btn-delete-vax" data-id="{{ $vax->id }}" title="Delete"
                                style="border-radius:6px;font-size:11px;padding:3px 9px;">
                            <i class="fa fa-trash"></i>
                        </button>
                        @endcan
                    </div>
                </div>
                @endforeach
                </div>

            </div>

            {{-- ── Documents Tab ── --}}
            <div id="tab-documents" class="tab-pane fade">
                @can('clinic.manage_appointments')
                <div style="background:#f8fafc;border:1px solid #e8ecf0;border-radius:8px;padding:14px;margin-bottom:16px;">
                    <div style="font-size:12px;font-weight:700;margin-bottom:10px;color:#1e293b;"><i class="fa fa-upload fa-fw" style="color:#16a085;"></i> Upload Document</div>
                    <div class="row" style="margin-bottom:8px;">
                        <div class="col-md-4">
                            <input type="text" id="doc_name" class="form-control" placeholder="Document name *" style="font-size:12px;">
                        </div>
                        <div class="col-md-3">
                            <select id="doc_type" class="form-control" style="font-size:12px;">
                                <option value="lab_report">Lab Report</option>
                                <option value="xray">X-Ray</option>
                                <option value="prescription">Prescription</option>
                                <option value="consent_form">Consent Form</option>
                                <option value="discharge_summary">Discharge Summary</option>
                                <option value="other">Other</option>
                            </select>
                        </div>
                        <div class="col-md-5">
                            <input type="text" id="doc_desc" class="form-control" placeholder="Description (optional)" style="font-size:12px;">
                        </div>
                    </div>
                    <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
                        <input type="file" id="doc_file" style="flex:1;font-size:12px;" accept=".pdf,.jpg,.jpeg,.png,.doc,.docx,.xlsx,.xls">
                        <button id="btn_upload_doc" class="btn btn-sm btn-success" style="white-space:nowrap;">
                            <i class="fa fa-upload"></i> Upload
                        </button>
                    </div>
                    <div id="doc_upload_progress" style="display:none;margin-top:8px;">
                        <div class="progress" style="margin-bottom:0;height:10px;">
                            <div class="progress-bar progress-bar-striped active" id="doc_progress_bar" style="width:0%;transition:width .3s;">0%</div>
                        </div>
                    </div>
                </div>
                @endcan

                <div id="doc_list">
                @if(($appointment->documents ?? collect())->isEmpty())
                    <p class="text-muted" id="no_docs_msg"><i class="fa fa-info-circle"></i> No documents uploaded yet.</p>
                @endif
                <div class="doc-grid" id="doc_grid">
                @foreach(($appointment->documents ?? collect()) as $doc)
                @php
                    $mime    = $doc->mime_type ?? '';
                    $isImg   = str_starts_with($mime, 'image/');
                    $isPdf   = $mime === 'application/pdf';
                    $isWord  = str_contains($mime, 'word') || str_contains($mime, 'officedocument.word');
                    $isXls   = str_contains($mime, 'excel') || str_contains($mime, 'spreadsheet');
                    $pBg     = $isPdf ? '#e74c3c' : ($isWord ? '#2980b9' : ($isXls ? '#27ae60' : '#7f8c8d'));
                    $pIcon   = $isPdf ? 'fa-file-pdf' : ($isWord ? 'fa-file-word' : ($isXls ? 'fa-file-excel' : 'fa-file'));
                @endphp
                <div class="doc-card" id="doc_item_{{ $doc->id }}">
                    <div class="doc-card-preview" style="{{ !$isImg ? 'background:'.$pBg.';' : '' }}">
                        @if($isImg)
                            <img src="{{ $doc->url }}" alt="{{ $doc->document_name }}">
                        @else
                            <i class="fa {{ $pIcon }}" style="font-size:48px;color:rgba(255,255,255,.9);"></i>
                        @endif
                        <a href="{{ $doc->url }}" target="_blank" class="doc-preview-overlay" style="text-decoration:none;">
                            <i class="fa fa-eye doc-eye"></i>
                        </a>
                    </div>
                    <div class="doc-card-body">
                        <div class="doc-card-name" title="{{ $doc->document_name }}">{{ $doc->document_name }}</div>
                        <div class="doc-card-badges">
                            <span class="label label-default" style="font-size:10px;">{{ $doc->type_label }}</span>
                        </div>
                        <div class="doc-card-meta">
                            {{ $doc->file_size_kb }}KB &bull; {{ \Carbon\Carbon::parse($doc->created_at)->format(session('business.date_format', 'd M Y')) }}
                            @if(optional($doc->uploader)->first_name)
                                &bull; {{ $doc->uploader->first_name }}
                            @endif
                        </div>
                        @if($doc->description)
                        <div class="doc-card-desc">{{ $doc->description }}</div>
                        @endif
                        <div class="doc-card-actions">
                            <a href="{{ $doc->url }}" target="_blank" class="btn btn-xs btn-info"><i class="fa fa-eye"></i> View</a>
                            <a href="{{ $doc->url }}" download="{{ $doc->file_name }}" class="btn btn-xs btn-default"><i class="fa fa-download"></i></a>
                            @can('clinic.manage_appointments')
                            <button class="btn btn-xs btn-danger btn-delete-doc" data-id="{{ $doc->id }}" title="Delete"><i class="fa fa-trash"></i></button>
                            @endcan
                        </div>
                    </div>
                </div>
                @endforeach
                </div>
                </div>
            </div>

            {{-- ── NX600 Lab Tab ── --}}
            @if($appointment->pet && false /* NX600Lab analyzer is vet-only; disabled in ClinicCore */)
            <div id="tab-lab" class="tab-pane fade vc-section-host
                 {{ in_array($appointment->status, ['in_progress', 'completed'], true) ? '' : 'vc-section-locked' }}">
                @include('nx600Lab::bridge.pet_lab_panel', ['pet' => $appointment->pet])
            </div>
            @endif

            {{-- ── Consents Tab ── --}}
            @php
                // Active consent templates for this business, de-duplicated by code
                // (a clinic's own clone shadows the matching system template).
                $drmConsentTemplates = \Modules\ClinicCore\Entities\ClcConsentTemplate::forBusiness(session('user.business_id'))
                    ->where('status', 'active')
                    ->orderBy('category')->orderBy('sort_order')->orderBy('name_en')->get()
                    ->groupBy('code')
                    ->map(function ($g) { return $g->sortByDesc('business_id')->first(); })
                    ->values();
                $drmConsentCats = \Modules\ClinicCore\Entities\ClcConsentTemplate::categories();
            @endphp
            <div id="tab-consents" class="tab-pane fade">
                <div style="display:flex;align-items:center;margin-bottom:12px;">
                    <div style="font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:#64748b;">
                        <i class="fas fa-file-signature fa-fw" style="color:#2563eb;"></i> Consent Forms
                    </div>
                    @can('clinic.manage_appointments')
                    <button class="btn btn-sm btn-primary" id="btn_issue_consent" style="margin-left:auto;"
                            data-toggle="modal" data-target="#issueConsentModal">
                        <i class="fa fa-plus"></i> Issue / Print Consent
                    </button>
                    @endcan
                </div>
                <div id="clc_consents_list">
                    @include('clinicCore::consents._appointment_list', ['appointment' => $appointment, 'consents' => $drmConsentList])
                </div>
            </div>

            {{-- ── Quotations Tab ── --}}
            @if(\Schema::hasTable('clc_quotation_meta'))
            <div id="tab-quotations" class="tab-pane fade">
                @can('clinic.manage_appointments')
                <div style="margin-bottom:12px;display:flex;gap:6px;align-items:center;flex-wrap:wrap;">
                    <strong>Link an existing ERP quotation:</strong>
                    <input type="text" id="vq_search" placeholder="Search by invoice # / ref #-"
                        style="flex:1;min-width:200px;padding:6px 10px;border:1px solid #cbd5e1;border-radius:5px;">
                    <button id="vq_search_btn" class="btn btn-default btn-sm">
                        <i class="fa fa-search"></i> Find quotations
                    </button>
                </div>
                <div id="vq_search_results" style="margin-bottom:14px;"></div>
                @endcan
                <div id="vq_linked_list">
                    <div style="text-align:center;color:#94a3b8;padding:16px;">
                        <i class="fa fa-spinner fa-spin"></i> Loading linked quotations-
                    </div>
                </div>
            </div>
            @endif

        </div>{{-- end tab-content --}}
    </div>{{-- end main col --}}

</div>{{-- end row --}}
</section>

{{-- ── Issue Consent Modal ───────────────────────────────────── --}}
@can('clinic.manage_appointments')
<div class="modal fade" id="issueConsentModal" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" style="font-size:15px;">&times;</button>
                <h4 class="modal-title"><i class="fa fa-file-signature"></i> Issue Consent Form</h4>
            </div>
            <div class="modal-body">
                <div class="form-group">
                    <label>Consent form <span class="text-danger">*</span></label>
                    <select id="ic_template" class="form-control">
                        <option value="">- Select a consent form -</option>
                        @foreach($drmConsentTemplates->groupBy('category') as $cat => $tpls)
                        <optgroup label="{{ $drmConsentCats[$cat] ?? ucfirst($cat) }}">
                            @foreach($tpls as $tpl)
                            <option value="{{ $tpl->id }}" data-witness="{{ $tpl->requires_witness ? 1 : 0 }}">{{ $tpl->name_en }}</option>
                            @endforeach
                        </optgroup>
                        @endforeach
                    </select>
                </div>
                <div class="form-group">
                    <label>Print language</label>
                    <div>
                        <label class="radio-inline"><input type="radio" name="ic_lang" value="both" checked> English + العربية</label>
                        <label class="radio-inline"><input type="radio" name="ic_lang" value="en"> English</label>
                        <label class="radio-inline"><input type="radio" name="ic_lang" value="ar"> العربية</label>
                    </div>
                </div>
                <div class="row">
                    <div class="col-md-7">
                        <div class="form-group">
                            <label>Signatory name</label>
                            <input type="text" id="ic_signatory" class="form-control" value="{{ optional($appointment->owner)->name }}">
                        </div>
                    </div>
                    <div class="col-md-5">
                        <div class="form-group">
                            <label>Relationship</label>
                            <input type="text" id="ic_relationship" class="form-control" value="Owner">
                        </div>
                    </div>
                </div>
                <div class="form-group" id="ic_witness_group" style="display:none;">
                    <label>Witness name <small class="text-muted">(this form needs a witness)</small></label>
                    <input type="text" id="ic_witness" class="form-control">
                </div>
                <div class="form-group">
                    <label>Note <small class="text-muted">(optional - printed on the form)</small></label>
                    <textarea id="ic_notes" class="form-control" rows="2"></textarea>
                </div>
                <p class="text-muted" style="font-size:11px;margin-bottom:0;">
                    <i class="fa fa-info-circle"></i> Issuing creates a dated record and opens the printable form in a new tab.
                </p>
            </div>
            <div class="modal-footer">
                <button class="btn btn-default" data-dismiss="modal">Cancel</button>
                <button class="btn btn-primary" id="btn_save_consent"><i class="fa fa-print"></i> Issue &amp; Print</button>
            </div>
        </div>
    </div>
</div>
{{-- hidden file input for uploading a signed consent copy --}}
<input type="file" id="signed_consent_file" accept=".pdf,.jpg,.jpeg,.png" style="display:none;">
@endcan

<script>
// Consents tab - issue / refresh / delete / upload signed copy. Deferred until jQuery is ready.
window.addEventListener('load', function () {
    var ISSUE_URL = '{{ route('clinic.consents.issue', $appointment->id) }}';
    var LIST_URL  = '{{ route('clinic.consents.appointment-list', $appointment->id) }}';
    var CONSENT_BASE = '{{ url('clinic/consents') }}';
    var TOKEN     = '{{ csrf_token() }}';

    $('#ic_template').on('change', function () {
        $('#ic_witness_group').toggle($(this).find('option:selected').data('witness') == 1);
    });

    function reloadConsents() {
        $.get(LIST_URL, function (html) {
            $('#clc_consents_list').html(html);
            $('a[href="#tab-consents"] .badge').text($('#clc_consents_list tbody tr').length);
        });
    }

    $('#btn_save_consent').on('click', function () {
        var tpl = $('#ic_template').val();
        if (!tpl) { toastr.warning('Please select a consent form.'); return; }
        var btn = $(this).prop('disabled', true);
        $.ajax({
            url: ISSUE_URL, method: 'POST',
            data: {
                _token: TOKEN,
                consent_template_id: tpl,
                language: $('input[name=ic_lang]:checked').val(),
                signatory_name: $('#ic_signatory').val(),
                signatory_relationship: $('#ic_relationship').val(),
                witness_name: $('#ic_witness').val(),
                notes: $('#ic_notes').val()
            },
            success: function (res) {
                if (res.success) {
                    $('#issueConsentModal').modal('hide');
                    toastr.success(res.msg);
                    window.open(res.print_url, '_blank');
                    reloadConsents();
                }
            },
            error: function (xhr) {
                toastr.error((xhr.responseJSON && xhr.responseJSON.message) || 'Could not issue consent.');
            },
            complete: function () { btn.prop('disabled', false); }
        });
    });

    $(document).on('click', '.btn-del-consent', function () {
        if (!confirm('Delete this issued consent?')) return;
        $.ajax({
            url: CONSENT_BASE + '/' + $(this).data('id'),
            method: 'DELETE', data: { _token: TOKEN },
            success: function (res) {
                if (res.success) { toastr.success(res.msg); reloadConsents(); }
                else if (res.msg) { toastr.warning(res.msg); }
            },
            error: function (xhr) {
                var m = xhr.responseJSON && xhr.responseJSON.msg;
                toastr.error(m || 'Could not delete consent.');
            }
        });
    });

    $(document).on('click', '.btn-archive-consent', function () {
        if (!confirm('Archive this signed consent? It will be kept for audit but hidden from the active list.')) return;
        $.ajax({
            url: CONSENT_BASE + '/' + $(this).data('id') + '/archive',
            method: 'POST', data: { _token: TOKEN },
            success: function (res) {
                if (res.success) { toastr.success(res.msg); reloadConsents(); }
            },
            error: function (xhr) {
                var m = xhr.responseJSON && xhr.responseJSON.msg;
                toastr.error(m || 'Could not archive consent.');
            }
        });
    });

    // ── Upload / replace / remove the scanned signed copy ──
    var signedTargetId = null;

    $(document).on('click', '.btn-upload-signed', function () {
        signedTargetId = $(this).data('id');
        $('#signed_consent_file').val('').trigger('click');
    });

    $('#signed_consent_file').on('change', function () {
        if (!this.files.length || !signedTargetId) return;
        var fd = new FormData();
        fd.append('_token', TOKEN);
        fd.append('file', this.files[0]);
        $.ajax({
            url: CONSENT_BASE + '/' + signedTargetId + '/signed',
            method: 'POST', data: fd, processData: false, contentType: false,
            success: function (res) { if (res.success) { toastr.success(res.msg); reloadConsents(); } },
            error: function (xhr) {
                var m = xhr.responseJSON && xhr.responseJSON.errors
                    ? Object.values(xhr.responseJSON.errors)[0][0]
                    : (xhr.responseJSON && xhr.responseJSON.msg) || 'Upload failed.';
                toastr.error(m);
            }
        });
    });

    $(document).on('click', '.btn-remove-signed', function () {
        if (!confirm('Remove the uploaded signed copy?')) return;
        $.ajax({
            url: CONSENT_BASE + '/' + $(this).data('id') + '/signed',
            method: 'DELETE', data: { _token: TOKEN },
            success: function (res) { if (res.success) { toastr.success(res.msg); reloadConsents(); } }
        });
    });
});
</script>

{{-- ── Controlled-Drug dispense modal (opens after consultation save
       when the plan contains controlled meds that haven't been logged yet) --}}
@can('clinic.manage_appointments')
<div class="modal fade" id="controlledDispenseModal" tabindex="-1" data-backdrop="static">
    <div class="modal-dialog modal-lg">
        <div class="modal-content">
            <div class="modal-header" style="background:#fef3c7;border-bottom:2px solid #fde68a;">
                <button type="button" class="close" data-dismiss="modal">&times;</button>
                <h4 class="modal-title" style="color:#9a3412;">
                    <i class="fa fa-shield-alt"></i> Log Controlled-Drug Dispensing
                </h4>
            </div>
            <div class="modal-body" style="font-size:12.5px;">
                <p class="text-muted" style="margin-bottom:10px;">
                    The treatment plan contains controlled substances that must be logged in the
                    register before they leave the clinic. Fill in batch / qty / witness for each item
                    and submit - every row will be appended to the register with this appointment's reference.
                </p>
                <div id="cd_rows"></div>
                <div class="form-group" style="margin-top:8px;">
                    <label>Witness (clinic staff present at dispensing) <span class="text-danger">*</span></label>
                    <select id="cd_witness" class="form-control input-sm">
                        <option value="">- Select a witness -</option>
                        @foreach(\App\User::where('business_id', session('user.business_id'))->where('user_type','user')->where('status','active')->orderBy('first_name')->get(['id','first_name','last_name']) as $u)
                            <option value="{{ $u->id }}">{{ trim($u->first_name.' '.$u->last_name) }}</option>
                        @endforeach
                    </select>
                </div>
            </div>
            <div class="modal-footer">
                <button class="btn btn-default" data-dismiss="modal">Skip for now</button>
                <button class="btn btn-warning" id="cd_save">
                    <i class="fa fa-shield-alt"></i> Log all to register
                </button>
            </div>
        </div>
    </div>
</div>

<script>
window.addEventListener('load', function () {
    var doctorId = {{ (int) ($appointment->doctor_id ?? 0) }};
    var patientId    = {{ (int) ($appointment->patient_id    ?? 0) }};
    var contactId= {{ (int) ($appointment->contact_id?? 0) }};
    var apptId   = {{ (int) $appointment->id }};
    var storeUrl = '{{ route('clinic.controlled-register.store') }}';
    var token    = '{{ csrf_token() }}';

    // Exposed globally so the consultation-save callback can trigger it.
    window.drmOpenControlledDispense = function (pending) {
        var $rows = $('#cd_rows').empty();
        pending.forEach(function (p, i) {
            $rows.append(
                '<div class="cd-row" data-med="' + p.medicine_id + '" style="border:1px solid #fed7aa;background:#fffbeb;border-radius:7px;padding:10px 12px;margin-bottom:8px;">' +
                  '<div style="display:flex;gap:8px;align-items:baseline;flex-wrap:wrap;margin-bottom:6px;">' +
                    '<strong>' + $('<i>').text(p.medicine_name).html() + '</strong>' +
                    '<span class="label label-' + p.schedule_color + '" style="font-size:9px;">' + $('<i>').text(p.schedule_label).html() + '</span>' +
                    (p.dosage ? '<span class="text-muted" style="font-size:11px;">' + $('<i>').text(p.dosage).html() + '</span>' : '') +
                    (p.freq   ? '<span class="text-muted" style="font-size:11px;">' + $('<i>').text(p.freq).html()   + '</span>' : '') +
                    (p.days   ? '<span class="text-muted" style="font-size:11px;">' + $('<i>').text(p.days).html()   + ' days</span>' : '') +
                  '</div>' +
                  '<div class="row">' +
                    '<div class="col-md-3"><label style="font-size:10.5px;">Quantity *</label>' +
                       '<input class="form-control input-sm cd-qty" type="number" min="0" step="0.001"></div>' +
                    '<div class="col-md-2"><label style="font-size:10.5px;">Unit</label>' +
                       '<input class="form-control input-sm cd-unit" value="' + $('<i>').text(p.unit||'').html() + '"></div>' +
                    '<div class="col-md-3"><label style="font-size:10.5px;">Batch *</label>' +
                       '<input class="form-control input-sm cd-batch"></div>' +
                    '<div class="col-md-4"><label style="font-size:10.5px;">Notes</label>' +
                       '<input class="form-control input-sm cd-notes" placeholder="optional"></div>' +
                  '</div>' +
                '</div>'
            );
        });
        $('#controlledDispenseModal').modal('show');
    };

    $('#cd_save').on('click', function () {
        var witness = $('#cd_witness').val();
        if (!witness) { toastr.warning('Pick a witness first.'); return; }
        var rows = $('#cd_rows .cd-row');
        var jobs = [];
        for (var i = 0; i < rows.length; i++) {
            var $r = $(rows[i]);
            var qty = parseFloat($r.find('.cd-qty').val());
            var batch = ($r.find('.cd-batch').val() || '').trim();
            if (!(qty > 0) || !batch) {
                toastr.warning('Each row needs a positive quantity and a batch number.');
                return;
            }
            jobs.push({
                medicine_id: $r.data('med'),
                qty: qty,
                unit: $r.find('.cd-unit').val(),
                batch_no: batch,
                notes: $r.find('.cd-notes').val()
            });
        }

        var $btn = $(this).prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i>');
        var done = 0, failed = 0, errMsg = '';
        function next() {
            if (done + failed >= jobs.length) {
                $btn.prop('disabled', false).html('<i class="fa fa-shield-alt"></i> Log all to register');
                if (failed) {
                    toastr.error((errMsg || 'Some rows failed.') + ' ' + done + ' of ' + jobs.length + ' logged.');
                } else {
                    toastr.success('All ' + done + ' controlled dispenses logged to the register.');
                    $('#controlledDispenseModal').modal('hide');
                }
                return;
            }
            var j = jobs[done + failed];
            $.ajax({
                url: storeUrl, method: 'POST',
                data: $.extend({}, j, {
                    _token: token,
                    reason_code: 'dispense',
                    appointment_id: apptId,
                    patient_id:     patientId  || null,
                    contact_id: contactId || null,
                    doctor_id:  doctorId || null,
                    witness_user_id: witness
                }),
                success: function (res) { if (res.success) done++; else { failed++; errMsg = res.msg || ''; } next(); },
                error: function (xhr) {
                    failed++;
                    errMsg = (xhr.responseJSON && (xhr.responseJSON.msg || xhr.responseJSON.message)) || '';
                    next();
                }
            });
        }
        next();
    });
});
</script>
@endcan

{{-- ── AI Assistant Modal ───────────────────────────────────── --}}
@if($drmShowAi)
<div class="modal fade" id="aiAssistantModal" tabindex="-1">
    <div class="modal-dialog modal-lg">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" style="font-size:15px;">&times;</button>
                <h4 class="modal-title">
                    <i class="fas fa-robot" style="color:#4f46e5;"></i>
                    <span id="ai_modal_stage_title">AI Assistant</span>
                </h4>
            </div>
            <div class="modal-body" id="ai_modal_body" style="max-height:74vh;overflow:auto;">
                @include('vetAI::partials.appointment_card', ['appointment' => $appointment])
            </div>
        </div>
    </div>
</div>
<script>
// Each SOAP card's robot icon opens this modal showing only its related
// AI checkpoint. drmOpenAi is exposed globally for the inline onclick handlers.
window.addEventListener('load', function () {
    var STAGE_TITLES = {
        exam_review: 'Examination Review',
        diagnosis:   'Expected Diagnoses',
        treatment:   'Treatment Plan',
        interaction: 'Interaction & Dose Check'
    };
    window.drmOpenAi = function (stage) {
        var $body = $('#ai_modal_body');
        var $stages = $body.find('.vai-ap-stage');
        var $target = $body.find('.vai-ap-stage[data-stage="' + stage + '"]');
        $stages.hide();
        ($target.length ? $target : $stages).show();
        $('#ai_modal_stage_title').text('AI - ' + (STAGE_TITLES[stage] || 'Assistant'));
        $('#aiAssistantModal').modal('show');
    };
});
</script>
@endif

{{-- ── SOAP Voice Dictation (Whisper / Web Speech) ─────────── --}}
@if($drmShowMic)

{{-- Shared recording modal - primary UI for both per-section and whole-consultation dictation. --}}
<div class="modal fade" id="drmMicModal" tabindex="-1" data-backdrop="static" data-keyboard="false">
    <div class="modal-dialog modal-sm">
        <div class="modal-content">
            <div class="modal-header" style="background:#dc2626;color:#fff;padding:11px 15px;">
                <h4 class="modal-title" style="font-size:14px;">
                    <i class="fa fa-microphone" id="drmMicHdrIcon"></i>
                    <span id="drmMicLabel">Recording...</span>
                </h4>
            </div>
            <div class="modal-body" style="text-align:center;padding:18px 16px 6px;">
                <div id="drmMicStateIcon" style="font-size:32px;color:#dc2626;">
                    <i class="fa fa-microphone-alt"></i>
                </div>
                {{-- Live input-level meter; bars react to the mic's audio
                     stream in real time so the doctor can confirm the mic is
                     actually picking up sound before they start dictating. --}}
                <div id="drmMicMeter" style="display:none;margin-top:10px;">
                    <div class="vc-mic-bars">
                        <span class="vc-mic-bar"></span><span class="vc-mic-bar"></span>
                        <span class="vc-mic-bar"></span><span class="vc-mic-bar"></span>
                        <span class="vc-mic-bar"></span><span class="vc-mic-bar"></span>
                        <span class="vc-mic-bar"></span><span class="vc-mic-bar"></span>
                        <span class="vc-mic-bar"></span><span class="vc-mic-bar"></span>
                        <span class="vc-mic-bar"></span><span class="vc-mic-bar"></span>
                    </div>
                    <div id="drmMicMeterStatus" class="vc-mic-status">
                        <i class="fa fa-spinner fa-spin"></i> Calibrating microphone-
                    </div>
                </div>
                <p style="color:#475569;font-size:12.5px;margin:8px 0 0;" id="drmMicHint">
                    Speak now - click <strong>Stop &amp; insert</strong> when you're done.
                </p>
                <div id="drmMicTranscript" style="display:none;max-height:140px;overflow:auto;background:#f1f5f9;padding:8px 10px;border-radius:6px;font-size:12px;text-align:left;margin-top:10px;color:#1e293b;line-height:1.45;"></div>
            </div>
            <div class="modal-footer" style="padding:10px 14px;">
                <button type="button" class="btn btn-default btn-sm" id="drmMicCancelBtn">
                    <i class="fa fa-times"></i> Cancel
                </button>
                <button type="button" class="btn btn-danger" id="drmMicStopBtn">
                    <i class="fa fa-stop"></i> Stop &amp; insert
                </button>
            </div>
        </div>
    </div>
</div>

<style>
.modal-backdrop.vc-mic-bd { opacity:.4; }
#drmMicStateIcon { animation:drmMicPulse 1.4s infinite; transform-origin:center; }
@keyframes drmMicPulse {
    0%,100% { transform:scale(1);   filter:drop-shadow(0 0 0 rgba(220,38,38,.6)); }
    50%     { transform:scale(1.1); filter:drop-shadow(0 0 10px rgba(220,38,38,.6)); }
}
/* Live mic-level meter (frequency bins drive each bar's height) */
.vc-mic-bars   { display:flex; gap:3px; justify-content:center; align-items:flex-end; height:36px; margin:0 auto; }
.vc-mic-bar    { width:6px; background:#22c55e; border-radius:2px; height:6px; transition:height 80ms linear, background 150ms linear; opacity:.45; }
.vc-mic-bar.on { opacity:1; }
.vc-mic-bar.hot{ background:#f59e0b; }
.vc-mic-bar.clip{ background:#dc2626; }
.vc-mic-status { font-size:11.5px; color:#16a34a; margin-top:6px; font-weight:600; }
.vc-mic-status.warn  { color:#dc2626; }
.vc-mic-status.idle  { color:#94a3b8; font-weight:500; }

/* A/B compare CSS removed alongside the showMicCompare flow. */
</style>

<script>
window.addEventListener('load', function () {
    // vai.* routes belong to the optional VetAI module — absent on the
    // human edition, so resolve them conditionally and bail out below.
    var TRANSCRIBE_URL = @json(\Illuminate\Support\Facades\Route::has('vai.transcribe') ? route('vai.transcribe') : null);
    var STRUCTURE_URL  = @json(\Illuminate\Support\Facades\Route::has('vai.soap-structure') ? route('vai.soap-structure') : null);
    var CLEAN_URL      = '{{ route('clinic.ai.clean-transcript') }}';
    if (!TRANSCRIBE_URL) { return; } // no VetAI transcription backend installed
    var TOKEN          = '{{ csrf_token() }}';
    var PROVIDER       = @json($drmMicProvider);          // 'webspeech' | 'whisper_*'
    var CONSENT_WARN   = @json($drmMicConsentWarn);
    var AUTO_STRUCTURE = @json($drmMicAutoStructure);
    // Doctor-configurable silence window: how long we keep listening through
    // dead air before auto-closing. Set in VetAI Settings; min 5s, default 30s.
    var SILENCE_LIMIT_MS = (parseInt(@json($drmMicSilenceSec), 10) || 30) * 1000;
    var consentGiven   = false;

    // Web Speech runs in the browser, so its transcript skips the server-side
    // medical cleanup the Whisper path gets. Route the recognised text through
    // the clean endpoint first; the server no-ops it when cleanup is off or no
    // text AI is configured, so side-talk is stripped for any provider
    // (e.g. a text-only provider like Bynara paired with browser Web Speech).
    function cleanThen(text, cb) {
        if (!text) { cb('', false); return; }
        $.ajax({ url: CLEAN_URL, method: 'POST', data: { _token: TOKEN, text: text }, timeout: 30000 })
            .done(function (res) {
                if (res && res.success && typeof res.text === 'string') cb(res.text, !!res.cleaned);
                else cb(text, false);
            })
            .fail(function () { cb(text, false); });
    }

    function confirmConsent() {
        if (!CONSENT_WARN || consentGiven || PROVIDER === 'webspeech') return true;
        var ok = confirm("Voice transcription is enabled. Audio will be sent to the configured provider (" + PROVIDER + ") and discarded after transcription. Proceed?");
        if (ok) consentGiven = true;
        return ok;
    }

    function appendToTextarea(id, text) {
        var $t = $('#' + id);
        if (!$t.length || !text) return;
        var cur = $t.val();
        $t.val((cur ? cur.replace(/\s+$/, '') + ' ' : '') + text).trigger('input').trigger('change');
    }

    // Build a Whisper bias-prompt for the target textarea: the visible chip
    // labels for that section, prefixed with a short English sentence so
    // language detection locks onto English even when t_language_hint is
    // 'auto'. Backend appends its general dermatology glossary after this.
    function buildSectionPrompt(targetId) {
        var $zone = null;
        if      (targetId === 'soap_s')     $zone = $('#complaint_chips');
        else if (targetId === 'soap_labs')  $zone = $('#lab_chips');
        else if (targetId === 'soap_exams') $zone = $('#exam_chips');
        var labels = [];
        if ($zone && $zone.length) {
            $zone.find('.complaint-chip').each(function () {
                var v = $(this).data('chip') || $(this).data('inv');
                if (v) labels.push(String(v));
            });
        }
        // Cap so the prompt stays under Whisper's ~224-token budget after
        // the backend default vocabulary is appended.
        if (labels.length > 30) labels = labels.slice(0, 30);
        var lead = 'Clinic consultation in English.';
        return labels.length
            ? lead + ' Section vocabulary: ' + labels.join(', ') + '.'
            : lead;
    }

    // ── Per-section dictation (drmMicDictate) ───────────────────────────
    var recState = {}; // keyed by button instance

    window.drmMicDictate = function (btn, targetId, section) {
        if (!confirmConsent()) return;
        var key = targetId;
        if (recState[key] && recState[key].recording) {
            recState[key].stop();
            return;
        }
        // Routing: respect whichever transcription provider is configured
        // under VetAI Settings. A/B compare was removed after Groq Whisper
        // proved consistently better than browser Web Speech in testing.
        if (PROVIDER === 'webspeech') return startWebSpeech(btn, targetId);
        startWhisperRecord(btn, targetId, section);
    };

    function setRecording(btn, on) {
        $(btn).toggleClass('recording', on)
              .attr('title', on ? 'Click to stop recording' : 'Dictate this section')
              .html(on ? '<i class="fa fa-stop"></i>' : '<i class="fa fa-microphone"></i>');
    }
    function setStarting(btn) {
        $(btn).addClass('recording').html('<i class="fa fa-spinner fa-spin"></i>');
    }
    // Persistent "recording" toast - pinned until cleared.
    // ── Shared modal that drives every dictation flow ───────────────
    // Callers register onStop / onCancel handlers; the modal's Stop button
    // commits whatever has been captured, Cancel discards it.
    var micActive = null;

    // Live input-level meter. Drives the 12 bars in the modal from a
    // MediaStream via the Web Audio API. If no stream is supplied (Web
    // Speech path - the browser owns the mic), opens its own audio-only
    // stream just for monitoring. Returns a cleanup() function.
    function startMicMeter(existingStream) {
        var $meter = $('#drmMicMeter').show();
        var $status = $('#drmMicMeterStatus');
        var $bars   = $meter.find('.vc-mic-bar');
        var ctx, source, analyser, dataArray;
        var stream = existingStream || null, ownsStream = false;
        var raf = null, stopped = false;
        var lastLoudAt = 0, startedAt = Date.now();

        function setupAudio(s) {
            try {
                var AC = window.AudioContext || window.webkitAudioContext;
                if (!AC) throw new Error('no AudioContext');
                ctx = new AC();
                source = ctx.createMediaStreamSource(s);
                analyser = ctx.createAnalyser();
                analyser.fftSize = 256;
                analyser.smoothingTimeConstant = 0.55;
                source.connect(analyser);
                dataArray = new Uint8Array(analyser.frequencyBinCount);
                $status.removeClass('warn').addClass('idle')
                       .html('<i class="fa fa-volume-off"></i> Listening-');
                loop();
            } catch (e) {
                $status.removeClass('idle').addClass('warn')
                       .html('<i class="fa fa-exclamation-triangle"></i> Meter unavailable');
            }
        }

        function loop() {
            if (stopped) return;
            analyser.getByteFrequencyData(dataArray);
            var barCount = $bars.length;
            // Spread the lower 60% of the FFT bins (speech band) across bars.
            var useBins = Math.floor(dataArray.length * 0.6);
            var binsPerBar = Math.max(1, Math.floor(useBins / barCount));
            var totalSum = 0, peak = 0;
            $bars.each(function (i) {
                var s = i * binsPerBar, sum = 0;
                for (var j = s; j < s + binsPerBar; j++) sum += dataArray[j] || 0;
                var avg = sum / binsPerBar;
                var h = Math.max(6, Math.min(36, Math.round(avg / 255 * 60)));
                var cls = avg > 210 ? 'clip' : (avg > 160 ? 'hot' : '');
                this.style.height = h + 'px';
                this.className = 'vc-mic-bar' + (avg > 12 ? ' on' : '') + (cls ? ' ' + cls : '');
                totalSum += avg;
                if (avg > peak) peak = avg;
            });
            var overall = totalSum / barCount;
            if (overall > 10) {
                lastLoudAt = Date.now();
                if (!$status.data('loud')) {
                    $status.removeClass('warn idle').data('loud', 1)
                           .html('<i class="fa fa-volume-up"></i> Hearing you');
                }
            } else if (lastLoudAt && (Date.now() - lastLoudAt > 2500)) {
                // Went quiet AFTER hearing something - normal pause.
                $status.removeClass('warn').addClass('idle').data('loud', 0)
                       .html('<i class="fa fa-volume-off"></i> Listening (quiet)-');
            } else if (!lastLoudAt && (Date.now() - startedAt > 3500)) {
                // Never heard anything since we opened - probable mic problem.
                $status.removeClass('idle').addClass('warn')
                       .html('<i class="fa fa-volume-mute"></i> No sound detected - check the mic / system input');
            }
            raf = requestAnimationFrame(loop);
        }

        if (stream) {
            setupAudio(stream);
        } else if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
            navigator.mediaDevices.getUserMedia({ audio: true }).then(function (s) {
                if (stopped) { s.getTracks().forEach(function (t) { try { t.stop(); } catch (e) {} }); return; }
                stream = s; ownsStream = true;
                setupAudio(s);
            }).catch(function (err) {
                $status.removeClass('idle').addClass('warn')
                       .html('<i class="fa fa-times-circle"></i> Mic access blocked');
            });
        } else {
            $status.removeClass('idle').addClass('warn')
                   .html('<i class="fa fa-times-circle"></i> Mic API unavailable');
        }

        return function cleanup() {
            stopped = true;
            if (raf) try { cancelAnimationFrame(raf); } catch (e) {}
            try { source && source.disconnect(); } catch (e) {}
            try { analyser && analyser.disconnect(); } catch (e) {}
            try { ctx && ctx.close(); } catch (e) {}
            // Only release the stream if we opened it ourselves; callers that
            // passed their own stream still need it for MediaRecorder.
            if (ownsStream && stream) {
                stream.getTracks().forEach(function (t) { try { t.stop(); } catch (e) {} });
            }
            $meter.hide();
            $bars.removeClass('on hot clip').css('height', '6px');
            $status.removeClass('warn idle').removeData('loud');
        };
    }

    function openMicModal(opts) {
        micActive = {
            onStop:   opts.onStop   || function () {},
            onCancel: opts.onCancel || function () {},
            meterCleanup: null,
            interim: '', final: ''
        };
        $('#drmMicLabel').text(opts.label || 'Recording-');
        $('#drmMicHint').html(opts.hint || 'Speak now - click <strong>Stop &amp; insert</strong> when you\'re done.');
        $('#drmMicTranscript').empty().hide();
        $('#drmMicStopBtn').prop('disabled', false).html('<i class="fa fa-stop"></i> Stop &amp; insert');
        $('#drmMicCancelBtn').prop('disabled', false).show();
        $('#drmMicStateIcon').html('<i class="fa fa-microphone-alt"></i>').css('color', '#dc2626');
        $('#drmMicHdrIcon').removeClass('fa-cog fa-spin').addClass('fa-microphone');
        $('#drmMicModal').modal('show');
        // Start the live level meter. If the caller has a stream from
        // getUserMedia (Whisper / compare path) reuse it; otherwise the
        // meter opens its own monitoring stream (Web Speech path).
        if (opts.meter !== false) {
            try { micActive.meterCleanup = startMicMeter(opts.stream || null); }
            catch (e) { /* meter failure must never break recording */ }
        }
    }
    function updateMicTranscript(finalText, interimText) {
        if (!micActive) return;
        micActive.final = finalText || '';
        micActive.interim = interimText || '';
        var html = '';
        if (finalText)   html += $('<i>').text(finalText).html() + ' ';
        if (interimText) html += '<span style="color:#94a3b8;font-style:italic;">' + $('<i>').text(interimText).html() + '</span>';
        if (html) $('#drmMicTranscript').html(html).show();
    }
    function setMicProcessing(label) {
        // Done listening - tear the meter down so it stops drawing.
        if (micActive && micActive.meterCleanup) {
            try { micActive.meterCleanup(); } catch (e) {}
            micActive.meterCleanup = null;
        }
        $('#drmMicStateIcon').html('<i class="fa fa-spinner fa-spin"></i>').css('color', '#0ea5e9');
        $('#drmMicHdrIcon').removeClass('fa-microphone').addClass('fa-cog fa-spin');
        $('#drmMicLabel').text(label || 'Processing-');
        $('#drmMicHint').text('Please wait - this only takes a moment.');
        $('#drmMicStopBtn').prop('disabled', true);
        $('#drmMicCancelBtn').prop('disabled', true);
    }
    function closeMicModal() {
        if (micActive && micActive.meterCleanup) {
            try { micActive.meterCleanup(); } catch (e) {}
        }
        micActive = null;
        $('#drmMicHdrIcon').removeClass('fa-cog fa-spin').addClass('fa-microphone');
        $('#drmMicModal').modal('hide');
    }
    $(document).on('click', '#drmMicStopBtn', function (e) {
        e.preventDefault();
        if (!micActive) return;
        var fn = micActive.onStop;
        micActive.onStop = micActive.onCancel = function(){};
        fn();
    });
    $(document).on('click', '#drmMicCancelBtn', function (e) {
        e.preventDefault();
        if (!micActive) return;
        var fn = micActive.onCancel;
        micActive.onStop = micActive.onCancel = function(){};
        fn();
        closeMicModal();
    });

    // Back-compat wrappers - existing callers still use these names.
    function announceRecording(label) {
        // Caller should use openMicModal directly; this is a no-op now (the
        // modal is opened by start* functions further below).
    }
    function clearRecordingToast() { closeMicModal(); }

    function startWhisperRecord(btn, targetId, section) {
        if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
            toastr.error('This browser does not allow microphone access.');
            return;
        }
        setStarting(btn);
        navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
            var rec = new MediaRecorder(stream);
            var chunks = [];
            // Two separate flags - 'cancelled' means "throw the audio away
            // before uploading", 'stopRequested' just guards against the user
            // double-clicking Stop. Previously a single 'finished' flag did
            // both jobs and the stop() path incorrectly marked it as
            // cancelled, so rec.onstop bailed before sending to Whisper.
            var cancelled = false;
            var stopRequested = false;

            function releaseStream() {
                stream.getTracks().forEach(function (t) { try { t.stop(); } catch (e) {} });
            }

            rec.ondataavailable = function (e) { if (e.data.size) chunks.push(e.data); };
            rec.onstop = function () {
                releaseStream();
                if (cancelled) return;       // user clicked Cancel - discard
                var blob = new Blob(chunks, { type: 'audio/webm' });
                if (blob.size < 800) {
                    setRecording(btn, false);
                    closeMicModal();
                    toastr.warning('Recording too short - try again.');
                    recState[targetId] = null;
                    return;
                }
                setMicProcessing('Transcribing-');
                var fd = new FormData();
                fd.append('_token', TOKEN);
                fd.append('audio', blob, 'dictation.webm');
                // Section-specific vocabulary hint: feed Whisper the labels
                // visible on this section's chip row so it's primed for the
                // exact words the doctor is most likely to say. Backend
                // appends its own general dermatology vocabulary on top.
                var sectionPrompt = buildSectionPrompt(targetId);
                if (sectionPrompt) fd.append('prompt', sectionPrompt);
                $.ajax({
                    url: TRANSCRIBE_URL, method: 'POST', data: fd,
                    processData: false, contentType: false,
                    success: function (res) {
                        if (res.success && res.text) {
                            appendToTextarea(targetId, res.text);
                            toastr.success('Dictation added (' + (res.text.length) + ' chars).');
                        } else {
                            toastr.error(res.msg || 'Transcription returned no text.');
                        }
                    },
                    error: function (xhr) {
                        toastr.error((xhr.responseJSON && xhr.responseJSON.msg) || 'Transcription failed.');
                    },
                    complete: function () {
                        setRecording(btn, false);
                        closeMicModal();
                        recState[targetId] = null;
                    }
                });
            };

            recState[targetId] = {
                recording: true,
                stop: function () {
                    if (stopRequested) return;
                    stopRequested = true;
                    try { rec.stop(); } catch (e) {}
                }
            };

            setRecording(btn, true);
            openMicModal({
                label: 'Recording this section',
                hint:  '<i class="fa fa-info-circle"></i> Speak now. Click <strong>Stop &amp; insert</strong> to upload &amp; transcribe.',
                stream: stream,
                onStop:   function () { recState[targetId].stop(); },
                onCancel: function () {
                    cancelled = true;
                    stopRequested = true;
                    try { rec.stop(); } catch (e) {}
                    chunks = [];
                    releaseStream();
                    setRecording(btn, false);
                    recState[targetId] = null;
                    toastr.info('Recording cancelled.');
                }
            });
            rec.start();
        }).catch(function (err) {
            setRecording(btn, false);
            closeMicModal();
            recState[targetId] = null;
            toastr.error('Microphone access denied: ' + err.message, '', { timeOut: 9000 });
        });
    }


    // A/B compare mode was removed once Groq Whisper proved consistently
    // better than browser Web Speech. The routing in drmMicDictate now just
    // picks the configured provider directly. Restore from git history if
    // a second-opinion view ever needs to come back.

    // Map raw Web Speech error codes to actionable messages.
    function webSpeechError(code) {
        return ({
            'not-allowed':       'Microphone access blocked. Click the 🔒 / ⓘ icon in the address bar, set Microphone to Allow, then reload - or configure a Whisper provider under VetAI → Settings.',
            'service-not-allowed': 'The browser refused speech recognition. Try Chrome/Edge over HTTPS, or switch to Whisper under VetAI → Settings.',
            'no-speech':         'No speech detected. Speak closer to the mic and try again.',
            'audio-capture':     'No microphone found. Plug one in or grant the browser access.',
            'aborted':           'Recording cancelled.',
            'network':           'Web Speech needs an internet connection.'
        })[code] || ('Web Speech error: ' + code);
    }

    function startWebSpeech(btn, targetId) {
        var SR = window.SpeechRecognition || window.webkitSpeechRecognition;
        if (!SR) { toastr.error('Web Speech is not supported in this browser. Configure a Whisper provider under VetAI → Settings.'); return; }
        // Chrome's built-in no-speech timeout is ~5-8s; we keep restarting the
        // recogniser whenever it ends silently until the user clicks Stop OR
        // the total silence since the last word exceeds SILENCE_LIMIT_MS
        // (inherited from outer scope; set in VetAI Settings).
        var r = new SR();
        r.continuous = true; r.interimResults = true;
        r.lang = (navigator.language || 'en-US');
        var finalText = '', interimText = '';
        var hadError = false, stopped = false, cancelled = false;
        var lastSpeechAt = Date.now();
        // Chrome's Web Speech relays audio to Google's service; the relay can
        // flap during auto-restart and emit 'network' even on healthy Wi-Fi.
        // Track consecutive network failures and only surface as fatal once
        // we've actually failed to recover.
        var netFailStreak = 0;
        var MAX_NET_FAILS = 4;
        function commit() {
            var text = (finalText + ' ' + interimText).replace(/\s+/g, ' ').trim();
            if (text) {
                cleanThen(text, function (out) {
                    appendToTextarea(targetId, out);
                    toastr.success('Dictation added (' + out.length + ' chars).');
                });
            } else if (!hadError) {
                toastr.info('Nothing transcribed - try speaking a little closer to the mic.');
            }
        }
        function cleanup() {
            stopped = true;
            setRecording(btn, false);
            closeMicModal();
            recState[targetId] = null;
        }
        r.onresult = function (ev) {
            interimText = '';
            for (var i = ev.resultIndex; i < ev.results.length; i++) {
                var t = ev.results[i][0].transcript;
                if (ev.results[i].isFinal) finalText += t + ' ';
                else                       interimText += t;
            }
            // Touch the silence clock on EVERY result (final or interim)
            // so the 30s budget tracks "time since the user last said something".
            lastSpeechAt = Date.now();
            netFailStreak = 0;                  // any speech result clears the streak
            updateMicTranscript(finalText.trim(), interimText.trim());
        };
        r.onerror = function (e) {
            // Once Stop/Cancel has been clicked, swallow any late async errors
            // — the user already saw the outcome, no need to flash a red banner.
            if (stopped) return;
            // 'no-speech' / 'aborted' are normal end-of-utterance signals on
            // continuous mode; let onend commit whatever was captured instead
            // of flashing a red error banner over a working transcript.
            if (e.error === 'no-speech' || e.error === 'aborted') return;
            // 'network' is usually transient (Chrome relay hiccup); let onend
            // retry up to MAX_NET_FAILS times before declaring it fatal.
            if (e.error === 'network') {
                netFailStreak++;
                if (netFailStreak < MAX_NET_FAILS) return;
                // fall through to fatal handling below
            }
            hadError = true;
            cleanup();
            toastr.error(webSpeechError(e.error), '', { timeOut: 9000 });
        };
        r.onend = function () {
            if (stopped) return;
            // Auto-restart while we're still within the silence budget — this
            // is how we extend Chrome's 5-8s timeout to the configured budget.
            // Also retry transient network blips; back off briefly so the
            // relay has a moment to recover.
            if (!cancelled && (Date.now() - lastSpeechAt) < SILENCE_LIMIT_MS) {
                var delay = netFailStreak > 0 ? 400 : 0;
                setTimeout(function () {
                    if (stopped) return;
                    try { r.start(); } catch (e) {
                        if (!cancelled) commit();
                        cleanup();
                    }
                }, delay);
                return;
            }
            if (!cancelled) commit();
            cleanup();
        };
        recState[targetId] = {
            recording: true,
            stop: function () {
                if (stopped) return;
                stopped = true;
                try { r.stop(); } catch (e) {}
                setTimeout(function () { try { r.abort(); } catch (e) {} }, 250);
                commit();
                setRecording(btn, false);
                closeMicModal();
                recState[targetId] = null;
            }
        };
        setStarting(btn);

        // Pre-flight: ask for mic permission via getUserMedia first - clearer
        // permission prompt than Web Speech alone, and surfaces denials early.
        var goAhead = function () {
            if (stopped) return;
            setRecording(btn, true);
            openMicModal({
                label: 'Recording this section',
                onStop:   function () { recState[targetId].stop(); },
                onCancel: function () {
                    cancelled = true; stopped = true;
                    try { r.stop(); } catch (e) {}
                    setTimeout(function () { try { r.abort(); } catch (e) {} }, 250);
                    setRecording(btn, false);
                    recState[targetId] = null;
                    toastr.info('Recording cancelled.');
                }
            });
            try { r.start(); } catch (e) { cleanup(); toastr.error(e.message); }
        };
        if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
            navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
                stream.getTracks().forEach(function (t) { t.stop(); });
                goAhead();
            }).catch(function () {
                cleanup();
                toastr.error(webSpeechError('not-allowed'), '', { timeOut: 9000 });
            });
        } else {
            goAhead();
        }
    }

    // ── Whole-consultation dictation (drmVoiceConsult) ──────────────────
    window.drmVoiceConsult = function (btn) {
        if (!confirmConsent()) return;
        if (recState.__consult && recState.__consult.recording) { recState.__consult.stop(); return; }
        if (PROVIDER === 'webspeech') return startConsultWebSpeech(btn);
        startConsultWhisper(btn);
    };

    function setConsultBtn(btn, on, label) {
        $(btn).toggleClass('btn-danger', on).toggleClass('btn-default', !on)
              .html(on ? '<i class="fa fa-stop"></i> Stop recording'
                       : (label || '<i class="fa fa-microphone"></i> Voice consultation'));
    }

    function applyStructured(s) {
        if (s.subjective) appendToTextarea('soap_s', s.subjective);
        if (s.objective)  appendToTextarea('soap_o', s.objective);
        if (s.assessment) appendToTextarea('soap_a', s.assessment);
        if (s.plan)       appendToTextarea('soap_p', s.plan);
        toastr.success('Transcript split into S/O/A/P - please verify.');
    }

    function postStructure(transcript, btn) {
        $.post(STRUCTURE_URL, { _token: TOKEN, transcript: transcript })
            .done(function (res) {
                if (res.success) {
                    applyStructured(res);
                } else {
                    appendToTextarea('soap_s', transcript);
                    toastr.warning('Could not auto-structure - transcript appended to Subjective.');
                }
            })
            .fail(function () {
                appendToTextarea('soap_s', transcript);
                toastr.warning('Structuring failed - transcript appended to Subjective.');
            })
            .always(function () { setConsultBtn(btn, false); });
    }

    function startConsultWhisper(btn) {
        $(btn).prop('disabled', false).html('<i class="fa fa-spinner fa-spin"></i> Requesting microphone-');
        navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
            var rec = new MediaRecorder(stream);
            var chunks = [];
            // Separate cancel-marker from re-entry guard so the Stop-button
            // path doesn't masquerade as Cancel and discard the audio before
            // it can be uploaded (same fix as startWhisperRecord).
            var cancelled = false;
            var stopRequested = false;

            function releaseStream() {
                stream.getTracks().forEach(function (t) { try { t.stop(); } catch (e) {} });
            }

            rec.ondataavailable = function (e) { if (e.data.size) chunks.push(e.data); };
            rec.onstop = function () {
                releaseStream();
                if (cancelled) return;       // user clicked Cancel - discard
                var blob = new Blob(chunks, { type: 'audio/webm' });
                if (blob.size < 800) {
                    setConsultBtn(btn, false);
                    closeMicModal();
                    toastr.warning('Recording too short - try again.');
                    recState.__consult = null;
                    return;
                }
                setMicProcessing('Transcribing-');
                var fd = new FormData();
                fd.append('_token', TOKEN);
                fd.append('audio', blob, 'consultation.webm');
                $.ajax({
                    url: TRANSCRIBE_URL, method: 'POST', data: fd,
                    processData: false, contentType: false,
                    success: function (res) {
                        if (!res.success || !res.text) {
                            toastr.error(res.msg || 'No text returned.');
                            setConsultBtn(btn, false);
                            closeMicModal();
                            return;
                        }
                        if (AUTO_STRUCTURE) {
                            setMicProcessing('Splitting into S/O/A/P-');
                            postStructure(res.text, btn);
                            closeMicModal();
                        } else {
                            appendToTextarea('soap_s', res.text);
                            setConsultBtn(btn, false);
                            closeMicModal();
                            toastr.success('Transcript added to Subjective.');
                        }
                    },
                    error: function (xhr) {
                        toastr.error((xhr.responseJSON && xhr.responseJSON.msg) || 'Transcription failed.');
                        setConsultBtn(btn, false);
                        closeMicModal();
                    }
                });
                recState.__consult = null;
            };
            recState.__consult = {
                recording: true,
                stop: function () {
                    if (stopRequested) return;
                    stopRequested = true;
                    try { rec.stop(); } catch (e) {}
                }
            };
            setConsultBtn(btn, true);
            openMicModal({
                label: 'Recording the whole consultation',
                hint:  '<i class="fa fa-info-circle"></i> Speak through the entire visit. Click <strong>Stop &amp; insert</strong> to transcribe and split into S/O/A/P.',
                stream: stream,
                onStop:   function () { recState.__consult.stop(); },
                onCancel: function () {
                    cancelled = true;
                    stopRequested = true;
                    try { rec.stop(); } catch (e) {}
                    chunks = [];
                    releaseStream();
                    setConsultBtn(btn, false);
                    recState.__consult = null;
                    toastr.info('Recording cancelled.');
                }
            });
            rec.start();
        }).catch(function (err) {
            setConsultBtn(btn, false);
            closeMicModal();
            recState.__consult = null;
            toastr.error('Microphone access denied: ' + err.message, '', { timeOut: 9000 });
        });
    }

    function startConsultWebSpeech(btn) {
        var SR = window.SpeechRecognition || window.webkitSpeechRecognition;
        if (!SR) { toastr.error('Web Speech is not supported. Configure a Whisper provider under VetAI → Settings.'); return; }
        // SILENCE_LIMIT_MS inherited from the outer scope (set in VetAI Settings).
        var r = new SR();
        r.continuous = true; r.interimResults = true;
        r.lang = (navigator.language || 'en-US');
        var finalText = '', interimText = '';
        var hadError = false, stopped = false, cancelled = false;
        var lastSpeechAt = Date.now();
        // See startWebSpeech for why we tolerate a streak of 'network' errors
        // rather than treating the first one as fatal.
        var netFailStreak = 0;
        var MAX_NET_FAILS = 4;

        function commit() {
            var text = (finalText + ' ' + interimText).replace(/\s+/g, ' ').trim();
            if (text) {
                cleanThen(text, function (out) {
                    if (AUTO_STRUCTURE) { setMicProcessing('Splitting into S/O/A/P-'); postStructure(out, btn); }
                    else { appendToTextarea('soap_s', out); }
                    toastr.success('Dictation added (' + out.length + ' chars).');
                });
            } else if (!hadError) {
                toastr.info('Nothing transcribed - try speaking a little closer to the mic.');
            }
        }
        function cleanup() {
            stopped = true;
            setConsultBtn(btn, false);
            closeMicModal();
            recState.__consult = null;
        }

        r.onresult = function (ev) {
            interimText = '';
            for (var i = ev.resultIndex; i < ev.results.length; i++) {
                var t = ev.results[i][0].transcript;
                if (ev.results[i].isFinal) finalText += t + ' ';
                else                       interimText += t;
            }
            lastSpeechAt = Date.now();
            netFailStreak = 0;
            updateMicTranscript(finalText.trim(), interimText.trim());
        };
        r.onerror = function (e) {
            // Swallow late async errors after the user has already stopped.
            if (stopped) return;
            // 'no-speech' / 'aborted' are normal end-of-utterance signals on
            // continuous mode; let onend commit whatever was captured instead
            // of flashing a red error banner over a working transcript.
            if (e.error === 'no-speech' || e.error === 'aborted') return;
            // 'network' is usually a transient relay hiccup — retry a few
            // times in onend before surfacing as fatal.
            if (e.error === 'network') {
                netFailStreak++;
                if (netFailStreak < MAX_NET_FAILS) return;
            }
            hadError = true;
            cleanup();
            toastr.error(webSpeechError(e.error), '', { timeOut: 9000 });
        };
        r.onend = function () {
            if (stopped) return;
            // Auto-restart while still within the silence budget so the doctor
            // can pause to think mid-dictation without losing the session.
            // Brief back-off after a network blip so the relay can recover.
            if (!cancelled && (Date.now() - lastSpeechAt) < SILENCE_LIMIT_MS) {
                var delay = netFailStreak > 0 ? 400 : 0;
                setTimeout(function () {
                    if (stopped) return;
                    try { r.start(); } catch (e) {
                        if (!cancelled) commit();
                        cleanup();
                    }
                }, delay);
                return;
            }
            if (!cancelled) commit();
            cleanup();
        };
        recState.__consult = {
            recording: true,
            stop: function () {
                if (stopped) return;
                stopped = true;
                try { r.stop(); } catch (e) {}
                setTimeout(function () { try { r.abort(); } catch (e) {} }, 250);
                commit();
                setConsultBtn(btn, false);
                closeMicModal();
                recState.__consult = null;
            }
        };
        $(btn).html('<i class="fa fa-spinner fa-spin"></i> Requesting microphone-');

        var goAhead = function () {
            if (stopped) return;
            setConsultBtn(btn, true);
            openMicModal({
                label: 'Recording the whole consultation',
                hint:  '<i class="fa fa-info-circle"></i> Speak through the entire visit. Click <strong>Stop &amp; insert</strong> when done.',
                onStop:   function () { recState.__consult.stop(); },
                onCancel: function () {
                    cancelled = true; stopped = true;
                    try { r.stop(); } catch (e) {}
                    setTimeout(function () { try { r.abort(); } catch (e) {} }, 250);
                    setConsultBtn(btn, false);
                    recState.__consult = null;
                    toastr.info('Recording cancelled.');
                }
            });
            try { r.start(); } catch (e) { cleanup(); toastr.error(e.message); }
        };
        if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
            navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
                stream.getTracks().forEach(function (t) { t.stop(); });
                goAhead();
            }).catch(function () {
                cleanup();
                toastr.error(webSpeechError('not-allowed'), '', { timeOut: 9000 });
            });
        } else {
            goAhead();
        }
    }
});
</script>
@endif

{{-- ── Edit Request Modal ────────────────────────────────────── --}}
@can('clinic.manage_appointments')
<div class="modal fade vc-er-modal" id="editRequestModal" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="vc-er-head">
                <h4><i class="fa fa-unlock-alt"></i> Request Edit Access</h4>
                <p>This appointment is completed and locked. Submit a request for a manager to unlock it for one edit.</p>
            </div>
            <div class="modal-body">
                @if($myEditRequest && $myEditRequest->status === 'rejected')
                <div class="alert alert-danger" style="font-size:12px;padding:8px 12px;">
                    <i class="fa fa-times-circle"></i> Your previous request was rejected.
                    @if($myEditRequest->review_notes)<br><em>{{ $myEditRequest->review_notes }}</em>@endif
                </div>
                @endif
                <div class="form-group">
                    <label>Reason for editing <span class="text-danger">*</span></label>
                    <textarea id="edit_request_reason" class="form-control" rows="4"
                              placeholder="Explain what needs to be corrected and why-"></textarea>
                </div>
                <p class="text-muted" style="font-size:11.5px;">
                    <i class="fa fa-info-circle"></i> Once approved you may save changes <strong>once</strong>;
                    the appointment then re-locks.
                </p>
            </div>
            <div class="modal-footer">
                <button class="btn btn-default" data-dismiss="modal">Cancel</button>
                <button class="btn btn-warning" id="btn_submit_edit_request">
                    <i class="fa fa-paper-plane"></i> Submit Request
                </button>
            </div>
        </div>
    </div>
</div>

{{-- ── Doctor-Change Request Modal ────────────────────────────── --}}
@unless($canChangeDoctor)
<div class="modal fade vc-er-modal" id="doctorChangeModal" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="vc-er-head" style="background:linear-gradient(135deg,#0ea5e9 0%,#0369a1 100%);">
                <h4><i class="fa fa-user-md"></i> Request Doctor Change</h4>
                <p>You do not have permission to change the doctor directly. Submit a request for a manager to approve.</p>
            </div>
            <div class="modal-body">
                <div class="form-group">
                    <label>Current doctor</label>
                    <input type="text" class="form-control" readonly
                           value="{{ optional($appointment->doctor)->name ?? '- none -' }}">
                </div>
                <div class="form-group">
                    <label>Requested doctor <span class="text-danger">*</span></label>
                    <select id="dcr_doctor_id" class="form-control" style="width:100%;">
                        <option value="">- Select doctor -</option>
                        @foreach($doctors as $doc)
                            @if($doc->id != $appointment->doctor_id)
                            <option value="{{ $doc->id }}">{{ $doc->name }}{{ $doc->specialization ? ' - ' . $doc->specialization : '' }}</option>
                            @endif
                        @endforeach
                    </select>
                </div>
                <div class="form-group">
                    <label>Apply change to <span class="text-danger">*</span></label>
                    <div class="radio" style="margin:4px 0;">
                        <label style="font-weight:500;">
                            <input type="radio" name="dcr_scope" value="this_appointment" checked>
                            This appointment only
                        </label>
                    </div>
                    <div class="radio" style="margin:4px 0;">
                        <label style="font-weight:500;">
                            <input type="radio" name="dcr_scope" value="set_preferred">
                            This appointment <strong>and</strong> set as the owner's preferred doctor
                        </label>
                    </div>
                </div>
                <div class="form-group">
                    <label>Reason <span class="text-danger">*</span></label>
                    <textarea id="dcr_reason" class="form-control" rows="3"
                              placeholder="Explain why the doctor needs to change-"></textarea>
                </div>
                <p class="text-muted" style="font-size:11.5px;">
                    <i class="fa fa-info-circle"></i> Once approved, the appointment doctor is updated automatically.
                </p>
            </div>
            <div class="modal-footer">
                <button class="btn btn-default" data-dismiss="modal">Cancel</button>
                <button class="btn btn-info" id="btn_submit_doctor_change">
                    <i class="fa fa-paper-plane"></i> Submit Request
                </button>
            </div>
        </div>
    </div>
</div>
@endunless
@endcan

{{-- ── Link Invoice Modal (qa-btn style) ────────────────────── --}}
<div class="modal fade" id="linkInvoiceModal" tabindex="-1">
    <div class="modal-dialog" style="max-width:600px;">
        <div class="modal-content" style="border:none;border-radius:12px;overflow:hidden;box-shadow:0 12px 30px rgba(0,0,0,.18);">
            {{-- Header - same gradient feel as the "Create Invoice" hero action --}}
            <div class="modal-header" style="background:linear-gradient(135deg,#8e44ad 0%,#6c3483 100%);color:#fff;border:none;padding:16px 22px;">
                <button type="button" class="close" data-dismiss="modal" style="color:#fff;opacity:.85;font-size:15px;line-height:1;margin-top:-2px;">&times;</button>
                <div style="display:flex;align-items:center;gap:12px;">
                    <span style="width:38px;height:38px;border-radius:8px;background:rgba(255,255,255,.18);display:flex;align-items:center;justify-content:center;flex-shrink:0;">
                        <i class="fa fa-link" style="font-size:16px;"></i>
                    </span>
                    <div>
                        <h4 class="modal-title" style="font-size:15px;font-weight:800;margin:0;letter-spacing:.2px;">Link Existing Invoice</h4>
                        <div style="font-size:11px;opacity:.85;margin-top:2px;">Search by invoice number and pick the matching sell record</div>
                    </div>
                </div>
            </div>

            <div class="modal-body" style="padding:18px 22px;background:#fafbfc;">
                {{-- Search input --}}
                <div style="position:relative;margin-bottom:14px;">
                    <span style="position:absolute;left:12px;top:50%;transform:translateY(-50%);color:#94a3b8;font-size:13px;"><i class="fa fa-search"></i></span>
                    <input type="text" id="inv_search_input" class="form-control"
                           placeholder="Type invoice number-"
                           style="padding-left:36px;height:40px;border-radius:8px;border:1.5px solid #e2e8f0;font-size:13px;background:#fff;">
                </div>

                {{-- Status placeholders --}}
                <div id="inv_search_hint" style="text-align:center;color:#94a3b8;font-size:12px;padding:18px 0;">
                    <i class="fa fa-search" style="margin-bottom:6px;display:block;font-size:22px;opacity:.5;"></i>
                    Start typing to search invoices
                </div>
                <div id="inv_searching" style="display:none;text-align:center;color:#64748b;font-size:12px;padding:14px 0;">
                    <i class="fa fa-spinner fa-spin"></i> Searching-
                </div>
                <div id="inv_no_results" style="display:none;text-align:center;color:#ef4444;font-size:12px;padding:14px 0;">
                    <i class="fa fa-times-circle" style="margin-bottom:4px;display:block;font-size:20px;"></i>
                    No invoices found for "<span id="inv_no_results_term"></span>"
                </div>

                {{-- Results list - each row uses the qa-btn pattern --}}
                <div id="inv_results_list" style="display:none;max-height:320px;overflow-y:auto;flex-direction:column;gap:6px;padding:2px;"></div>

                {{-- Selected preview (green qa-btn-style card) --}}
                <div id="inv_selected_preview" style="display:none;margin-top:14px;">
                    <div class="qa-btn" style="background:#f0fdf4;border-color:#bbf7d0;cursor:default;margin-bottom:0;">
                        <span class="qa-icon" style="background:#22c55e;"><i class="fa fa-check"></i></span>
                        <div style="flex:1;min-width:0;">
                            <div style="font-weight:700;font-size:13px;color:#166534;" id="inv_sel_invoice_no"></div>
                            <div style="font-size:11px;color:#4ade80;margin-top:1px;" id="inv_sel_meta"></div>
                        </div>
                        <button type="button" id="inv_sel_clear" class="btn btn-xs btn-default" title="Clear selection" style="flex-shrink:0;">
                            <i class="fa fa-times"></i>
                        </button>
                    </div>
                </div>

                <input type="hidden" id="link_invoice_id" value="">
            </div>

            <div class="modal-footer" style="background:#fff;border-top:1px solid #f1f5f9;padding:12px 22px;">
                <button class="btn btn-default" data-dismiss="modal">Cancel</button>
                <button class="btn btn-primary" id="btn_link_invoice" disabled style="background:#8e44ad;border-color:#8e44ad;font-weight:700;">
                    <i class="fa fa-link"></i> Link Invoice
                </button>
            </div>
        </div>
    </div>
</div>

{{-- ── Record Treatment Modal ─────────────────────────────── --}}
<div class="modal fade" id="vaccinationModal" tabindex="-1">
    <div class="modal-dialog modal-lg" style="max-width:640px;">
        <div class="modal-content" style="border-radius:14px;overflow:hidden;border:none;">

            {{-- Header --}}
            <div class="modal-header" style="background:linear-gradient(135deg,#16a34a,#22c55e);color:#fff;border:none;padding:18px 22px;">
                <button type="button" class="close" data-dismiss="modal" style="color:#fff;opacity:.8;font-size:15px;">&times;</button>
                <h4 class="modal-title" id="vaxModalTitle" style="font-size:16px;font-weight:800;display:flex;align-items:center;gap:9px;margin:0;">
                    <span style="width:34px;height:34px;border-radius:9px;background:rgba(255,255,255,.18);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;">💉</span>
                    Record Treatment
                </h4>
            </div>

            <div class="modal-body" style="padding:20px 22px;background:#fafbfc;">
                <input type="hidden" id="vax_edit_id" value="">

                {{-- ① Treatment --}}
                <div class="vax-modal-section">
                    <div class="vax-modal-section-title">
                        <i class="fas fa-syringe" style="color:#22c55e;"></i> Treatment
                    </div>
                    <select id="modal_vaccine_id" class="form-control select2-vaccine" style="width:100%;border-radius:8px;border:1.5px solid #e2e8f0;font-size:13px;"></select>
                </div>

                {{-- ② Where was it given? --}}
                <div class="vax-modal-section">
                    <div class="vax-modal-section-title">
                        <i class="fa fa-map-marker" style="color:#3b82f6;"></i> Treatment Status
                    </div>
                    <div class="vax-status-options">
                        <div class="vax-status-opt selected-clinic" data-value="taken_in" onclick="selectVaxStatus(this)">
                            <div class="so-icon">🏥</div>
                            <div class="so-label">At Clinic</div>
                            <div class="so-sub">Taken In</div>
                        </div>
                        <div class="vax-status-opt" data-value="taken_out" onclick="selectVaxStatus(this)">
                            <div class="so-icon">🏠</div>
                            <div class="so-label">External</div>
                            <div class="so-sub">Taken Out</div>
                        </div>
                        <div class="vax-status-opt" data-value="not_taken" onclick="selectVaxStatus(this)">
                            <div class="so-icon">⏳</div>
                            <div class="so-label">Scheduled</div>
                            <div class="so-sub">Not Given Yet</div>
                        </div>
                    </div>
                    <input type="hidden" id="modal_vax_taken_status" value="taken_in">
                </div>

                {{-- ③ Dates --}}
                <div class="vax-modal-section">
                    <div class="vax-modal-section-title">
                        <i class="fa fa-calendar" style="color:#f59e0b;"></i> Dates
                    </div>
                    <div class="row">
                        <div class="col-sm-6">
                            <div class="form-group" style="margin-bottom:0;">
                                <label style="font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.4px;">
                                    Administered Date <span class="text-danger">*</span>
                                </label>
                                <div style="position:relative;">
                                    <span style="position:absolute;left:10px;top:50%;transform:translateY(-50%);color:#94a3b8;font-size:12px;"><i class="fa fa-calendar"></i></span>
                                    <input type="text" id="modal_vax_date" class="form-control" autocomplete="off"
                                           style="padding-left:30px;border-radius:8px;border:1.5px solid #e2e8f0;font-size:13px;">
                                </div>
                            </div>
                        </div>
                        <div class="col-sm-6">
                            <div class="form-group" style="margin-bottom:0;">
                                <label style="font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.4px;">
                                    Next Due Date <small style="font-size:9px;color:#94a3b8;text-transform:none;letter-spacing:0;font-weight:500;">auto-calculated</small>
                                </label>
                                <div style="position:relative;">
                                    <span style="position:absolute;left:10px;top:50%;transform:translateY(-50%);color:#94a3b8;font-size:12px;"><i class="fa fa-calendar-check"></i></span>
                                    <input type="text" id="modal_vax_next_due" class="form-control" autocomplete="off"
                                           style="padding-left:30px;border-radius:8px;border:1.5px solid #e2e8f0;font-size:13px;">
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                {{-- ④ Dose & Batch --}}
                <div class="vax-modal-section">
                    <div class="vax-modal-section-title">
                        <i class="fa fa-flask" style="color:#8b5cf6;"></i> Dose & Batch
                    </div>
                    <div class="row">
                        <div class="col-sm-6">
                            <label style="font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.4px;">Dose</label>
                            <div style="position:relative;">
                                <span style="position:absolute;left:10px;top:50%;transform:translateY(-50%);color:#94a3b8;font-size:12px;"><i class="fa fa-tint"></i></span>
                                <input type="text" id="modal_vax_dose" class="form-control" placeholder="e.g. 1 ml, 0.5 ml, Booster"
                                       style="padding-left:30px;border-radius:8px;border:1.5px solid #e2e8f0;font-size:13px;">
                            </div>
                        </div>
                        <div class="col-sm-6">
                            <label style="font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.4px;">Batch No.</label>
                            <div style="position:relative;">
                                <span style="position:absolute;left:10px;top:50%;transform:translateY(-50%);color:#94a3b8;font-size:12px;"><i class="fa fa-barcode"></i></span>
                                <input type="text" id="modal_vax_batch" class="form-control" placeholder="e.g. BX-2024-001"
                                       style="padding-left:30px;border-radius:8px;border:1.5px solid #e2e8f0;font-size:13px;">
                            </div>
                        </div>
                    </div>
                </div>

                {{-- ⑤ Vitals --}}
                <div class="vax-modal-section">
                    <div class="vax-modal-section-title">
                        <i class="fa fa-heartbeat" style="color:#ef4444;"></i> Vitals at Time of Treatment
                    </div>
                    <div class="row">
                        <div class="col-sm-6">
                            <label style="font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.4px;">Weight</label>
                            <div class="input-group" style="border-radius:8px;overflow:hidden;border:1.5px solid #e2e8f0;">
                                <span class="input-group-addon" style="background:#f8fafc;border:none;padding:0 10px;font-size:13px;color:#64748b;"><i class="fa fa-balance-scale"></i></span>
                                <input type="number" step="0.01" id="modal_vax_weight" class="form-control"
                                       placeholder="4.5" style="border:none;font-size:13px;">
                                <span class="input-group-addon" style="background:#f8fafc;border:none;font-weight:700;color:#64748b;font-size:12px;">kg</span>
                            </div>
                        </div>
                        <div class="col-sm-6">
                            <label style="font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.4px;">Temperature</label>
                            <div class="input-group" style="border-radius:8px;overflow:hidden;border:1.5px solid #e2e8f0;">
                                <span class="input-group-addon" style="background:#f8fafc;border:none;padding:0 10px;font-size:13px;color:#64748b;"><i class="fa fa-thermometer-half"></i></span>
                                <input type="number" step="0.1" id="modal_vax_temp" class="form-control"
                                       placeholder="38.5" style="border:none;font-size:13px;">
                                <span class="input-group-addon" style="background:#f8fafc;border:none;font-weight:700;color:#64748b;font-size:12px;">-C</span>
                            </div>
                        </div>
                    </div>
                </div>

                {{-- ⑥ Notes --}}
                <div>
                    <div class="vax-modal-section-title" style="margin-bottom:8px;">
                        <i class="fa fa-sticky-note" style="color:#94a3b8;"></i> Notes
                    </div>
                    <textarea id="modal_vax_notes" class="form-control" rows="2"
                              placeholder="Any reactions, observations, or remarks-"
                              style="border-radius:8px;border:1.5px solid #e2e8f0;font-size:13px;resize:vertical;"></textarea>
                </div>
            </div>

            <div class="modal-footer" style="background:#f8fafc;border-top:1px solid #e8ecf1;border:none;padding:14px 22px;display:flex;justify-content:flex-end;gap:8px;">
                <button class="btn btn-default" data-dismiss="modal"
                        style="border-radius:8px;font-weight:600;border:1.5px solid #e2e8f0;">Cancel</button>
                <button class="btn btn-success" id="btn_save_vaccination"
                        style="border-radius:8px;font-weight:700;padding:6px 20px;">
                    <i class="fas fa-syringe"></i> Save Treatment
                </button>
            </div>
        </div>
    </div>
</div>

@include('clinicCore::partials._ai_panel')

@endsection

@section('javascript')
<script>
var aptStatus        = '{{ $appointment->status }}';
var statusUrl        = '{{ route('clinic.appointments.status', $appointment->id) }}';
var vitalRangesUrl   = '{{ route('clinic.appointments.vital-ranges', $appointment->id) }}';

// ── Item 1.3 — Live vital-range warnings ────────────────────────────
// Fetch the reference range for this patient (species + age aware) once
// on load, then evaluate each vital input on every keystroke to
// colour-code the field + show "normal X-Y" hint. Critical (>15%
// outside band) flags the box red; merely low/high flags amber.
(function () {
    var rangeCache = null;
    var inputMap = {
        weight_kg:        '#v_weight',
        temperature:      '#v_temp',
        heart_rate:       '#v_hr',
        respiratory_rate: '#v_rr',
        systolic_bp:      '#v_sys',
        diastolic_bp:     '#v_dia',
        spo2:             '#v_spo2',
        blood_glucose:    '#v_glucose',
        bmi:              '#v_bmi',
    };
    function applyRange(metric, lo, hi, unit) {
        var sel = inputMap[metric];
        var $box = $(sel).closest('.vital-box');
        var $hint = $box.find('[data-vital-range]');
        $hint.text((lo != null && hi != null) ? 'normal ' + lo + '–' + hi + ' ' + (unit || '') : '');
        var v = parseFloat($(sel).val());
        $box.removeClass('is-normal is-low is-high is-critical');
        if (isNaN(v) || lo == null || hi == null) return;
        if (v < lo) $box.addClass(v < lo * 0.85 ? 'is-critical' : 'is-low');
        else if (v > hi) $box.addClass(v > hi * 1.15 ? 'is-critical' : 'is-high');
        else $box.addClass('is-normal');
    }
    function refreshAll() {
        if (!rangeCache || !rangeCache.has_range) return;
        Object.keys(inputMap).forEach(function (m) {
            var r = rangeCache.ranges[m] || {};
            applyRange(m, r.low, r.high, r.unit);
        });
    }
    $.getJSON(vitalRangesUrl).done(function (res) {
        rangeCache = res;
        if (!res.success) return;
        if (!res.has_range) {
            $('#v_range_label').text('(no reference range configured)');
            return;
        }
        $('#v_range_label').text('reference range: ' + (res.species ? (res.species + ' / ') : '') + res.life_stage);
        refreshAll();
    });
    $('#v_weight, #v_temp, #v_hr, #v_rr, #v_sys, #v_dia, #v_spo2, #v_glucose, #v_bmi').on('input', refreshAll);

    // Auto-BMI from weight + stored patient height (cm). Recomputes whenever
    // weight changes; user can still override the BMI box manually.
    var bmiUserEdited = false;
    $('#v_bmi').on('input', function(){ bmiUserEdited = true; });
    $('#v_weight').on('input', function(){
        var h = parseFloat($('#v_bmi').data('height'));
        var w = parseFloat($('#v_weight').val());
        if (h > 0 && w > 0 && !bmiUserEdited) {
            var m = h / 100.0;
            $('#v_bmi').val((w / (m * m)).toFixed(1));
            refreshAll();
        }
    });
})();
var consultUrl       = '{{ route('clinic.appointments.consultation', $appointment->id) }}';
var rxFromConsultUrl = '{{ route('clinic.prescriptions.from-consultation') }}';
var notesUrl         = '{{ route('clinic.appointments.notes.store', $appointment->id) }}';
var vaxStoreUrl      = '{{ route('clinic.vaccinations.store') }}';
var docUploadUrl     = '{{ route('clinic.appointments.documents.upload', $appointment->id) }}';
var docDeleteBase    = '{{ url('clinic/appointments/'.$appointment->id.'/documents') }}';
var petSpecies       = '{{ strtolower(optional($appointment->pet)->species ?? '') }}';
var csrfToken        = '{{ csrf_token() }}';

$(document).ready(function () {

    // ── Status step click ──────────────────────────────
    var statusOrder = ['pending','confirmed','waiting','in_progress','completed'];

    // Tracks consultation edits not yet persisted to the server.
    window.drmServerDirty = window.drmServerDirty || false;

    /** Guard: block marking completed while consultation changes are unsaved. */
    function guardCompletion(targetStatus, proceed) {
        if (targetStatus === 'completed' && window.drmServerDirty) {
            if (!confirm('You have unsaved consultation changes.\n\n'
                + 'Click Cancel to go back and save the consultation first, '
                + 'or OK to mark as completed anyway (your changes will NOT be saved).')) {
                return;
            }
        }
        proceed();
    }

    // Called after EVERY successful status update from any path (status
    // pills, dropdown, quick-action buttons). Centralised so item-1.2's
    // "unlock evaluations + force rating on Complete" logic only lives
    // in one place. Pre-completion status changes are no-ops here.
    function onStatusChanged(newStatus) {
        // Item #1 - toggle the section lock based on the new status.
        // in_progress and completed both unlock the consultation/vacc/lab
        // tabs; everything else (pending, confirmed, waiting, cancelled,
        // no_show) re-locks them.
        var unlocked = (newStatus === 'in_progress' || newStatus === 'completed');
        $('.vc-section-host').toggleClass('vc-section-locked', !unlocked);
        $('#clc_section_lock_banner').toggle(!unlocked);

        if (newStatus !== 'completed') return;
        // 1) Unlock the rate-owner / rate-clinic buttons without a refresh.
        $('.vc-eval-trigger').removeClass('vc-eval-locked');
        $('#clc_eval_locked_note').hide();
        // 2) Force the doctor toward rating the owner. Only auto-open if a
        //    pet-owner evaluation trigger actually exists for this user
        //    (permission gate) and the modal isn't already showing.
        var $rateBtn = $('.vc-eval-trigger[data-category="pet_owner"]');
        if (!$rateBtn.length || $('#drmEvaluationModal').hasClass('in')) return;
        // Brief delay so the success toast for the status change is
        // visible before the modal pops over it.
        setTimeout(function () {
            window.drmForcedRating = true;       // flag for the dismiss-warning
            $rateBtn.trigger('click');
        }, 350);
    }

    $('.status-step[data-status]').on('click', function () {
        var newStatus = $(this).data('status');
        if (newStatus === aptStatus) return;
        var label = $(this).text().trim();
        guardCompletion(newStatus, function () {
            $.post(statusUrl, { _token: csrfToken, status: newStatus }, function (res) {
                if (res.success) {
                    aptStatus = newStatus;
                    $('#apt_status_select').val(newStatus);
                    toastr.success('Status updated to: ' + label);
                    updateStatusBar();
                    onStatusChanged(newStatus);
                }
            }).fail(function(xhr){
                // Item #2 - surface the no-vitals 422 with the specific
                // message from the controller so the doctor knows exactly
                // what's missing.
                var msg = (xhr.responseJSON && xhr.responseJSON.msg) || 'Failed to update status';
                if (xhr.status === 422 && xhr.responseJSON && xhr.responseJSON.reason === 'no_vitals') {
                    toastr.warning(msg, '', { timeOut: 9000 });
                    // Focus the first vital input for convenience.
                    setTimeout(function () {
                        var $vital = $('#v_temp, #v_weight, #v_hr, #v_rr').filter(function () { return !$(this).val(); }).first();
                        if ($vital.length && $vital[0].scrollIntoView) {
                            $vital[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
                            $vital.focus();
                        }
                    }, 250);
                } else {
                    toastr.error(msg);
                }
            });
        });
    });

    function updateStatusBar() {
        var currentIdx = statusOrder.indexOf(aptStatus);
        $('.status-step[data-status]').each(function () {
            var idx = statusOrder.indexOf($(this).data('status'));
            $(this).removeClass('s-done s-active');
            if (idx < currentIdx)  $(this).addClass('s-done');
            if (idx === currentIdx) $(this).addClass('s-active');
        });
        $('.s-connector').each(function (i) {
            $(this).toggleClass('s-done', i < currentIdx);
        });
    }

    // ── Status dropdown ────────────────────────────────
    $('#btn_update_status').on('click', function () {
        var newStatus = $('#apt_status_select').val();
        guardCompletion(newStatus, function () {
            $.ajax({
                url: statusUrl, method: 'POST',
                data: { _token: csrfToken, status: newStatus },
                success: function (res) {
                    if (res.success) {
                        aptStatus = newStatus;
                        toastr.success(res.msg);
                        updateStatusBar();
                        onStatusChanged(newStatus);
                    }
                },
                error: function (xhr) {
                    var msg = (xhr.responseJSON && xhr.responseJSON.msg) || 'Failed to update status';
                    // Item #2 - revert the select since the change was rejected.
                    $('#apt_status_select').val(aptStatus);
                    if (xhr.status === 422 && xhr.responseJSON && xhr.responseJSON.reason === 'no_vitals') {
                        toastr.warning(msg, '', { timeOut: 9000 });
                    } else {
                        toastr.error(msg);
                    }
                }
            });
        });
    });

    // ── Patient Arrived → Waiting ──────────────────────
    $('#btn_patient_arrived').on('click', function () {
        var $btn = $(this);
        $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Updating-');
        $.post(statusUrl, { _token: csrfToken, status: 'waiting' }, function (res) {
            if (res.success) {
                aptStatus = 'waiting';
                $('#apt_status_select').val('waiting');
                toastr.success('Patient marked as arrived - now in waiting list.');
                updateStatusBar();
                $btn.closest('button').remove();
            }
        }).fail(function () {
            toastr.error('Failed to update status');
            $btn.prop('disabled', false).html('<span class="qa-icon" style="background:#0d9488;"><i class="fas fa-door-open"></i></span> Patient Arrived (→ Waiting)');
        });
    });

    // ── Waiting → In Consultation ──────────────────────
    $('#btn_start_consultation').on('click', function () {
        var $btn = $(this);
        $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Starting-');
        $.post(statusUrl, { _token: csrfToken, status: 'in_progress' }, function (res) {
            if (res.success) {
                aptStatus = 'in_progress';
                $('#apt_status_select').val('in_progress');
                toastr.success('Consultation started.');
                updateStatusBar();
                $btn.closest('button').remove();
            }
        }).fail(function () {
            toastr.error('Failed to update status');
            $btn.prop('disabled', false).html('<span class="qa-icon" style="background:#6366f1;"><i class="fas fa-stethoscope"></i></span> Start Consultation');
        });
    });

    // ── Save Consultation ──────────────────────────────
    $('#btn_save_consultation').on('click', function () {
        var $btn = $(this).prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Saving-');
        var extraVitals = {};
        $('.clc-extra-vital').each(function () {
            var k = $(this).data('extra-key');
            if (k) extraVitals[k] = $(this).val();
        });
        $.ajax({
            url: consultUrl, method: 'POST',
            data: {
                _token:              csrfToken,
                extra_vitals:        extraVitals,
                weight_kg:           $('#v_weight').val(),
                temperature:         $('#v_temp').val(),
                heart_rate:          $('#v_hr').val(),
                respiratory_rate:    $('#v_rr').val(),
                systolic_bp:         $('#v_sys').val(),
                diastolic_bp:        $('#v_dia').val(),
                spo2:                $('#v_spo2').val(),
                blood_glucose:       $('#v_glucose').val(),
                bmi:                 $('#v_bmi').val(),
                soap_subjective:          $('#soap_s').val(),
                soap_objective:           $('#soap_o').val(),
                soap_assessment:          $('#soap_a').val(),
                soap_lab_investigations:  $('#soap_labs').val(),
                soap_exam_investigations: $('#soap_exams').val(),
                soap_plan:                $('#soap_p').val(),
                soap_plan_meds_json:      JSON.stringify(planMeds),
                exam_results:             JSON.stringify(window.drmSelectedExams || []),
                // Item #3 - send next-visit fields as NULL when the
                // follow-up toggle is OFF, so the controller doesn't
                // auto-create an appointment the doctor didn't ask for.
                next_visit_date:          $('#nv_schedule_followup').is(':checked') ? ($('#next_visit_date_iso').val() || null) : null,
                next_visit_time:          $('#nv_schedule_followup').is(':checked') ? (($('#next_visit_time').val() || $('#nv_time_manual').val() || '') || null) : null,
                next_visit_duration:      $('#nv_schedule_followup').is(':checked') ? ($('#next_visit_duration').val() || 30) : 30,
                next_visit_doctor_id:     $('#nv_schedule_followup').is(':checked') ? ($('#next_visit_doctor_id').val() || null) : null,
                next_visit_department_id: $('#nv_schedule_followup').is(':checked') ? ($('#next_visit_department_id').val() || null) : null,
                next_visit_reason:        $('#nv_schedule_followup').is(':checked') ? (($('#next_visit_reason').val() || '') || null) : null,
                next_visit_notes:         $('#nv_schedule_followup').is(':checked') ? ($('#next_visit_notes').val() || null) : null,
                next_visit_type_ids:      $('#nv_schedule_followup').is(':checked') ? JSON.stringify($('#next_visit_type_ids').val() || []) : JSON.stringify([]),
                next_visit_reason_ids:    $('#nv_schedule_followup').is(':checked') ? JSON.stringify($('#next_visit_reason_ids').val() || []) : JSON.stringify([]),
                doctor_notes:        ''
            },
            success: function (res) {
                if (!res.success) return;
                toastr.success(res.msg);
                // Controlled meds in the plan → require batch+witness for each
                if (res.pending_controlled && res.pending_controlled.length) {
                    window.drmOpenControlledDispense(res.pending_controlled);
                }
                // Clear draft
                try { localStorage.removeItem(draftKey); } catch(e) {}
                draftDirty = false;
                window.drmServerDirty = false;   // consultation now persisted to the server
                $('#draft_status').removeClass('dirty saved restored')
                    .html('<i class="fa fa-check-circle" style="color:#27ae60;"></i> Saved');
                setTimeout(function() {
                    $('#draft_status').html('<i class="fa fa-circle" style="font-size:6px;"></i> No unsaved changes');
                }, 2500);
                // Save selected conditions to patient profile
                if ($('#chk_add_conditions').is(':checked') && diagConditions.length) {
                    diagConditions.forEach(function(c) {
                        $.post(addCondUrl, { _token: csrfToken, condition_id: c.id, status: 'active' });
                    });
                    toastr.info(diagConditions.length + ' condition(s) added to patient profile.');
                }
                // Auto-create prescription from plan medications
                if (planMeds.length > 0) {
                    var rxItems = planMeds.map(function(m) {
                        return { medicine_id: m.id || null, medicine_name: m.name,
                                 dosage: m.dosage || '', frequency: m.freq || '',
                                 duration_days: m.days || null, note: m.note || '' };
                    });
                    $.ajax({
                        url: rxFromConsultUrl, method: 'POST',
                        data: { _token: csrfToken, appointment_id: {{ $appointment->id }},
                                diagnosis: $('#soap_a').val(), items: rxItems },
                        success: function(rxRes) {
                            if (rxRes.success) {
                                toastr.success('Prescription created automatically.');
                                $('#qa_rx_zone').html(
                                    '<a class="qa-btn" href="'+rxRes.show_url+'">' +
                                    '<span class="qa-icon" style="background:#2980b9;"><i class="fas fa-prescription-bottle-alt"></i></span>' +
                                    'View Prescription</a>' +
                                    '<a class="qa-btn" href="'+rxRes.print_url+'" target="_blank">' +
                                    '<span class="qa-icon" style="background:#34495e;"><i class="fa fa-print"></i></span>' +
                                    'Print Prescription</a>'
                                );
                            }
                        }
                    });
                }
            },
            error:    function () { toastr.error('Failed to save consultation'); },
            complete: function () { $btn.prop('disabled', false).html('<i class="fa fa-save"></i> Save Consultation'); }
        });
    });

    // ── Smart Consultation ─────────────────────────────
    var conditionsUrl = '{{ route('clinic.medical-conditions.search') }}';
    var medicinesUrl  = '{{ route('clinic.medicines.search') }}';
    var addCondUrl    = '{{ route('clinic.patients.conditions.add', $appointment->patient_id) }}';
    var draftKey      = 'clc_apt_draft_{{ $appointment->id }}';
    var draftDirty    = false;
    var diagConditions = [];
    var planMeds       = {!! json_encode(json_decode($appointment->soap_plan_meds_json ?? '[]') ?? []) !!};
    var systemStates   = {};
    var editingMedIdx  = -1;

    // ── DB-driven chip options ────────────────────────────────────────────────
    var soapDataUrl   = '{{ route('clinic.soap-options.data') }}';
    var soapSearchUrl = '{{ route('clinic.soap-options.search') }}';
    var CHIP_LIMIT    = {{ $soapChipLimit ?? 15 }}; // configured in Settings → Module Settings

    // Loaded chip labels per section (only the visible set)
    var visibleChips = { complaint: [], lab_test: [], exam_test: [] };

    // ── Generic chip renderers ────────────────────────────────────────────────
    // Lookup map: label → { id, product_id, price } per kind (lab / exam)
    var invCatalog = { lab: {}, exam: {} };

    function makeInvChip(d, type) {
        var label = (typeof d === 'string') ? d : d.label;
        var meta  = invCatalog[type][label] || {};
        var esc = $('<span>').text(label).html();
        return '<span class="complaint-chip" data-inv="' + esc + '" data-inv-type="' + type + '"'
            + ' data-soap-id="'    + (meta.id || '')        + '"'
            + ' data-product-id="' + (meta.product_id || '') + '"'
            + ' data-price="'      + (meta.price != null ? meta.price : '') + '">'
            + esc + '</span>';
    }
    function makeComplaintChip(label) {
        return '<span class="complaint-chip" data-chip="' + $('<span>').text(label).html() + '">' + $('<span>').text(label).html() + '</span>';
    }

    // ── Load chips from DB (limited to CHIP_LIMIT) ────────────────────────────
    function loadChips(section, $zone, renderer, callback, invKind) {
        $.getJSON(soapDataUrl, { section: section, species: petSpecies }, function (data) {
            // Cache id/product_id/price per label for lab/exam (used by status panel)
            if (invKind) {
                data.forEach(function (d) {
                    invCatalog[invKind][d.label] = { id: d.id, product_id: d.product_id || null, price: d.price != null ? parseFloat(d.price) : null };
                });
            }
            var shown = data.slice(0, CHIP_LIMIT);
            visibleChips[section] = shown.map(function(d){ return d.label; });
            $zone.html(shown.map(function(d){ return renderer(d); }).join(''));
            if (callback) callback();
        });
    }

    function renderLabChips()      { loadChips('lab_test',  $('#lab_chips'),      function(d){ return makeInvChip(d,'lab'); },  function(){ syncInvChips(); drmSyncSelectedExams(); }, 'lab'); }
    function renderExamChips()     { loadChips('exam_test', $('#exam_chips'),      function(d){ return makeInvChip(d,'exam'); }, function(){ syncInvChips(); drmSyncSelectedExams(); }, 'exam'); }
    function renderComplaintChips(){ loadChips('complaint', $('#complaint_chips'), function(d){ return makeComplaintChip(d.label); }, syncSubjectiveChips); }

    // ── Frequency: populate select from DB ───────────────────────────────────
    function renderFreqSelect() {
        $.getJSON(soapDataUrl, { section: 'frequency', species: 'all' }, function (data) {
            var cur = $('#plan_freq').val() || 'Once daily';
            var opts = data.map(function(d){
                return '<option value="' + $('<span>').text(d.label).html() + '"' + (d.label === cur ? ' selected' : '') + '>' + $('<span>').text(d.label).html() + '</option>';
            }).join('');
            $('#plan_freq').html(opts);
        });
    }

    // ── Inv chip click ────────────────────────────────────────────────────────
    $(document).on('click', '[data-inv]', function() {
        var item = $(this).data('inv');
        var type = $(this).data('inv-type');
        var $ta  = (type === 'lab') ? $('#soap_labs') : $('#soap_exams');
        if ($(this).hasClass('active')) {
            $(this).removeClass('active');
            var val = $ta.val().replace('\n- ' + item, '').replace('- ' + item, '').trim();
            $ta.val(val);
        } else {
            $(this).addClass('active');
            var cur = $ta.val().trim();
            $ta.val(cur ? cur + '\n- ' + item : '- ' + item);
        }
        autoGrow($ta[0]);
        markDirty();
        drmSyncSelectedExams();
    });

    // ═════════════════════════════════════════════════════════
    //  EXAM STATUS PANEL  (tasks 1.2 + 1.5)
    // ═════════════════════════════════════════════════════════
    @php
        // Pre-compute exam status, vaccination cost, and consultation cost server-side
        // so the Blade @json() directive only handles simple scalars/arrays.
        $drmExamResults     = $appointment->exam_results ?? [];
        $drmCurrencySymbol  = session('business.currency_symbol', '');

        // Treatments cost (administered, non-waived) - sums product variation prices
        $drmTreatmentCost = 0;
        if (\Illuminate\Support\Facades\Schema::hasColumn('clc_vaccine_types', 'product_id')) {
            foreach (($appointment->vaccinations ?? collect()) as $_v) {
                if (($_v->status ?? '') !== 'administered' || ($_v->waived_at ?? null)) continue;
                $_vt = $_v->vaccineType ?? null;
                if (!$_vt || empty($_vt->product_id)) continue;
                $_vr = \DB::table('variations')
                    ->where('product_id', $_vt->product_id)
                    ->whereNull('deleted_at')->orderBy('id')
                    ->first(['default_sell_price']);
                if ($_vr) $drmTreatmentCost += (float) $_vr->default_sell_price;
            }
        }
        $drmTreatmentCost = round($drmTreatmentCost, 2);

        // Visit/consultation cost - doctor's specific consultation_fee wins;
        // fall back to the linked product's default_sell_price only when fee is 0/null.
        // (Mirrors the POS bridge: doctor.consultation_fee overrides product price.)
        $drmConsultationCost = 0;
        $_doc = $appointment->doctor ?? null;
        if ($_doc) {
            $_docFee = (float) ($_doc->consultation_fee ?? 0);
            if ($_docFee > 0) {
                $drmConsultationCost = $_docFee;
            } else {
                $_docProductId = \Illuminate\Support\Facades\Schema::hasColumn('clc_doctors', 'product_id') ? ($_doc->product_id ?? null) : null;
                if ($_docProductId) {
                    $_vr = \DB::table('variations')
                        ->where('product_id', $_docProductId)
                        ->whereNull('deleted_at')->orderBy('id')
                        ->first(['default_sell_price']);
                    if ($_vr) $drmConsultationCost = (float) $_vr->default_sell_price;
                }
            }
        }
    @endphp
    window.drmSelectedExams      = @json($drmExamResults);
    window.drmExamNotDoneReasons = [];
    window.drmCurrencySymbol     = @json($drmCurrencySymbol);
    window.drmTreatmentCost    = @json($drmTreatmentCost);
    window.drmConsultationCost   = @json($drmConsultationCost);

    function drmGenKey() { return 'x_' + Date.now() + '_' + Math.floor(Math.random() * 9999); }

    // Parse current "- label" lines from a textarea
    function drmParseTextareaItems($ta) {
        return ($ta.val() || '').split('\n')
            .map(function (l) { return l.replace(/^-\s*/, '').trim(); })
            .filter(Boolean);
    }

    // Rebuild drmSelectedExams from current textareas + existing JSON (preserve status/result/etc.)
    function drmSyncSelectedExams() {
        var labs  = drmParseTextareaItems($('#soap_labs'));
        var exams = drmParseTextareaItems($('#soap_exams'));
        var nextList = [];

        function upsert(label, kind) {
            // Always refresh product_id / price / soap_option_id from the catalogue cache,
            // so newly-linked products on existing exam_results entries propagate without
            // requiring the user to re-add the chip.
            var meta = invCatalog[kind][label] || {};
            var existing = (window.drmSelectedExams || []).filter(function (x) {
                return x.label === label && x.kind === kind;
            })[0];
            if (existing) {
                if (meta.id)         existing.soap_option_id = meta.id;
                if (meta.product_id) existing.product_id     = meta.product_id;
                if (meta.price != null) existing.price       = meta.price;
                nextList.push(existing);
                return;
            }
            nextList.push({
                key:            drmGenKey(),
                soap_option_id: meta.id || null,
                label:          label,
                kind:           kind,
                status:         'pending',
                result_text:    '',
                document_id:    null,
                reason_id:      null,
                reason_note:    '',
                product_id:     meta.product_id || null,
                price:          meta.price != null ? meta.price : null
            });
        }
        labs.forEach(function (l) { upsert(l, 'lab'); });
        exams.forEach(function (l) { upsert(l, 'exam'); });

        window.drmSelectedExams = nextList;
        drmRenderExamStatus();
        drmUpdateEstimatedCost();
    }

    function drmRenderExamStatus() {
        var list = window.drmSelectedExams || [];
        var $card = $('#exam_status_card');
        var $list = $('#exam_results_list');

        if (!list.length) {
            $card.hide();
            $('#exam_status_summary').text('');
            return;
        }
        $card.show();
        var doneCount = list.filter(function(x){ return x.status === 'done'; }).length;
        var notDoneCount = list.filter(function(x){ return x.status === 'not_done'; }).length;
        $('#exam_status_summary').html(
            list.length + ' item(s) - '
            + '<span style="color:#16a34a;">' + doneCount + ' done</span> - '
            + '<span style="color:#ef4444;">' + notDoneCount + ' not done</span>'
        );

        var html = '';
        list.forEach(function (x, idx) {
            var kindIcon  = x.kind === 'lab' ? 'fa-flask' : 'fa-stethoscope';
            var kindColor = x.kind === 'lab' ? '#16a085' : '#0ea5e9';
            var priceTag  = x.price != null
                ? '<span style="font-size:10.5px;color:#64748b;background:#f1f5f9;padding:1px 7px;border-radius:9px;">'
                  + window.drmCurrencySymbol + ' ' + (x.price).toFixed(2) + '</span>'
                : '<span style="font-size:10px;color:#94a3b8;font-style:italic;">no product linked</span>';
            html += '<div class="vc-exam-row" data-idx="' + idx + '"'
                + ' style="display:flex;flex-direction:column;gap:7px;padding:10px 12px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:7px;margin-bottom:8px;">';
            html += '<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">';
            html += '<i class="fa ' + kindIcon + '" style="color:' + kindColor + ';"></i>';
            html += '<strong style="font-size:13px;color:#1e293b;">' + $('<span>').text(x.label).html() + '</strong>';
            html += priceTag;
            html += '<div class="btn-group" style="margin-left:auto;">';
            ['pending','done','not_done'].forEach(function (s) {
                var label = s === 'pending' ? 'Pending' : (s === 'done' ? 'Done' : 'Not Done');
                var cls = x.status === s
                    ? (s === 'done' ? 'btn-success' : (s === 'not_done' ? 'btn-danger' : 'btn-warning'))
                    : 'btn-default';
                html += '<button type="button" class="btn btn-xs ' + cls + ' vc-exam-status-btn"'
                    + ' data-idx="' + idx + '" data-status="' + s + '">' + label + '</button>';
            });
            html += '</div></div>';

            if (x.status === 'done') {
                html += '<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:flex-start;">';
                html += '<textarea class="form-control vc-exam-result" data-idx="' + idx + '" rows="2"'
                    + ' style="flex:1;min-width:240px;font-size:12px;" placeholder="Result / findings-">'
                    + $('<span>').text(x.result_text || '').html() + '</textarea>';
                html += '<label class="btn btn-xs btn-info" style="margin:0;cursor:pointer;height:30px;display:inline-flex;align-items:center;gap:5px;">'
                    + '<i class="fa fa-paperclip"></i> Upload'
                    + '<input type="file" class="vc-exam-doc-upload" data-idx="' + idx + '" style="display:none;">'
                    + '</label>';
                if (x.document_id) {
                    html += '<a class="btn btn-xs btn-default" href="{{ url('storage') }}/' + (x.document_path || '') + '" target="_blank" title="View attached document">'
                        + '<i class="fa fa-file"></i> Doc</a>';
                }
                html += '</div>';
            } else if (x.status === 'not_done') {
                html += '<div style="display:flex;gap:6px;flex-wrap:wrap;">';
                html += '<select class="form-control vc-exam-reason" data-idx="' + idx + '" style="flex:1;min-width:180px;font-size:12px;height:30px;"><option value="">- Select reason -</option></select>';
                html += '<input type="text" class="form-control vc-exam-reason-note" data-idx="' + idx + '"'
                    + ' style="flex:2;min-width:200px;font-size:12px;height:30px;" placeholder="Note (optional)-"'
                    + ' value="' + $('<span>').text(x.reason_note || '').html() + '">';
                html += '</div>';
            }
            html += '</div>';
        });
        $list.html(html);

        // Populate reason selects
        if (window.drmExamNotDoneReasons.length) {
            $list.find('.vc-exam-reason').each(function () {
                var idx = $(this).data('idx');
                var current = (window.drmSelectedExams[idx] || {}).reason_id || '';
                var $sel = $(this);
                window.drmExamNotDoneReasons.forEach(function (r) {
                    $sel.append('<option value="' + r.id + '"' + (String(current) === String(r.id) ? ' selected' : '') + '>' + $('<span>').text(r.name).html() + '</option>');
                });
            });
        }
    }

    // Status button click
    $(document).on('click', '.vc-exam-status-btn', function () {
        var idx = $(this).data('idx');
        var s   = $(this).data('status');
        if (!window.drmSelectedExams[idx]) return;
        window.drmSelectedExams[idx].status = s;
        if (s !== 'done')      { window.drmSelectedExams[idx].result_text = ''; window.drmSelectedExams[idx].document_id = null; }
        if (s !== 'not_done')  { window.drmSelectedExams[idx].reason_id = null; window.drmSelectedExams[idx].reason_note = ''; }
        drmRenderExamStatus();
        drmUpdateEstimatedCost();
        markDirty();
    });

    // Result / reason / note input
    $(document).on('input change', '.vc-exam-result, .vc-exam-reason, .vc-exam-reason-note', function () {
        var idx = $(this).data('idx');
        if (!window.drmSelectedExams[idx]) return;
        if ($(this).hasClass('vc-exam-result'))       window.drmSelectedExams[idx].result_text = $(this).val();
        if ($(this).hasClass('vc-exam-reason'))       window.drmSelectedExams[idx].reason_id   = $(this).val() ? parseInt($(this).val()) : null;
        if ($(this).hasClass('vc-exam-reason-note'))  window.drmSelectedExams[idx].reason_note = $(this).val();
        markDirty();
    });

    // Per-exam document upload - reuse the existing document-upload endpoint then link back
    $(document).on('change', '.vc-exam-doc-upload', function () {
        var idx = $(this).data('idx');
        var file = this.files[0];
        if (!file || !window.drmSelectedExams[idx]) return;
        var item = window.drmSelectedExams[idx];
        var fd = new FormData();
        fd.append('_token', csrfToken);
        fd.append('file', file);
        fd.append('document_name', item.label + ' - result');
        fd.append('document_type', 'lab_result');
        fd.append('description', 'Auto-attached from consultation exam: ' + item.label);

        toastr.info('Uploading-');
        $.ajax({
            url: '{{ url('clinic/appointments') }}/{{ $appointment->id }}/documents', method: 'POST',
            data: fd, processData: false, contentType: false,
            success: function (res) {
                if (res.success && res.doc) {
                    item.document_id   = res.doc.id;
                    item.document_path = res.doc.url ? res.doc.url.split('storage/')[1] : null;
                    toastr.success('Uploaded and linked.');
                    markDirty();
                    drmRenderExamStatus();
                }
            },
            error: function () { toastr.error('Upload failed.'); }
        });
    });

    // ── Estimated visit cost label (task 1.5) ──────────────────
    function drmUpdateEstimatedCost() {
        var examsTotal = 0;
        (window.drmSelectedExams || []).forEach(function (x) {
            if (x.status !== 'not_done' && x.price != null) examsTotal += parseFloat(x.price) || 0;
        });
        var total = examsTotal + (parseFloat(window.drmTreatmentCost) || 0) + (parseFloat(window.drmConsultationCost) || 0);
        var sym = window.drmCurrencySymbol || '';
        $('#estimated_cost_value').text(sym + ' ' + total.toFixed(2));
        var bd = [];
        if (window.drmConsultationCost) bd.push('Visit ' + (parseFloat(window.drmConsultationCost)).toFixed(2));
        if (examsTotal)                bd.push('Exams ' + examsTotal.toFixed(2));
        if (window.drmTreatmentCost)  bd.push('Vax ' + (parseFloat(window.drmTreatmentCost)).toFixed(2));
        $('#estimated_cost_breakdown').text(bd.length ? '(' + bd.join(' - ') + ')' : '');
        $('#estimated_cost_badge').css('display', total > 0 ? 'inline-flex' : 'none');
    }

    // Load reasons once, then init panel
    $.getJSON('{{ route('clinic.exam-not-done-reasons.list') }}', function (data) {
        window.drmExamNotDoneReasons = data || [];
        drmRenderExamStatus();
    });
    drmUpdateEstimatedCost();

    // ═════════════════════════════════════════════════════════
    //  Next Visit (task 1.3 - enhanced)
    //  Slot picker mirrors the create-appointment flow.
    // ═════════════════════════════════════════════════════════

    // Item #3 - opt-in toggle for the entire next-visit form.
    $('#nv_schedule_followup').on('change', function () {
        var on = this.checked;
        $('#nv_form_body').toggle(on);
        $('#nv_skip_note').toggle(!on);
        if (typeof markDirty === 'function') markDirty();
    });

    var NV_SLOTS_URL = '{{ route('clinic.appointments.slots') }}';
    var NV_DOCTOR_SCHEDULE_URL_BASE = '{{ url('clinic/doctors') }}';
    var nvSelectedSlots = [];      // sequence of selected consecutive slots
    var nvAllowedDays   = null;    // null = no doctor → no constraint, else Set of 0-6
    var nvDayNames      = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];

    // Select2 for type & reason (matching create-appointment UX)
    if ($('#next_visit_type_ids').length) {
        $('#next_visit_type_ids').select2({ width:'100%', placeholder:'- Select type(s) -' })
            .on('change', markDirty);
    }
    if ($('#next_visit_reason_ids').length) {
        $('#next_visit_reason_ids').select2({ width:'100%', placeholder:'- Select reason(s) -' })
            .on('change', markDirty);
    }

    // Datepicker - disables days the selected doctor isn't scheduled for
    function nvInitDatepicker() {
        try { $('#next_visit_date_display').datepicker('destroy'); } catch (e) {}
        $('#next_visit_date_display').datepicker({
            todayHighlight: true, autoclose: true, format: datepicker_date_format,
            beforeShowDay: function (date) {
                if (!nvAllowedDays || !nvAllowedDays.size) {
                    // No doctor selected yet → allow everything (slot picker will handle the rest)
                    return true;
                }
                if (nvAllowedDays.has(date.getDay())) {
                    return { enabled: true };
                }
                return { enabled: false, classes: 'nv-day-off', tooltip: 'Doctor not scheduled on ' + nvDayNames[date.getDay()] };
            }
        }).on('changeDate', function (e) {
            if (e.dates.length) {
                var d = e.dates[0];
                $('#next_visit_date_iso').val(
                    d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0')
                );
                markDirty();
                nvLoadSlots();
            }
        });
        // Re-apply current value to redraw the calendar
        var iso = $('#next_visit_date_iso').val();
        if (iso) {
            var p = iso.split('-');
            $('#next_visit_date_display').datepicker('setDate', new Date(+p[0], +p[1]-1, +p[2]));
        }
    }
    nvInitDatepicker();
    var nvExisting = $('#next_visit_date_iso').val();

    // Fetch the chosen doctor's allowed weekdays and rebuild datepicker
    function nvRefreshAllowedDays() {
        var docId = $('#next_visit_doctor_id').val();
        var deptId = $('#next_visit_department_id').val();
        if (!docId) {
            nvAllowedDays = null;
            nvInitDatepicker();
            $('#nv_pre_doctor_hint').text('Pick doctor + date to see available slots.').show();
            return;
        }
        $('#nv_pre_doctor_hint').text('Loading doctor schedule-').show();
        $.getJSON(NV_DOCTOR_SCHEDULE_URL_BASE + '/' + docId + '/schedules', function (rows) {
            var days = new Set();
            (rows || []).forEach(function (s) {
                if ((s.status || 'active') !== 'active') return;
                // If a department is picked, restrict to schedules matching it (or generic - null department_id)
                if (deptId && s.department_id && String(s.department_id) !== String(deptId)) return;
                days.add(parseInt(s.day_of_week));
            });
            nvAllowedDays = days;

            if (!days.size) {
                $('#nv_pre_doctor_hint').html('<i class="fa fa-exclamation-triangle text-warning"></i> This doctor has no active schedule' + (deptId ? ' in this department.' : '.') + ' Please add one in the Doctors page or pick another doctor.').show();
            } else {
                var labels = Array.from(days).sort().map(function (d) { return nvDayNames[d]; }).join(', ');
                $('#nv_pre_doctor_hint').html('Available days: <strong>' + labels + '</strong>. Pick one to see time slots.').show();
            }

            // Validate currently-picked date against the new allowed set
            var iso = $('#next_visit_date_iso').val();
            if (iso && days.size) {
                var parts = iso.split('-');
                var picked = new Date(+parts[0], +parts[1]-1, +parts[2]);
                if (!days.has(picked.getDay())) {
                    // Clear it - the user must reselect a valid day
                    $('#next_visit_date_iso').val('');
                    $('#next_visit_date_display').val('');
                    toastr.warning('Previously-picked date is not in the new doctor\'s schedule - please pick a highlighted day.');
                    nvResetSlots();
                }
            }
            nvInitDatepicker();
        }).fail(function () {
            nvAllowedDays = null;
            nvInitDatepicker();
        });
    }

    // When doctor or department change, refresh allowed days + slots
    $('#next_visit_doctor_id, #next_visit_department_id').on('change', function () {
        markDirty();
        nvRefreshAllowedDays();
        nvLoadSlots();
    });
    $('#next_visit_notes, #next_visit_reason').on('input change', markDirty);
    $('#nv_time_manual').on('input change', function () {
        $('#next_visit_time').val($(this).val());
        markDirty();
    });

    function nvResetSlots() {
        nvSelectedSlots = [];
        $('#nv_slot_grid').html('');
        $('#nv_slot_picker').hide();
        $('#nv_manual_time_group').hide();
        $('#nv_overlap_alert').hide();
        $('#nv_selection_summary').hide();
        $('#nv_pre_doctor_hint').show();
    }

    function nvLoadSlots() {
        var docId = $('#next_visit_doctor_id').val();
        var deptId = $('#next_visit_department_id').val();
        var date   = $('#next_visit_date_iso').val();
        if (!docId || !date) { nvResetSlots(); return; }

        $('#nv_pre_doctor_hint').hide();
        $.getJSON(NV_SLOTS_URL, { doctor_id: docId, department_id: deptId, date: date }, function (slots) {
            nvSelectedSlots = [];
            $('#nv_overlap_alert').hide();
            $('#nv_selection_summary').hide();
            if (!slots || !slots.length) {
                $('#nv_slot_picker').hide();
                $('#nv_manual_time_group').show();
                return;
            }
            $('#nv_manual_time_group').hide();
            var preselectTime = $('#next_visit_time').val(); // e.g. "10:00:00"
            var html = '';
            slots.forEach(function (s) {
                var cls = 'qc-slot-btn';
                if (!s.available)  cls += ' unavailable';
                else if (s.booked) cls += ' booked-warn';
                if (preselectTime && (s.start === preselectTime.substr(0,5) || preselectTime.indexOf(s.start) === 0)) {
                    cls += ' selected';
                }
                html += '<button type="button" class="' + cls + '"'
                    + ' data-start="' + s.start + '" data-end="' + s.end + '"'
                    + ' data-duration="' + s.duration + '"'
                    + ' data-booked="' + (s.booked ? '1' : '0') + '"'
                    + ' data-schedule="' + (s.schedule_id || '') + '"'
                    + (s.available ? '' : ' disabled') + '>'
                    + s.start + ' - ' + s.end
                    + (s.booked && s.available ? ' <i class="fa fa-exclamation-circle" style="font-size:9px;"></i>' : '')
                    + '</button>';
            });
            $('#nv_slot_grid').html(html);
            $('#nv_slot_picker').show();
        }).fail(function () {
            $('#nv_slot_picker').hide();
            $('#nv_manual_time_group').show();
        });
    }

    function nvSlotFrom($btn) {
        return {
            start:       $btn.data('start'),
            end:         $btn.data('end'),
            duration:    parseInt($btn.data('duration')) || 30,
            schedule_id: $btn.data('schedule') || '',
            booked:      $btn.data('booked') == '1',
        };
    }

    function nvApplySelection() {
        $('#nv_slot_grid .qc-slot-btn').removeClass('selected');
        if (!nvSelectedSlots.length) {
            $('#next_visit_time').val('');
            $('#next_visit_schedule_id').val('');
            $('#next_visit_duration').val(30);
            $('#nv_overlap_alert').hide();
            $('#nv_selection_summary').hide();
            return;
        }
        var first = nvSelectedSlots[0].start;
        var last  = nvSelectedSlots[nvSelectedSlots.length - 1].end;
        var dur   = nvSelectedSlots[0].duration;
        var total = nvSelectedSlots.length * dur;
        var anyBooked = nvSelectedSlots.some(function (s) { return s.booked; });

        $('#nv_slot_grid .qc-slot-btn').each(function () {
            var s = $(this).data('start'), e = $(this).data('end');
            if (s >= first && e <= last) $(this).addClass('selected');
        });

        $('#next_visit_time').val(first);
        $('#next_visit_schedule_id').val(nvSelectedSlots[0].schedule_id || '');
        $('#next_visit_duration').val(total);
        if (anyBooked) $('#nv_overlap_alert').show(); else $('#nv_overlap_alert').hide();
        $('#nv_selection_detail').html('<strong>' + first + ' - ' + last + '</strong> &nbsp;(' + total + ' min)'
            + (anyBooked ? ' &nbsp;<span class="label label-warning">Overlap</span>' : ''));
        $('#nv_selection_summary').show();
        markDirty();
    }

    $(document).on('click', '#nv_slot_grid .qc-slot-btn:not([disabled])', function () {
        var $btn = $(this);
        var clickedStart = $btn.data('start');
        if (!nvSelectedSlots.length) {
            nvSelectedSlots = [nvSlotFrom($btn)];
        } else {
            var last = nvSelectedSlots[nvSelectedSlots.length - 1];
            if (last.end === clickedStart) {
                nvSelectedSlots.push(nvSlotFrom($btn));
            } else if (nvSelectedSlots[0].start === clickedStart && nvSelectedSlots.length === 1) {
                nvSelectedSlots = [];
            } else {
                nvSelectedSlots = [nvSlotFrom($btn)];
            }
        }
        nvApplySelection();
    });

    // First load: if a doctor is already selected (default), populate allowed days + slots
    if ($('#next_visit_doctor_id').val()) {
        nvRefreshAllowedDays();
        if (nvExisting) nvLoadSlots();
    }

    // After save, surface the auto-created next appointment id
    $(document).ajaxSuccess(function (event, xhr, settings) {
        if (settings.url && settings.url.indexOf('/save-consultation') > -1) {
            try {
                var res = xhr.responseJSON;
                if (res && res.next_appointment_id) {
                    toastr.info('Next appointment auto-scheduled.');
                }
            } catch (e) {}
        }
    });

    // ── Complaint chip click ──────────────────────────────────────────────────
    $(document).on('click', '.complaint-chip:not(.freq-chip)', function() {
        if ($(this).data('freq') !== undefined) return; // skip freq chips
        var chip = $(this).data('chip');
        if (!chip) return;
        var $ta  = $('#soap_s');
        if ($(this).hasClass('active')) {
            $(this).removeClass('active');
            var val = $ta.val().replace('\n- ' + chip + '.', '').replace('- ' + chip + '.', '').trim();
            $ta.val(val);
        } else {
            $(this).addClass('active');
            var cur = $ta.val().trim();
            $ta.val(cur ? cur + '\n- ' + chip + '.' : '- ' + chip + '.');
        }
        autoGrow($ta[0]);
        markDirty();
    });

    // ── Generic "search more" for any chip zone ───────────────────────────────
    function setupSearchMore(inputId, resultsId, section, $zone, chipMaker, onSelect) {
        var $inp = $('#' + inputId);
        var $res = $('#' + resultsId);
        var timer;

        $inp.on('input', function () {
            clearTimeout(timer);
            var q = $(this).val().trim();
            if (!q) { $res.hide().empty(); return; }
            timer = setTimeout(function () {
                $.getJSON(soapSearchUrl, { section: section, species: petSpecies, q: q }, function (data) {
                    if (!data.length) { $res.html('<div style="padding:8px 12px;color:#94a3b8;font-size:12px;">No results</div>').show(); return; }
                    var html = data.map(function(d){
                        var alreadyVisible = (visibleChips[section] || []).indexOf(d.label) !== -1;
                        var indicator = alreadyVisible
                            ? '<span style="margin-left:auto;font-size:11px;color:#22c55e;font-weight:700;">&#10003; visible</span>'
                            : '';
                        var style = 'padding:7px 12px;font-size:12.5px;cursor:pointer;border-bottom:1px solid #f0f0f0;display:flex;align-items:center;gap:6px;'
                                  + (alreadyVisible ? 'background:#f0fdf4;' : '');
                        return '<div class="soap-search-result" data-label="' + $('<span>').text(d.label).html() + '" data-visible="' + (alreadyVisible ? '1' : '0') + '" style="' + style + '">'
                             + '<span>' + $('<span>').text(d.label).html() + '</span>'
                             + indicator
                             + '</div>';
                    }).join('');
                    $res.html(html).show();
                });
            }, 250);
        });

        $(document).on('click', '#' + resultsId + ' .soap-search-result', function () {
            var label   = $(this).data('label');
            var visible = $(this).data('visible') === 1 || $(this).data('visible') === '1';
            $res.hide().empty();
            $inp.val('');
            if (visible) {
                // Chip already in zone - just activate it and call onSelect to update textarea
                onSelect(label, $zone);
            } else {
                // New chip - add to zone and activate
                visibleChips[section].push(label);
                $zone.append(chipMaker(label));
                onSelect(label, $zone);
            }
        });

        // Hide on outside click
        $(document).on('click', function(e) {
            if (!$(e.target).closest('#' + inputId + ', #' + resultsId).length) {
                $res.hide();
            }
        });
    }

    // Body systems - per-system icon map; group-level icons defined in groupMeta
    var sysIconMap = {
        'cardiovascular':   'fa-heart',
        'respiratory':      'fa-lungs',
        'gastrointestinal': 'fa-procedures',
        'musculoskeletal':  'fa-bone',
        'neurological':     'fa-brain',
        'dermatological':   'fa-hand-paper',
        'ophthalmological': 'fa-eye',
        'ears / ent':       'fa-deaf',
        'urogenital':       'fa-tint',
        'lymphatic':        'fa-circle',
        'endocrine':        'fa-flask',
        'dental / oral':    'fa-tooth',
        'reproductive':     'fa-venus-mars',
        'urinary':          'fa-tint',
    };

    // Group appearance - must match settings page definition
    var groupMeta = {
        'Cardio-Respiratory':      { color: '#ef4444', bg: '#fee2e2', icon: 'fa-heartbeat' },
        'Digestive & Metabolic':   { color: '#f59e0b', bg: '#fef3c7', icon: 'fa-procedures' },
        'Musculo-Neurological':    { color: '#6366f1', bg: '#e0e7ff', icon: 'fa-brain' },
        'Integumentary & Sensory': { color: '#8b5cf6', bg: '#ede9fe', icon: 'fa-eye' },
        'Urogenital & Lymphatic':  { color: '#14b8a6', bg: '#ccfbf1', icon: 'fa-tint' },
    };
    var defaultGroupMeta = { color: '#64748b', bg: '#f1f5f9', icon: 'fa-circle' };

    var bodySystems = []; // populated from DB

    // ─────────────────────────────────────────────────────────────────
    // Per-system finding catalogues — modelled on the Dr Paws Animal
    // Hospital paper exam-report layout. Each finding entry supports:
    //   value       - persisted machine key
    //   label       - display text
    //   severity    - true => show Mild/Moderate/Severe radio
    //   lr          - true => show L/R toggle (lateralisation)
    //   critical    - true => any positive selection flags the visit
    //                 into the Medical Critical Check report (item 4.1)
    //
    // Keys are matched against sys.key (snake_case of the system label
    // from the clc_soap_options table). Multiple alias keys are listed
    // per system so different label spellings still hit the catalogue.
    // Future work: move to a DB table so per-clinic catalogues can be
    // edited from the settings UI without code changes.
    // ─────────────────────────────────────────────────────────────────
    var systemFindings = {
        heart: [
            // Item 1.4 Phases B-D — Cardiology specialist catalogue
            { value: 'bradycardia',  label: 'Bradycardia (slow)',           critical: true  },
            { value: 'tachycardia',  label: 'Tachycardia (fast)',           critical: true  },
            { value: 'murmur',       label: 'Murmur',                       severity: true, lr: true, critical: true },
            { value: 'murmur_grade1', label: 'Murmur grade I/VI (faint)' },
            { value: 'murmur_grade3', label: 'Murmur grade III/VI (moderate)',           severity: true },
            { value: 'murmur_grade5', label: 'Murmur grade V/VI (palpable thrill)',                     critical: true },
            { value: 'arrhythmia',   label: 'Arrhythmia',                                  critical: true  },
            { value: 'gallop',       label: 'Gallop rhythm',                              critical: true  },
            { value: 'pulse_weak',   label: 'Weak / thready pulse',                       critical: true  },
            { value: 'pulse_async',  label: 'Asynchronous pulse / deficit',               critical: true  },
            { value: 'pulse_bounding', label: 'Bounding / water-hammer pulse' },
            { value: 'jugular_dist',  label: 'Jugular distention',                                       critical: true },
            { value: 'hepato_jug',    label: 'Hepatojugular reflux positive',                            critical: true },
            { value: 'pmi_shifted',   label: 'Point of maximal intensity shifted' },
        ],
        lungs: [
            { value: 'crackles',     label: 'Crackles',                                    critical: true  },
            { value: 'wheezes',      label: 'Wheezes' },
            { value: 'rapid_breath', label: 'Rapid breathing' },
            { value: 'coughing',     label: 'Coughing' },
            { value: 'tracheitis',   label: 'Tracheitis' },
            { value: 'congestion',   label: 'Congestion' },
            { value: 'labored',      label: 'Laboured breathing / dyspnea',                critical: true  },
            { value: 'discharge',    label: 'Discharge' },
            { value: 'lung_absent',  label: 'Absent / muffled sounds',                     critical: true  },
        ],
        eyes: [
            // Item 1.4 Phases B-D — Ophthalmology specialist catalogue
            { value: 'discharge',     label: 'Discharge',           severity: true, lr: true },
            { value: 'entropion',     label: 'Entropion',                            lr: true },
            { value: 'ectropion',     label: 'Ectropion',                            lr: true },
            { value: 'corneal_ulcer', label: 'Corneal ulcer',                        lr: true, critical: true },
            { value: 'corneal_edema', label: 'Corneal edema',       severity: true, lr: true },
            { value: 'cataracts',     label: 'Cataracts',           severity: true, lr: true },
            { value: 'lid_growth',    label: 'Lid growth / mass',                    lr: true },
            { value: 'conjunctivitis',label: 'Conjunctivitis',     severity: true, lr: true },
            { value: 'kcs_dry_eye',   label: 'Dry eye (KCS suspect)',                lr: true },
            { value: 'schirmer_low',  label: 'Schirmer < 10 mm/min',                  lr: true, critical: true },
            { value: 'iop_high',      label: 'IOP elevated (>25 mmHg)',               lr: true, critical: true },
            { value: 'iop_low',       label: 'IOP low (<10 mmHg)',                    lr: true, critical: true },
            { value: 'fluorescein_pos',label: 'Fluorescein stain positive',           lr: true, critical: true },
            { value: 'lens_lux',      label: 'Lens luxation / subluxation',           lr: true, critical: true },
            { value: 'fundic_abnorm', label: 'Fundic exam abnormal',                  lr: true },
            { value: 'retinal_detach',label: 'Retinal detachment',                    lr: true, critical: true },
            { value: 'third_eyelid',  label: 'Third eyelid prominence',               lr: true },
            { value: 'plr_abnormal',  label: 'PLR abnormal',                         lr: true, critical: true },
            { value: 'anisocoria',    label: 'Anisocoria',                                     critical: true },
        ],
        ears: [
            { value: 'scaly_border', label: 'Scaly border',                lr: true },
            { value: 'ear_mites',    label: 'Ear mites',                   lr: true },
            { value: 'excess_hair',  label: 'Excess hair',                 lr: true },
            { value: 'excess_wax',   label: 'Excess wax',                  lr: true },
            { value: 'exudate',      label: 'Exudate',     severity: true, lr: true },
            { value: 'inflamed',     label: 'Inflamed',    severity: true, lr: true },
            { value: 'pus',          label: 'Pus / purulent',              lr: true, critical: true },
            { value: 'pain',         label: 'Pain on palpation', severity: true, lr: true },
            { value: 'mass',         label: 'Mass / growth',               lr: true },
        ],
        nose_throat: [
            { value: 'dry_crusty',     label: 'Dry / crusty' },
            { value: 'enl_tonsils',    label: 'Enlarged tonsils' },
            { value: 'enl_lymph',      label: 'Enlarged lymph glands',          critical: true },
            { value: 'nose_discharge', label: 'Nasal discharge', severity: true, lr: true },
            { value: 'mass',           label: 'Mass / growth',                   critical: true },
        ],
        mouth_teeth_gums: [
            // Item 1.4 Phases B-D — Dental specialist catalogue
            { value: 'loose_tooth',    label: 'Loose tooth',     severity: true },
            { value: 'broken_tooth',   label: 'Broken tooth' },
            { value: 'stained_tooth',  label: 'Stained tooth' },
            { value: 'pale_membranes', label: 'Pale mucous membranes',            critical: true },
            { value: 'cyanotic_mm',    label: 'Cyanotic mucous membranes',        critical: true },
            { value: 'icteric_mm',     label: 'Icteric mucous membranes',         critical: true },
            { value: 'tartar',         label: 'Tartar',      severity: true },
            { value: 'gingivitis',     label: 'Gingivitis',  severity: true },
            { value: 'periodontitis',  label: 'Periodontitis (pocket >3mm)',                 critical: true },
            { value: 'furcation',      label: 'Furcation exposure' },
            { value: 'tooth_mobility', label: 'Tooth mobility grade 2-3',                    critical: true },
            { value: 'missing_teeth',  label: 'Missing teeth (record numbers in notes)' },
            { value: 'malocclusion',   label: 'Malocclusion' },
            { value: 'halitosis',      label: 'Halitosis',   severity: true },
            { value: 'mass_ulcer',     label: 'Mass / ulcer',                     critical: true },
            { value: 'feline_resorp',  label: 'Feline tooth resorption lesion',              critical: true },
            { value: 'recommend_dental',label: 'Dental cleaning recommended' },
        ],
        abdomen: [
            { value: 'mass',           label: 'Abdominal mass',                   critical: true },
            { value: 'ascites',        label: 'Ascites (fluid)',                  critical: true },
            { value: 'tense',          label: 'Tense / painful' },
            { value: 'hernia_umb',     label: 'Umbilical hernia' },
            { value: 'hernia_ing',     label: 'Inguinal hernia' },
            { value: 'hepatomegaly',   label: 'Hepatomegaly' },
            { value: 'splenomegaly',   label: 'Splenomegaly' },
            { value: 'bladder_pain',   label: 'Bladder pain / distention',        critical: true },
        ],
        musculoskeletal: [
            { value: 'pain',           label: 'Pain',          severity: true, lr: true },
            { value: 'lameness',       label: 'Lameness',      severity: true, lr: true },
            { value: 'nwb',            label: 'Non-weight-bearing',               critical: true, lr: true },
            { value: 'nail_problem',   label: 'Nail problem',                   lr: true },
            { value: 'joint_problem',  label: 'Joint problem(s)',                lr: true },
            { value: 'muscle_atrophy', label: 'Muscle atrophy',                  lr: true },
            { value: 'swelling',       label: 'Swelling',                        lr: true },
        ],
        skin_hair_coat: [
            // Item 1.4 Phases B-D — Clinic specialist findings
            { value: 'dull_dry',       label: 'Dull / dry coat' },
            { value: 'shedding',       label: 'Excess shedding' },
            { value: 'scaly',          label: 'Scaly' },
            { value: 'scabs',          label: 'Scabs' },
            { value: 'hair_loss',      label: 'Hair loss / alopecia', severity: true, lr: true },
            { value: 'matted',         label: 'Matted' },
            { value: 'hot_spot',       label: 'Hot spot',                         lr: true },
            { value: 'staph',          label: 'Staph infection (pyoderma)', severity: true },
            { value: 'abscess',        label: 'Abscess',                          critical: true, lr: true },
            { value: 'cyst',           label: 'Cyst',                             lr: true },
            { value: 'fleas',          label: 'Fleas' },
            { value: 'ticks',          label: 'Ticks' },
            { value: 'mange',          label: 'Mange / mites' },
            { value: 'tumor',          label: 'Tumor / mass',                     critical: true, lr: true },
            { value: 'wart',           label: 'Wart',                             lr: true },
            { value: 'pruritus',       label: 'Excessive licking / pruritus', severity: true },
            // Phase B-D additions:
            { value: 'papules',        label: 'Papules / pustules',  severity: true },
            { value: 'erythema',       label: 'Erythema',            severity: true },
            { value: 'lichenification',label: 'Lichenification' },
            { value: 'hyperpigment',   label: 'Hyperpigmentation' },
            { value: 'malasezzia',     label: 'Suspect Malassezia (greasy / odour)' },
            { value: 'ringworm_susp',  label: 'Suspect dermatophytosis' },
            { value: 'woods_lamp_pos', label: "Wood's lamp positive" },
            { value: 'cytology_done',  label: 'Cytology performed' },
            { value: 'scrape_done',    label: 'Skin scrape performed' },
        ],

        // ── Neurological — Phase B-D deep specialist catalogue ──────
        // Mentation, cranial nerves, postural reactions per limb, spinal
        // reflexes, deep pain. Patterned on standard dermatology neuro
        // exam. Critical flags: any deep-pain loss, obtunded mentation,
        // significant CN deficit -> auto-feeds the Medical Critical
        // Check report (item 4.1).
        neurological: [
            // Mentation
            { value: 'mentation_dull',     label: 'Mentation: dull',           severity: true },
            { value: 'mentation_obtunded', label: 'Mentation: obtunded',                              critical: true },
            { value: 'mentation_stupor',   label: 'Mentation: stupor',                                critical: true },
            { value: 'mentation_coma',     label: 'Mentation: coma',                                  critical: true },
            // Gait / posture
            { value: 'gait_ataxic',        label: 'Ataxic gait',               severity: true },
            { value: 'gait_circling',      label: 'Circling',                                      lr: true },
            { value: 'head_tilt',          label: 'Head tilt',                                      lr: true },
            { value: 'nystagmus',          label: 'Nystagmus',                  severity: true },
            { value: 'tremors',            label: 'Tremors',                    severity: true },
            { value: 'seizure_observed',   label: 'Seizure activity observed',                       critical: true },
            // Postural reactions per limb
            { value: 'pr_deficit',         label: 'Postural reaction deficit',  severity: true, lr: true },
            { value: 'hopping_abnormal',   label: 'Hopping response abnormal',                       lr: true },
            { value: 'pp_deficit',         label: 'Proprioception deficit',     severity: true, lr: true },
            // Spinal reflexes per limb
            { value: 'reflex_hyper',       label: 'Hyperreflexia (UMN sign)',   severity: true, lr: true },
            { value: 'reflex_hypo',        label: 'Hyporeflexia (LMN sign)',    severity: true, lr: true },
            { value: 'crossed_extensor',   label: 'Crossed-extensor reflex',                         lr: true, critical: true },
            // Cranial nerves
            { value: 'cn_facial_paresis',  label: 'Facial paresis (CN VII)',                         lr: true },
            { value: 'cn_horners',         label: 'Horner syndrome',                                 lr: true },
            { value: 'cn_menace_absent',   label: 'Menace response absent (CN II/VII)',              lr: true },
            { value: 'cn_corneal_absent',  label: 'Corneal reflex absent (CN V/VII)',                lr: true },
            // Deep pain (red-flag)
            { value: 'dp_intact',          label: 'Deep pain intact (all limbs)' },
            { value: 'dp_absent',          label: 'Deep pain ABSENT',                                critical: true, lr: true },
            // Autonomic / spinal pain
            { value: 'spinal_pain',        label: 'Spinal palpation pain',     severity: true,                       critical: true },
            { value: 'anal_tone_absent',   label: 'Anal tone absent',                                                  critical: true },
            { value: 'incontinent',        label: 'Urinary / faecal incontinence',                                     critical: true },
        ],
    };

    // Common reasons a system would be marked Not Examined - same shape
    // as findings so the UI re-uses the checkbox renderer.
    var neReasons = [
        { value: 'patient_anxious',  label: 'Patient too anxious / aggressive' },
        { value: 'owner_declined',   label: 'Patient / guardian declined' },
        { value: 'equipment_unavail',label: 'Equipment unavailable' },
        { value: 'time_limited',     label: 'Insufficient time / triage priority' },
        { value: 'sedation_pending', label: 'Sedation pending' },
        { value: 'not_applicable',   label: 'Not applicable (e.g. no organ)' },
    ];

    // Map a system's label/key to the catalogue, with multiple aliases so
    // different label spellings (e.g. "Heart" / "Cardiovascular") all hit.
    function findingsForSystem(sys) {
        if (!sys) return [];
        var k = sys.key || '';
        var lbl = (sys.label || '').toLowerCase();
        var aliases = {
            heart:           ['heart','cardiac','cardiovascular'],
            lungs:           ['lung','lungs','respiratory','pulmonary'],
            eyes:            ['eye','eyes','ophthalmic','ocular'],
            ears:            ['ear','ears','aural','otic'],
            nose_throat:     ['nose','throat','nasal','upper_resp','upper_respiratory'],
            mouth_teeth_gums:['mouth','teeth','gum','oral','dental'],
            abdomen:         ['abdomen','abdominal','gastrointestinal','gi','gut'],
            musculoskeletal: ['musculoskeletal','msk','orthopedic','bone','joint'],
            skin_hair_coat:  ['skin','coat','dermatology','derma','integument'],
            // Item 1.4 Phases B-D — Neurological catalogue (seeded as a
            // separate body-system row by the 2026_05_31_000006 migration).
            neurological:    ['neurological','neuro','nervous','cns','nervous_system'],
        };
        for (var cat in aliases) {
            if (!systemFindings[cat]) continue;
            for (var i = 0; i < aliases[cat].length; i++) {
                var a = aliases[cat][i];
                if (k === a || k.indexOf(a) !== -1 || lbl.indexOf(a) !== -1) {
                    return systemFindings[cat];
                }
            }
        }
        return [];
    }

    function loadBodySystems(callback) {
        $.getJSON(soapDataUrl, { section: 'body_system', species: 'all' }, function (data) {
            // Merge any DB-stored group icons into groupMeta (user-defined override)
            data.forEach(function(d) {
                if (!d.group_name) return;
                if (d.group_icon && !groupMeta[d.group_name]) {
                    groupMeta[d.group_name] = {
                        icon:  d.group_icon,
                        color: d.group_color || defaultGroupMeta.color,
                        bg:    d.group_bg    || defaultGroupMeta.bg,
                    };
                } else if (d.group_icon && groupMeta[d.group_name]) {
                    groupMeta[d.group_name].icon = d.group_icon;
                }
            });
            bodySystems = data.map(function(d) {
                var key  = d.label.toLowerCase().replace(/[^a-z0-9]/g, '_');
                var icon = sysIconMap[d.label.toLowerCase()] || 'fa-circle';
                return { key: key, label: d.label, icon: icon, group: d.group_name || '' };
            });
            if (callback) callback();
        });
    }

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

    // Build flat group index from bodySystems (preserves seeder order)
    function buildGroups() {
        var groups = {}, order = [];
        bodySystems.forEach(function(sys) {
            var g = sys.group || '';
            if (!groups[g]) { groups[g] = []; order.push(g); }
            groups[g].push(sys);
        });
        return { groups: groups, order: order };
    }

    // HTML for a single system card with the enhanced finding checklist.
    // When state is 'abnormal' or 'ne', the panel below the N/Abn/N/E
    // toggle reveals a checkbox list of common findings for that system
    // (with optional severity + lateralisation per finding) plus a
    // multi-line notes textarea.
    function makeSystemRow(sys) {
        var st = (systemStates[sys.key] || {}).state || '';
        var stateData = systemStates[sys.key] || {};
        var isExpanded = (st === 'abnormal' || st === 'ne');

        var html = '<div class="system-row' + (st === 'abnormal' ? ' is-abn' : '') +
                   (st === 'ne' ? ' is-ne' : '') + '" data-sys="' + sys.key + '">' +
            '<div class="system-row-top">' +
            '<span class="system-label"><i class="fa ' + sys.icon + ' fa-fw"></i> ' + sys.label + '</span>' +
            '<div class="sys-btns">' +
            '<button type="button" class="sys-btn ' + (st === 'normal'   ? 'snormal' : '') + '" data-sys="' + sys.key + '" data-state="normal"   title="Seems Normal">N</button>' +
            '<button type="button" class="sys-btn ' + (st === 'abnormal' ? 'sabn'    : '') + '" data-sys="' + sys.key + '" data-state="abnormal" title="Abnormal findings">Abn</button>' +
            '<button type="button" class="sys-btn ' + (st === 'ne'       ? 'sne'     : '') + '" data-sys="' + sys.key + '" data-state="ne"        title="Not Examined">N/E</button>' +
            '</div></div>' +
            '<div class="sys-findings-panel' + (isExpanded ? ' show' : '') + '" data-sys-panel="' + sys.key + '">';

        if (st === 'ne') {
            html += '<div class="sys-findings-hint"><i class="fa fa-info-circle"></i> Why not examined?</div>';
            html += renderFindingChecklist(sys.key, neReasons, stateData, /*isReason*/ true);
        } else {
            // Default to abnormal layout (renders even before user picks Abn,
            // so they see the list when they switch and can pick directly).
            var catalogue = findingsForSystem(sys);
            if (catalogue.length) {
                html += '<div class="sys-findings-hint"><i class="fa fa-list-ul"></i> Common findings &mdash; tick all that apply:</div>';
                html += renderFindingChecklist(sys.key, catalogue, stateData, false);
            }
        }
        html += '<textarea class="sys-notes" data-sys-notes="' + sys.key + '" rows="2" ' +
                'placeholder="Additional notes (multi-line)-">' +
                $('<i>').text(stateData.notes || '').html() + '</textarea>';
        html += '</div>'; // /panel
        html += '</div>'; // /row
        return html;
    }

    function renderFindingChecklist(sysKey, items, stateData, isReason) {
        var selected = (stateData.findings || []).reduce(function (acc, k) { acc[k] = true; return acc; }, {});
        var sevMap   = stateData.severity || {};
        var sideMap  = stateData.sides    || {};

        return '<div class="sys-finding-list">' + items.map(function (it) {
            var checked   = !!selected[it.value];
            var sev       = sevMap[it.value] || '';
            var sides     = sideMap[it.value] || [];
            var lOn       = sides.indexOf('l') !== -1;
            var rOn       = sides.indexOf('r') !== -1;
            var critBadge = it.critical
                ? '<i class="fa fa-exclamation-triangle" title="Flags into Medical Critical Check report" style="color:#dc2626;font-size:10px;margin-left:3px;"></i>'
                : '';

            var html = '<label class="sys-finding-item' + (checked ? ' on' : '') + '">' +
                '<input type="checkbox" class="sys-finding-chk" ' +
                'data-sys="' + sysKey + '" data-val="' + it.value + '"' +
                (checked ? ' checked' : '') + '> ' +
                '<span class="sys-finding-label">' + $('<i>').text(it.label).html() + critBadge + '</span>';

            if (it.severity && !isReason) {
                html += '<span class="sys-finding-sev">' +
                    ['mild','moderate','severe'].map(function (s) {
                        return '<label class="sev-pill' + (sev === s ? ' on' : '') + '">' +
                               '<input type="radio" name="sev_' + sysKey + '_' + it.value + '"' +
                               ' class="sys-finding-sev-radio"' +
                               ' data-sys="' + sysKey + '" data-val="' + it.value + '"' +
                               ' value="' + s + '"' + (sev === s ? ' checked' : '') + '>' +
                               s.charAt(0).toUpperCase() + s.slice(1) +
                               '</label>';
                    }).join('') + '</span>';
            }
            if (it.lr && !isReason) {
                html += '<span class="sys-finding-side">' +
                    '<label class="side-pill' + (lOn ? ' on' : '') + '">' +
                    '<input type="checkbox" class="sys-finding-side-chk" data-sys="' + sysKey +
                    '" data-val="' + it.value + '" data-side="l"' + (lOn ? ' checked' : '') + '> L' +
                    '</label>' +
                    '<label class="side-pill' + (rOn ? ' on' : '') + '">' +
                    '<input type="checkbox" class="sys-finding-side-chk" data-sys="' + sysKey +
                    '" data-val="' + it.value + '" data-side="r"' + (rOn ? ' checked' : '') + '> R' +
                    '</label>' +
                    '</span>';
            }
            html += '</label>';
            return html;
        }).join('') + '</div>';
    }

    // Count states for a group's systems
    function countStates(systems) {
        var n = 0, abn = 0, ne = 0;
        systems.forEach(function(sys) {
            var st = (systemStates[sys.key] || {}).state;
            if (st === 'normal')   n++;
            if (st === 'abnormal') abn++;
            if (st === 'ne')       ne++;
        });
        return { n: n, abn: abn, ne: ne };
    }

    // Render badge pills for a group
    function badgeHtml(counts) {
        var b = '';
        if (counts.n   > 0) b += '<span class="sys-grp-badge gnormal">' + counts.n   + ' N</span>';
        if (counts.abn > 0) b += '<span class="sys-grp-badge gabn">'    + counts.abn + ' Abn</span>';
        if (counts.ne  > 0) b += '<span class="sys-grp-badge gne">'     + counts.ne  + ' N/E</span>';
        return b;
    }

    // ── Side-nav render ───────────────────────────────────────────────────────

    var activeGroup = null; // tracks which rail item is shown in the panel

    // Render one rail nav item
    function makeNavItem(gname, systems, isActive) {
        var meta   = (gname && groupMeta[gname]) ? groupMeta[gname] : defaultGroupMeta;
        var label  = gname || 'Other Systems';
        var counts = countStates(systems);
        var badges = badgeHtml(counts);
        var dotCls = counts.abn > 0 ? 'has-abn' : (counts.n > 0 ? 'has-normal' : '');

        return '<div class="sys-nav-item ' + (isActive ? 'active ' : '') + dotCls + '"' +
               ' data-group="' + $('<span>').text(gname).html() + '"' +
               ' style="--gc:' + meta.color + ';--gbg:' + meta.bg + ';">' +
               '<div class="sni-top">' +
               '<i class="fa ' + meta.icon + ' sni-icon fa-fw"></i>' +
               '<span class="sni-label">' + $('<span>').text(label).html() + '</span>' +
               '<span class="sni-dot"></span>' +
               '</div>' +
               (badges ? '<div class="sni-badges">' + badges + '</div>' : '') +
               '</div>';
    }

    // Render the right panel for the given group
    function makePanelHtml(gname, systems) {
        if (!systems || !systems.length) {
            return '<div class="sys-panel-empty"><i class="fa fa-inbox fa-fw"></i>&nbsp;No systems in this group.</div>';
        }
        var meta  = (gname && groupMeta[gname]) ? groupMeta[gname] : defaultGroupMeta;
        var label = gname || 'Other Systems';

        var bar = '<div class="sys-panel-bar" style="--gc:' + meta.color + ';">' +
            '<div class="sys-panel-title"><i class="fa ' + meta.icon + ' fa-fw"></i> ' + $('<span>').text(label).html() + '</div>' +
            '<div class="sys-panel-actions">' +
            '<button type="button" class="sys-panel-btn mark-all-n"   data-group="' + $('<span>').text(gname).html() + '"><i class="fa fa-check fa-fw"></i> All Normal</button>' +
            '<button type="button" class="sys-panel-btn mark-all-ne"  data-group="' + $('<span>').text(gname).html() + '">All N/E</button>' +
            '<button type="button" class="sys-panel-btn"              data-group="' + $('<span>').text(gname).html() + '" data-action="clear"><i class="fa fa-times fa-fw"></i> Clear</button>' +
            '</div></div>';

        return bar + systems.map(function(sys) { return makeSystemRow(sys); }).join('');
    }

    // Full re-render of the whole side-nav widget
    function renderSystems() {
        var gi = buildGroups();
        if (!gi.order.length) { $('#systems_grid').html(''); return; }

        if (!activeGroup || !gi.groups[activeGroup]) activeGroup = gi.order[0];

        var rail = gi.order.map(function(g) {
            return makeNavItem(g, gi.groups[g], g === activeGroup);
        }).join('');

        var panel = makePanelHtml(activeGroup, gi.groups[activeGroup]);

        $('#systems_grid').html(
            '<div class="sys-nav-shell">' +
            '<div class="sys-nav-rail" id="sys_nav_rail">' + rail + '</div>' +
            '<div class="sys-nav-panel" id="sys_nav_panel">' + panel + '</div>' +
            '</div>'
        );
    }

    // Cheap refresh - update only rail badges & dot classes, no panel rebuild
    function refreshGroupBadges() {
        var gi = buildGroups();
        $('#sys_nav_rail .sys-nav-item').each(function() {
            var gname   = $(this).data('group');
            var systems = gi.groups[gname] || [];
            var counts  = countStates(systems);
            var badges  = badgeHtml(counts);
            var dotCls  = counts.abn > 0 ? 'has-abn' : (counts.n > 0 ? 'has-normal' : '');

            $(this).removeClass('has-abn has-normal').addClass(dotCls);
            var $b = $(this).find('.sni-badges');
            if (badges) {
                if ($b.length) $b.html(badges);
                else $(this).append('<div class="sni-badges">' + badges + '</div>');
            } else {
                $b.remove();
            }
        });
    }

    // ── Nav item click - switch active group ──────────────────────────────────
    $(document).on('click', '.sys-nav-item', function() {
        var gname = $(this).data('group');
        if (gname === activeGroup) return;
        activeGroup = gname;
        var gi = buildGroups();
        $('#sys_nav_rail .sys-nav-item').removeClass('active');
        $(this).addClass('active');
        $('#sys_nav_panel').html(makePanelHtml(gname, gi.groups[gname] || []));
    });

    // ── Quick-action buttons (All Normal / All N/E / Clear) ───────────────────
    $(document).on('click', '.sys-panel-btn', function(e) {
        e.stopPropagation();
        var gname  = $(this).data('group');
        var action = $(this).data('action') || $(this).hasClass('mark-all-n') ? 'normal' : ($(this).hasClass('mark-all-ne') ? 'ne' : 'clear');
        // Re-derive action from classes if data-action not set
        if ($(this).hasClass('mark-all-n'))  action = 'normal';
        if ($(this).hasClass('mark-all-ne')) action = 'ne';
        if ($(this).data('action') === 'clear') action = 'clear';

        var gi = buildGroups();
        (gi.groups[gname] || []).forEach(function(sys) {
            if (!systemStates[sys.key]) systemStates[sys.key] = {};
            systemStates[sys.key].state   = action === 'clear' ? '' : action;
            systemStates[sys.key].finding = '';
        });

        // Rebuild panel to reflect new states
        $('#sys_nav_panel').html(makePanelHtml(gname, gi.groups[gname] || []));
        refreshGroupBadges();
        buildObjectiveSummary();
        markDirty();
    });

    // ── State change: N / Abn / N/E button click ─────────────────────
    // Re-renders the system row so the appropriate checklist panel
    // appears (abnormal => findings catalogue, ne => reasons catalogue,
    // normal => panel collapses).
    $(document).on('click', '.sys-btn', function() {
        var key   = $(this).data('sys');
        var state = $(this).data('state');
        if (!systemStates[key]) systemStates[key] = {};
        // Clear findings on state change to avoid stale picks (e.g.
        // switching from Abn->Normal shouldn't keep finding entries).
        if (systemStates[key].state !== state) {
            systemStates[key].findings = [];
            systemStates[key].severity = {};
            systemStates[key].sides    = {};
        }
        systemStates[key].state = state;

        // Find this system to re-render the row in place.
        var sys = bodySystems.filter(function (s) { return s.key === key; })[0];
        if (sys) {
            var $row = $(this).closest('.system-row');
            $row.replaceWith(makeSystemRow(sys));
        }
        buildObjectiveSummary();
        refreshGroupBadges();
        markDirty();
    });

    // ── Per-finding checkbox ─────────────────────────────────────────
    $(document).on('change', '.sys-finding-chk', function () {
        var key = $(this).data('sys');
        var val = String($(this).data('val'));
        if (!systemStates[key]) systemStates[key] = { state: 'abnormal' };
        if (!systemStates[key].findings) systemStates[key].findings = [];
        var idx = systemStates[key].findings.indexOf(val);
        if (this.checked && idx === -1) systemStates[key].findings.push(val);
        if (!this.checked && idx !== -1) {
            systemStates[key].findings.splice(idx, 1);
            // Tidy up severity / sides entries when a finding is unchecked.
            if (systemStates[key].severity) delete systemStates[key].severity[val];
            if (systemStates[key].sides)    delete systemStates[key].sides[val];
        }
        // Auto-promote to Abnormal when the doctor ticks anything in the
        // findings list while state is still empty / Normal.
        if (this.checked && systemStates[key].state !== 'ne') {
            systemStates[key].state = 'abnormal';
            // Refresh the N/Abn/N/E pills + row class without full re-render
            var $row = $(this).closest('.system-row');
            $row.find('.sys-btn').removeClass('snormal sabn sne');
            $row.find('.sys-btn[data-state="abnormal"]').addClass('sabn');
            $row.removeClass('is-ne').addClass('is-abn');
        }
        $(this).closest('.sys-finding-item').toggleClass('on', this.checked);
        buildObjectiveSummary();
        refreshGroupBadges();
        markDirty();
    });

    // ── Severity radio ───────────────────────────────────────────────
    $(document).on('change', '.sys-finding-sev-radio', function () {
        var key = $(this).data('sys');
        var val = String($(this).data('val'));
        var sev = $(this).val();
        if (!systemStates[key]) systemStates[key] = { state: 'abnormal' };
        if (!systemStates[key].severity) systemStates[key].severity = {};
        systemStates[key].severity[val] = sev;
        // Visual: only one severity pill active at a time per finding.
        var $item = $(this).closest('.sys-finding-item');
        $item.find('.sev-pill').removeClass('on');
        $(this).closest('.sev-pill').addClass('on');
        buildObjectiveSummary();
        markDirty();
    });

    // ── L/R lateralisation checkbox ──────────────────────────────────
    $(document).on('change', '.sys-finding-side-chk', function () {
        var key  = $(this).data('sys');
        var val  = String($(this).data('val'));
        var side = $(this).data('side');
        if (!systemStates[key]) systemStates[key] = { state: 'abnormal' };
        if (!systemStates[key].sides) systemStates[key].sides = {};
        if (!systemStates[key].sides[val]) systemStates[key].sides[val] = [];
        var arr = systemStates[key].sides[val];
        var idx = arr.indexOf(side);
        if (this.checked && idx === -1) arr.push(side);
        if (!this.checked && idx !== -1) arr.splice(idx, 1);
        $(this).closest('.side-pill').toggleClass('on', this.checked);
        buildObjectiveSummary();
        markDirty();
    });

    // ── Multi-line notes ─────────────────────────────────────────────
    $(document).on('input', '[data-sys-notes]', function () {
        var key = $(this).data('sys-notes');
        if (!systemStates[key]) systemStates[key] = {};
        systemStates[key].notes = $(this).val();
        buildObjectiveSummary();
        markDirty();
    });

    // ── Summary builder: format the rich structured findings into the
    //     soap_objective textarea so it stays human-readable AND
    //     re-parseable next visit. Format example:
    //
    //     - Heart: Abnormal. Murmur (L, moderate), Tachycardia. [Notes: ...]
    //     - Eyes: Abnormal. Corneal ulcer (R), Cataracts (L).
    //     - Skin: Not examined. Reason: patient too anxious.
    //
    // Critical-flag callout is left to the report consumer (item 4.1),
    // not the on-screen summary, to keep this text clean for printing.
    function buildObjectiveSummary() {
        var lines = [];
        bodySystems.forEach(function (sys) {
            var st = systemStates[sys.key] || {};
            if (!st.state) return;
            if (st.state === 'normal') {
                lines.push('- ' + sys.label + ': Normal.' +
                    (st.notes ? ' Notes: ' + st.notes : ''));
                return;
            }
            var prefix = st.state === 'ne' ? 'Not examined' : 'Abnormal';
            var catalogue = st.state === 'ne' ? neReasons : findingsForSystem(sys);
            var findings = (st.findings || []).map(function (val) {
                var item = catalogue.filter(function (c) { return c.value === val; })[0];
                if (!item) return val;
                var parts = [item.label];
                var sevSide = [];
                var sides = (st.sides || {})[val] || [];
                if (sides.length) sevSide.push(sides.map(function (s) { return s.toUpperCase(); }).join('/'));
                var sev = (st.severity || {})[val];
                if (sev) sevSide.push(sev);
                if (sevSide.length) parts.push('(' + sevSide.join(', ') + ')');
                return parts.join(' ');
            });
            var detail = findings.length ? findings.join(', ') : prefix;
            var line = '- ' + sys.label + ': ' + prefix +
                       (findings.length ? '. ' + detail : '') + '.';
            if (st.notes) line += ' Notes: ' + st.notes;
            lines.push(line);
        });
        if (lines.length) { $('#soap_o').val(lines.join('\n')); autoGrow(document.getElementById('soap_o')); }
    }

    // Diagnosis Select2
    $('#diag_search').select2({
        width: '100%', placeholder: 'Search conditions / diagnoses-', minimumInputLength: 1,
        ajax: {
            url: conditionsUrl, dataType: 'json', delay: 250,
            data: function(p) { return { q: p.term }; },
            processResults: function(d) { return { results: d }; }
        }
    }).on('select2:select', function(e) {
        var item = e.params.data;
        if (!diagConditions.find(function(c) { return c.id == item.id; })) {
            diagConditions.push({ id: item.id, name: item.text });
            renderDiagTags();
            buildAssessmentText();
            markDirty();
        }
        $(this).val(null).trigger('change');
    });

    function renderDiagTags() {
        $('#diag_tags').html(diagConditions.map(function(c, i) {
            return '<span class="diag-tag">' + c.name +
                ' <span class="diag-tag-rm" onclick="removeDiagCond('+i+')">&#x2715;</span></span>';
        }).join(''));
    }

    window.removeDiagCond = function(i) {
        diagConditions.splice(i, 1);
        renderDiagTags();
        buildAssessmentText();
        markDirty();
    };

    function buildAssessmentText() {
        var lines = diagConditions.map(function(c) { return '- ' + c.name + '.'; });
        var extra = $('#soap_a').val().replace(/^(- .+\.?\n?)+/, '').trimStart();
        $('#soap_a').val(lines.join('\n') + (extra ? '\n\n' + extra : ''));
        autoGrow(document.getElementById('soap_a'));
    }

    // Plan medicine Select2
    $('#plan_med_search').select2({
        width: '100%', placeholder: 'Search medicine-', minimumInputLength: 1,
        ajax: {
            url: medicinesUrl, dataType: 'json', delay: 250,
            data: function(p) { return { q: p.term }; },
            processResults: function(d) { return { results: d }; }
        }
    });

    function resetMedForm() {
        editingMedIdx = -1;
        $('#plan_med_search').val(null).trigger('change');
        $('#plan_dosage').val('');
        $('#plan_freq option:first-child').prop('selected', true);
        $('#plan_days').val(''); $('#plan_note').val('');
        $('#btn_add_plan_med').html('<i class="fa fa-plus"></i> Add').removeClass('btn-warning').addClass('btn-success');
        $('#btn_cancel_edit_med').hide();
    }

    $('#btn_add_plan_med').on('click', function() {
        var medData = ($('#plan_med_search').select2('data') || [])[0];
        if (!medData) { toastr.warning('Select a medicine first.'); return; }
        var medObj = {
            id:     medData.id,
            name:   medData.text,
            dosage: $('#plan_dosage').val().trim(),
            freq:   $('#plan_freq').val(),
            days:   $('#plan_days').val(),
            note:   $('#plan_note').val().trim()
        };
        if (editingMedIdx >= 0) {
            planMeds[editingMedIdx] = medObj;
        } else {
            planMeds.push(medObj);
        }
        resetMedForm();
        renderPlanMeds();
        markDirty();
    });

    $('#btn_cancel_edit_med').on('click', function() {
        resetMedForm();
        renderPlanMeds();
    });

    function renderPlanMeds() {
        $('#plan_med_list').html(planMeds.map(function(m, i) {
            var detail = [m.dosage, m.freq, m.days ? m.days + ' days' : ''].filter(Boolean).join(' - ');
            var isEditing = (i === editingMedIdx);
            var rowStyle  = isEditing ? 'border-color:#f39c12;background:#fffbf0;' : '';
            return '<div class="rx-row" style="' + rowStyle + '">' +
                '<div class="rx-row-name">' +
                    '<i class="fa fa-pills" style="color:#8e44ad;margin-right:5px;"></i>' + m.name +
                    (m.note ? '<div class="rx-row-note">' + m.note + '</div>' : '') +
                '</div>' +
                '<div class="rx-row-detail">' + detail +
                    (isEditing ? ' <span style="color:#f39c12;font-size:10px;font-weight:700;margin-left:4px;">[editing-]</span>' : '') +
                '</div>' +
                '<div class="rx-actions">' +
                    (isEditing
                        ? '<span class="rx-btn" style="color:#f39c12;border-color:#f39c12;background:#fff;" title="Currently editing"><i class="fa fa-edit"></i></span>'
                        : '<span class="rx-btn rx-btn-edit" onclick="editPlanMed(' + i + ')" title="Edit"><i class="fa fa-edit"></i></span>') +
                    '<span class="rx-btn rx-btn-rm" onclick="removePlanMed(' + i + ')" title="Remove"><i class="fa fa-times"></i></span>' +
                '</div>' +
            '</div>';
        }).join(''));
    }

    window.editPlanMed = function(i) {
        var m = planMeds[i];
        editingMedIdx = i;
        // Populate the add-form with the row values
        var option = new Option(m.name, m.id || 0, true, true);
        $('#plan_med_search').empty().append(option).trigger('change');
        $('#plan_dosage').val(m.dosage || '');
        $('#plan_freq').val(m.freq || '');
        $('#plan_days').val(m.days || '');
        $('#plan_note').val(m.note || '');
        // Switch button to Save
        $('#btn_add_plan_med').html('<i class="fa fa-check"></i> Save').removeClass('btn-success').addClass('btn-warning');
        $('#btn_cancel_edit_med').show();
        renderPlanMeds();
        // Scroll the P card into view
        document.getElementById('btn_add_plan_med').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    };

    window.removePlanMed = function(i) {
        if (editingMedIdx === i) resetMedForm();
        planMeds.splice(i, 1);
        if (editingMedIdx > i) editingMedIdx--;
        renderPlanMeds();
        markDirty();
    };

    function buildPlanText() {
        var lines = planMeds.map(function(m) {
            return '- ' + m.name + (m.dosage?' - '+m.dosage:'') + (m.freq?', '+m.freq:'') + (m.days?' × '+m.days+' days':'');
        });
        var extra = $('#soap_p').val().replace(/^(- .+\n?)+/, '').trimStart();
        $('#soap_p').val(lines.join('\n') + (extra ? '\n\n' + extra : ''));
        autoGrow(document.getElementById('soap_p'));
    }

    // Auto-grow SOAP textareas
    // Set height:0 first so scrollHeight reflects true content height (not rows attribute)
    function autoGrow(el) {
        el.style.height = '0';
        el.style.height = el.scrollHeight + 'px';
    }
    $(document).on('input', '.soap-card textarea', function () { autoGrow(this); });

    // SOAP card collapse
    window.toggleSoapCard = function(hd) {
        var card = $(hd).closest('.soap-card');
        card.toggleClass('collapsed');
        if (!card.hasClass('collapsed')) {
            card.find('textarea').each(function () { autoGrow(this); });
        }
    };

    // Draft management
    function markDirty() {
        draftDirty = true;
        window.drmServerDirty = true;   // unsaved to the server until consultation is saved
        $('#draft_status').removeClass('saved restored').addClass('dirty')
            .html('<i class="fa fa-circle" style="font-size:6px;"></i> Unsaved changes');
    }

    function saveDraft() {
        if (!draftDirty) return;
        try {
            localStorage.setItem(draftKey, JSON.stringify({
                s: $('#soap_s').val(), o: $('#soap_o').val(),
                a: $('#soap_a').val(), labs: $('#soap_labs').val(), exams: $('#soap_exams').val(),
                p: $('#soap_p').val(),
                w: $('#v_weight').val(), t: $('#v_temp').val(),
                hr: $('#v_hr').val(), rr: $('#v_rr').val(),
                diag: diagConditions, meds: planMeds, sys: systemStates
            }));
        } catch(e) {}
        draftDirty = false;
        $('#draft_status').removeClass('dirty').addClass('saved')
            .html('<i class="fa fa-check-circle"></i> Draft auto-saved');
        setTimeout(function() {
            $('#draft_status').removeClass('saved')
                .html('<i class="fa fa-circle" style="font-size:6px;"></i> No unsaved changes');
        }, 2000);
    }

    function restoreDraft() {
        var raw; try { raw = localStorage.getItem(draftKey); } catch(e) {}
        if (!raw) return;
        try {
            var d = JSON.parse(raw);
            if (d.s)    $('#soap_s').val(d.s);
            if (d.o)    $('#soap_o').val(d.o);
            if (d.a)    $('#soap_a').val(d.a);
            if (d.labs) $('#soap_labs').val(d.labs);
            if (d.exams)$('#soap_exams').val(d.exams);
            if (d.p)    $('#soap_p').val(d.p);
            if (d.w) $('#v_weight').val(d.w);
            if (d.t) $('#v_temp').val(d.t);
            if (d.hr) $('#v_hr').val(d.hr);
            if (d.rr) $('#v_rr').val(d.rr);
            if (d.diag && d.diag.length) { diagConditions = d.diag; renderDiagTags(); }
            if (d.meds && d.meds.length) { planMeds = d.meds; renderPlanMeds(); }
            if (d.sys && Object.keys(d.sys).length) { systemStates = d.sys; renderSystems(); }
            $('#draft_banner').show();
            $('#draft_status').addClass('restored').html('<i class="fa fa-history"></i> Draft restored');
        } catch(e) {}
    }

    $('#draft_discard').on('click', function(e) {
        e.preventDefault();
        try { localStorage.removeItem(draftKey); } catch(e2) {}
        $('#draft_banner').hide();
        draftDirty = false;
        $('#draft_status').removeClass('dirty saved restored')
            .html('<i class="fa fa-circle" style="font-size:6px;"></i> No unsaved changes');
    });

    $('#soap_s, #soap_o, #soap_a, #soap_labs, #soap_exams, #soap_p, #v_weight, #v_temp, #v_hr, #v_rr').on('input', markDirty);

    // Re-sync chip "active" state whenever the source textarea changes -
    // catches both free-typed mentions and text appended by voice dictation
    // (appendToTextarea fires 'input' + 'change' on the target). The literal
    // matcher runs synchronously; the AI synonym matcher runs debounced and
    // merges its additional picks on top.
    //
    // Wrapped in try/catch so a future error in chip handling can never
    // freeze the dictation modal: appendToTextarea triggers these events
    // from the Whisper success path, and an uncaught throw here would
    // propagate up and break the modal cleanup chain.
    $('#soap_s').on('input change', function () {
        try { syncSubjectiveChips(); } catch (e) { console.warn('syncSubjectiveChips failed:', e); }
        try { syncChipsWithAi('subjective'); } catch (e) { console.warn('syncChipsWithAi failed:', e); }
    });
    $('#soap_labs').on('input change', function () {
        try { syncInvChips(); } catch (e) { console.warn('syncInvChips failed:', e); }
        try { syncChipsWithAi('lab'); } catch (e) { console.warn('syncChipsWithAi failed:', e); }
    });
    $('#soap_exams').on('input change', function () {
        try { syncInvChips(); } catch (e) { console.warn('syncInvChips failed:', e); }
        try { syncChipsWithAi('exam'); } catch (e) { console.warn('syncChipsWithAi failed:', e); }
    });

    setInterval(saveDraft, 30000);

    // Restore body-system button states from soap_o bullet text
    function syncObjectiveStates() {
        var text = $('#soap_o').val();
        if (!text) return;
        var changed = false;
        text.split('\n').forEach(function (line) {
            line = line.trim();
            if (line.indexOf('- ') !== 0) return;
            line = line.substring(2);
            var col = line.indexOf(': ');
            if (col === -1) return;
            var label   = line.substring(0, col);
            var finding = line.substring(col + 2).replace(/\.$/, '');
            var sys = bodySystems.filter(function (s) { return s.label === label; })[0];
            if (!sys) return;
            if (!systemStates[sys.key]) systemStates[sys.key] = {};
            if (finding === 'Normal') {
                systemStates[sys.key] = { state: 'normal', finding: '' };
            } else if (finding === 'Not examined') {
                systemStates[sys.key] = { state: 'ne', finding: '' };
            } else {
                systemStates[sys.key] = { state: 'abnormal', finding: finding === 'Abnormal' ? '' : finding };
            }
            changed = true;
        });
        if (changed) { renderSystems(); refreshGroupBadges(); }
    }

    // Sync chip active state from current textarea content.
    //
    // Two passes:
    //   1. The original structured-bullet check ('- Label.') catches chips
    //      activated by the chip-click and search-more flows.
    //   2. A word-boundary, case-insensitive substring match against the
    //      raw text body catches free-typed and voice-transcribed mentions
    //      (e.g. "the dog is vomiting and not eating" auto-lights up the
    //      Vomiting and Not eating chips).
    function chipMatchesText(label, text, lowerText) {
        if (!label) return false;
        if (text.indexOf('- ' + label) !== -1) return true;
        var lower = String(label).toLowerCase().trim();
        if (!lower) return false;
        // Escape regex metacharacters in the label, then wrap in word
        // boundaries so "eat" doesn't match "eating", but "not eating"
        // matches the literal phrase.
        var esc = lower.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        var re = new RegExp('(^|[^a-z0-9])' + esc + '($|[^a-z0-9])', 'i');
        return re.test(lowerText);
    }
    function syncSubjectiveChips() {
        var text = $('#soap_s').val() || '';
        var lower = text.toLowerCase();
        $('#complaint_chips .complaint-chip').each(function () {
            $(this).toggleClass('active', chipMatchesText($(this).data('chip'), text, lower));
        });
    }
    function syncInvChips() {
        var labText   = $('#soap_labs').val()  || '';
        var examText  = $('#soap_exams').val() || '';
        var labLower  = labText.toLowerCase();
        var examLower = examText.toLowerCase();
        $('[data-inv]').each(function () {
            var isLab = $(this).data('inv-type') === 'lab';
            var text  = isLab ? labText  : examText;
            var lower = isLab ? labLower : examLower;
            $(this).toggleClass('active', chipMatchesText($(this).data('inv'), text, lower));
        });
    }

    // ── AI-assisted synonym matcher ───────────────────────────────────
    // Augments the literal chipMatchesText() with an LLM pass so phrases
    // like "threw up" -> Vomiting, "won't eat" -> Not eating, "loose
    // stools" -> Diarrhea light up the right chips. Falls back silently
    // to the literal match when AI is unconfigured or fails.
    //
    // - Debounced (900 ms) so typing doesn't fire a request per keystroke.
    // - Cached by text-hash so the same dictation never costs twice.
    // - One in-flight request per zone (subjective / lab / exam) - new
    //   requests abort the previous to keep results consistent with the
    //   latest text the doctor sees.
    //
    // AI_AVAILABLE is rendered server-side here (not pulled from the mic
    // dictation closure) so this function works whether or not the doctor
    // has voice-transcription permission - typing alone still benefits.
    var MATCH_CHIPS_URL = @json(\Illuminate\Support\Facades\Route::has('vai.match-chips') ? route('vai.match-chips') : null);
    var AI_AVAILABLE    = @json(((bool) ($drmShowAi ?? false) || (bool) ($drmShowMic ?? false)) && \Illuminate\Support\Facades\Route::has('vai.match-chips'));
    var CHIP_AJAX_TOKEN = @json(csrf_token());
    var aiChipCache = { subjective: {}, lab: {}, exam: {} };
    var aiChipInFlight = { subjective: null, lab: null, exam: null };
    var aiChipTimers   = { subjective: null, lab: null, exam: null };

    // Cheap deterministic string hash so we can key the cache by text.
    function _hash(s) {
        var h = 0, i, c;
        for (i = 0; i < s.length; i++) { c = s.charCodeAt(i); h = ((h << 5) - h) + c; h |= 0; }
        return h;
    }

    function collectChipLabels(zone) {
        var $chips, key;
        if (zone === 'subjective') { $chips = $('#complaint_chips .complaint-chip'); key = 'chip'; }
        else if (zone === 'lab')   { $chips = $('#lab_chips  .complaint-chip[data-inv]'); key = 'inv'; }
        else                       { $chips = $('#exam_chips .complaint-chip[data-inv]'); key = 'inv'; }
        var labels = [];
        $chips.each(function () {
            var v = $(this).data(key);
            if (v) labels.push(String(v));
        });
        return labels;
    }

    // Map zone -> the metadata applyAiMatches and addChipToZone need.
    // Centralised so the routing logic isn't repeated four times.
    function _zoneMeta(zone) {
        if (zone === 'subjective') {
            return {
                $zone: $('#complaint_chips'),
                chipSelector: '.complaint-chip',
                dataKey: 'chip',
                attrName: 'data-chip',
                makeChip: makeComplaintChip,
                section: 'complaint',
                visibleListKey: 'complaint'
            };
        }
        if (zone === 'lab') {
            return {
                $zone: $('#lab_chips'),
                chipSelector: '.complaint-chip[data-inv]',
                dataKey: 'inv',
                attrName: 'data-inv',
                makeChip: function (label) { return makeInvChip(label, 'lab'); },
                section: 'lab_test',
                visibleListKey: 'lab_test'
            };
        }
        return {
            $zone: $('#exam_chips'),
            chipSelector: '.complaint-chip[data-inv]',
            dataKey: 'inv',
            attrName: 'data-inv',
            makeChip: function (label) { return makeInvChip(label, 'exam'); },
            section: 'exam_test',
            visibleListKey: 'exam_test'
        };
    }

    // Inject a brand-new chip into a zone (when the AI matched a label that
    // wasn't on the visible row yet). Mirrors what setupSearchMore does on
    // click of a search result - but WITHOUT appending to the textarea,
    // because the voice transcription already contains the text and we
    // don't want to duplicate it. Just adds the chip and marks active.
    function addChipToZone(zone, label) {
        var meta = _zoneMeta(zone);
        // De-dup against what's already in the zone (race-safe).
        var $existing = meta.$zone.find(meta.chipSelector).filter(function () {
            return String($(this).data(meta.dataKey)) === String(label);
        });
        if ($existing.length) {
            $existing.addClass('active vc-chip-ai-flash');
            setTimeout(function () { $existing.removeClass('vc-chip-ai-flash'); }, 1400);
            return $existing;
        }
        // Track in visibleChips so setupSearchMore won't try to re-add it.
        if (visibleChips[meta.visibleListKey]
            && visibleChips[meta.visibleListKey].indexOf(label) === -1) {
            visibleChips[meta.visibleListKey].push(label);
        }
        var $chip = $(meta.makeChip(label)).addClass('active vc-chip-ai-flash');
        meta.$zone.append($chip);
        setTimeout(function () { $chip.removeClass('vc-chip-ai-flash'); }, 1400);
        return $chip;
    }

    function applyAiMatches(zone, matched) {
        if (!matched || !matched.length) return;
        var meta = _zoneMeta(zone);
        var set = {};
        for (var i = 0; i < matched.length; i++) set[matched[i]] = true;

        // 1) Activate chips that ARE already visible.
        var visibleSet = {};
        meta.$zone.find(meta.chipSelector).each(function () {
            var label = $(this).data(meta.dataKey);
            visibleSet[label] = true;
            if (set[label] && !$(this).hasClass('active')) {
                var $c = $(this).addClass('active vc-chip-ai-flash');
                setTimeout(function () { $c.removeClass('vc-chip-ai-flash'); }, 1400);
            }
        });

        // 2) Inject any matches NOT yet on the visible row (these came from
        //    the backend's full-section pool, e.g. "Anorexia" added even
        //    though only "Not eating" was on the row).
        for (var j = 0; j < matched.length; j++) {
            if (!visibleSet[matched[j]]) addChipToZone(zone, matched[j]);
        }
    }

    function syncChipsWithAi(zone) {
        if (!AI_AVAILABLE) return;
        clearTimeout(aiChipTimers[zone]);
        aiChipTimers[zone] = setTimeout(function () {
            var text;
            if (zone === 'subjective') text = $('#soap_s').val()    || '';
            else if (zone === 'lab')   text = $('#soap_labs').val() || '';
            else                       text = $('#soap_exams').val()|| '';
            text = text.trim();
            if (text.length < 4) return; // not worth a round-trip

            var labels = collectChipLabels(zone);
            if (!labels.length) return;

            // Cache key now includes section so the same text dictated into
            // different zones doesn't cross-contaminate cache hits.
            var meta = _zoneMeta(zone);
            var cacheKey = _hash(text + '|' + meta.section + '|' + labels.length);
            if (aiChipCache[zone][cacheKey] !== undefined) {
                applyAiMatches(zone, aiChipCache[zone][cacheKey]);
                return;
            }

            if (aiChipInFlight[zone]) {
                try { aiChipInFlight[zone].abort(); } catch (e) {}
            }
            aiChipInFlight[zone] = $.ajax({
                url: MATCH_CHIPS_URL, method: 'POST',
                // Pass section + species so the backend pulls the full DB of
                // chips for that section and lets the LLM consider labels
                // that aren't yet on the visible chip row.
                data: {
                    _token: CHIP_AJAX_TOKEN,
                    text: text,
                    labels: labels,
                    section: meta.section,
                    species: (typeof petSpecies !== 'undefined') ? petSpecies : null
                },
                success: function (res) {
                    aiChipInFlight[zone] = null;
                    if (res && res.success) {
                        aiChipCache[zone][cacheKey] = res.matched || [];
                        applyAiMatches(zone, res.matched || []);
                    }
                },
                error: function (xhr, status) {
                    aiChipInFlight[zone] = null;
                    // Silent fallback - the literal matcher already ran on
                    // the input event so the doctor still gets useful hits.
                }
            });
        }, 900);
    }

    // ── Item #4b — AI suggest-from-Subjective ────────────────────────
    // Reads Subjective text after a typing pause and asks the LLM which
    // specialist exam panels to open. Surfaces the picks as clickable
    // chips above the body-systems grid. Each chip click expands the
    // matching system row and marks it Abnormal so the checklist is
    // ready for input. Reuses AI_AVAILABLE + CHIP_AJAX_TOKEN.
    var SUGGEST_EXAMS_URL    = @json(\Illuminate\Support\Facades\Route::has('vai.suggest-specialist-exams') ? route('vai.suggest-specialist-exams') : null);
    var EXTRACT_SYMPTOMS_URL = @json(\Illuminate\Support\Facades\Route::has('vai.extract-symptoms') ? route('vai.extract-symptoms') : null);
    var CACHE_ADVICE_URL     = @json(\Illuminate\Support\Facades\Route::has('vai.cache-advice') ? route('vai.cache-advice') : null);
    var VC_APPT_ID           = {{ (int) $appointment->id }};
    // Item #3 - server-rendered cached AI advice for this appointment.
    // If the doctor's current Subjective hashes to the same value, we
    // render these and skip the LLM call entirely.
    var VC_AI_CACHE = {
        hash:        @json($appointment->ai_subjective_hash ?? null),
        suggestions: @json($appointment->ai_exam_suggestions_json ?? null),
        symptoms:    @json($appointment->ai_symptoms_json ?? null),
        updated_at:  @json(optional($appointment->ai_advice_updated_at)->toIso8601String() ?? null)
    };
    function _sha1_hex(s) {
        // Minimal SHA-1 polyfill so we can compare against the PHP sha1()
        // value cached server-side without bringing in a crypto library.
        // Adapted from public-domain implementations; ~60 lines folded
        // into a single helper.
        function rotl(n, s) { return (n << s) | (n >>> (32 - s)); }
        function tohex(n) { var h = ''; for (var i = 7; i >= 0; i--) h += ((n >>> (i*4)) & 0xF).toString(16); return h; }
        var b = []; for (var i = 0; i < s.length; i++) {
            var c = s.charCodeAt(i);
            if (c < 0x80) b.push(c);
            else if (c < 0x800) { b.push(0xC0 | (c >> 6)); b.push(0x80 | (c & 0x3F)); }
            else { b.push(0xE0 | (c >> 12)); b.push(0x80 | ((c >> 6) & 0x3F)); b.push(0x80 | (c & 0x3F)); }
        }
        var ml = b.length * 8;
        b.push(0x80);
        while (b.length % 64 !== 56) b.push(0);
        for (var i = 7; i >= 0; i--) b.push((ml >>> (i*8)) & 0xFF);
        var h0=0x67452301,h1=0xEFCDAB89,h2=0x98BADCFE,h3=0x10325476,h4=0xC3D2E1F0;
        for (var off = 0; off < b.length; off += 64) {
            var w = []; for (var i = 0; i < 16; i++) w.push((b[off+i*4]<<24)|(b[off+i*4+1]<<16)|(b[off+i*4+2]<<8)|b[off+i*4+3]);
            for (var i = 16; i < 80; i++) w.push(rotl(w[i-3]^w[i-8]^w[i-14]^w[i-16],1));
            var a=h0,bx=h1,cx=h2,dx=h3,ex=h4;
            for (var i = 0; i < 80; i++) {
                var f, k;
                if (i<20) { f=(bx&cx)|((~bx)&dx); k=0x5A827999; }
                else if (i<40) { f=bx^cx^dx; k=0x6ED9EBA1; }
                else if (i<60) { f=(bx&cx)|(bx&dx)|(cx&dx); k=0x8F1BBCDC; }
                else { f=bx^cx^dx; k=0xCA62C1D6; }
                var t = (rotl(a,5) + f + ex + k + w[i]) | 0;
                ex = dx; dx = cx; cx = rotl(bx, 30); bx = a; a = t;
            }
            h0=(h0+a)|0; h1=(h1+bx)|0; h2=(h2+cx)|0; h3=(h3+dx)|0; h4=(h4+ex)|0;
        }
        return tohex(h0) + tohex(h1) + tohex(h2) + tohex(h3) + tohex(h4);
    }
    var aiSuggestTimer = null;
    var aiSuggestInFlight = null;
    var aiSuggestDismissed = false;       // per-session: doctor clicked X
    var aiSuggestLastSig = '';            // de-dup against repeated calls
    // Map specialist-key -> sys.key alias hints that identify which
    // body-system row to expand when the suggestion is accepted.
    var SUGGEST_TO_SYSTEM = {
        orthopedic:   ['musculoskeletal','msk','orthopedic','bone','joint'],
        neurological: ['neurological','neuro','nervous','cns','nervous_system'],
        dermatology:  ['skin','coat','dermatology','derma','integument'],
        ophthalmic:   ['eye','eyes','ophthalmic','ocular'],
        cardiology:   ['heart','cardiac','cardiovascular'],
        dental:       ['mouth','teeth','gum','oral','dental'],
        // NEW: urology lives on the Abdomen row (bladder palpation,
        // bladder pain, organomegaly findings) until a dedicated
        // Urogenital row is seeded for the clinic.
        urology:      ['abdomen','abdominal','urogenital','genitourinary','urinary'],
        // NEW: internal-medicine workup opens the General / vitals area
        // - the "General" system row covers BCS, hydration, demeanour,
        //   mucous membranes.
        internal_medicine: ['general','general_appearance','vitals','demeanour','demeanor'],
    };
    var SUGGEST_LABELS = {
        orthopedic:'Orthopedic exam', neurological:'Neurological exam',
        dermatology:'Clinic exam', ophthalmic:'Ophthalmic exam',
        cardiology:'Cardiology exam', dental:'Dental exam',
        urology:'Urology / urinary workup',
        internal_medicine:'Internal medicine workup'
    };

    function applySuggestedExam(suggestKey) {
        if (typeof bodySystems === 'undefined' || !bodySystems || !bodySystems.length) {
            console.warn('applySuggestedExam: bodySystems not loaded yet');
            return false;
        }
        var aliases = SUGGEST_TO_SYSTEM[suggestKey] || [];
        var match = null;
        for (var i = 0; i < bodySystems.length; i++) {
            var s = bodySystems[i];
            var k = (s.key || '').toLowerCase();
            var lbl = (s.label || '').toLowerCase();
            for (var j = 0; j < aliases.length; j++) {
                var a = aliases[j];
                if (k === a || k.indexOf(a) !== -1 || lbl.indexOf(a) !== -1) {
                    match = s; break;
                }
            }
            if (match) break;
        }
        if (!match) {
            console.warn('applySuggestedExam: no matching body-system row for', suggestKey, aliases);
            return false;
        }

        // 1) Mark Abnormal so makeSystemRow renders the checklist.
        if (typeof systemStates === 'undefined') return false;
        if (!systemStates[match.key]) systemStates[match.key] = {};
        systemStates[match.key].state = 'abnormal';

        // 2) Switch the side-nav to this system's group AND rebuild the
        //    right panel. Without this the row stays out of view because
        //    only the active group's rows are rendered.
        try {
            if (typeof buildGroups === 'function' && typeof makePanelHtml === 'function') {
                var gi = buildGroups();
                var targetGroup = match.group || '';
                if (gi.groups[targetGroup]) {
                    activeGroup = targetGroup;
                    $('#sys_nav_rail .sys-nav-item').removeClass('active');
                    $('#sys_nav_rail .sys-nav-item').filter(function () {
                        return String($(this).data('group')) === String(targetGroup);
                    }).addClass('active');
                    $('#sys_nav_panel').html(makePanelHtml(targetGroup, gi.groups[targetGroup]));
                }
            }
            if (typeof refreshGroupBadges === 'function') refreshGroupBadges();
            if (typeof buildObjectiveSummary === 'function') buildObjectiveSummary();
            if (typeof markDirty === 'function') markDirty();
        } catch (e) {
            console.error('applySuggestedExam: failed to render panel', e);
            return false;
        }

        // 3) Scroll the systems grid into view so the doctor sees the
        //    row they just opened (otherwise the chip click visibly
        //    "does nothing" if the grid is off-screen).
        var $grid = $('#systems_grid');
        if ($grid.length && $grid[0].scrollIntoView) {
            $grid[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
        }
        return true;
    }

    function fetchAndShowExamSuggestions() {
        if (!AI_AVAILABLE) {
            console.warn('Exam suggest skipped: AI_AVAILABLE is false (no vet_ai.use_assistant / clinic.manage_appointments permission, or AI not configured).');
            return;
        }
        if (aiSuggestDismissed) return;
        var text = ($('#soap_s').val() || '').trim();
        if (text.length < 12) {                       // too short to be meaningful
            $('#clc_ai_exam_suggest').hide();
            return;
        }
        var sig = _hash(text);
        if (sig === aiSuggestLastSig) return;          // same text — skip
        aiSuggestLastSig = sig;

        // Visible loading state so the doctor sees AI is analysing.
        $('#clc_ai_suggest_chips').html('<span class="text-muted" style="font-size:11.5px;"><i class="fa fa-spinner fa-spin"></i> Analysing complaint-</span>');
        $('#clc_ai_suggest_reason').text('');
        $('#clc_ai_exam_suggest').show();

        if (aiSuggestInFlight) { try { aiSuggestInFlight.abort(); } catch (e) {} }
        aiSuggestInFlight = $.ajax({
            url: SUGGEST_EXAMS_URL, method: 'POST',
            data: {
                _token: CHIP_AJAX_TOKEN,
                subjective: text,
                available_exams: Object.keys(SUGGEST_LABELS),
            },
            success: function (res) {
                aiSuggestInFlight = null;
                if (!res || !res.success) {
                    $('#clc_ai_suggest_chips').html('<small class="text-muted">AI did not return a usable answer.</small>');
                    return;
                }
                if (!Array.isArray(res.suggested) || !res.suggested.length) {
                    $('#clc_ai_suggest_chips').html('<small class="text-muted">No specialist exam panels indicated for this complaint.</small>');
                    $('#clc_ai_suggest_reason').text(res.reason ? '— ' + res.reason : '');
                    return;
                }
                var chips = res.suggested.map(function (key) {
                    var label = SUGGEST_LABELS[key] || key;
                    return '<span class="vc-ai-suggest-chip" data-suggest="' + key + '">'
                         + '<i class="fa fa-plus"></i> ' + $('<i>').text(label).html()
                         + '</span>';
                }).join('');
                $('#clc_ai_suggest_chips').html(chips);
                $('#clc_ai_suggest_reason').text(res.reason ? '— ' + res.reason : '');
            },
            error: function (xhr, status) {
                aiSuggestInFlight = null;
                if (status === 'abort') return;
                var msg = (xhr && xhr.responseJSON && xhr.responseJSON.msg)
                       || ('AI request failed (' + (xhr ? xhr.status : 'no response') + '). Check VetAI Settings.');
                $('#clc_ai_suggest_chips').html('<small style="color:#dc2626;"><i class="fa fa-exclamation-triangle"></i> ' + $('<i>').text(msg).html() + '</small>');
                console.warn('Exam suggest failed:', xhr);
            }
        });
    }

    // ── Symptom extraction (key clinical findings panel) ─────────
    var aiSymptomsTimer = null;
    var aiSymptomsInFlight = null;
    var aiSymptomsDismissed = false;
    var aiSymptomsLastSig = '';

    function fetchAndShowSymptoms() {
        if (!AI_AVAILABLE || aiSymptomsDismissed) return;
        var text = ($('#soap_s').val() || '').trim();
        if (text.length < 10) {
            $('#clc_ai_symptoms').hide();
            return;
        }
        var sig = _hash(text);
        if (sig === aiSymptomsLastSig) return;
        aiSymptomsLastSig = sig;

        // Visible loading state.
        $('#clc_ai_symptoms_list').html('<span class="text-muted" style="font-size:11.5px;"><i class="fa fa-spinner fa-spin"></i> Extracting clinical findings-</span>');
        $('#clc_ai_symptoms_concern').attr('class', 'vc-ai-concern').text('');
        $('#clc_ai_symptoms').show();

        if (aiSymptomsInFlight) { try { aiSymptomsInFlight.abort(); } catch (e) {} }
        aiSymptomsInFlight = $.ajax({
            url: EXTRACT_SYMPTOMS_URL, method: 'POST',
            data: { _token: CHIP_AJAX_TOKEN, subjective: text },
            success: function (res) {
                aiSymptomsInFlight = null;
                if (!res || !res.success) {
                    $('#clc_ai_symptoms_list').html('<small class="text-muted">AI did not return a usable answer.</small>');
                    return;
                }
                if (!Array.isArray(res.findings) || !res.findings.length) {
                    $('#clc_ai_symptoms_list').html('<small class="text-muted">No discrete clinical findings detected yet.</small>');
                    return;
                }
                var pills = res.findings.map(function (f) {
                    var sev = (f.severity || 'mild').toLowerCase();
                    var icon = sev === 'critical' ? 'fa-exclamation-triangle'
                            : (sev === 'moderate' ? 'fa-exclamation-circle' : 'fa-circle');
                    var qt = (f.quote || '').toString();
                    return '<span class="vc-ai-symptom-pill ' + sev + '"'
                         + (qt ? ' title="from: “' + $('<i>').text(qt).html() + '”"' : '')
                         + '><i class="fa ' + icon + '"></i> '
                         + $('<i>').text(f.label).html()
                         + '</span>';
                }).join('');
                $('#clc_ai_symptoms_list').html(pills);
                var concern = (res.overall_concern || 'low').toLowerCase();
                $('#clc_ai_symptoms_concern')
                    .attr('class', 'vc-ai-concern ' + concern)
                    .text(concern.toUpperCase() + ' concern');
            },
            error: function (xhr, status) {
                aiSymptomsInFlight = null;
                if (status === 'abort') return;
                var msg = (xhr && xhr.responseJSON && xhr.responseJSON.msg)
                       || ('AI request failed (' + (xhr ? xhr.status : 'no response') + ').');
                $('#clc_ai_symptoms_list').html('<small style="color:#dc2626;"><i class="fa fa-exclamation-triangle"></i> ' + $('<i>').text(msg).html() + '</small>');
                console.warn('Extract symptoms failed:', xhr);
            }
        });
    }

    // Debounce on Subjective edits (typed and voice-inserted). Both the
    // exam-suggest banner AND the symptom-extraction panel fire together.
    // After the AJAX results land we snapshot to the DB cache so the next
    // reload skips the LLM call entirely.
    $('#soap_s').on('input change', function () {
        clearTimeout(aiSuggestTimer);
        aiSuggestTimer = setTimeout(function () {
            fetchAndShowExamSuggestions();
            // Cache snapshot a few seconds after the fetch fires.
            setTimeout(cacheAdviceToServer, 3500);
        }, 1200);
        clearTimeout(aiSymptomsTimer);
        aiSymptomsTimer = setTimeout(fetchAndShowSymptoms, 1200);
    });

    // ── Item #3 - cached-advice helpers ─────────────────────────
    function renderCachedExamSuggestions(payload) {
        if (!payload || !Array.isArray(payload.suggested) || !payload.suggested.length) {
            $('#clc_ai_suggest_chips').html('<small class="text-muted">No specialist exam panels indicated for this complaint. <em>(cached)</em></small>');
            $('#clc_ai_suggest_reason').text(payload && payload.reason ? '— ' + payload.reason : '');
            $('#clc_ai_exam_suggest').show();
            return;
        }
        var chips = payload.suggested.map(function (key) {
            var label = SUGGEST_LABELS[key] || key;
            return '<span class="vc-ai-suggest-chip" data-suggest="' + key + '">'
                 + '<i class="fa fa-plus"></i> ' + $('<i>').text(label).html()
                 + '</span>';
        }).join('');
        $('#clc_ai_suggest_chips').html(chips);
        $('#clc_ai_suggest_reason').text((payload.reason ? '— ' + payload.reason : '') + ' (cached)');
        $('#clc_ai_exam_suggest').show();
        aiSuggestLastSig = _hash(($('#soap_s').val() || '').trim()); // prevent immediate re-fetch
    }
    function renderCachedSymptoms(payload) {
        if (!payload || !Array.isArray(payload.findings) || !payload.findings.length) {
            $('#clc_ai_symptoms_list').html('<small class="text-muted">No discrete clinical findings detected. <em>(cached)</em></small>');
            $('#clc_ai_symptoms').show();
            return;
        }
        var pills = payload.findings.map(function (f) {
            var sev = (f.severity || 'mild').toLowerCase();
            var icon = sev === 'critical' ? 'fa-exclamation-triangle'
                    : (sev === 'moderate' ? 'fa-exclamation-circle' : 'fa-circle');
            var qt = (f.quote || '').toString();
            return '<span class="vc-ai-symptom-pill ' + sev + '"'
                 + (qt ? ' title="from: “' + $('<i>').text(qt).html() + '”"' : '')
                 + '><i class="fa ' + icon + '"></i> '
                 + $('<i>').text(f.label).html()
                 + '</span>';
        }).join('');
        $('#clc_ai_symptoms_list').html(pills);
        var concern = (payload.overall_concern || 'low').toLowerCase();
        $('#clc_ai_symptoms_concern')
            .attr('class', 'vc-ai-concern ' + concern)
            .text(concern.toUpperCase() + ' (cached)');
        $('#clc_ai_symptoms').show();
        aiSymptomsLastSig = _hash(($('#soap_s').val() || '').trim());
    }

    function cacheAdviceToServer() {
        if (!AI_AVAILABLE) return;
        // Snapshot the rendered state and ship it to the server. Fire-and-
        // forget: a failure here only means the next reload re-asks the AI.
        var payload = {
            _token: CHIP_AJAX_TOKEN,
            appointment_id: VC_APPT_ID,
            subjective: ($('#soap_s').val() || '').trim(),
            exam_suggestions: JSON.stringify({
                suggested: $('#clc_ai_suggest_chips .vc-ai-suggest-chip').map(function () { return $(this).data('suggest'); }).get(),
                reason: $('#clc_ai_suggest_reason').text().replace(/^[—\-\s]+/, '').replace(/\s*\(cached\)\s*$/i, ''),
            }),
            symptoms: JSON.stringify({
                findings: $('#clc_ai_symptoms_list .vc-ai-symptom-pill').map(function () {
                    var $p = $(this);
                    var sev = ($p.attr('class').match(/(critical|moderate|mild)/) || [])[1] || 'mild';
                    var title = $p.attr('title') || '';
                    var quote = (title.match(/“([^”]+)”/) || [])[1] || '';
                    return { label: $p.text().trim(), severity: sev, quote: quote };
                }).get(),
                overall_concern: ($('#clc_ai_symptoms_concern').attr('class') || '').replace('vc-ai-concern', '').trim() || 'low',
            }),
        };
        $.post(CACHE_ADVICE_URL, payload).fail(function () {
            console.warn('AI advice cache write failed (non-fatal)');
        });
    }

    // Item #3 + #4 - on page load, prefer the SERVER-CACHED advice if its
    // hash matches the current Subjective text. Only fall back to a live
    // AI call when the text has changed since the last cache.
    setTimeout(function () {
        var initText = ($('#soap_s').val() || '').trim();
        if (!AI_AVAILABLE || initText.length < 12) return;

        var currentHash = _sha1_hex(initText);
        if (VC_AI_CACHE.hash && VC_AI_CACHE.hash === currentHash) {
            console.log('AI advice loaded from server cache (no API call)');
            if (VC_AI_CACHE.suggestions) {
                var sug = typeof VC_AI_CACHE.suggestions === 'string'
                        ? JSON.parse(VC_AI_CACHE.suggestions) : VC_AI_CACHE.suggestions;
                renderCachedExamSuggestions(sug);
            }
            if (VC_AI_CACHE.symptoms) {
                var sym = typeof VC_AI_CACHE.symptoms === 'string'
                        ? JSON.parse(VC_AI_CACHE.symptoms) : VC_AI_CACHE.symptoms;
                renderCachedSymptoms(sym);
            }
            return;
        }
        // Subjective changed (or no cache) → spend an LLM call, then
        // persist the result so the NEXT reload uses the cache.
        fetchAndShowExamSuggestions();
        fetchAndShowSymptoms();
        // Write the cache once both AJAX requests have settled. Use a
        // delayed snapshot so we capture the rendered DOM after the
        // success handlers have populated the panels.
        setTimeout(cacheAdviceToServer, 4000);
    }, 800);
    // Click a chip → expand the matching body-system as Abnormal.
    $(document).on('click', '#clc_ai_suggest_chips .vc-ai-suggest-chip', function () {
        var $c = $(this);
        if ($c.hasClass('applied')) return;
        var key = $c.data('suggest');
        if (applySuggestedExam(key)) {
            $c.addClass('applied').html('<i class="fa fa-check"></i> ' +
                $('<i>').text(SUGGEST_LABELS[key] || key).html() + ' — opened');
        } else {
            toastr.warning('Could not find the matching body-system row.');
        }
    });
    $('#clc_ai_suggest_close').on('click', function () {
        aiSuggestDismissed = true;
        $('#clc_ai_exam_suggest').hide();
    });
    $('#clc_ai_symptoms_close').on('click', function () {
        aiSymptomsDismissed = true;
        $('#clc_ai_symptoms').hide();
    });

    // Init - load DB chips, body systems, freq select, then wire search-more
    renderComplaintChips();
    renderLabChips();
    renderExamChips();
    renderFreqSelect();
    loadBodySystems(function() {
        renderSystems();
        syncObjectiveStates();
        // Item #4a - hydrate the systemFindings catalogue from DB so the
        // admin UI's edits take effect on the next reload. Fetches in
        // parallel per known system_key; on each response, overwrites
        // the hardcoded catalogue and re-renders so the panel reflects
        // the live DB content. Falls back to hardcoded values silently
        // if the endpoint errors.
        var SYS_KEYS = ['heart','lungs','eyes','ears','nose_throat','mouth_teeth_gums','abdomen','musculoskeletal','skin_hair_coat','neurological'];
        var catalogueBaseUrl = '{{ url('clinic/system-findings/catalogue') }}/';
        var pendingHydrations = SYS_KEYS.length;
        SYS_KEYS.forEach(function (key) {
            $.getJSON(catalogueBaseUrl + key + '.json').done(function (res) {
                if (res && res.success && Array.isArray(res.items) && res.items.length) {
                    systemFindings[key] = res.items;
                }
            }).always(function () {
                if (--pendingHydrations === 0) {
                    // One re-render after all fetches settle so any open
                    // Abnormal panel reflects DB edits.
                    try { renderSystems(); } catch (e) {}
                }
            });
        });
    });
    if (planMeds.length) renderPlanMeds();
    restoreDraft();

    // Wire search-more inputs (complaints, labs, exams only - freq uses select)
    setupSearchMore('complaint_search', 'complaint_search_results', 'complaint', $('#complaint_chips'),
        makeComplaintChip,
        function(label) {
            $('#complaint_chips .complaint-chip[data-chip="' + label + '"]').addClass('active');
            var cur = $('#soap_s').val().trim();
            $('#soap_s').val(cur ? cur + '\n- ' + label + '.' : '- ' + label + '.');
            autoGrow($('#soap_s')[0]);
            markDirty();
        }
    );
    setupSearchMore('lab_search', 'lab_search_results', 'lab_test', $('#lab_chips'),
        function(label){ return makeInvChip(label, 'lab'); },
        function(label) {
            $('#lab_chips .complaint-chip[data-inv="' + label + '"]').addClass('active');
            var cur = $('#soap_labs').val().trim();
            $('#soap_labs').val(cur ? cur + '\n- ' + label : '- ' + label);
            autoGrow($('#soap_labs')[0]);
            markDirty();
        }
    );
    setupSearchMore('exam_search', 'exam_search_results', 'exam_test', $('#exam_chips'),
        function(label){ return makeInvChip(label, 'exam'); },
        function(label) {
            $('#exam_chips .complaint-chip[data-inv="' + label + '"]').addClass('active');
            var cur = $('#soap_exams').val().trim();
            $('#soap_exams').val(cur ? cur + '\n- ' + label : '- ' + label);
            autoGrow($('#soap_exams')[0]);
            markDirty();
        }
    );
    // Sync chip active state from saved textarea content
    syncSubjectiveChips();
    syncInvChips();
    // syncObjectiveStates() is called inside loadBodySystems callback above
    // Expand pre-filled textareas
    $('.soap-card textarea').each(function () { autoGrow(this); });

    // ── Document Upload ────────────────────────────────
    function buildDocCard(doc) {
        var mime   = doc.mime || '';
        var isImg  = doc.is_image || mime.indexOf('image') !== -1;
        var isPdf  = mime === 'application/pdf';
        var isWord = mime.indexOf('word') !== -1 || mime.indexOf('officedocument.word') !== -1;
        var isXls  = mime.indexOf('excel') !== -1 || mime.indexOf('spreadsheet') !== -1;
        var pBg    = isPdf ? '#e74c3c' : isWord ? '#2980b9' : isXls ? '#27ae60' : '#7f8c8d';
        var pIcon  = isPdf ? 'fa-file-pdf' : isWord ? 'fa-file-word' : isXls ? 'fa-file-excel' : 'fa-file';

        var preview = isImg
            ? '<img src="' + doc.url + '" alt="' + doc.name + '" style="width:100%;height:100%;object-fit:cover;">'
            : '<i class="fa ' + pIcon + '" style="font-size:48px;color:rgba(255,255,255,.9);"></i>';
        var previewBg = isImg ? '' : 'background:' + pBg + ';';

        var descHtml = doc.description
            ? '<div class="doc-card-desc">' + doc.description + '</div>'
            : '';
        var uploaderHtml = doc.uploaded_by
            ? ' &bull; ' + doc.uploaded_by
            : '';

        return '<div class="doc-card" id="doc_item_' + doc.id + '">' +
            '<div class="doc-card-preview" style="' + previewBg + '">' +
                preview +
                '<a href="' + doc.url + '" target="_blank" class="doc-preview-overlay" style="text-decoration:none;">' +
                    '<i class="fa fa-eye doc-eye"></i>' +
                '</a>' +
            '</div>' +
            '<div class="doc-card-body">' +
                '<div class="doc-card-name" title="' + doc.name + '">' + doc.name + '</div>' +
                '<div class="doc-card-badges"><span class="label label-default" style="font-size:10px;">' + doc.type_label + '</span></div>' +
                '<div class="doc-card-meta">' + (doc.size_kb || '') + 'KB &bull; ' + doc.created_at + uploaderHtml + '</div>' +
                descHtml +
                '<div class="doc-card-actions">' +
                    '<a href="' + doc.url + '" target="_blank" class="btn btn-xs btn-info"><i class="fa fa-eye"></i> View</a>' +
                    '<a href="' + doc.url + '" download class="btn btn-xs btn-default"><i class="fa fa-download"></i></a>' +
                    '<button class="btn btn-xs btn-danger btn-delete-doc" data-id="' + doc.id + '" title="Delete"><i class="fa fa-trash"></i></button>' +
                '</div>' +
            '</div>' +
        '</div>';
    }

    function addDocToList(doc) {
        $('#no_docs_msg').hide();
        $('#doc_grid').prepend(buildDocCard(doc));
        var cnt = parseInt($('#doc_count_badge').text() || '0') + 1;
        $('#doc_count_badge').text(cnt);
    }

    $('#btn_upload_doc').on('click', function () {
        var name = $('#doc_name').val().trim();
        var file = $('#doc_file')[0].files[0];
        if (!name) { toastr.warning('Enter a document name.'); return; }
        if (!file) { toastr.warning('Select a file to upload.'); return; }
        var fd = new FormData();
        fd.append('_token', csrfToken);
        fd.append('document_name', name);
        fd.append('document_type', $('#doc_type').val());
        fd.append('description', $('#doc_desc').val());
        fd.append('file', file);
        var $btn = $(this).prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i>');
        $('#doc_upload_progress').show();
        $.ajax({
            url: docUploadUrl, method: 'POST', data: fd,
            processData: false, contentType: false,
            xhr: function() {
                var xhr = new window.XMLHttpRequest();
                xhr.upload.addEventListener('progress', function(e) {
                    if (e.lengthComputable) {
                        var pct = Math.round(e.loaded / e.total * 100) + '%';
                        $('#doc_progress_bar').css('width', pct).text(pct);
                    }
                }, false);
                return xhr;
            },
            success: function(res) {
                if (res.success) {
                    toastr.success(res.msg);
                    addDocToList(res.doc);
                    $('#doc_name').val(''); $('#doc_desc').val('');
                    $('#doc_file').val('');
                }
            },
            error: function() { toastr.error('Upload failed.'); },
            complete: function() {
                $btn.prop('disabled', false).html('<i class="fa fa-upload"></i> Upload');
                $('#doc_upload_progress').hide();
                $('#doc_progress_bar').css('width', '0%').text('0%');
            }
        });
    });

    $(document).on('click', '.btn-delete-doc', function () {
        if (!confirm('Delete this document?')) return;
        var docId = $(this).data('id');
        var $item = $('#doc_item_' + docId);
        $.ajax({
            url: docDeleteBase + '/' + docId, method: 'DELETE',
            data: { _token: csrfToken },
            success: function(res) {
                if (res.success) {
                    $item.fadeOut(300, function() {
                        $(this).remove();
                        if ($('#doc_grid .doc-card').length === 0) $('#no_docs_msg').show();
                    });
                    toastr.success(res.msg);
                    var cnt = Math.max(0, parseInt($('#doc_count_badge').text() || '1') - 1);
                    $('#doc_count_badge').text(cnt);
                }
            },
            error: function() { toastr.error('Delete failed.'); }
        });
    });

    // ── Add Note ──────────────────────────────────────
    $('#btn_add_note').on('click', function () {
        var text = $('#note_text').val().trim();
        if (!text) { toastr.warning('Note text is required.'); return; }
        var $btn  = $(this).prop('disabled', true);
        var title = $('#note_title').val().trim();
        $.post(notesUrl, { _token: csrfToken, title: title, notes: text }, function (res) {
            if (!res.success) return;
            toastr.success(res.msg);
            // Hide "no notes" placeholder
            $('#tab-notes .text-muted').hide();
            // Build timeline item (escape user text via DOM)
            var $titleSpan = $('<span>').text(title || 'Note');
            var $bodySpan  = $('<span>').text(text);
            var bodyHtml   = $bodySpan.html().replace(/\n/g, '<br>');
            var html = '<li class="tl-item">' +
                '<div class="tl-dot"></div>' +
                '<div class="tl-card">' +
                    '<div class="tl-head">' +
                        '<strong>' + $titleSpan.html() + '</strong>' +
                        '<span class="tl-meta">' + (res.note.user_name || '') + ' &mdash; ' + res.note.created_at + '</span>' +
                    '</div>' +
                    '<div class="tl-body">' + bodyHtml + '</div>' +
                '</div></li>';
            // Ensure the timeline list exists
            if (!$('#notes_list').length) {
                $('#tab-notes hr').after('<ul class="vc-timeline" id="notes_list"></ul>');
            }
            $('#notes_list').prepend(html);
            // Update Notes badge
            var $badge = $('a[href="#tab-notes"] .badge');
            $badge.text(parseInt($badge.text() || '0') + 1);
            // Clear inputs
            $('#note_title').val(''); $('#note_text').val('');
        }).fail(function(){ toastr.error('Failed to add note'); })
          .always(function(){ $btn.prop('disabled', false); });
    });

    // ── Treatment Modal ──────────────────────────────
    var vaxValidMonths = 12;
    var vaxEditMode    = false;
    var pendingVaxData = null;

    function selectVaxStatus(el) {
        $('.vax-status-opt').removeClass('selected-clinic selected-external selected-scheduled');
        var val = $(el).data('value');
        if (val === 'taken_in')  $(el).addClass('selected-clinic');
        else if (val === 'taken_out') $(el).addClass('selected-external');
        else                          $(el).addClass('selected-scheduled');
        $('#modal_vax_taken_status').val(val);
    }
    window.selectVaxStatus = selectVaxStatus;

    function resetVaxStatusCards(val) {
        $('.vax-status-opt').removeClass('selected-clinic selected-external selected-scheduled');
        var $opt = $('.vax-status-opt[data-value="' + val + '"]');
        if (val === 'taken_in')  $opt.addClass('selected-clinic');
        else if (val === 'taken_out') $opt.addClass('selected-external');
        else                          $opt.addClass('selected-scheduled');
        $('#modal_vax_taken_status').val(val);
    }

    $('#vaccinationModal').on('show.bs.modal', function () {
        var isEdit = vaxEditMode;
        vaxEditMode = false;
        if (!isEdit) {
            $('#vax_edit_id').val('');
            $('#vaxModalTitle').html('<span style="width:34px;height:34px;border-radius:9px;background:rgba(255,255,255,.18);display:inline-flex;align-items:center;justify-content:center;font-size:16px;">💉</span> Record Treatment');
            $('#btn_save_vaccination').html('<i class="fas fa-syringe"></i> Save Treatment');
            $('#modal_vaccine_id').val(null).trigger('change');
            resetVaxStatusCards('taken_in');
            $('#modal_vax_dose, #modal_vax_batch, #modal_vax_weight, #modal_vax_temp, #modal_vax_notes').val('');
        }
    }).on('shown.bs.modal', function () {
        $('#modal_vax_date, #modal_vax_next_due').datepicker('destroy').datepicker({
            todayHighlight: true,
            autoclose: true,
            format: datepicker_date_format
        });
        $('#modal_vax_date').off('changeDate').on('changeDate', calcVaxNextDue);
        if (pendingVaxData) {
            if (pendingVaxData.administered_date_iso) {
                $('#modal_vax_date').datepicker('setDate', new Date(pendingVaxData.administered_date_iso + 'T00:00:00'));
            }
            if (pendingVaxData.next_due_date_iso) {
                $('#modal_vax_next_due').datepicker('setDate', new Date(pendingVaxData.next_due_date_iso + 'T00:00:00'));
            }
            pendingVaxData = null;
        } else {
            $('#modal_vax_date').datepicker('setDate', new Date());
        }
    });

    function calcVaxNextDue() {
        var dp = $('#modal_vax_date').data('datepicker');
        var d  = dp ? dp.getDate() : null;
        if (!d) return;
        var nd = new Date(d.getFullYear(), d.getMonth() + parseInt(vaxValidMonths), d.getDate());
        $('#modal_vax_next_due').datepicker('setDate', nd);
    }

    function getIso(id) {
        var dp = $(id).data('datepicker');
        if (!dp) return $(id).val();
        var d = dp.getDate();
        if (!d) return $(id).val();
        return d.getFullYear() + '-' +
               String(d.getMonth()+1).padStart(2,'0') + '-' +
               String(d.getDate()).padStart(2,'0');
    }

    $('.select2-vaccine').select2({
        width: '100%', minimumInputLength: 0,
        ajax: {
            url: '{{ route('clinic.vaccine-types.search') }}',
            dataType: 'json', delay: 250,
            data: function (p) { return { q: p.term || '', species: petSpecies }; },
            processResults: function (d) { return { results: d }; }
        },
        templateResult: function (d) {
            if (!d.id) return d.text;
            var catColors = {'core_vaccine':'primary','non_core_vaccine':'info','spot_on':'warning','deworming':'success','oral':'default','nasal':'default','topical':'default','preventive':'info','other':'default'};
            var color  = catColors[d.category] || 'default';
            var badge  = d.category_label ? ' <span class="label label-'+color+'" style="font-size:10px;">'+d.category_label+'</span>' : '';
            var mfr    = d.manufacturer ? '<br><small style="color:#aaa;">'+d.manufacturer+'</small>' : '';
            return $('<span>'+d.text+badge+mfr+'</span>');
        },
        templateSelection: function (d) { return d.text || d.id; },
        placeholder: 'Search vaccine-'
    }).on('select2:select', function (e) {
        vaxValidMonths = e.params.data.valid_for_months || 12;
        calcVaxNextDue();
    });

    $('#btn_save_vaccination').on('click', function () {
        var editId  = $('#vax_edit_id').val();
        var url     = editId ? ('{{ url('clinic/vaccinations') }}/' + editId + '/update') : vaxStoreUrl;
        var $btn    = $(this).prop('disabled', true);
        $.ajax({
            url: url, method: 'POST',
            data: {
                _token:              csrfToken,
                patient_id:              {{ $appointment->patient_id }},
                appointment_id:      {{ $appointment->id }},
                vaccine_type_id:     $('#modal_vaccine_id').val(),
                taken_status:        $('#modal_vax_taken_status').val(),
                dose:                $('#modal_vax_dose').val(),
                administered_date:   getIso('#modal_vax_date'),
                next_due_date:       getIso('#modal_vax_next_due'),
                batch_no:            $('#modal_vax_batch').val(),
                weight_at_time:      $('#modal_vax_weight').val(),
                temperature_at_time: $('#modal_vax_temp').val(),
                administered_by:     {{ (int) ($appointment->doctor_id ?? 0) }} || null,
                notes:               $('#modal_vax_notes').val()
            },
            success: function (res) {
                if (!res.success) return;
                $('#vaccinationModal').modal('hide');
                toastr.success(res.msg);
                if (editId) {
                    // Edit: simple reload to reflect changes
                    location.reload();
                } else {
                    // Add: append row without reload
                    appendVaxRow(res.vaccination);
                    var $badge = $('a[href="#tab-vaccinations"] .badge');
                    $badge.text(parseInt($badge.text() || '0') + 1);
                }
            },
            error: function (xhr) { toastr.error(xhr.responseJSON ? (xhr.responseJSON.message || 'Error') : 'Error saving vaccination'); },
            complete: function () { $btn.prop('disabled', false); }
        });
    });

    var takenWhereMap = {
        'taken_in':  { cls: 'vax-where-clinic',    icon: '🏥', label: 'At Clinic'  },
        'taken_out': { cls: 'vax-where-external',  icon: '🏠', label: 'External'   },
        'not_taken': { cls: 'vax-where-scheduled', icon: '⏳', label: 'Scheduled'  },
    };

    function appendVaxRow(vax) {
        if (!vax) return;
        $('#no_vax_msg').hide();
        $('#vax_card_grid').show();

        var where  = takenWhereMap[vax.taken_status] || takenWhereMap['not_taken'];
        var dueHtml = vax.next_due_date
            ? '<div class="vax-date-value">' + vax.next_due_date + '</div>'
            : '<div class="vax-date-value" style="color:#94a3b8;">-</div>';

        var chips = '';
        if (vax.dose)               chips += '<span class="vax-detail-chip"><i class="fa fa-tint"></i> Dose: ' + $('<span>').text(vax.dose).html() + '</span>';
        if (vax.batch_no)           chips += '<span class="vax-detail-chip"><i class="fa fa-barcode"></i> Batch: ' + $('<span>').text(vax.batch_no).html() + '</span>';
        if (vax.weight_at_time)     chips += '<span class="vax-detail-chip"><i class="fa fa-balance-scale"></i> ' + vax.weight_at_time + ' kg</span>';
        if (vax.temperature_at_time)chips += '<span class="vax-detail-chip"><i class="fa fa-thermometer-half"></i> ' + vax.temperature_at_time + '-C</span>';

        var notesHtml = vax.notes
            ? '<div class="vax-card-notes">' + $('<span>').text(vax.notes).html() + '</div>'
            : '';

        var card = '<div class="vax-card" id="vax_row_' + vax.id + '" style="opacity:0;transform:translateY(8px);transition:opacity .3s,transform .3s;">' +
            '<div class="vax-card-top">' +
                '<div class="vax-card-name">' + $('<span>').text(vax.vaccine_name).html() + '</div>' +
                '<span class="vax-where-badge ' + where.cls + '">' + where.icon + ' ' + where.label + '</span>' +
            '</div>' +
            '<div class="vax-card-dates">' +
                '<div class="vax-date-block"><div class="vax-date-label">💉 Administered</div><div class="vax-date-value">' + vax.administered_date + '</div></div>' +
                '<div class="vax-date-arrow"><i class="fa fa-arrow-right"></i></div>' +
                '<div class="vax-date-block"><div class="vax-date-label">📅 Next Due</div>' + dueHtml + '</div>' +
            '</div>' +
            (chips ? '<div class="vax-card-details">' + chips + '</div>' : '') +
            notesHtml +
            '<div class="vax-card-footer">' +
                '<button class="btn btn-xs btn-warning btn-edit-vax" data-id="' + vax.id + '" style="border-radius:6px;font-size:11px;padding:3px 9px;" title="Edit"><i class="fa fa-pencil-alt"></i> Edit</button> ' +
                '<button class="btn btn-xs btn-danger btn-delete-vax" data-id="' + vax.id + '" style="border-radius:6px;font-size:11px;padding:3px 9px;" title="Delete"><i class="fa fa-trash"></i></button>' +
            '</div>' +
        '</div>';

        $('#vax_card_grid').append(card);
        // Animate in
        setTimeout(function () {
            var $c = $('#vax_row_' + vax.id);
            $c.css({ opacity: 1, transform: 'translateY(0)' });
        }, 30);
    }

    // ── Link Invoice ───────────────────────────────────
    var linkInvoiceUrl  = '{{ route('clinic.appointments.link-invoice', $appointment->id) }}';
    var searchInvUrl    = '{{ route('clinic.appointments.search-invoices') }}';
    var invSearchTimer;

    // Reset modal state when opened
    $('#linkInvoiceModal').on('show.bs.modal', function () {
        $('#inv_search_input').val('');
        $('#link_invoice_id').val('');
        $('#inv_results_list').hide().empty();
        $('#inv_selected_preview').hide();
        $('#inv_no_results').hide();
        $('#inv_searching').hide();
        $('#inv_search_hint').show();
        $('#btn_link_invoice').prop('disabled', true);
    }).on('shown.bs.modal', function () {
        $('#inv_search_input').trigger('focus');
    });

    // Live search
    $('#inv_search_input').on('input', function () {
        clearTimeout(invSearchTimer);
        var term = $(this).val().trim();
        $('#inv_results_list').hide().empty();
        $('#inv_selected_preview').hide();
        $('#inv_no_results').hide();
        $('#inv_searching').hide();
        $('#inv_search_hint').hide();
        $('#link_invoice_id').val('');
        $('#btn_link_invoice').prop('disabled', true);

        if (term.length < 1) {
            $('#inv_search_hint').show();
            return;
        }
        $('#inv_searching').show();

        invSearchTimer = setTimeout(function () {
            $.get(searchInvUrl, { q: term }, function (data) {
                $('#inv_searching').hide();
                if (!data.length) {
                    $('#inv_no_results_term').text(term);
                    $('#inv_no_results').show();
                    return;
                }
                var html = '';
                $.each(data, function (i, inv) {
                    var paid = inv.payment_status === 'Paid';
                    var payBadge = paid
                        ? '<span style="color:#16a34a;font-size:10px;font-weight:700;background:#dcfce7;padding:1px 7px;border-radius:8px;margin-left:5px;">Paid</span>'
                        : '<span style="color:#d97706;font-size:10px;font-weight:700;background:#fef3c7;padding:1px 7px;border-radius:8px;margin-left:5px;">' + inv.payment_status + '</span>';
                    var iconColor = paid ? '#22c55e' : '#f59e0b';
                    // qa-btn pattern: coloured icon - main label/meta - right-aligned amount
                    html += '<button type="button" class="qa-btn inv-result-row" data-id="' + inv.id + '"'
                        + ' data-invoice="' + inv.invoice_no + '"'
                        + ' data-meta="' + inv.contact_name + ' - ' + inv.transaction_date + ' - ' + inv.final_total + '"'
                        + ' style="text-align:left;margin-bottom:0;">'
                        + '<span class="qa-icon" style="background:' + iconColor + ';"><i class="fa fa-file-alt"></i></span>'
                        + '<div style="flex:1;min-width:0;">'
                        +   '<div style="font-weight:700;font-size:13px;color:#1e293b;">' + inv.invoice_no + payBadge + '</div>'
                        +   '<div style="font-size:11px;color:#64748b;margin-top:1px;font-weight:500;">'
                        +     inv.contact_name + (inv.contact_mobile ? ' - ' + inv.contact_mobile : '')
                        +   '</div>'
                        + '</div>'
                        + '<div style="text-align:right;flex-shrink:0;">'
                        +   '<div style="font-size:12.5px;font-weight:800;color:#1e293b;">' + inv.final_total + '</div>'
                        +   '<div style="font-size:10px;color:#94a3b8;font-weight:500;">' + inv.transaction_date + '</div>'
                        + '</div>'
                        + '</button>';
                });
                $('#inv_results_list').html(html).css('display','flex');
            }).fail(function () {
                $('#inv_searching').hide();
                $('#inv_search_hint').show();
                toastr.error('Search failed. Please try again.');
            });
        }, 350);
    });

    // Row hover is handled by the .qa-btn:hover CSS - no inline override needed.

    // Row select
    $(document).on('click', '.inv-result-row', function () {
        var id      = $(this).data('id');
        var invoice = $(this).data('invoice');
        var meta    = $(this).data('meta');
        $('#link_invoice_id').val(id);
        $('#inv_sel_invoice_no').text(invoice);
        $('#inv_sel_meta').text(meta);
        $('#inv_results_list').hide();
        $('#inv_search_hint, #inv_no_results').hide();
        $('#inv_selected_preview').show();
        $('#btn_link_invoice').prop('disabled', false);
    });

    // Clear selection
    $('#inv_sel_clear').on('click', function () {
        $('#link_invoice_id').val('');
        $('#inv_selected_preview').hide();
        $('#inv_results_list').hide().empty();
        $('#inv_search_input').val('').trigger('focus');
        $('#inv_search_hint').show();
        $('#btn_link_invoice').prop('disabled', true);
    });

    // Submit link
    $('#btn_link_invoice').on('click', function () {
        var sellId = parseInt($('#link_invoice_id').val());
        if (!sellId || sellId < 1) { toastr.warning('Please select an invoice first.'); return; }
        var $btn = $(this).prop('disabled', true);
        $.post(linkInvoiceUrl, { _token: csrfToken, erp_sell_id: sellId }, function (res) {
            if (!res.success) { toastr.error(res.msg || 'Error'); $btn.prop('disabled', false); return; }
            toastr.success(res.msg);
            $('#linkInvoiceModal').modal('hide');
            setTimeout(function () { location.reload(); }, 500);
        }).fail(function (xhr) {
            toastr.error((xhr.responseJSON && xhr.responseJSON.msg) || 'Failed to link invoice.');
            $btn.prop('disabled', false);
        });
    });

    // ── Treatment Delete ─────────────────────────────
    $(document).on('click', '.btn-delete-vax', function () {
        if (!confirm('Delete this vaccination record?')) return;
        var id   = $(this).data('id');
        var $card = $('#vax_row_' + id);
        $.ajax({
            url: '{{ url('clinic/vaccinations') }}/' + id,
            method: 'DELETE',
            data: { _token: csrfToken },
            success: function (res) {
                if (res.success) {
                    $card.css({ opacity: 0, transform: 'scale(.95)', transition: 'all .25s' });
                    setTimeout(function () {
                        $card.remove();
                        var remaining = $('#vax_card_grid .vax-card').length;
                        if (remaining === 0) {
                            $('#no_vax_msg').show();
                            $('#vax_card_grid').hide();
                        }
                        var $badge = $('a[href="#tab-vaccinations"] .badge');
                        $badge.text(Math.max(0, parseInt($badge.text() || '1') - 1));
                        var $cnt = $('.vax-count-badge');
                        $cnt.text(Math.max(0, parseInt($cnt.text()) - 1) + ' recorded');
                    }, 280);
                    toastr.success(res.msg);
                }
            },
            error: function () { toastr.error('Delete failed.'); }
        });
    });

    // ── Treatment Edit ───────────────────────────────
    var vaxEditUrl = '{{ url('clinic/vaccinations') }}';
    $(document).on('click', '.btn-edit-vax', function () {
        var id = $(this).data('id');
        $.get(vaxEditUrl + '/' + id + '/edit', function (data) {
            vaxEditMode    = true;
            pendingVaxData = data;
            $('#vax_edit_id').val(id);
            $('#vaxModalTitle').html('<span style="width:34px;height:34px;border-radius:9px;background:rgba(255,255,255,.18);display:inline-flex;align-items:center;justify-content:center;font-size:16px;">✏️</span> Edit Treatment');
            $('#btn_save_vaccination').html('<i class="fa fa-save"></i> Update Treatment');
            if (data.vaccine_type_id && data.vaccine_type_name) {
                var opt = new Option(data.vaccine_type_name, data.vaccine_type_id, true, true);
                $('#modal_vaccine_id').empty().append(opt).trigger('change');
            }
            resetVaxStatusCards(data.taken_status || 'taken_in');
            $('#modal_vax_dose').val(data.dose || '');
            $('#modal_vax_batch').val(data.batch_no || '');
            $('#modal_vax_weight').val(data.weight_at_time || '');
            $('#modal_vax_temp').val(data.temperature_at_time || '');
            $('#modal_vax_notes').val(data.notes || '');
            $('#vaccinationModal').modal('show');
        }).fail(function () { toastr.error('Failed to load vaccination data.'); });
    });

    // ── Evaluations ────────────────────────────────────
    @if(auth()->user()->can('clinic.evaluate_pet_owner') || auth()->user()->can('clinic.evaluate_doctor'))
    var drmEvalAppointmentId = {{ (int) $appointment->id }};
    var drmEvalCategory      = 'pet_owner';
    var drmEvalFormUrl       = '{{ url('clinic/appointments') }}/' + drmEvalAppointmentId + '/evaluation/form';
    var drmEvalSaveUrl       = '{{ url('clinic/appointments') }}/' + drmEvalAppointmentId + '/evaluation';
    var drmEvalStatusUrl     = '{{ url('clinic/appointments') }}/' + drmEvalAppointmentId + '/evaluation/status';

    function refreshEvalStatuses() {
        $.get(drmEvalStatusUrl).done(function (res) {
            if (!res.success) return;
            ['pet_owner', 'clinic'].forEach(function (cat) {
                var info = res.data[cat];
                var $el = $('#clc_eval_status_' + cat);
                if (!$el.length) return;
                if (!info) {
                    $el.text('').removeClass('text-success text-warning text-danger');
                    return;
                }
                var avg = info.avg_rating ? parseFloat(info.avg_rating).toFixed(2) : '-';
                var lbl = '✓ ' + avg + '/5';
                if (info.flagged) {
                    lbl += ' ⚠ Flagged';
                    $el.text(lbl).removeClass('text-success text-warning').addClass('text-danger');
                } else {
                    $el.text(lbl).removeClass('text-warning text-danger').addClass('text-success');
                }
            });
        });
    }
    refreshEvalStatuses();

    $(document).on('click', '.vc-eval-trigger', function () {
        drmEvalCategory = $(this).data('category');
        $('#clc_eval_modal_body').html('<div class="text-center" style="padding:30px;"><i class="fa fa-spinner fa-spin fa-2x text-primary"></i></div>');
        $('#drmEvaluationModal').modal('show');
        $.get(drmEvalFormUrl, { category: drmEvalCategory })
            .done(function (res) {
                if (res.success) {
                    $('#clc_eval_modal_body').html(res.html);
                } else {
                    $('#clc_eval_modal_body').html('<div class="alert alert-warning">' + (res.msg || 'Could not load form.') + '</div>');
                }
            })
            .fail(function (xhr) {
                $('#clc_eval_modal_body').html('<div class="alert alert-danger">' + (xhr.responseJSON?.msg ?? 'Error loading form') + '</div>');
            });
    });

    // Item 1.2 — Force-rating dismiss warning. When the rating modal was
    // auto-opened by the completion flow and the doctor dismisses it
    // without saving, nudge them with a warning toast so they remember
    // the rating is still pending. Clears the forced-rating flag either
    // way so reopening the modal manually won't re-trigger the warning.
    $('#drmEvaluationModal').on('hidden.bs.modal', function () {
        if (window.drmForcedRating && drmEvalCategory === 'pet_owner') {
            // The save handler clears drmForcedRating on success, so if it's
            // still set here the user dismissed without saving.
            toastr.warning(
                'Please rate the patient - the appointment is complete but feedback is still pending.',
                '', { timeOut: 7000 }
            );
        }
        window.drmForcedRating = false;
    });

    $('#btn_clc_eval_save').on('click', function () {
        var $form = $('#clc_evaluation_form');
        if (!$form.length) { toastr.error('No questions to submit.'); return; }

        var missing = false;
        $form.find('[data-required="1"]').each(function () {
            var v = $(this).val();
            if (v === '' || v === null || v === undefined) missing = true;
        });
        if (missing) { toastr.warning('Please answer all required questions.'); return; }

        var $btn = $(this).prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Saving...');
        $.post(drmEvalSaveUrl, $form.serialize())
            .done(function (res) {
                if (res.success) {
                    toastr.success(res.msg);
                    if (res.flagged) toastr.warning('Average rating below 3 - flagged for management review.');
                    // Clear the forced-rating flag BEFORE hiding the modal,
                    // so the hidden.bs.modal listener doesn't show the
                    // "please rate" warning even though they did rate.
                    window.drmForcedRating = false;
                    $('#drmEvaluationModal').modal('hide');
                    refreshEvalStatuses();
                } else {
                    toastr.error(res.msg || 'Save failed');
                }
            })
            .fail(function (xhr) { toastr.error(xhr.responseJSON?.msg ?? 'Save failed'); })
            .always(function () { $btn.prop('disabled', false).html('<i class="fa fa-save"></i> Save Evaluation'); });
    });
    @endif

    // ── Edit Request (locked completed appointment) ────────
    $('#btn_submit_edit_request').on('click', function () {
        var reason = $.trim($('#edit_request_reason').val());
        if (!reason) { toastr.warning('Please enter a reason for the edit.'); return; }
        var $btn = $(this).prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Submitting-');
        $.post('{{ route('clinic.edit-requests.store', $appointment->id) }}', {
            _token: '{{ csrf_token() }}',
            reason: reason
        }).done(function (res) {
            if (res.success) {
                toastr.success(res.msg);
                $('#editRequestModal').modal('hide');
                setTimeout(function () { location.reload(); }, 900);
            } else {
                toastr.error(res.msg || 'Could not submit request');
            }
        }).fail(function (xhr) {
            toastr.error(xhr.responseJSON?.msg ?? 'Could not submit request');
        }).always(function () {
            $btn.prop('disabled', false).html('<i class="fa fa-paper-plane"></i> Submit Request');
        });
    });

    // ── Doctor-Change Request ──────────────────────────────
    if ($('#dcr_doctor_id').length) {
        $('#dcr_doctor_id').select2({ width: '100%', dropdownParent: $('#doctorChangeModal') });
    }
    $('#btn_submit_doctor_change').on('click', function () {
        var doctorId = $('#dcr_doctor_id').val();
        var reason   = $.trim($('#dcr_reason').val());
        var scope    = $('input[name="dcr_scope"]:checked').val();
        if (!doctorId) { toastr.warning('Please select the requested doctor.'); return; }
        if (!reason)   { toastr.warning('Please enter a reason for the change.'); return; }
        var $btn = $(this).prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Submitting-');
        $.post('{{ route('clinic.doctor-change-requests.store', $appointment->id) }}', {
            _token: '{{ csrf_token() }}',
            requested_doctor_id: doctorId,
            scope: scope,
            reason: reason
        }).done(function (res) {
            if (res.success) {
                toastr.success(res.msg);
                $('#doctorChangeModal').modal('hide');
                setTimeout(function () { location.reload(); }, 900);
            } else {
                toastr.error(res.msg || 'Could not submit request');
            }
        }).fail(function (xhr) {
            toastr.error(xhr.responseJSON?.msg ?? 'Could not submit request');
        }).always(function () {
            $btn.prop('disabled', false).html('<i class="fa fa-paper-plane"></i> Submit Request');
        });
    });

});
</script>

@if(\Schema::hasTable('clc_quotation_meta'))
<script>
// ── Quotation tab - link existing ERP quotations + send approval link ──
$(function () {
    var TOKEN = $('meta[name="csrf-token"]').attr('content');
    var APT_ID = {{ $appointment->id }};
    var CONTACT_ID = {{ (int) ($appointment->contact_id ?? 0) }};
    var SEARCH_URL   = '{{ route('clinic.quotations.search') }}';
    var LIST_URL     = '{{ route('clinic.quotations.appointment-list', $appointment->id) }}';
    var LINK_URL     = '{{ route('clinic.quotations.link', $appointment->id) }}';
    var UNLINK_BASE  = '{{ url('/clinic/quotations/meta') }}';
    var SEND_BASE    = '{{ url('/clinic/quotations/meta') }}';
    var statusColor  = { draft: 'default', sent: 'info', viewed: 'primary', approved: 'success', rejected: 'danger', expired: 'warning', cancelled: 'default' };

    function fmtMoney(v) { return parseFloat(v || 0).toFixed(2); }

    function reloadLinked() {
        $.get(LIST_URL).done(function (res) {
            if (!res.data || !res.data.length) {
                $('#vq_linked_list').html('<div style="text-align:center;color:#94a3b8;padding:18px;">No quotations linked yet.</div>');
                return;
            }
            var html = '<table class="table table-bordered table-condensed" style="margin-bottom:0;">';
            html += '<thead><tr><th>Invoice #</th><th>Total</th><th>Status</th><th>Sent</th><th>Approved</th><th>Actions</th></tr></thead><tbody>';
            res.data.forEach(function (m) {
                var t = m.transaction || {};
                html += '<tr>'
                    + '<td><strong>' + (t.invoice_no || '-') + '</strong>'
                    +     (t.ref_no ? '<br><small class="text-muted">' + t.ref_no + '</small>' : '') + '</td>'
                    + '<td>' + fmtMoney(t.final_total) + '</td>'
                    + '<td><span class="label label-' + (statusColor[m.status] || 'default') + '">' + m.status + '</span></td>'
                    + '<td><small>' + (m.sent_at || '-') + '</small></td>'
                    + '<td><small>' + (m.approved_at ? (m.approved_at + (m.approved_by_name ? '<br>' + m.approved_by_name : '')) : '-') + '</small></td>'
                    + '<td style="white-space:nowrap;">'
                    +     '<button class="btn btn-xs btn-primary vq-send" data-id="' + m.id + '"><i class="fa fa-paper-plane"></i> Send link</button> '
                    +     (m.status === 'approved' ? '' : '<button class="btn btn-xs btn-default vq-unlink" data-id="' + m.id + '"><i class="fa fa-unlink"></i></button>')
                    + '</td>'
                    + '</tr>';
            });
            html += '</tbody></table>';
            $('#vq_linked_list').html(html);
        });
    }

    $('#vq_search_btn').on('click', function () {
        var q = $('#vq_search').val().trim();
        $.get(SEARCH_URL, { q: q, contact_id: CONTACT_ID }).done(function (res) {
            if (!res.data || !res.data.length) {
                $('#vq_search_results').html('<div class="alert alert-default">No matching quotations.</div>');
                return;
            }
            var html = '<div class="list-group" style="margin-bottom:0;">';
            res.data.forEach(function (q) {
                html += '<div class="list-group-item" style="display:flex;justify-content:space-between;align-items:center;">'
                    + '<div><strong>' + (q.invoice_no || '-') + '</strong> - ' + fmtMoney(q.final_total)
                    + '<br><small class="text-muted">' + (q.transaction_date || '') + '</small></div>'
                    + '<button class="btn btn-xs btn-success vq-link" data-id="' + q.id + '"><i class="fa fa-link"></i> Link to appointment</button>'
                    + '</div>';
            });
            html += '</div>';
            $('#vq_search_results').html(html);
        });
    });

    $(document).on('click', '.vq-link', function () {
        $.post(LINK_URL, { _token: TOKEN, transaction_id: $(this).data('id') })
            .done(function (res) {
                if (res.success) { toastr.success('Linked.'); $('#vq_search_results').empty(); $('#vq_search').val(''); reloadLinked(); }
            }).fail(function (xhr) { toastr.error((xhr.responseJSON && xhr.responseJSON.msg) || 'Could not link.'); });
    });

    $(document).on('click', '.vq-send', function () {
        var id = $(this).data('id');
        $.post(SEND_BASE + '/' + id + '/send-approval', { _token: TOKEN })
            .done(function (res) {
                if (res.success) {
                    toastr.success(res.msg);
                    if (res.approval_url) { prompt('Approval link (also emailed):', res.approval_url); }
                    reloadLinked();
                }
            }).fail(function (xhr) { toastr.error((xhr.responseJSON && xhr.responseJSON.msg) || 'Could not send.'); });
    });

    $(document).on('click', '.vq-unlink', function () {
        if (!confirm('Unlink this quotation from the appointment? (Does not delete the quotation.)')) return;
        $.ajax({
            url: UNLINK_BASE + '/' + $(this).data('id'),
            method: 'DELETE', data: { _token: TOKEN },
        }).done(function (res) { if (res.success) { toastr.success('Unlinked.'); reloadLinked(); } })
          .fail(function (xhr) { toastr.error((xhr.responseJSON && xhr.responseJSON.msg) || 'Could not unlink.'); });
    });

    // Lazy-load when the tab opens for the first time.
    $('a[href="#tab-quotations"]').one('shown.bs.tab', reloadLinked);
});
</script>
@endif


@endsection
