/*
 * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * See the LICENSE file in the project root for the full license text.
 */
/**
 * /sprints/{id}/settings — vanilla JS + SortableJS.
 *  - debounced PATCH /sprints/{id} for sprint meta on change; when the
 *    response signals weeks were resynced, reload to pick up the new rows
 *  - PATCH /sprints/{id}/week/{week_id} for per-week weekday mask
 *  - POST /sprints/{id}/workers for adding a worker, DELETE for removing
 *  - PATCH /sprints/{id}/workers/{sw_id} for RTB and reorder
 *  - SortableJS replaces jQuery UI sortable on the in-sprint worker list
 */
(function () {
    'use strict';

    const root = document.querySelector('[data-sprint-root]');
    if (!root) { return; }

    const sprintId = parseInt(root.getAttribute('data-sprint-id'), 10);
    const csrf     = String(root.getAttribute('data-csrf') || '');

    function qs(sel, ctx)  { return (ctx || root).querySelector(sel); }
    function qsa(sel, ctx) { return Array.from((ctx || root).querySelectorAll(sel)); }
    function on(ctx, ev, sel, fn) {
        ctx.addEventListener(ev, function (e) {
            const t = e.target.closest(sel);
            if (t && ctx.contains(t)) { fn.call(t, e, t); }
        });
    }

    function request(method, url, body) {
        const opts = {
            method,
            headers: { Accept: 'application/json', 'X-CSRF-Token': csrf },
            credentials: 'same-origin',
        };
        if (body !== undefined) {
            opts.headers['Content-Type'] = 'application/json';
            opts.body = JSON.stringify(body);
        }
        return fetch(url, opts).then(async function (res) {
            let payload = null;
            try { payload = await res.json(); } catch (_) { /* ignore */ }
            if (!res.ok || !payload || payload.ok !== true) {
                const msg = (payload && payload.error && payload.error.message)
                    ? payload.error.message
                    : (res.statusText || 'Request failed');
                const err = new Error(msg);
                err.status  = res.status;
                err.payload = payload;
                throw err;
            }
            return payload.data;
        });
    }

    const statusEl = qs('[data-status]');
    const successCls = ['text-green-700', 'bg-green-50', 'border-green-200'];
    const errorCls   = ['text-red-700',   'bg-red-50',   'border-red-200'];
    let statusTimer = null;
    function flash(text, isError) {
        if (!statusEl) { return; }
        statusEl.textContent = text;
        successCls.concat(errorCls).forEach((c) => statusEl.classList.remove(c));
        (isError ? errorCls : successCls).forEach((c) => statusEl.classList.add(c));
        statusEl.classList.remove('opacity-0');
        statusEl.classList.add('opacity-100');
        clearTimeout(statusTimer);
        statusTimer = setTimeout(function () {
            statusEl.classList.remove('opacity-100');
            statusEl.classList.add('opacity-0');
        }, 2500);
    }

    // ---- Sprint meta ----------------------------------------------------

    function patchMeta(payload) {
        return request('PATCH', '/sprints/' + sprintId, payload)
            .then(function (data) {
                flash('Saved');
                // Server resyncs the week set when start_date/end_date change.
                // Reload so the table reflects added/removed rows.
                if (data && data.weeks_synced) {
                    window.location.reload();
                }
            })
            .catch((e) => flash(e.message, true));
    }
    const metaDebounce = {};
    on(root, 'change', '[data-meta]', function () {
        const field = this.getAttribute('name');
        let v = this.value;
        if (field === 'reserve_fraction') { v = Number(v) / 100; }
        clearTimeout(metaDebounce[field]);
        metaDebounce[field] = setTimeout(function () {
            const payload = {}; payload[field] = v;
            patchMeta(payload);
        }, 0);
    });

    // ---- Per-week weekday checkboxes (Phase 12) ------------------------

    function maskFromRow(row) {
        let mask = 0;
        qsa('[data-day-toggle]', row).forEach(function (cb) {
            if (cb.checked) {
                const bit = parseInt(cb.getAttribute('data-bit'), 10);
                if (Number.isInteger(bit)) { mask |= (1 << bit); }
            }
        });
        return mask;
    }
    function popcount5(mask) {
        let n = 0;
        for (let i = 0; i < 5; i++) { if ((mask >> i) & 1) { n++; } }
        return n;
    }
    const weekDebounce = {};
    on(root, 'change', '[data-day-toggle]', function () {
        const row    = this.closest('[data-week-row]');
        const weekId = parseInt(row.getAttribute('data-week-id'), 10);
        const mask   = maskFromRow(row);
        const cnt = qs('[data-week-count]', row);
        if (cnt) { cnt.textContent = String(popcount5(mask)); }

        clearTimeout(weekDebounce[weekId]);
        weekDebounce[weekId] = setTimeout(function () {
            request('PATCH', '/sprints/' + sprintId + '/week/' + weekId, { active_days_mask: mask })
                .then(function (data) {
                    if (data && data.sprint_week && cnt) {
                        cnt.textContent = String(data.sprint_week.max_working_days);
                    }
                    flash('Saved');
                })
                .catch((e) => flash(e.message, true));
        }, 250);
    });

    // ---- Worker picker --------------------------------------------------

    const available = qs('[data-available]');
    const inSprint  = qs('[data-in-sprint]');

    function workerRowTemplate(sw) {
        const li = document.createElement('li');
        li.className = 'flex items-center gap-2 px-3 py-2 border-b bg-white last:border-b-0 dark:bg-slate-800 dark:border-slate-700';
        li.setAttribute('data-sw-id',     String(sw.id));
        li.setAttribute('data-worker-id', String(sw.worker_id));
        const handle = document.createElement('span');
        handle.className = 'handle cursor-grab text-slate-400 select-none dark:text-slate-500';
        handle.innerHTML = '&#8801;';
        const name = document.createElement('span');
        name.className = 'flex-1';
        name.textContent = sw.worker_name || '';
        const rtb = document.createElement('input');
        rtb.type = 'number'; rtb.step = '0.05'; rtb.min = '0'; rtb.max = '1';
        rtb.value = Number(sw.rtb).toFixed(2);
        rtb.setAttribute('data-rtb', '');
        rtb.className = 'w-20 rounded border border-slate-300 px-2 py-1 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-slate-400 dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100 dark:focus:ring-slate-500';
        const remove = document.createElement('button');
        remove.type = 'button';
        remove.setAttribute('data-remove', '');
        remove.className = 'text-sm text-red-600 hover:underline dark:text-red-400';
        remove.textContent = 'Remove';
        li.appendChild(handle);
        li.appendChild(name);
        li.appendChild(rtb);
        li.appendChild(remove);
        return li;
    }

    function availableRowTemplate(worker) {
        const li = document.createElement('li');
        li.className = 'flex items-center gap-2 px-3 py-2 border-b last:border-b-0 dark:border-slate-700';
        li.setAttribute('data-worker-id', String(worker.id));
        const name = document.createElement('span');
        name.className = 'flex-1';
        name.textContent = worker.name || '';
        const add = document.createElement('button');
        add.type = 'button';
        add.setAttribute('data-add', '');
        add.className = 'text-sm text-blue-700 hover:underline dark:text-blue-400 dark:hover:text-blue-300';
        add.textContent = 'Add →';
        li.appendChild(name);
        li.appendChild(add);
        return li;
    }

    if (available) {
        on(available, 'click', '[data-add]', function () {
            const li = this.closest('li');
            const workerId = parseInt(li.getAttribute('data-worker-id'), 10);
            const span = li.querySelector('span.flex-1');
            const name = span ? span.textContent : '';
            request('POST', '/sprints/' + sprintId + '/workers', { worker_id: workerId })
                .then(function (data) {
                    const sw = data.sprint_worker;
                    sw.worker_name = sw.worker_name || name;
                    inSprint.appendChild(workerRowTemplate(sw));
                    li.remove();
                    flash('Worker added');
                    refreshEmptyStates();
                })
                .catch((e) => flash(e.message, true));
        });
    }

    if (inSprint) {
        on(inSprint, 'click', '[data-remove]', function () {
            const li = this.closest('li');
            const swId     = parseInt(li.getAttribute('data-sw-id'),     10);
            const workerId = parseInt(li.getAttribute('data-worker-id'), 10);
            const span = li.querySelector('span.flex-1');
            const name = span ? span.textContent : '';
            request('DELETE', '/sprints/' + sprintId + '/workers/' + swId)
                .then(function () {
                    li.remove();
                    available.appendChild(availableRowTemplate({ id: workerId, name }));
                    flash('Worker removed');
                    refreshEmptyStates();
                })
                .catch((e) => flash(e.message, true));
        });

        on(inSprint, 'change', '[data-rtb]', function () {
            const li = this.closest('li');
            const swId = parseInt(li.getAttribute('data-sw-id'), 10);
            let v = Number(this.value);
            if (Number.isNaN(v) || v < 0 || v > 1) { flash('RTB must be 0–1', true); return; }
            v = Math.round(v * 20) / 20;
            this.value = v.toFixed(2);
            request('PATCH', '/sprints/' + sprintId + '/workers/' + swId, { rtb: v })
                .then(() => flash('Saved'))
                .catch((e) => flash(e.message, true));
        });

        if (typeof window.Sortable === 'function') {
            window.Sortable.create(inSprint, {
                handle: '.handle',
                animation: 150,
                onEnd: function () {
                    const ordering = qsa('li', inSprint).map(function (li, i) {
                        return {
                            sprint_worker_id: parseInt(li.getAttribute('data-sw-id'), 10),
                            sort_order:       i + 1,
                        };
                    });
                    request('POST', '/sprints/' + sprintId + '/workers/reorder', ordering)
                        .then((data) => flash(data.moved ? 'Order saved' : 'No changes'))
                        .catch((e) => flash(e.message, true));
                },
            });
        } else {
            // eslint-disable-next-line no-console
            console.warn('[sprint-settings] SortableJS not loaded — drag reorder disabled.');
        }
    }

    function refreshEmptyStates() {
        const ea = qs('[data-empty-available]');
        const es = qs('[data-empty-sprint]');
        if (ea && available) { ea.style.display = available.querySelectorAll('li').length === 0 ? '' : 'none'; }
        if (es && inSprint)  { es.style.display = inSprint.querySelectorAll('li').length === 0 ? '' : 'none'; }
    }
    refreshEmptyStates();

    // ----- Danger zone: type-the-name confirmation gate -----------------
    //
    // The submit button stays disabled until the input matches the sprint
    // name verbatim. SprintController::delete repeats the check server-side
    // — this is just a UX guard, not the authoritative one.
    (function () {
        const form = document.querySelector('[data-delete-sprint-form]');
        if (!form) { return; }
        const expected = String(form.getAttribute('data-confirm-name') || '');
        const input    = form.querySelector('[data-delete-confirm-input]');
        const btn      = form.querySelector('[data-delete-confirm-btn]');
        if (!input || !btn) { return; }

        function refresh() {
            btn.disabled = String(input.value) !== expected;
        }
        input.addEventListener('input', refresh);
        refresh();

        form.addEventListener('submit', function (ev) {
            if (String(input.value) !== expected) {
                ev.preventDefault();
                return;
            }
            // Final native confirm so a misclick on the now-enabled button
            // doesn't fire the destructive POST.
            if (!window.confirm('Delete sprint "' + expected + '" and all its data? This cannot be undone.')) {
                ev.preventDefault();
            }
        });
    })();
})();
