// =============================================================
// APP.JS — Hyper Analytics, дизайн-система «Ink & Amber».
// Клиентский слой: форматтеры, SVG-графики, сортировка таблиц,
// табы, тосты, bottom-sheet навигации, относительное время.
// Каркас (topbar/док/рейл) рендерит PHP (lib/layout.php);
// здесь — только поведение.
// =============================================================

window.UI = (function () {

    // ---- Bottom-sheet навигации «Ещё» --------------------------
    function openSheet() { document.body.classList.add('sheet-open'); }
    function closeSheet() { document.body.classList.remove('sheet-open'); }

    // ---- Форматтеры -------------------------------------------
    function usd(v, sign) {
        if (v === null || v === undefined) return '—';
        const s = (sign === false) ? '' : (v >= 0 ? '+' : '');
        return s + v.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' $';
    }
    function pct(v, sign) {
        if (v === null || v === undefined) return '—';
        const s = (sign === false) ? '' : (v >= 0 ? '+' : '');
        return s + v.toFixed(2) + '%';
    }
    function pnlClass(v) { return v === null || v === undefined ? '' : (v >= 0 ? 'pos' : 'neg'); }
    function shortAddr(a) { return a ? a.slice(0, 6) + '…' + a.slice(-4) : ''; }
    function esc(s) { return String(s).replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c])); }

    // ---- SVG-графики -------------------------------------------
    // Палитра Ink & Amber: янтарь — акцент, мята/коралл — семантика PnL.
    const C = {
        pos: '#3DDBA5', neg: '#FF6B7A', line: '#7AA5FF', warn: '#F5B942', brand: '#F5B942',
        grid: 'rgba(233,238,255,.08)', axis: '#5C6478',
    };
    let gidSeq = 0;
    function gradientDef(color) {
        const id = 'iag' + (++gidSeq);
        return {
            id: id,
            html: '<defs><linearGradient id="' + id + '" x1="0" y1="0" x2="0" y2="1">'
                + '<stop offset="0" stop-color="' + color + '" stop-opacity=".32"/>'
                + '<stop offset="1" stop-color="' + color + '" stop-opacity="0"/></linearGradient></defs>',
        };
    }

    // Спарклайн накопительного PnL по массиву дневных значений —
    // площадь с градиентом, как в концепте.
    function sparkline(values, w, h, color) {
        w = w || 120; h = h || 32;
        if (!values || !values.length) return '';
        const cum = [];
        let run = 0;
        values.forEach(v => { run += v; cum.push(run); });
        const cmin = Math.min(0, ...cum), cmax = Math.max(0, ...cum);
        const cspan = (cmax - cmin) || 1;
        const pts = cum.map((v, i) => {
            const x = (i / Math.max(cum.length - 1, 1)) * (w - 2) + 1;
            const y = h - 3 - ((v - cmin) / cspan) * (h - 6);
            return x.toFixed(1) + ',' + y.toFixed(1);
        });
        const col = color || (cum[cum.length - 1] >= 0 ? C.pos : C.neg);
        const g = gradientDef(col);
        return '<svg viewBox="0 0 ' + w + ' ' + h + '" preserveAspectRatio="none" style="height:' + h + 'px">'
            + g.html
            + '<polygon points="1,' + h + ' ' + pts.join(' ') + ' ' + (w - 1) + ',' + h + '" fill="url(#' + g.id + ')"/>'
            + '<polyline points="' + pts.join(' ') + '" fill="none" stroke="' + col + '" stroke-width="1.7" stroke-linejoin="round" stroke-linecap="round"/></svg>';
    }

    // Бар-чарт ± с нулевой осью
    function barChart(el, labels, values, suffix, opts) {
        opts = opts || {};
        const node = document.getElementById(el);
        if (!node) return;
        if (!values || !values.length) { node.innerHTML = '<div class="empty">Нет данных за выбранный период</div>'; return; }
        const w = opts.w || 560, h = opts.h || 200, padL = 46, padB = 22, padT = 10, padR = 6;
        const plotW = w - padL - padR, plotH = h - padT - padB;
        const max = Math.max(0, ...values), min = Math.min(0, ...values);
        const span = (max - min) || 1;
        const zeroY = padT + (max / span) * plotH;
        const bw = Math.min(26, Math.max(2, plotW / values.length - 3));
        let out = '<svg viewBox="0 0 ' + w + ' ' + h + '">';
        for (let i = 0; i <= 4; i++) {
            const gy = padT + plotH * i / 4;
            const gv = max - span * i / 4;
            out += '<line x1="' + padL + '" y1="' + gy + '" x2="' + (w - padR) + '" y2="' + gy + '" stroke="' + C.grid + '" stroke-width="1"/>'
                + '<text x="' + (padL - 6) + '" y="' + (gy + 3.5) + '" text-anchor="end" font-size="9.5" fill="' + C.axis + '">' + fmtAxis(gv) + '</text>';
        }
        values.forEach((v, i) => {
            const x = padL + (i + 0.5) * (plotW / values.length) - bw / 2;
            const bh = Math.abs(v) / span * plotH;
            const y = v >= 0 ? zeroY - bh : zeroY;
            out += '<rect class="bar-rect" x="' + x.toFixed(1) + '" y="' + y.toFixed(1) + '" width="' + bw.toFixed(1) + '" height="' + Math.max(bh, 1.5).toFixed(1) + '" rx="3.5" opacity=".9" fill="' + (v >= 0 ? C.pos : C.neg) + '"><title>' + esc(labels[i]) + ': ' + (v >= 0 ? '+' : '') + v + (suffix || '') + '</title></rect>';
            const every = values.length <= 16 ? 1 : Math.ceil(values.length / 10);
            if (i % every === 0) {
                out += '<text x="' + (x + bw / 2) + '" y="' + (h - 6) + '" text-anchor="middle" font-size="9" fill="' + C.axis + '">' + esc(String(labels[i])) + '</text>';
            }
        });
        out += '<line x1="' + padL + '" y1="' + zeroY + '" x2="' + (w - padR) + '" y2="' + zeroY + '" stroke="' + C.axis + '" stroke-width="1"/>';
        out += '</svg>';
        node.innerHTML = out;
    }

    // Линия с градиентной заливкой (кривая капитала, просадка)
    function lineChart(el, labels, values, opts) {
        opts = opts || {};
        const node = document.getElementById(el);
        if (!node) return;
        if (!values || values.length < 2) { node.innerHTML = '<div class="empty">Недостаточно данных для графика</div>'; return; }
        const w = 900, h = opts.h || 220, padL = 50, padB = 22, padT = 12, padR = 10;
        const plotW = w - padL - padR, plotH = h - padT - padB;
        const max = Math.max(0, ...values), min = Math.min(0, ...values);
        const span = (max - min) || 1;
        const X = i => padL + (i / (values.length - 1)) * plotW;
        const Y = v => padT + ((max - v) / span) * plotH;
        const color = opts.color || C.brand;
        const g = gradientDef(color);
        let out = '<svg viewBox="0 0 ' + w + ' ' + h + '">' + g.html;
        for (let i = 0; i <= 4; i++) {
            const gy = padT + plotH * i / 4;
            const gv = max - span * i / 4;
            out += '<line x1="' + padL + '" y1="' + gy + '" x2="' + (w - padR) + '" y2="' + gy + '" stroke="' + C.grid + '"/>'
                + '<text x="' + (padL - 6) + '" y="' + (gy + 3.5) + '" text-anchor="end" font-size="9.5" fill="' + C.axis + '">' + fmtAxis(gv) + '</text>';
        }
        const pts = values.map((v, i) => X(i).toFixed(1) + ',' + Y(v).toFixed(1)).join(' ');
        const baseY = Y(Math.max(min, 0));
        out += '<polygon points="' + padL + ',' + baseY + ' ' + pts + ' ' + (padL + plotW) + ',' + baseY + '" fill="url(#' + g.id + ')"/>';
        out += '<polyline points="' + pts + '" fill="none" stroke="' + color + '" stroke-width="2" stroke-linejoin="round"/>';
        values.forEach((v, i) => {
            out += '<circle cx="' + X(i).toFixed(1) + '" cy="' + Y(v).toFixed(1) + '" r="7" fill="transparent"><title>' + esc(String(labels[i])) + ': ' + v + (opts.suffix || '') + '</title></circle>';
        });
        const step = Math.ceil(labels.length / 8);
        labels.forEach((l, i) => {
            if (i % step === 0) out += '<text x="' + X(i).toFixed(1) + '" y="' + (h - 6) + '" text-anchor="middle" font-size="9" fill="' + C.axis + '">' + esc(String(l)) + '</text>';
        });
        const li = values.length - 1;
        out += '<circle cx="' + X(li).toFixed(1) + '" cy="' + Y(values[li]).toFixed(1) + '" r="3.5" fill="' + color + '"/>';
        out += '</svg>';
        node.innerHTML = out;
    }

    // Donut. items: [{label, count}], colors — массив
    function donut(el, items, colors) {
        const node = document.getElementById(el);
        if (!node) return;
        const total = items.reduce((s, d) => s + d.count, 0);
        if (!total) { node.innerHTML = '<div class="empty">Нет данных</div>'; return; }
        const cx = 60, cy = 60, r = 46, sw = 17;
        let angle = -90;
        let out = '<div style="display:flex; align-items:center; gap:20px; flex-wrap:wrap">'
            + '<svg viewBox="0 0 120 120" style="width:150px; flex-shrink:0">';
        items.forEach((d, i) => {
            const frac = d.count / total;
            if (frac <= 0) return;
            const a0 = angle * Math.PI / 180, a1 = (angle + Math.min(frac * 360, 359.9) - 1.2) * Math.PI / 180;
            angle += frac * 360;
            const large = frac > 0.5 ? 1 : 0;
            const x0 = cx + r * Math.cos(a0), y0 = cy + r * Math.sin(a0);
            const x1 = cx + r * Math.cos(a1), y1 = cy + r * Math.sin(a1);
            out += '<path class="donut-seg" d="M ' + x0.toFixed(2) + ' ' + y0.toFixed(2) + ' A ' + r + ' ' + r + ' 0 ' + large + ' 1 ' + x1.toFixed(2) + ' ' + y1.toFixed(2) + '" fill="none" stroke="' + colors[i % colors.length] + '" stroke-width="' + sw + '"><title>' + esc(d.label) + ': ' + d.count + ' (' + Math.round(frac * 100) + '%)</title></path>';
        });
        out += '<text x="60" y="57" text-anchor="middle" font-size="17" font-weight="700" fill="#EEF1F8" font-family="IBM Plex Mono, monospace">' + total + '</text>'
            + '<text x="60" y="72" text-anchor="middle" font-size="8.5" fill="' + C.axis + '">сделок</text></svg>'
            + '<div class="chart-legend" style="flex-direction:column; align-items:flex-start; gap:7px; margin-top:0">';
        items.forEach((d, i) => {
            out += '<span class="lg-item"><span class="lg-dot" style="background:' + colors[i % colors.length] + '"></span>' + esc(d.label) + ' <b style="color:var(--text)">' + d.count + '</b> <span class="dim">(' + Math.round(d.count / total * 100) + '%)</span></span>';
        });
        out += '</div></div>';
        node.innerHTML = out;
    }

    function fmtAxis(v) {
        if (Math.abs(v) >= 1000) return Math.round(v).toLocaleString('en-US');
        if (Math.abs(v) >= 10) return String(Math.round(v));
        return String(Math.round(v * 10) / 10);
    }

    // ---- Сортировка таблиц (data-sort / текст) -----------------
    function cellValue(row, idx) {
        const td = row.cells[idx];
        if (!td) return null;
        if (td.dataset.sort !== undefined) {
            const n = parseFloat(td.dataset.sort);
            return isNaN(n) ? td.dataset.sort : n;
        }
        return td.textContent.trim().toLowerCase();
    }
    // Общий компаратор: пустые/«—» всегда в конец, числа сравниваются как числа,
    // строки — локально (ru). desc инвертирует непустые значения.
    function compareVals(a, b, desc) {
        const ae = a === null || a === '' || a === '—', be = b === null || b === '' || b === '—';
        if (ae && be) return 0; if (ae) return 1; if (be) return -1;
        const cmp = (typeof a === 'number' && typeof b === 'number') ? a - b : String(a).localeCompare(String(b), 'ru');
        return desc ? -cmp : cmp;
    }
    function sortTableBy(table, idx, desc) {
        const tbody = table.tBodies[0];
        if (!tbody) return;
        Array.from(tbody.rows)
            .sort((r1, r2) => compareVals(cellValue(r1, idx), cellValue(r2, idx), desc))
            .forEach(r => tbody.appendChild(r));
    }
    function initSortableTables(root) {
        (root || document).querySelectorAll('table.sortable').forEach(table => {
            if (table.dataset.sortInit) return;
            table.dataset.sortInit = '1';
            table.querySelectorAll('thead th').forEach((th, idx) => {
                if (th.classList.contains('nosort')) return;
                th.classList.add('sortable-th');
                th.addEventListener('click', () => {
                    const desc = !th.classList.contains('sort-desc');
                    table.querySelectorAll('thead th').forEach(h => h.classList.remove('sort-asc', 'sort-desc'));
                    th.classList.add(desc ? 'sort-desc' : 'sort-asc');
                    sortTableBy(table, idx, desc);
                });
            });
        });
    }

    // ---- Мобильная сортировка (заголовки скрыты / таблица → карточки) ----
    // На телефоне table.stack прячет <thead>, поэтому клик-сортировка
    // недоступна. Хелпер вешает сортировку на <select>:
    // option.value = "<ключ>:<asc|desc>" (пустое = не трогать порядок).
    function initMobileSort(select, getItems, getValue) {
        if (!select) return;
        function apply() {
            const v = select.value || '';
            if (!v) return;
            const sep = v.lastIndexOf(':');
            const key = sep >= 0 ? v.slice(0, sep) : v;
            const desc = v.slice(sep + 1) !== 'asc';
            const items = getItems();
            if (!items.length) return;
            const parent = items[0].parentNode;
            items.map(el => [el, getValue(el, key)])
                .sort((p1, p2) => compareVals(p1[1], p2[1], desc))
                .forEach(pair => parent.appendChild(pair[0]));
        }
        select.addEventListener('change', apply);
        apply();
    }
    // Готовый инициализатор для table.stack: ключ = индекс колонки, значение —
    // из data-sort ячейки (та же логика, что у клика по заголовку на десктопе).
    function initMobileSortTable(select, table) {
        if (!select || !table) return;
        initMobileSort(
            select,
            () => Array.from(table.tBodies[0] ? table.tBodies[0].rows : [])
                .filter(r => !r.classList.contains('no-data')),
            (row, key) => cellValue(row, parseInt(key, 10))
        );
    }

    // ---- Относительное время (data-reltime="<unix ts>") --------
    function formatRel(diffSec) {
        if (diffSec < 0) return null;
        if (diffSec < 60) return 'только что';
        if (diffSec < 3600) return Math.floor(diffSec / 60) + ' мин назад';
        if (diffSec < 86400) return Math.floor(diffSec / 3600) + ' ч назад';
        if (diffSec < 7 * 86400) return Math.floor(diffSec / 86400) + ' д назад';
        return null;
    }
    function initRelativeTimes(root) {
        const now = Date.now() / 1000;
        (root || document).querySelectorAll('[data-reltime]').forEach(el => {
            const ts = parseInt(el.dataset.reltime, 10);
            if (!ts) return;
            const rel = formatRel(now - ts);
            if (rel === null) return;
            el.title = el.textContent.trim();
            el.textContent = rel;
        });
    }

    // ---- Тост ---------------------------------------------------
    let toastTimer = null;
    function toast(msg) {
        const el = document.getElementById('toast');
        if (!el) return;
        el.textContent = msg;
        el.classList.add('show');
        clearTimeout(toastTimer);
        toastTimer = setTimeout(() => el.classList.remove('show'), 2400);
    }

    // ---- Tabs ---------------------------------------------------
    function initTabs(container) {
        const root = container ? document.querySelector(container) : document;
        root.querySelectorAll('.tabs .tab-btn').forEach(btn => {
            btn.addEventListener('click', () => {
                const tabs = btn.closest('.tabs');
                const scope = tabs.dataset.scope ? document.querySelector(tabs.dataset.scope) : document;
                tabs.querySelectorAll('.tab-btn').forEach(b => b.classList.toggle('active', b === btn));
                scope.querySelectorAll('.tab-panel').forEach(p => {
                    if (p.closest('.tabs')) return;
                    p.classList.toggle('active', p.id === btn.dataset.tab);
                });
                btn.scrollIntoView({ block: 'nearest', inline: 'center', behavior: 'smooth' });
                if (btn.dataset.hash) history.replaceState(null, '', '#' + btn.dataset.hash);
            });
        });
        const hash = location.hash.slice(1);
        if (hash) {
            const btn = root.querySelector('.tab-btn[data-hash="' + hash + '"]');
            if (btn) btn.click();
        }
    }

    // ---- Строки-ссылки (tr.row-link[data-href]) -----------------
    function initRowLinks(root) {
        (root || document).querySelectorAll('.row-link[data-href]').forEach(row => {
            if (row.dataset.linkInit) return;
            row.dataset.linkInit = '1';
            row.addEventListener('click', e => {
                if (e.target.closest('a, button, form, input, select, label')) return;
                location.href = row.dataset.href;
            });
        });
    }

    document.addEventListener('DOMContentLoaded', () => {
        initSortableTables();
        initRelativeTimes();
        initRowLinks();
    });

    return {
        openSheet, closeSheet,
        usd, pct, pnlClass, shortAddr, esc,
        sparkline, barChart, lineChart, donut, C,
        toast, initTabs, initSortableTables, initRelativeTimes, initRowLinks,
        sortTableBy, initMobileSort, initMobileSortTable,
    };
})();
