/**
 * Alpine factory for the Purchase create / edit form (draft slice).
 *
 * Owns:
 *   - header state (supplier_id, store_id, dates, currency, invoice no)
 *   - items array (each row carries a stable `_uid` for x-for keys)
 *   - the product picker dropdown (single top-of-table search; results
 *     are added as new rows; the user fine-tunes qty / unit cost /
 *     discount / tax on the row)
 *   - reactive totals — subtotal / discount / tax / grand
 *
 * Server is authoritative on totals — the action layer recomputes
 * them at save (see `ComputePurchaseTotals`). The numbers shown here
 * are a live preview so the user can sanity-check before submit.
 *
 * Supplier selection auto-fills:
 *   - `currency_code` from the supplier's `default_currency_code`
 *     (unless the user has already touched the currency picker)
 *   - `due_date` = `purchase_date` + supplier.payment_terms_days
 *     (only when due_date is blank)
 */
import { posGet } from '../lib/http.js';

export function purchaseForm({
    searchUrl,
    items                = [],
    suppliers            = [],
    taxGroups            = [],
    initialSupplierId    = '',
    initialStoreId       = '',
    initialPurchaseDate  = '',
    initialDueDate       = '',
    initialCurrency      = '',
    initialInvoiceNumber = '',
    initialNotes         = '',
    baseCurrency         = '',
} = {}) {
    let _uid = 0;

    // Pre-index suppliers / tax groups for O(1) lookups during reactive
    // recompute.
    const suppliersById = Object.fromEntries(suppliers.map((s) => [String(s.id), s]));
    // Tax group → combined percent rate (sum of each component's `rate`,
    // which is stored as a percent on the `tax_components` table).
    const taxRateById = Object.fromEntries(taxGroups.map((g) => {
        const rate = (g.components || []).reduce((sum, c) => sum + (parseFloat(c.rate) || 0), 0);
        return [String(g.id), rate];
    }));

    const lineFrom = (raw = {}) => ({
        _uid:             ++_uid,
        product_id:       raw.product_id        ?? '',
        variant_id:       raw.variant_id        ?? '',
        product_label:    raw.product_label     ?? '',
        sku:              raw.sku               ?? '',
        quantity:         raw.quantity          ?? '',
        unit_cost:        raw.unit_cost         ?? '',
        discount_percent: raw.discount_percent  ?? '0',
        tax_group_id:     raw.tax_group_id      ?? '',
        // Batch / expiry — visible only on `track_batches` lines. The
        // values still ride through on every form submit even when the
        // row is hidden (e.g. duplicated from a batch-tracked line); the
        // server happily ignores them for non-batch products.
        track_batches:    !!raw.track_batches,
        track_expiry:     !!raw.track_expiry,
        batch_number:     raw.batch_number      ?? '',
        manufacture_date: raw.manufacture_date  ?? '',
        expiry_date:      raw.expiry_date       ?? '',
    });

    return {
        // ── Header reactive state ─────────────────────────────────
        supplierId:    initialSupplierId  ? String(initialSupplierId)  : '',
        storeId:       initialStoreId     ? String(initialStoreId)     : '',
        purchaseDate:  initialPurchaseDate || new Date().toISOString().slice(0, 10),
        dueDate:       initialDueDate     || '',
        currencyCode:  initialCurrency    || baseCurrency || '',
        invoiceNumber: initialInvoiceNumber || '',
        notes:         initialNotes       || '',
        currencyTouched: !!initialCurrency,

        items: items.map(lineFrom),

        // ── Product picker state ─────────────────────────────────
        query:        '',
        results:      [],
        searching:    false,
        searchOpen:   false,
        _searchTimer: null,

        /**
         * Supplier change handler — invoked from `x-effect` on the
         * supplier `<select>`. `x-effect` re-runs whenever `supplierId`
         * changes (driven by x-model on TomSelect's underlying select),
         * which is the proven pattern from Categories.
         *
         * This must be safe on the initial mount (when supplierId is
         * blank) and idempotent (the effect re-fires after we write to
         * any tracked property).
         *
         * Writes the reactive props AND pokes the wrapped controls
         * directly — TomSelect's chip and flatpickr's calendar don't
         * follow x-model writes; only user input updates them.
         */
        onSupplierChange(supplierId) {
            if (!supplierId) return;
            const s = suppliersById[String(supplierId)];
            if (!s) return;

            if (!this.currencyTouched && s.default_currency_code) {
                if (this.currencyCode !== s.default_currency_code) {
                    // Just write the reactive prop — the currency
                    // <select>'s own `x-effect="ts && ts.setValue(...)"`
                    // pushes the value into TomSelect's visible chip.
                    this.currencyCode = s.default_currency_code;
                }
            }

            if (!this.dueDate && this.purchaseDate && s.payment_terms_days != null) {
                const d = new Date(this.purchaseDate);
                d.setDate(d.getDate() + parseInt(s.payment_terms_days, 10));
                const iso = d.toISOString().slice(0, 10);
                this.dueDate = iso;
                this.$refs.dueDateInput?._flatpickr?.setDate(iso, false);
            }
        },

        // ── Currency: mark touched so supplier auto-fill stops ──
        onCurrencyChange() { this.currencyTouched = true; },

        /**
         * Format an amount in the currently-picked currency using the
         * system-wide `posFormatMoney()` helper — same rules as PHP's
         * `format_money()` (decimals, separators, symbol placement).
         * See `resources/js/lib/format-money.js`.
         */
        formatMoney(amount) {
            return window.posFormatMoney
                ? window.posFormatMoney(amount, this.currencyCode)
                : Number(amount || 0).toFixed(2);
        },

        // ── Per-line computed numbers — preview only ─────────────
        lineNet(line) {
            const qty   = parseFloat(line.quantity)  || 0;
            const unit  = parseFloat(line.unit_cost) || 0;
            const dpct  = parseFloat(line.discount_percent) || 0;
            const gross = qty * unit;
            return gross - (gross * dpct) / 100;
        },
        lineTax(line) {
            const rate = taxRateById[String(line.tax_group_id)] || 0;
            return (this.lineNet(line) * rate) / 100;
        },
        lineTotal(line) {
            return this.lineNet(line) + this.lineTax(line);
        },

        // ── Aggregate totals — Alpine recomputes on any line change.
        get subtotal()      { return this.items.reduce((s, l) => s + this.lineNet(l), 0); },
        get discountTotal() {
            return this.items.reduce((s, l) => {
                const gross = (parseFloat(l.quantity) || 0) * (parseFloat(l.unit_cost) || 0);
                const dpct  = parseFloat(l.discount_percent) || 0;
                return s + (gross * dpct) / 100;
            }, 0);
        },
        get taxTotal()      { return this.items.reduce((s, l) => s + this.lineTax(l), 0); },
        get grandTotal()    { return this.subtotal + this.taxTotal; },

        // ── Product picker ───────────────────────────────────────
        focusSearch() {
            this.searchOpen = true;
            if (!this.results.length) this._fetch();
        },
        onSearchInput() {
            this.searchOpen = true;
            clearTimeout(this._searchTimer);
            this._searchTimer = setTimeout(() => this._fetch(), 250);
        },
        closeSearch() {
            // Tiny delay so a click on a result fires before the dropdown collapses.
            setTimeout(() => { this.searchOpen = false; }, 120);
        },
        async _fetch() {
            this.searching = true;
            try {
                const { data } = await posGet(searchUrl, this.query ? { q: this.query } : null);
                this.results = Array.isArray(data) ? data : [];
            } catch (e) {
                this.results = [];
            } finally {
                this.searching = false;
            }
        },
        addProduct(row, variant = null) {
            this.items.push(lineFrom({
                product_id:    row.value,
                variant_id:    variant ? variant.id : '',
                product_label: variant ? `${row.label} — ${variant.label}` : row.label,
                quantity:      '1',
                track_batches: !!row.track_batches,
                track_expiry:  !!row.track_expiry,
            }));
            this.query      = '';
            this.results    = [];
            this.searchOpen = false;
            this.$nextTick(() => {
                const inputs = this.$root.querySelectorAll('.pur-line-qty input');
                inputs[inputs.length - 1]?.focus();
            });
        },

        duplicateLine(uid) {
            const src = this.items.find((l) => l._uid === uid);
            if (!src) return;
            const copy = lineFrom({ ...src, _uid: undefined });
            const i = this.items.indexOf(src);
            this.items.splice(i + 1, 0, copy);
        },

        removeLine(uid) {
            this.items = this.items.filter((l) => l._uid !== uid);
        },
    };
}
