import { apiGet, apiPost, apiPut, apiDelete } from '../api.js';
import { pageHeaderHTML, showToast } from '../app.js';
import { confirmAction } from '../confirm-modal.js';
import {
    isClientArchived,
    confirmAndPatchClientArchive,
    clientTableActionsHTML,
} from '../client-archive.js';

const PAGE_SIZE = 50;
const ARCHIVE_FILTER_KEY = 'clientsArchiveFilter';

let clientsList = [];
let coachesList = [];
let scoringProfileCatalog = [];
let filterSearch = '';
let archiveFilter = localStorage.getItem(ARCHIVE_FILTER_KEY) || 'active';
let page = 0;
let expandedClientCode = null;
let expandedQnKey = null;
let expandedVersionKey = null;
let detailByClient = {};

export async function clientsPage() {
    const app = document.getElementById('app');

    app.innerHTML = `
        ${pageHeaderHTML('Clients', '<button class="btn btn-primary" id="showCreateFormBtn">+ Create Client</button>')}
        <div id="createClientWrapper" style="display:none"></div>
        <div id="clientsContent"><div class="spinner"></div></div>
    `;

    document.getElementById('showCreateFormBtn').addEventListener('click', () => {
        const wrapper = document.getElementById('createClientWrapper');
        if (wrapper.style.display === 'none') {
            showCreateForm();
        } else {
            hideCreateForm();
        }
    });

    await loadClients();
}

async function loadClients() {
    try {
        const archiveQuery = archiveFilter !== 'active'
            ? `?archiveFilter=${encodeURIComponent(archiveFilter)}`
            : '';
        const [clientsData, assignData] = await Promise.all([
            apiGet(`clients.php${archiveQuery}`),
            apiGet('assignments.php'),
        ]);
        clientsList = clientsData.clients || [];
        scoringProfileCatalog = clientsData.scoringProfiles || [];
        coachesList = assignData.coaches || [];
        renderClients();
    } catch (e) {
        showToast(e.message, 'error');
        document.getElementById('clientsContent').innerHTML =
            `<p class="error-text" style="padding:16px">${esc(e.message)}</p>`;
    }
}

function renderClients() {
    const container = document.getElementById('clientsContent');

    if (!clientsList.length) {
        const emptyMsg = archiveFilter === 'archived'
            ? '<h3>No archived clients</h3><p>Archived clients are hidden from assignments and follow-up.</p>'
            : archiveFilter === 'all'
                ? '<h3>No clients found</h3><p>Create the first client above.</p>'
                : '<h3>No active clients</h3><p>Create a client above or switch the filter to view archived clients.</p>';
        container.innerHTML = `
            <div class="card">
                <div class="filter-bar clients-list-filters">
                    ${archiveFilterSelectHTML()}
                    <input type="search" id="clientListSearch" class="filter-search" placeholder="Search client, counselor, or note…" value="${esc(filterSearch)}">
                </div>
                <div class="empty-state">${emptyMsg}</div>
            </div>`;
        bindArchiveFilterControl();
        document.getElementById('clientListSearch')?.addEventListener('input', (e) => {
            filterSearch = e.target.value.trim();
        });
        return;
    }

    const showProfileColumn = scoringProfileCatalog.length > 0;
    const profileLegend = showProfileColumn ? scoringLegendHTML() : '';

    container.innerHTML = `
        <div class="card">
            <div class="filter-bar clients-list-filters">
                ${archiveFilterSelectHTML()}
                <input type="search" id="clientListSearch" class="filter-search" placeholder="Search client, counselor, or note…" value="${esc(filterSearch)}">
                <span class="data-toolbar-hint" id="clientListCount"></span>
            </div>
            ${profileLegend}
            <p class="data-toolbar-hint" style="margin:0 0 12px">
                Expand a row to view questionnaire responses and upload version history.
            </p>
            <div class="table-wrapper">
                <table class="data-table">
                    <thead>
                        <tr>
                            <th>Client Code</th>
                            <th>Current counselor</th>
                            <th>Note</th>
                            ${showProfileColumn ? '<th>Profile scores</th>' : ''}
                            <th>Actions</th>
                        </tr>
                    </thead>
                    <tbody id="clientsTableBody"></tbody>
                </table>
            </div>
            <div class="pagination-bar" id="clientsPagination"></div>
        </div>`;

    bindArchiveFilterControl();
    document.getElementById('clientListSearch').addEventListener('input', (e) => {
        filterSearch = e.target.value.trim();
        page = 0;
        clearExpandIfHidden();
        renderClientTableBody();
    });
    ensureCoachBandGlobalClick();
    renderClientTableBody();
}

function archiveFilterSelectHTML() {
    return `
        <select id="clientArchiveFilter" class="clients-archive-filter" aria-label="Client status filter">
            <option value="active" ${archiveFilter === 'active' ? 'selected' : ''}>Active</option>
            <option value="archived" ${archiveFilter === 'archived' ? 'selected' : ''}>Archived</option>
            <option value="all" ${archiveFilter === 'all' ? 'selected' : ''}>All</option>
        </select>`;
}

function bindArchiveFilterControl() {
    const sel = document.getElementById('clientArchiveFilter');
    if (!sel) return;
    sel.addEventListener('change', async (e) => {
        archiveFilter = e.target.value;
        localStorage.setItem(ARCHIVE_FILTER_KEY, archiveFilter);
        page = 0;
        expandedClientCode = null;
        expandedQnKey = null;
        expandedVersionKey = null;
        detailByClient = {};
        document.getElementById('clientsContent').innerHTML = '<div class="spinner"></div>';
        await loadClients();
    });
}

function getFilteredClientsList() {
    let list = clientsList;
    if (filterSearch) {
        const q = filterSearch.toLowerCase();
        list = list.filter(c => {
            const hay = `${c.clientCode || ''} ${c.coachUsername || ''} ${c.note || ''}`.toLowerCase();
            return hay.includes(q);
        });
    }
    return list;
}

