(() => {
    'use strict';

    const baseUrl = window.projectsBaseUrl || '';
    const csrfToken = window.projectsCsrfToken || '';
    const collection = document.getElementById('projectsCollection');
    if (!collection) return;

    const search = document.getElementById('projectsSearch');
    const databaseFilter = document.getElementById('projectsDatabaseFilter');
    const healthFilter = document.getElementById('projectsHealthFilter');
    const sortSelect = document.getElementById('projectsSort');
    const noResults = document.getElementById('projectsNoResults');
    const resultCount = document.getElementById('projectsResultCount');
    const settingsElement = document.getElementById('projectSettingsModal');
    const settingsForm = document.getElementById('projectSettingsForm');
    const settingsModal = settingsElement ? bootstrap.Modal.getOrCreateInstance(settingsElement) : null;
    let activeTab = 'active';
    let activeProject = null;

    // A SweetAlert2 dialog (e.g. the delete-project confirm) can open on top of the
    // Bootstrap settings modal. Bootstrap's focus trap keeps yanking focus back into
    // the modal, so the Swal text input can never be focused or typed into. Running
    // in the capture phase and stopping propagation before Bootstrap's document-level
    // focusin handler sees it lets the Swal input keep focus.
    document.addEventListener('focusin', (event) => {
        if (event.target.closest && event.target.closest('.swal2-container')) {
            event.stopImmediatePropagation();
        }
    }, true);

    function cards() {
        return Array.from(collection.querySelectorAll('[data-project-card]'));
    }

    function toast(icon, title) {
        return Swal.fire({ toast: true, position: 'top-end', timer: 2200, timerProgressBar: true, showConfirmButton: false, icon, title });
    }

    async function readJson(response) {
        const text = await response.text();
        try { return JSON.parse(text); }
        catch { throw new Error('The server returned an invalid response.'); }
    }

    async function post(slug, action, payload = {}) {
        const response = await fetch(`${baseUrl}/project/${encodeURIComponent(slug)}/${action}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
            body: JSON.stringify({ ...payload, _token: csrfToken }),
        });
        const data = await readJson(response);
        if (!response.ok || !data.success) throw new Error(data.message || 'The project could not be updated.');
        return data;
    }

    function matchesTab(card) {
        const archived = card.dataset.archived === '1';
        const pinned = card.dataset.pinned === '1';
        return activeTab === 'all'
            || (activeTab === 'active' && !archived)
            || (activeTab === 'archived' && archived)
            || (activeTab === 'pinned' && pinned && !archived);
    }

    function sortCards(items) {
        const mode = sortSelect?.value || 'activity';
        return items.sort((left, right) => {
            const archivedOrder = Number(left.dataset.archived) - Number(right.dataset.archived);
            if (activeTab === 'all' && archivedOrder !== 0) return archivedOrder;
            const pinOrder = Number(right.dataset.pinned) - Number(left.dataset.pinned);
            if (pinOrder !== 0) return pinOrder;
            if (mode === 'name') return left.dataset.name.localeCompare(right.dataset.name, undefined, { sensitivity: 'base' });
            if (mode === 'created') return String(right.dataset.created).localeCompare(String(left.dataset.created));
            if (mode === 'modified') return String(right.dataset.modified).localeCompare(String(left.dataset.modified));
            return String(right.dataset.activity).localeCompare(String(left.dataset.activity));
        });
    }

    function updateCounts() {
        const all = cards();
        const counts = {
            all: all.length,
            active: all.filter(card => card.dataset.archived !== '1').length,
            archived: all.filter(card => card.dataset.archived === '1').length,
            pinned: all.filter(card => card.dataset.pinned === '1' && card.dataset.archived !== '1').length,
            ready: all.filter(card => card.dataset.health === 'ready' && card.dataset.archived !== '1').length,
        };
        document.querySelectorAll('[data-project-tab-count]').forEach(element => {
            element.textContent = String(counts[element.dataset.projectTabCount] ?? 0);
        });
        document.querySelectorAll('[data-project-metric]').forEach(element => {
            element.textContent = String(counts[element.dataset.projectMetric] ?? 0);
        });
    }

    function applyFilters() {
        const term = String(search?.value || '').trim().toLowerCase();
        const database = databaseFilter?.value || '';
        const health = healthFilter?.value || '';
        let visible = 0;
        const sorted = sortCards(cards());
        sorted.forEach(card => {
            const show = matchesTab(card)
                && (!term || card.dataset.search.includes(term))
                && (!database || card.dataset.database === database)
                && (!health || card.dataset.health === health);
            card.hidden = !show;
            if (show) visible++;
            collection.append(card);
        });
        noResults?.classList.toggle('show', visible === 0);
        if (resultCount) resultCount.textContent = `${visible} project${visible === 1 ? '' : 's'}`;
        updateCounts();
    }

    function setView(mode) {
        mode = mode === 'list' ? 'list' : 'grid';
        collection.classList.toggle('view-list', mode === 'list');
        document.querySelectorAll('[data-project-view]').forEach(button => {
            const selected = button.dataset.projectView === mode;
            button.classList.toggle('active', selected);
            button.setAttribute('aria-pressed', selected ? 'true' : 'false');
        });
        localStorage.setItem('ppphpProjectsView', mode);
    }

    function setTab(tab) {
        activeTab = ['all', 'active', 'archived', 'pinned'].includes(tab) ? tab : 'active';
        document.querySelectorAll('[data-project-tab]').forEach(button => {
            const selected = button.dataset.projectTab === activeTab;
            button.classList.toggle('active', selected);
            button.setAttribute('aria-selected', selected ? 'true' : 'false');
        });
        applyFilters();
    }

    function updatePinUi(card, pinned) {
        card.dataset.pinned = pinned ? '1' : '0';
        card.querySelectorAll('[data-project-action="pin"]').forEach(button => {
            button.classList.toggle('active', pinned);
            button.setAttribute('aria-pressed', pinned ? 'true' : 'false');
            button.title = pinned ? 'Unpin project' : 'Pin project';
            const label = button.querySelector('[data-action-label]');
            if (label) label.textContent = pinned ? 'Unpin project' : 'Pin project';
            const icon = button.querySelector('i');
            if (icon) {
                icon.classList.toggle('bi-star-fill', pinned);
                icon.classList.toggle('bi-star', !pinned);
            }
        });
    }

    function updateArchiveUi(card, archived) {
        card.dataset.archived = archived ? '1' : '0';
        card.classList.toggle('is-archived', archived);
        card.querySelectorAll('[data-project-action="archive"]').forEach(button => {
            const label = button.querySelector('[data-action-label]');
            if (label) label.textContent = archived ? 'Restore project' : 'Archive project';
            const icon = button.querySelector('i');
            if (icon) icon.className = `bi ${archived ? 'bi-arrow-counterclockwise' : 'bi-archive'}`;
        });
    }

    function chooseSettingsTab(tab) {
        document.querySelectorAll('[data-settings-tab]').forEach(button => button.classList.toggle('active', button.dataset.settingsTab === tab));
        document.querySelectorAll('[data-settings-panel]').forEach(panel => panel.classList.toggle('active', panel.dataset.settingsPanel === tab));
    }

    function setAppearanceSelection(color, icon) {
        color = color || '#4f46e5';
        icon = icon || 'folder2-open';
        document.getElementById('settingsProjectColor').value = color;
        document.getElementById('settingsProjectIcon').value = icon;
        document.querySelectorAll('[data-project-color]').forEach(button => button.classList.toggle('active', button.dataset.projectColor === color));
        document.querySelectorAll('[data-project-icon-choice]').forEach(button => button.classList.toggle('active', button.dataset.projectIconChoice === icon));
        const mark = document.getElementById('settingsProjectMark');
        if (mark) {
            mark.style.background = color;
            mark.querySelector('i').className = `bi bi-${icon}`;
        }
    }

    function selectAppearance(card) {
        setAppearanceSelection(card.dataset.color, card.dataset.icon);
    }

    function updateDescriptionCount() {
        const field = document.getElementById('settingsProjectDescription');
        const count = document.getElementById('settingsDescriptionCount');
        if (field && count) count.textContent = `${field.value.length} / 1000`;
    }

    function openSettings(card, initialTab = 'general') {
        if (!card || !settingsModal) return;
        activeProject = card;
        document.getElementById('settingsProjectSlug').value = card.dataset.projectSlug;
        document.getElementById('settingsProjectSlugReadonly').value = card.dataset.projectSlug;
        document.getElementById('settingsProjectName').value = card.dataset.name;
        document.getElementById('settingsProjectDescription').value = card.dataset.description || '';
        document.getElementById('settingsProjectTitle').textContent = card.dataset.name;
        document.getElementById('settingsDatabaseLabel').textContent = `${card.dataset.databaseLabel} · ${card.dataset.databaseTarget || 'Configured source'}`;

        const databaseButton = document.getElementById('settingsOpenDatabase');
        databaseButton.dataset.projectSlug = card.dataset.projectSlug;
        databaseButton.dataset.projectName = card.dataset.name;

        const archived = card.dataset.archived === '1';
        document.getElementById('settingsArchiveTitle').textContent = archived ? 'Restore project' : 'Archive project';
        document.getElementById('settingsArchiveCopy').textContent = archived
            ? 'Return this project to the Active projects list.'
            : 'Hide this project from Active while preserving its files, settings, and database.';
        const archiveButton = document.getElementById('settingsArchiveButton');
        archiveButton.dataset.archived = archived ? '0' : '1';
        archiveButton.querySelector('span').textContent = archived ? 'Restore' : 'Archive';
        archiveButton.querySelector('i').className = `bi ${archived ? 'bi-arrow-counterclockwise' : 'bi-archive'} me-1`;

        selectAppearance(card);
        updateDescriptionCount();
        chooseSettingsTab(initialTab);
        settingsModal.show();
    }

    async function togglePin(card) {
        const pinned = card.dataset.pinned !== '1';
        try {
            const data = await post(card.dataset.projectSlug, 'pin', { pinned });
            updatePinUi(card, !!data.catalog.is_pinned);
            applyFilters();
            toast('success', data.message);
        } catch (error) {
            Swal.fire('Could not update project', error.message, 'error');
        }
    }

    async function toggleArchive(card, archived) {
        const action = archived ? 'archive' : 'restore';
        const confirmation = await Swal.fire({
            title: `${action[0].toUpperCase()}${action.slice(1)} project?`,
            text: archived
                ? 'The project will leave Active, but its files and database remain untouched.'
                : 'The project will return to your Active projects.',
            icon: archived ? 'question' : 'info',
            showCancelButton: true,
            confirmButtonText: archived ? 'Archive project' : 'Restore project',
        });
        if (!confirmation.isConfirmed) return;
        try {
            const data = await post(card.dataset.projectSlug, 'archive', { archived });
            updateArchiveUi(card, !!data.catalog.archived_at);
            settingsModal?.hide();
            applyFilters();
            toast('success', data.message);
        } catch (error) {
            Swal.fire('Could not update project', error.message, 'error');
        }
    }

    async function deleteProject(card) {
        const name = card.dataset.name;
        const result = await Swal.fire({
            title: 'Delete project permanently?',
            html: '<p class="small text-muted mb-2">This removes the editable project files. Type the project name to confirm.</p>'
                + '<label class="form-check text-start small"><input class="form-check-input" type="checkbox" id="deleteDbCheck"> Also delete the connected database</label>',
            input: 'text',
            inputLabel: `Type “${name}”`,
            inputPlaceholder: name,
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'Delete permanently',
            confirmButtonColor: '#dc2626',
            preConfirm: value => {
                if (value !== name) {
                    Swal.showValidationMessage('The project name does not match.');
                    return false;
                }
                return { deleteDb: !!document.getElementById('deleteDbCheck')?.checked };
            },
        });
        if (!result.isConfirmed) return;
        try {
            const response = await fetch(`${baseUrl}/project/delete/${encodeURIComponent(card.dataset.projectSlug)}`, {
                method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
                body: JSON.stringify({ delete_db: result.value.deleteDb, _token: csrfToken }),
            });
            const data = await readJson(response);
            if (!response.ok || !data.success) throw new Error(data.message || 'Project deletion failed.');
            settingsModal?.hide();
            card.remove();
            applyFilters();
            Swal.fire('Project deleted', data.message, 'success');
        } catch (error) {
            Swal.fire('Could not delete project', error.message, 'error');
        }
    }

    collection.addEventListener('click', event => {
        const action = event.target.closest('[data-project-action]');
        if (!action) return;
        event.preventDefault();
        event.stopPropagation();
        const card = action.closest('[data-project-card]');
        if (!card) return;
        const type = action.dataset.projectAction;
        if (type === 'pin') togglePin(card);
        if (type === 'settings') openSettings(card);
        if (type === 'archive') toggleArchive(card, card.dataset.archived !== '1');
    });

    document.querySelectorAll('[data-project-tab]').forEach(button => button.addEventListener('click', () => setTab(button.dataset.projectTab)));
    document.querySelectorAll('[data-project-view]').forEach(button => button.addEventListener('click', () => setView(button.dataset.projectView)));
    [search, databaseFilter, healthFilter, sortSelect].forEach(control => control?.addEventListener(control === search ? 'input' : 'change', applyFilters));
    document.querySelectorAll('[data-settings-tab]').forEach(button => button.addEventListener('click', () => chooseSettingsTab(button.dataset.settingsTab)));

    document.querySelectorAll('[data-project-color]').forEach(button => button.addEventListener('click', () => {
        if (!activeProject) return;
        setAppearanceSelection(button.dataset.projectColor, document.getElementById('settingsProjectIcon').value);
    }));
    document.querySelectorAll('[data-project-icon-choice]').forEach(button => button.addEventListener('click', () => {
        if (!activeProject) return;
        setAppearanceSelection(document.getElementById('settingsProjectColor').value, button.dataset.projectIconChoice);
    }));
    document.getElementById('settingsProjectDescription')?.addEventListener('input', updateDescriptionCount);
    document.getElementById('settingsArchiveButton')?.addEventListener('click', event => {
        if (activeProject) toggleArchive(activeProject, event.currentTarget.dataset.archived === '1');
    });
    document.getElementById('settingsDeleteButton')?.addEventListener('click', () => activeProject && deleteProject(activeProject));

    settingsForm?.addEventListener('submit', async event => {
        event.preventDefault();
        if (!activeProject || !settingsForm.reportValidity()) return;
        const save = document.getElementById('saveProjectSettings');
        const original = save.innerHTML;
        save.disabled = true;
        save.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Saving…';
        try {
            const data = await post(activeProject.dataset.projectSlug, 'settings', Object.fromEntries(new FormData(settingsForm).entries()));
            // Owner renamed the slug: the project's URL and files moved, so refresh
            // every slug-bearing reference on the card and in the open modal.
            if (data.project.slug_changed) {
                const oldSlug = activeProject.dataset.projectSlug;
                const newSlug = data.project.slug;
                activeProject.dataset.projectSlug = newSlug;
                const slugLabel = activeProject.querySelector('.project-card-slug');
                if (slugLabel) slugLabel.textContent = '/' + newSlug;
                activeProject.querySelectorAll('[data-project-slug]').forEach(el => { el.dataset.projectSlug = newSlug; });
                activeProject.querySelectorAll('a[href*="/project/"]').forEach(a => {
                    a.setAttribute('href', a.getAttribute('href').replace(
                        '/project/' + encodeURIComponent(oldSlug),
                        '/project/' + encodeURIComponent(newSlug)));
                });
                document.getElementById('settingsProjectSlug').value = newSlug;
                document.getElementById('settingsProjectSlugReadonly').value = newSlug;
            }
            activeProject.dataset.name = data.project.name;
            activeProject.dataset.description = data.project.description;
            activeProject.dataset.color = data.project.color;
            activeProject.dataset.icon = data.project.icon;
            activeProject.dataset.modified = data.project.updated_at || new Date().toISOString();
            activeProject.dataset.search = `${data.project.name} ${activeProject.dataset.projectSlug} ${data.project.description} ${activeProject.dataset.databaseLabel} ${activeProject.dataset.health}`.toLowerCase();
            activeProject.style.setProperty('--project-accent', data.project.color);
            activeProject.querySelector('[data-project-name]').textContent = data.project.name;
            activeProject.querySelector('[data-project-description]').textContent = data.project.description || 'No description yet.';
            activeProject.querySelector('[data-card-icon]').className = `bi bi-${data.project.icon}`;
            activeProject.querySelectorAll('[data-project-name-value]').forEach(element => element.dataset.projectName = data.project.name);
            document.getElementById('settingsProjectTitle').textContent = data.project.name;
            document.getElementById('settingsOpenDatabase').dataset.projectName = data.project.name;
            settingsModal.hide();
            applyFilters();
            toast('success', data.message);
        } catch (error) {
            Swal.fire('Could not save settings', error.message, 'error');
        } finally {
            save.disabled = false;
            save.innerHTML = original;
        }
    });

    document.querySelectorAll('[data-relative-time]').forEach(element => {
        const date = new Date(element.dataset.relativeTime);
        if (Number.isNaN(date.getTime())) return;
        const seconds = Math.round((date.getTime() - Date.now()) / 1000);
        const formatter = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' });
        const ranges = [[31536000, 'year'], [2592000, 'month'], [604800, 'week'], [86400, 'day'], [3600, 'hour'], [60, 'minute']];
        const range = ranges.find(([size]) => Math.abs(seconds) >= size);
        element.textContent = range ? formatter.format(Math.round(seconds / range[0]), range[1]) : 'just now';
    });

    setView(localStorage.getItem('ppphpProjectsView') || 'grid');
    setTab('active');
})();
