import { composeDataTable } from './data-table.js';
import { posPatch } from '../lib/http.js';

/**
 * Alpine factory for the Customers index page.
 *
 * Composes `composeDataTable` (so client-side pagination + search keep
 * working) with row-state tracking + an AJAX `toggleActive` method so
 * the inline status toggle on each row works without leaving the page.
 *
 * `rowState` mirrors what's visible in the UI for every row id we
 * shipped from the server. The toggle does an optimistic flip + AJAX
 * PATCH, reverting on error.
 */
export function customersIndexPage(opts = {}) {
    const toggleUrlTemplate = opts.toggleUrlTemplate ?? '';
    const rows              = opts.rows ?? [];

    return composeDataTable(
        {
            rowsSelector:       'tbody > tr[data-dt-row]',
            searchableSelector: null,
            initialPageSize:    25,
        },
        {
            /** Mirror of the visible row data we need to read in the
             *  template (currently just `is_active`). Keyed by id. */
            rowState:    Object.fromEntries(rows.map((r) => [r.id, { is_active: !!r.is_active }])),
            togglingIds: [],
            _filtering:  false,

            init() {
                this.initDataTable();
            },

            /**
             * Apply the filter form over AJAX — no full-page reload. Builds a
             * clean query string from the form, pushes it to the URL so the
             * state is shareable/bookmarkable, then swaps the table region.
             */
            applyFilters() {
                const form = this.$refs.filterForm;
                if (!form) return;
                const params = new URLSearchParams();
                new FormData(form).forEach((v, k) => {
                    const s = (v ?? '').toString().trim();
                    if (s !== '') params.set(k, s);
                });
                const qs  = params.toString();
                const url = form.action + (qs ? ('?' + qs) : '');
                // replaceState (not push) — filter tweaks shouldn't stack up
                // in history, but the URL stays shareable/refreshable.
                window.history.replaceState({}, '', url);
                this._reloadTable(url);
            },

            /** Clear every filter control back to its default, then reload. */
            resetFilters() {
                const form = this.$refs.filterForm;
                if (form) {
                    form.querySelectorAll('select').forEach((el) => {
                        const def = el.name === 'status' ? 'all' : '';
                        if (el.tomselect) el.tomselect.setValue(def, true);
                        else el.value = def;
                    });
                    const q = form.querySelector('input[name="q"]');
                    if (q) q.value = '';
                    window.history.replaceState({}, '', form.action);
                    this._reloadTable(form.action);
                }
            },

            /**
             * Fetch a URL's HTML and swap only the `[data-customers-region]`
             * block (table OR empty-state, whatever the server rendered), then
             * re-wire Alpine, rebuild the row-state map for the inline toggle,
             * and re-run the client-side data-table pass. Falls back to a full
             * reload if anything looks off.
             */
            async _reloadTable(url) {
                if (this._filtering) return;
                this._filtering = true;
                try {
                    const html = await window.posGetHtml(url);
                    if (!html) { window.location.reload(); return; }

                    const doc    = new DOMParser().parseFromString(html, 'text/html');
                    const fresh  = doc.querySelector('[data-customers-region]');
                    const region = this.$refs.tableRegion;
                    if (!fresh || !region) { window.location.reload(); return; }

                    region.innerHTML = fresh.innerHTML;
                    if (window.Alpine?.initTree) window.Alpine.initTree(region);

                    // Rebuild the inline-toggle state from the freshly rendered
                    // rows (a filter can surface customers not on the first page).
                    const state = {};
                    region.querySelectorAll('[data-dt-row]').forEach((r) => {
                        state[r.dataset.dtId] = { is_active: r.dataset.dtActive === '1' };
                    });
                    this.rowState = state;

                    // Refresh the toolbar row-count title.
                    const freshTitle = doc.querySelector('.dt-toolbar-title');
                    const localTitle = this.$el.querySelector('.dt-toolbar-title');
                    if (freshTitle && localTitle) localTitle.innerHTML = freshTitle.innerHTML;

                    // Refresh the summary cards (recomputed for the filter).
                    const freshCards = doc.querySelector('[data-summary-cards]');
                    const liveCards  = document.querySelector('[data-summary-cards]');
                    if (freshCards && liveCards) liveCards.innerHTML = freshCards.innerHTML;

                    // Re-collect rows so client-side pagination/search/sort apply.
                    this.refreshDataTable();
                } catch {
                    window.location.reload();
                } finally {
                    this._filtering = false;
                }
            },

            async toggleActive(id) {
                if (this.togglingIds.includes(id)) return;
                const next = !(this.rowState[id]?.is_active);

                // Optimistic flip — Alpine repaints instantly.
                this.rowState[id] = { ...(this.rowState[id] ?? {}), is_active: next };
                this.togglingIds = [...this.togglingIds, id];

                const url = toggleUrlTemplate.replace('__ID__', id);
                let data;
                try {
                    ({ data } = await posPatch(url));
                } catch (e) {
                    this.togglingIds = this.togglingIds.filter((x) => x !== id);
                    this._revertToggle(id, next);
                    if (e?.status === undefined) {
                        this.$store.toasts.push({ type: 'error', message: 'Network error.' });
                    } else {
                        // Surface the server-supplied message (e.g.
                        // "supplier has open purchases") instead of a
                        // generic fallback — the action knows why it
                        // rejected, the user shouldn't have to guess.
                        const msg = e?.message
                            || e?.errors?._action?.[0]
                            || 'Could not toggle status.';
                        this.$store.toasts.push({ type: 'error', message: msg });
                    }
                    return;
                }

                this.togglingIds = this.togglingIds.filter((x) => x !== id);

                if (data?.message) {
                    this.$store.toasts.push({ type: 'success', message: data.message });
                }
                // Reconcile with the authoritative server value.
                if (typeof data?.is_active === 'boolean') {
                    this.rowState[id] = { ...(this.rowState[id] ?? {}), is_active: data.is_active };
                }
            },

            _revertToggle(id, attempted) {
                this.rowState[id] = { ...(this.rowState[id] ?? {}), is_active: !attempted };
            },
        },
    );
}