function renderClientTableBody() {
    const body = document.getElementById('clientsTableBody');
    const countEl = document.getElementById('clientListCount');
    const filtered = getFilteredClientsList();
    const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
    const colCount = scoringProfileCatalog.length > 0 ? 5 : 4;
    if (page >= totalPages) page = totalPages - 1;
    const slice = filtered.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);

    if (countEl) {
        countEl.textContent = `${filtered.length} client${filtered.length === 1 ? '' : 's'}`;
    }

    if (!slice.length) {
        body.innerHTML = `<tr><td colspan="${colCount}" style="text-align:center;color:var(--text-secondary);padding:24px">No clients match</td></tr>`;
    } else {
        body.innerHTML = slice.map(c => clientRowHTML(c)).join('');
        body.querySelectorAll('.client-expand-btn').forEach(btn => {
            btn.addEventListener('click', () => toggleClient(btn.dataset.code));
        });
        body.querySelectorAll('.client-qn-expand-btn').forEach(btn => {
            btn.addEventListener('click', () => toggleQn(btn.dataset.client, btn.dataset.qn));
        });
        body.querySelectorAll('.client-version-expand-btn').forEach(btn => {
            btn.addEventListener('click', () => toggleVersion(
                btn.dataset.client,
                btn.dataset.qn,
                btn.dataset.submission
            ));
        });
        body.querySelectorAll('.delete-client-btn').forEach(btn => {
            btn.addEventListener('click', () => deleteClient(btn.dataset.code));
        });
        body.querySelectorAll('.archive-client-btn').forEach(btn => {
            btn.addEventListener('click', () => setClientArchived(btn.dataset.code, true));
        });
        body.querySelectorAll('.restore-client-btn').forEach(btn => {
            btn.addEventListener('click', () => setClientArchived(btn.dataset.code, false));
        });
        body.querySelectorAll('.edit-client-note-btn').forEach(btn => {
            btn.addEventListener('click', () => openClientNoteEditor(btn.dataset.client));
        });
        bindCoachBandPickerEvents(body);
    }

    const pag = document.getElementById('clientsPagination');
    if (!pag) return;
    if (filtered.length <= PAGE_SIZE) {
        pag.innerHTML = '';
        return;
    }
    const from = page * PAGE_SIZE + 1;
    const to = Math.min((page + 1) * PAGE_SIZE, filtered.length);
    pag.innerHTML = `
        <button type="button" class="btn btn-sm" id="clientsPagePrev" ${page <= 0 ? 'disabled' : ''}>Prev</button>
        <span>Rows ${from}–${to} of ${filtered.length}</span>
        <button type="button" class="btn btn-sm" id="clientsPageNext" ${page >= totalPages - 1 ? 'disabled' : ''}>Next</button>
    `;
    document.getElementById('clientsPagePrev')?.addEventListener('click', () => {
        page--;
        clearExpandIfHidden();
        renderClientTableBody();
    });
    document.getElementById('clientsPageNext')?.addEventListener('click', () => {
        page++;
        clearExpandIfHidden();
        renderClientTableBody();
    });
}

function qnKey(clientCode, questionnaireID) {
    return `${clientCode}|${questionnaireID}`;
}

function versionKey(clientCode, questionnaireID, submissionID) {
    return `${clientCode}|${questionnaireID}|${submissionID}`;
}

function clearExpandIfHidden() {
    const filtered = getFilteredClientsList();
    const slice = filtered.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
    if (expandedClientCode && !slice.some(c => c.clientCode === expandedClientCode)) {
        expandedClientCode = null;
        expandedQnKey = null;
        expandedVersionKey = null;
    }
}

function answerTableHTML(rows, { highlightLiveDiff = false } = {}) {
    if (!rows?.length) {
        return '<p class="data-toolbar-hint">No answers recorded</p>';
    }
    return `
        <table class="data-table data-table-compact">
            <thead><tr><th>Question</th><th>Answer</th></tr></thead>
            <tbody>
                ${rows.map(r => `
                    <tr class="${highlightLiveDiff && r.changedFromLive ? 'answer-changed' : ''}">
                        <td>${esc(r.label)}</td>
                        <td class="answer-value-cell">${esc(r.value || '—')}</td>
                    </tr>
                `).join('')}
            </tbody>
        </table>`;
}

function clientDetailPanelHTML(clientCode) {
    const detail = detailByClient[clientCode];
    if (!detail) {
        return '<p class="data-toolbar-hint">Loading…</p>';
    }
    const cl = detail.client || {};
    const questionnaires = detail.questionnaires || [];
    if (!questionnaires.length) {
        return `
            <p class="client-detail-meta">
                Counselor: ${esc(cl.coachUsername || '—')}
                ${cl.supervisorUsername ? ` · Supervisor: ${esc(cl.supervisorUsername)}` : ''}
            </p>
            <p class="data-toolbar-hint">No questionnaire data for this client yet.</p>`;
    }

    return `
        <p class="client-detail-meta">
            Counselor: ${esc(cl.coachUsername || '—')}
            ${cl.supervisorUsername ? ` · Supervisor: ${esc(cl.supervisorUsername)}` : ''}
        </p>
        ${renderClientScoringProfiles(detail.scoringProfiles || [], clientCode)}
        ${questionnaires.map(q => clientQnBlockHTML(clientCode, q)).join('')}`;
}

function scoringLegendHTML() {
    return `
        <div class="client-scoring-legend">
            <p class="client-scoring-legend-title"><strong>How profile scores work</strong></p>
            <p class="data-toolbar-hint client-scoring-legend-body">
                Each profile adds up questionnaire points (× weight) into a <strong>weighted total</strong>,
                then maps that total to <span class="band-badge band-green">green</span>
                <span class="band-badge band-yellow">yellow</span>
                <span class="band-badge band-red">red</span> using the thresholds on the Questionnaires page.
            </p>
            <dl class="client-scoring-legend-dl">
                <div>
                    <dt>Calculated</dt>
                    <dd>Automatic category from answers and profile rules (shown before Counselor review).</dd>
                </div>
                <div>
                    <dt>Counselor</dt>
                    <dd>Category confirmed or changed by the counselor in the app (“Review scores”).</dd>
                </div>
                <div>
                    <dt>Incomplete</dt>
                    <dd>Not all questionnaires in that profile are completed yet.</dd>
                </div>
            </dl>
        </div>`;
}

function bandBadgeHTML(band, { pending = false, incomplete = false, label = '' } = {}) {
    const text = label || band || '';
    if (incomplete) {
        return '<span class="band-badge band-none">incomplete</span>';
    }
    if (pending) {
        return '<span class="band-badge band-none">awaiting review</span>';
    }
    if (!band) {
        return '<span class="band-badge band-none">—</span>';
    }
    return `<span class="band-badge band-${esc(band)}">${esc(text || band)}</span>`;
}

const COACH_BANDS = ['green', 'yellow', 'red'];

function bandLabel(band) {
    if (!band) return '—';
    return band.charAt(0).toUpperCase() + band.slice(1);
}

function coachBandPickerHTML(p, clientCode) {
    const calcBand = p.calculatedBand || p.band;
    if (!calcBand || !clientCode || !p.profileID) {
        return '';
    }
    const current = p.coachBand || '';
    const coachPending = p.pendingReview !== false && !current;
    const triggerBadge = coachPending
        ? bandBadgeHTML(null, { pending: true })
        : bandBadgeHTML(current);
    const showAgree = coachPending || (current && current !== calcBand);
    const options = COACH_BANDS.map(b => `
        <button type="button" class="coach-band-option${current === b ? ' is-selected' : ''}"
            data-band="${esc(b)}" role="option" aria-selected="${current === b}">
            ${bandBadgeHTML(b, { label: bandLabel(b) })}
        </button>`).join('');
    const agreeBtn = showAgree ? `
        <button type="button" class="coach-band-option coach-band-option-agree${current === calcBand ? ' is-selected' : ''}"
            data-band="${esc(calcBand)}" role="option">
            <span class="coach-band-option-agree-label">Match calculated</span>
            ${bandBadgeHTML(calcBand, { label: bandLabel(calcBand) })}
        </button>` : '';
    return `
        <div class="coach-band-picker"
            data-client="${esc(clientCode)}"
            data-profile="${esc(p.profileID)}"
            data-profile-name="${esc(p.name || p.profileID)}"
            data-calculated="${esc(calcBand)}"
            data-weighted="${esc(String(p.weightedTotal ?? ''))}"
            data-coach="${esc(current)}">
            <button type="button" class="coach-band-trigger" aria-haspopup="listbox" aria-expanded="false"
                title="Change Counselor category">
                <span class="coach-band-trigger-badge">${triggerBadge}</span>
                <span class="coach-band-trigger-caret" aria-hidden="true">▾</span>
            </button>
            <div class="coach-band-menu" role="listbox" hidden>
                <p class="coach-band-menu-label">Counselor category</p>
                <div class="coach-band-menu-options">${options}</div>
                ${agreeBtn}
            </div>
        </div>`;
}

function profileScoreBlockHTML(p, clientCode = '') {
    const calcBand = p.calculatedBand || p.band;
    if (!calcBand) {
        return `
            <div class="client-profile-score-block is-incomplete">
                <div class="client-profile-score-block-title">${esc(p.name)}</div>
                <div class="client-profile-score-row">
                    <span class="client-profile-score-label">Calculated</span>
                    ${bandBadgeHTML(null, { incomplete: true })}
                </div>
                <p class="client-profile-score-hint">Not all questionnaires in this profile are completed yet.</p>
            </div>`;
    }
    const coachPending = p.pendingReview !== false && !p.coachBand;
    const coachDiffers = p.coachBand && p.coachBand !== calcBand;
    const coachControl = clientCode
        ? coachBandPickerHTML(p, clientCode)
        : (coachPending ? bandBadgeHTML(null, { pending: true }) : bandBadgeHTML(p.coachBand));
    return `
        <div class="client-profile-score-block${coachDiffers ? ' has-coach-override' : ''}" data-profile-id="${esc(p.profileID || '')}">
            <div class="client-profile-score-block-title">${esc(p.name)}</div>
            <div class="client-profile-score-row">
                <span class="client-profile-score-label">Calculated</span>
                ${bandBadgeHTML(calcBand)}
                <span class="client-profile-score-total" title="Weighted total">${esc(String(p.weightedTotal))}</span>
            </div>
            <div class="client-profile-score-row">
                <span class="client-profile-score-label">Counselor</span>
                ${coachControl}
                ${coachDiffers ? '<span class="client-profile-override-hint">override</span>' : ''}
                ${coachDiffers && p.coachReviewedByUsername
                    ? `<span class="client-profile-override-meta">by ${esc(p.coachReviewedByUsername)}${p.coachReviewedAt ? ` · ${esc(p.coachReviewedAt)}` : ''}</span>`
                    : ''}
            </div>
        </div>`;
}

function renderClientScoringProfiles(profiles, clientCode = '') {
    if (!profiles.length) return '';
    return `
        <div class="client-scoring-profiles-detail">
            <strong>Scoring profiles</strong>
            <p class="data-toolbar-hint" style="margin:4px 0 10px">
                Weighted totals and calculated vs Counselor categories for this client.
            </p>
            <div class="client-profile-scores">${profiles.map(p => {
                const calc = p.calculatedBand || p.band;
                if (!calc) return profileScoreBlockHTML(p, clientCode);
                const coachPending = p.pendingReview !== false && !p.coachBand;
                const computedAt = p.computedAt ? `Last computed ${esc(p.computedAt)}` : '';
                const reviewedAt = p.coachReviewedAt ? `Counselor reviewed ${esc(p.coachReviewedAt)}` : '';
                const overrideMeta = p.coachOverride && p.coachReviewedByUsername
                    ? `Overridden by ${esc(p.coachReviewedByUsername)}${p.coachReviewedAt ? ` · ${esc(p.coachReviewedAt)}` : ''}`
                    : '';
                const meta = [computedAt, reviewedAt, overrideMeta].filter(Boolean).join(' · ');
                return `
                    <div class="scoring-profile-detail-card">
                        ${profileScoreBlockHTML(p, clientCode)}
                        ${meta ? `<p class="client-profile-score-meta">${meta}</p>` : ''}
                        ${coachPending ? '<p class="client-profile-score-hint">Counselor has not reviewed this profile in the app yet.</p>' : ''}
                    </div>`;
            }).join('')}</div>
        </div>`;
}

function clientQnBlockHTML(clientCode, q) {
    const key = qnKey(clientCode, q.questionnaireID);
    const qnExpanded = expandedQnKey === key;
    const meta = [
        q.status ? `Status: ${q.status}` : '',
        q.sumPoints != null ? `Points: ${q.sumPoints}` : '',
        q.completedAt ? `Completed: ${q.completedAt}` : '',
        q.submissionCount ? `${q.submissionCount} upload(s)` : '',
    ].filter(Boolean).join(' · ');

    const versionsHTML = (q.submissions || []).map(s => {
        const vKey = versionKey(clientCode, q.questionnaireID, s.submissionID);
        const vExpanded = expandedVersionKey === vKey;
        const vMeta = [
            s.submittedAt ? `Uploaded: ${s.submittedAt}` : '',
            s.status ? `Status: ${s.status}` : '',
            s.sumPoints != null ? `Points: ${s.sumPoints}` : '',
        ].filter(Boolean).join(' · ');
        return `
            <div class="client-qn-block">
                <div>
                    <button type="button" class="btn btn-sm btn-link client-version-expand-btn"
                        data-client="${esc(clientCode)}"
                        data-qn="${esc(q.questionnaireID)}"
                        data-submission="${esc(s.submissionID)}">${vExpanded ? '▼' : '▶'}</button>
                    <strong>Version ${s.version}</strong>
                    ${s.structureRevision ? `<span class="badge badge-q">struct rev ${s.structureRevision}</span>` : ''}
                    ${s.changedCount > 0
                        ? `<span class="badge badge-warn">${s.changedCount} diff vs live</span>`
                        : '<span class="client-qn-summary">Same as live</span>'}
                    ${vMeta ? `<span class="client-qn-summary">${esc(vMeta)}</span>` : ''}
                </div>
                ${vExpanded ? `
                    <div class="client-version-panel">
                        ${s.changedCount > 0
                            ? '<p class="data-toolbar-hint">Highlighted rows differ from current live data.</p>'
                            : ''}
                        ${answerTableHTML(s.answers, { highlightLiveDiff: true })}
                    </div>
                ` : ''}
            </div>`;
    }).join('');

    return `
        <div class="client-qn-block" style="margin-bottom:10px">
            <div>
                <button type="button" class="btn btn-sm btn-link client-qn-expand-btn"
                    data-client="${esc(clientCode)}"
                    data-qn="${esc(q.questionnaireID)}">${qnExpanded ? '▼' : '▶'}</button>
                <strong>${esc(q.name)}</strong>
                ${q.questionnaireVersion ? `<span class="badge badge-q">v${esc(q.questionnaireVersion)}</span>` : ''}
                ${meta ? `<span class="client-qn-summary">${esc(meta)}</span>` : ''}
            </div>
            ${qnExpanded ? `
                <div class="client-qn-panel">
                    <h4 style="margin:0 0 8px;font-size:0.9rem">Current data (live)</h4>
                    ${answerTableHTML(q.currentAnswers)}
                    ${(q.submissions || []).length ? `
                        <h4 style="margin:16px 0 8px;font-size:0.9rem">Upload versions</h4>
                        ${versionsHTML}
                    ` : '<p class="data-toolbar-hint" style="margin-top:12px">No archived upload versions.</p>'}
                </div>
            ` : ''}
        </div>`;
}

function profileDotsHTML(profiles, clientCode) {
    if (!profiles?.length) {
        return '<span class="client-profile-dots-empty">No scoring profiles</span>';
    }
    return `<div class="client-profile-scores">${profiles.map(p => profileScoreBlockHTML(p, clientCode)).join('')}</div>`;
}

function patchClientScoringBand(clientCode, profileID, coachBand) {
    const client = clientsList.find(c => c.clientCode === clientCode);
    if (client?.scoringProfiles) {
        const profile = client.scoringProfiles.find(p => p.profileID === profileID);
        if (profile) {
            profile.coachBand = coachBand;
            profile.pendingReview = false;
        }
    }
    const detail = detailByClient[clientCode];
    if (detail?.scoringProfiles) {
        const profile = detail.scoringProfiles.find(p => p.profileID === profileID);
        if (profile) {
            profile.coachBand = coachBand;
            profile.pendingReview = false;
        }
    }
}

function resetCoachBandMenuPosition(menu) {
    if (!menu) return;
    menu.style.position = '';
    menu.style.left = '';
    menu.style.top = '';
    menu.style.minWidth = '';
    menu.style.zIndex = '';
    menu.style.visibility = '';
}

function positionCoachBandMenu(picker) {
    const trigger = picker.querySelector('.coach-band-trigger');
    const menu = picker.querySelector('.coach-band-menu');
    if (!trigger || !menu) return;
    menu.hidden = false;
    menu.style.visibility = 'hidden';
    const rect = trigger.getBoundingClientRect();
    const menuWidth = Math.max(rect.width, 184);
    const left = Math.min(rect.left, window.innerWidth - menuWidth - 8);
    menu.style.position = 'fixed';
    menu.style.left = `${Math.max(8, left)}px`;
    menu.style.minWidth = `${menuWidth}px`;
    menu.style.zIndex = '10001';
    const menuHeight = menu.offsetHeight;
    let top = rect.bottom + 6;
    if (top + menuHeight > window.innerHeight - 8) {
        top = rect.top - menuHeight - 6;
    }
    menu.style.top = `${Math.max(8, top)}px`;
    menu.style.visibility = '';
}

function closeAllCoachBandMenus() {
    document.querySelectorAll('.coach-band-picker.is-open').forEach(picker => {
        picker.classList.remove('is-open');
        const menu = picker.querySelector('.coach-band-menu');
        const trigger = picker.querySelector('.coach-band-trigger');
        resetCoachBandMenuPosition(menu);
        if (menu) menu.hidden = true;
        if (trigger) trigger.setAttribute('aria-expanded', 'false');
    });
}

function toggleCoachBandMenu(picker) {
    const isOpen = picker.classList.contains('is-open');
    closeAllCoachBandMenus();
    if (isOpen) return;
    picker.classList.add('is-open');
    const trigger = picker.querySelector('.coach-band-trigger');
    positionCoachBandMenu(picker);
    if (trigger) trigger.setAttribute('aria-expanded', 'true');
}

function openCoachBandConfirmModal({ clientCode, profileName, calculatedBand, coachBand }) {
    return new Promise(resolve => {
        const overlay = document.createElement('div');
        overlay.className = 'modal-overlay coach-band-confirm-modal';
        overlay.setAttribute('role', 'dialog');
        overlay.setAttribute('aria-modal', 'true');
        overlay.innerHTML = `
            <div class="modal-dialog">
                <h2>Set Counselor category</h2>
                <p class="modal-subtitle">
                    <strong>${esc(profileName)}</strong> · ${esc(clientCode)}
                </p>
                <div class="coach-band-confirm-compare">
                    <div class="coach-band-confirm-col">
                        <span class="coach-band-confirm-label">Calculated</span>
                        ${bandBadgeHTML(calculatedBand, { label: bandLabel(calculatedBand) })}
                    </div>
                    <div class="coach-band-confirm-arrow" aria-hidden="true">→</div>
                    <div class="coach-band-confirm-col">
                        <span class="coach-band-confirm-label">Counselor</span>
                        ${bandBadgeHTML(coachBand, { label: bandLabel(coachBand) })}
                    </div>
                </div>
                <p class="modal-hint">This overrides the Counselor category for this client and profile.</p>
                <div class="modal-actions">
                    <button type="button" class="btn" data-coach-cancel>Cancel</button>
                    <button type="button" class="btn btn-primary" data-coach-confirm>Save category</button>
                </div>
            </div>`;
        document.body.appendChild(overlay);
        document.body.classList.add('modal-open');

        const close = (confirmed) => {
            overlay.remove();
            document.body.classList.remove('modal-open');
            resolve(confirmed);
        };
        overlay.querySelector('[data-coach-cancel]').addEventListener('click', () => close(false));
        overlay.querySelector('[data-coach-confirm]').addEventListener('click', () => close(true));
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) close(false);
        });
        overlay.querySelector('.modal-dialog').addEventListener('click', (e) => e.stopPropagation());
        const onKey = (e) => {
            if (e.key === 'Escape') close(false);
        };
        document.addEventListener('keydown', onKey, { once: true });
        overlay.querySelector('[data-coach-confirm]').focus();
    });
}

async function promptCoachBandSave(picker, coachBand) {
    const clientCode = picker.dataset.client;
    const profileID = picker.dataset.profile;
    const profileName = picker.dataset.profileName || profileID;
    const calculatedBand = picker.dataset.calculated;
    const current = picker.dataset.coach || '';

    if (!coachBand || coachBand === current) return;

    const confirmed = await openCoachBandConfirmModal({
        clientCode,
        profileName,
        calculatedBand,
        coachBand,
    });
    if (!confirmed) return;

    const weightedRaw = picker.dataset.weighted;
    const weightedTotal = weightedRaw !== '' ? Number(weightedRaw) : null;
    picker.classList.add('is-saving');
    try {
        await apiPut('clients.php', {
            clientCode,
            profileID,
            coachBand,
            calculatedBand,
            weightedTotal,
        });
        patchClientScoringBand(clientCode, profileID, coachBand);
        showToast(`Counselor category saved for ${profileName}`, 'success');
        renderClientTableBody();
    } catch (e) {
        showToast(e.message, 'error');
    } finally {
        picker.classList.remove('is-saving');
    }
}

function bindCoachBandPickerEvents(root = document) {
    root.querySelectorAll('.coach-band-trigger').forEach(btn => {
        btn.addEventListener('click', (e) => {
            e.stopPropagation();
            toggleCoachBandMenu(btn.closest('.coach-band-picker'));
        });
    });
    root.querySelectorAll('.coach-band-option').forEach(btn => {
        btn.addEventListener('click', (e) => {
            e.stopPropagation();
            const picker = btn.closest('.coach-band-picker');
            closeAllCoachBandMenus();
            promptCoachBandSave(picker, btn.dataset.band);
        });
    });
}

let coachBandGlobalClickBound = false;
function ensureCoachBandGlobalClick() {
    if (coachBandGlobalClickBound) return;
    coachBandGlobalClickBound = true;
    document.addEventListener('click', () => closeAllCoachBandMenus());
    document.addEventListener('keydown', (e) => {
        if (e.key === 'Escape') closeAllCoachBandMenus();
    });
}

function clientHasResponseData(c) {
    return Number(c.hasResponseData) === 1;
}

function clientArchivedLabel(c) {
    if (!isClientArchived(c) || !c.archivedAt) return '';
    const d = new Date(Number(c.archivedAt) * 1000);
    const when = Number.isNaN(d.getTime()) ? '' : d.toLocaleDateString();
    return when ? ` <span class="client-archived-badge" title="Archived ${esc(when)}">Archived</span>` : '';
}

function clientRowHTML(c) {
    const coach = c.coachUsername
        ? `<strong>${esc(c.coachUsername)}</strong>`
        : `<span style="color:var(--text-secondary);font-style:italic">Unassigned</span>`;
    const archived = isClientArchived(c);
    const canExpand = clientHasResponseData(c);
    const expanded = canExpand && expandedClientCode === c.clientCode;
    const expandControl = canExpand
        ? `<button type="button" class="btn btn-sm btn-link client-expand-btn" data-code="${esc(c.clientCode)}">${expanded ? '▼' : '▶'}</button> `
        : '';
    const profileCol = scoringProfileCatalog.length > 0
        ? `<td class="client-profile-dots-cell">${profileDotsHTML(c.scoringProfiles || [], c.clientCode)}</td>`
        : '';
    const colCount = scoringProfileCatalog.length > 0 ? 5 : 4;
    const actions = clientTableActionsHTML({
        clientCode: esc(c.clientCode),
        archived,
        showDelete: true,
    });
    const note = String(c.note || '').trim();
    const noteCell = `
        <td class="client-note-cell">
            <div class="client-note-row">
                ${note
                    ? `<span class="client-note-text">${esc(note)}</span>`
                    : `<span class="client-note-empty">—</span>`}
                <button type="button" class="btn btn-sm btn-link edit-client-note-btn"
                    data-client="${esc(c.clientCode)}" title="Edit note">Edit</button>
            </div>
        </td>`;

    return `
        <tr class="${archived ? 'client-row-archived' : ''}">
            <td>
                ${expandControl}<strong>${esc(c.clientCode)}</strong>${clientArchivedLabel(c)}
            </td>
            <td>${coach}</td>
            ${noteCell}
            ${profileCol}
            <td class="client-actions-cell">${actions}</td>
        </tr>
        ${expanded ? `
        <tr class="client-detail-row">
            <td colspan="${colCount}">
                <div class="client-detail-panel">${clientDetailPanelHTML(c.clientCode)}</div>
            </td>
        </tr>` : ''}`;
}

async function toggleClient(clientCode) {
    const row = clientsList.find(c => c.clientCode === clientCode);
    if (!row || !clientHasResponseData(row)) {
        return;
    }
    if (expandedClientCode === clientCode) {
        expandedClientCode = null;
        expandedQnKey = null;
        expandedVersionKey = null;
        renderClientTableBody();
        return;
    }
    expandedClientCode = clientCode;
    expandedQnKey = null;
    expandedVersionKey = null;
    renderClientTableBody();
    if (!detailByClient[clientCode]) {
        try {
            const data = await apiGet(
                `clients.php?clientCode=${encodeURIComponent(clientCode)}&detail=1`
            );
            detailByClient[clientCode] = data;
        } catch (e) {
            showToast(e.message, 'error');
            detailByClient[clientCode] = { client: {}, questionnaires: [] };
        }
        renderClientTableBody();
    }
}

function toggleQn(clientCode, questionnaireID) {
    const key = qnKey(clientCode, questionnaireID);
    expandedQnKey = expandedQnKey === key ? null : key;
    expandedVersionKey = null;
    renderClientTableBody();
}

function toggleVersion(clientCode, questionnaireID, submissionID) {
    const key = versionKey(clientCode, questionnaireID, submissionID);
    expandedVersionKey = expandedVersionKey === key ? null : key;
    renderClientTableBody();
}

async function openClientNoteEditor(clientCode) {
    const row = clientsList.find(c => c.clientCode === clientCode);
    if (!row) return;

    const overlay = document.createElement('div');
    overlay.className = 'modal-overlay client-note-modal';
    overlay.setAttribute('role', 'dialog');
    overlay.setAttribute('aria-modal', 'true');
    overlay.innerHTML = `
        <div class="modal-dialog client-note-modal-dialog">
            <h2>Edit note</h2>
            <p class="modal-subtitle"><strong>${esc(clientCode)}</strong></p>
            <label class="client-note-modal-label" for="clientNoteModalInput">Note (max 200 characters)</label>
            <textarea id="clientNoteModalInput" class="client-note-modal-input" rows="4"
                maxlength="200" placeholder="Client note…">${esc(row.note || '')}</textarea>
            <p class="client-note-modal-count" id="clientNoteModalCount"></p>
            <div class="modal-actions">
                <button type="button" class="btn" data-note-cancel>Cancel</button>
                <button type="button" class="btn btn-primary" data-note-save>Save</button>
            </div>
        </div>`;
    document.body.appendChild(overlay);
    document.body.classList.add('modal-open');

    const ta = overlay.querySelector('#clientNoteModalInput');
    const countEl = overlay.querySelector('#clientNoteModalCount');
    const saveBtn = overlay.querySelector('[data-note-save]');

    const updateCount = () => {
        const len = ta.value.length;
        countEl.textContent = `${len} / 200`;
    };
    updateCount();
    ta.addEventListener('input', updateCount);
    ta.focus();
    ta.setSelectionRange(ta.value.length, ta.value.length);

    const close = () => {
        overlay.remove();
        document.body.classList.remove('modal-open');
        document.removeEventListener('keydown', onKey);
    };
    const onKey = (e) => {
        if (e.key === 'Escape') close();
    };
    document.addEventListener('keydown', onKey);

    overlay.querySelector('[data-note-cancel]').addEventListener('click', close);
    overlay.addEventListener('click', (e) => {
        if (e.target === overlay) close();
    });
    overlay.querySelector('.modal-dialog').addEventListener('click', (e) => e.stopPropagation());

    saveBtn.addEventListener('click', async () => {
        const note = ta.value.trim();
        if (note.length > 200) {
            showToast('Note must be at most 200 characters', 'error');
            return;
        }
        saveBtn.disabled = true;
        saveBtn.textContent = 'Saving…';
        try {
            const res = await apiPut('analytics.php', { clientCode, note });
            const saved = res?.note ?? note;
            row.note = saved;
            showToast('Note saved', 'success');
            close();
            renderClientTableBody();
        } catch (e) {
            showToast(e.message, 'error');
            saveBtn.disabled = false;
            saveBtn.textContent = 'Save';
        }
    });
}

async function setClientArchived(clientCode, archived) {
    const ok = await confirmAndPatchClientArchive(clientCode, archived);
    if (!ok) return;

    if (archived && archiveFilter === 'active') {
        clientsList = clientsList.filter(c => c.clientCode !== clientCode);
    } else if (!archived && archiveFilter === 'archived') {
        clientsList = clientsList.filter(c => c.clientCode !== clientCode);
    } else {
        const row = clientsList.find(c => c.clientCode === clientCode);
        if (row) {
            row.archived = archived ? 1 : 0;
            row.archivedAt = archived ? Math.floor(Date.now() / 1000) : 0;
        }
    }
    if (expandedClientCode === clientCode && archived) {
        expandedClientCode = null;
        expandedQnKey = null;
        expandedVersionKey = null;
    }
    if (!clientsList.length) renderClients();
    else renderClientTableBody();
}

async function deleteClient(clientCode) {
    if (!(await confirmAction({
        title: 'Delete client',
        message: `Delete client "${clientCode}"? This will also remove all their answers and results. This cannot be undone.`,
        confirmLabel: 'Delete',
        variant: 'danger',
    }))) return;
    try {
        await apiDelete('clients.php', { clientCode });
        clientsList = clientsList.filter(c => c.clientCode !== clientCode);
        delete detailByClient[clientCode];
        if (expandedClientCode === clientCode) {
            expandedClientCode = null;
            expandedQnKey = null;
            expandedVersionKey = null;
        }
        showToast(`Client "${clientCode}" deleted`, 'success');
        if (!clientsList.length) renderClients();
        else renderClientTableBody();
    } catch (e) {
        showToast(e.message, 'error');
    }
}

function showCreateForm() {
    if (!coachesList.length) {
        showToast('No counselors available. Create a counselor first.', 'error');
        return;
    }

    const wrapper = document.getElementById('createClientWrapper');
    wrapper.style.display = '';
    wrapper.innerHTML = `
        <div class="inline-form-card client-create-card" style="margin-bottom:20px">
            <h4>Create Clients</h4>
            <div class="tab-bar client-create-tabs" id="cc_tabs">
                <button type="button" class="active" data-tab="single">Single</button>
                <button type="button" data-tab="range">Range</button>
            </div>

            <div id="cc_panel_single">
                <div class="form-row">
                    <div class="form-group" style="flex:2">
                        <label for="cc_code">Client Code</label>
                        <input type="text" id="cc_code" autocomplete="off" placeholder="e.g. CLIENT-001">
                    </div>
                </div>
            </div>

            <div id="cc_panel_range" style="display:none">
                <p class="form-hint">Use matching prefix and suffix with a numeric segment in between.</p>
                <div class="form-row client-range-row">
                    <div class="form-group">
                        <label for="cc_from">From</label>
                        <input type="text" id="cc_from" autocomplete="off" placeholder="e.g. C001">
                    </div>
                    <div class="client-range-arrow" aria-hidden="true">→</div>
                    <div class="form-group">
                        <label for="cc_to">To</label>
                        <input type="text" id="cc_to" autocomplete="off" placeholder="e.g. C050">
                    </div>
                </div>
                <div id="cc_range_preview" class="client-range-preview" hidden></div>
            </div>

            <div class="form-group" style="margin-top:4px">
                <label for="cc_coach">Assign to counselor</label>
                <select id="cc_coach">
                    <option value="">— select counselor —</option>
                    ${coachSelectOptions()}
                </select>
            </div>

            <div class="client-create-actions">
                <button class="btn btn-primary btn-sm" id="cc_submit">Create Client</button>
                <button class="btn btn-sm" id="cc_cancel">Cancel</button>
            </div>
            <p id="cc_error" class="error-text" style="display:none;margin-top:8px"></p>
        </div>
    `;

    let createMode = 'single';

    const setMode = (mode) => {
        createMode = mode;
        document.querySelectorAll('#cc_tabs button').forEach((btn) => {
            btn.classList.toggle('active', btn.dataset.tab === mode);
        });
        document.getElementById('cc_panel_single').style.display = mode === 'single' ? '' : 'none';
        document.getElementById('cc_panel_range').style.display = mode === 'range' ? '' : 'none';
        const submitBtn = document.getElementById('cc_submit');
        if (mode === 'range') {
            updateRangePreview();
            submitBtn.textContent = rangePreviewCount()
                ? `Create ${rangePreviewCount()} Clients`
                : 'Create Clients';
            document.getElementById('cc_from').focus();
        } else {
            submitBtn.textContent = 'Create Client';
            document.getElementById('cc_code').focus();
        }
        document.getElementById('cc_error').style.display = 'none';
    };

    document.querySelectorAll('#cc_tabs button').forEach((btn) => {
        btn.addEventListener('click', () => setMode(btn.dataset.tab));
    });

    ['cc_from', 'cc_to'].forEach((id) => {
        document.getElementById(id).addEventListener('input', () => {
            if (createMode !== 'range') return;
            updateRangePreview();
            const count = rangePreviewCount();
            document.getElementById('cc_submit').textContent = count
                ? `Create ${count} Clients`
                : 'Create Clients';
        });
    });

    wrapper.querySelectorAll('input').forEach((inp) => {
        inp.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                if (createMode === 'range') submitCreateClientRange();
                else submitCreateClient();
            }
            if (e.key === 'Escape') hideCreateForm();
        });
    });

    document.getElementById('cc_submit').addEventListener('click', () => {
        if (createMode === 'range') submitCreateClientRange();
        else submitCreateClient();
    });
    document.getElementById('cc_cancel').addEventListener('click', hideCreateForm);

    document.getElementById('cc_code').focus();
}

function coachSelectOptions() {
    return coachesList.map((c) =>
        `<option value="${c.coachID}">${esc(c.username)}${c.supervisorUsername ? ` (${esc(c.supervisorUsername)})` : ''}</option>`
    ).join('');
}

function parseClientCodePattern(code) {
    const trimmed = String(code).trim();
    if (!/\d/.test(trimmed)) {
        return { error: 'Code must contain a numeric segment (e.g. C001 or A01Demo)' };
    }

    let suffix = '';
    let core = trimmed;
    const suffixMatch = trimmed.match(/[^0-9]+$/);
    if (suffixMatch) {
        suffix = suffixMatch[0];
        core = trimmed.slice(0, -suffix.length);
    }

    const coreMatch = core.match(/^(.*?)(\d+)$/);
    if (!coreMatch || !coreMatch[2]) {
        return { error: 'Code must contain a numeric segment (e.g. C001 or A01Demo)' };
    }

    return {
        prefix: coreMatch[1],
        digits: coreMatch[2],
        suffix,
    };
}

function parseClientCodeRange(from, to) {
    const fromParts = parseClientCodePattern(from);
    if (fromParts.error) return fromParts;
    const toParts = parseClientCodePattern(to);
    if (toParts.error) return toParts;

    if (fromParts.prefix !== toParts.prefix || fromParts.suffix !== toParts.suffix) {
        return { error: 'Prefix and suffix must match (e.g. C001…C050 or A01Demo…A10Demo)' };
    }

    const prefix = fromParts.prefix;
    const suffix = fromParts.suffix;
    const padWidth = fromParts.digits.length;
    const start = parseInt(fromParts.digits, 10);
    const end = parseInt(toParts.digits, 10);
    if (!Number.isFinite(start) || !Number.isFinite(end)) {
        return { error: 'Invalid numeric segment' };
    }
    if (start > end) {
        return { error: 'From number must be less than or equal to to' };
    }

    const count = end - start + 1;
    const maxCount = 500;
    if (count > maxCount) {
        return { error: `Range exceeds maximum of ${maxCount} clients` };
    }

    const codes = [];
    for (let n = start; n <= end; n++) {
        codes.push(`${prefix}${String(n).padStart(padWidth, '0')}${suffix}`);
    }
    return { codes, count, prefix, suffix };
}

function rangePreviewCount() {
    const from = document.getElementById('cc_from')?.value ?? '';
    const to = document.getElementById('cc_to')?.value ?? '';
    const parsed = parseClientCodeRange(from, to);
    return parsed.codes?.length ?? 0;
}

function formatRangePreview(codes) {
    if (!codes.length) return '';
    if (codes.length <= 6) return codes.join(', ');
    return `${codes.slice(0, 3).join(', ')} … ${codes.slice(-2).join(', ')}`;
}

function updateRangePreview() {
    const preview = document.getElementById('cc_range_preview');
    if (!preview) return;

    const from = document.getElementById('cc_from').value.trim();
    const to = document.getElementById('cc_to').value.trim();
    if (!from && !to) {
        preview.hidden = true;
        preview.innerHTML = '';
        return;
    }

    const parsed = parseClientCodeRange(from, to);
    preview.hidden = false;
    if (parsed.error) {
        preview.className = 'client-range-preview client-range-preview-error';
        preview.innerHTML = `<span>${esc(parsed.error)}</span>`;
        return;
    }

    preview.className = 'client-range-preview';
    preview.innerHTML = `
        <strong>${parsed.count} client${parsed.count === 1 ? '' : 's'}</strong> will be created
        <div class="range-codes">${esc(formatRangePreview(parsed.codes))}</div>
    `;
}

function hideCreateForm() {
    const wrapper = document.getElementById('createClientWrapper');
    if (wrapper) { wrapper.style.display = 'none'; wrapper.innerHTML = ''; }
}

async function submitCreateClient() {
    const errEl = document.getElementById('cc_error');
    errEl.style.display = 'none';

    const clientCode = document.getElementById('cc_code').value.trim();
    const coachID    = document.getElementById('cc_coach').value;

    if (!clientCode) { showError(errEl, 'Client code is required'); return; }
    if (!coachID)    { showError(errEl, 'Please select a counselor'); return; }

    const btn = document.getElementById('cc_submit');
    btn.disabled = true;
    btn.textContent = 'Creating...';

    try {
        const data = await apiPost('clients.php', { clientCode, coachID });
        const coach = coachesList.find(c => c.coachID === coachID);
        clientsList.push({
            clientCode:   data.clientCode,
            coachID:      coachID,
            coachUsername: coach?.username ?? null,
        });
        showToast(`Client "${data.clientCode}" created`, 'success');
        hideCreateForm();
        renderClients();
    } catch (e) {
        showError(errEl, e.message);
        btn.disabled = false;
        btn.textContent = 'Create Client';
    }
}

async function submitCreateClientRange() {
    const errEl = document.getElementById('cc_error');
    errEl.style.display = 'none';

    const fromCode = document.getElementById('cc_from').value.trim();
    const toCode = document.getElementById('cc_to').value.trim();
    const coachID = document.getElementById('cc_coach').value;

    if (!fromCode || !toCode) {
        showError(errEl, 'From and to codes are required');
        return;
    }
    if (!coachID) {
        showError(errEl, 'Please select a counselor');
        return;
    }

    const parsed = parseClientCodeRange(fromCode, toCode);
    if (parsed.error) {
        showError(errEl, parsed.error);
        return;
    }

    const btn = document.getElementById('cc_submit');
    btn.disabled = true;
    btn.textContent = 'Creating...';

    try {
        const data = await apiPost('clients.php', {
            bulk: true,
            fromCode,
            toCode,
            coachID,
        });
        const coach = coachesList.find((c) => c.coachID === coachID);
        const created = data.created ?? [];
        created.forEach((clientCode) => {
            clientsList.push({
                clientCode,
                coachID,
                coachUsername: coach?.username ?? null,
            });
        });

        const skipped = data.skippedCount ?? 0;
        let message = `${data.createdCount ?? created.length} client${created.length === 1 ? '' : 's'} created`;
        if (skipped > 0) {
            message += ` · ${skipped} skipped (already exist)`;
        }
        showToast(message, 'success');
        hideCreateForm();
        renderClients();
    } catch (e) {
        showError(errEl, e.message);
        btn.disabled = false;
        const count = parsed.count ?? 0;
        btn.textContent = count ? `Create ${count} Clients` : 'Create Clients';
    }
}

function showError(el, msg) {
    el.textContent = msg;
    el.style.display = '';
}

function esc(s) {
    const d = document.createElement('div');
    d.textContent = s ?? '';
    return d.innerHTML;
}
