/** @odoo-module
 *
 * LaundryOrdersViewPopup — in-POS customer laundry orders manager.
 *
 * Owns its own data lifecycle: fetches on mount, refetches on search,
 * refetches the single affected order after each workflow action.
 *
 * All writes go through the whitelisted `pos_action_*` RPCs on
 * `laundry.order` — never touches line/price/customer fields, so
 * Phase 3 locking remains authoritative and the popup cannot open a
 * side channel around it.
 */
import { Component, useState, onWillStart, useRef, onMounted } from "@odoo/owl";
import { Dialog } from "@web/core/dialog/dialog";
import { _t } from "@web/core/l10n/translation";
import { usePos } from "@point_of_sale/app/hooks/pos_hook";
import { useService } from "@web/core/utils/hooks";
import { ask } from "@point_of_sale/app/utils/make_awaitable_dialog";
// Thermal receipt is intentionally NOT imported — the component is
// excluded from the asset bundle until individually re-validated.
// Print falls back to the standard PDF report (always available).

const WORKFLOW_BADGE = {
    intake:     { label: "Intake",      klass: "badge-state-intake"    },
    processing: { label: "Processing",  klass: "badge-state-processing"},
    ready:      { label: "Ready",       klass: "badge-state-ready"     },
    delivered:  { label: "Delivered",   klass: "badge-state-delivered" },
    cancelled:  { label: "Cancelled",   klass: "badge-state-cancelled" },
};

const PAYMENT_BADGE = {
    paid:     { label: "Paid",      klass: "badge-payment-paid"     },
    deferred: { label: "Deferred",  klass: "badge-payment-deferred" },
    settled:  { label: "Settled",   klass: "badge-payment-settled"  },
    due:      { label: "Due",       klass: "badge-payment-due"      },
};

export class LaundryOrdersViewPopup extends Component {
    static components = { Dialog };
    static template = "laundry_management.LaundryOrdersViewPopup";
    static props = {
        partnerId: { type: Number },
        partnerName: { type: String },
        partnerPhone: { type: String, optional: true },
        close: { type: Function },
    };
    static defaultProps = {
        partnerPhone: "",
    };

    setup() {
        this.pos = usePos();
        this.notification = useService("notification");
        this.dialog = useService("dialog");
        this.orm = this.pos.data;    // shared ORM adapter
        this.searchRef = useRef("searchInput");

        this.state = useState({
            orders: [],
            loading: true,
            error: null,
            searchQuery: "",
            busyOrderIds: {},        // { [id]: 'action_key' } while RPC in flight
        });

        onWillStart(async () => {
            await this._fetch();
        });
        onMounted(() => {
            if (this.searchRef.el) this.searchRef.el.focus();
        });
    }

    // ─── Data ─────────────────────────────────────────────────────────
    async _fetch() {
        this.state.loading = true;
        this.state.error = null;
        try {
            const rows = await this.orm.call(
                "laundry.order",
                "pos_search_customer_orders",
                [],
                {
                    partner_id: this.props.partnerId,
                    search_query: this.state.searchQuery || false,
                    limit: 20,
                }
            );
            this.state.orders = rows || [];
        } catch (err) {
            this.state.error = this._humanizeError(err);
        } finally {
            this.state.loading = false;
        }
    }

    _replaceOrder(updated) {
        if (!updated?.id) return;
        const idx = this.state.orders.findIndex((o) => o.id === updated.id);
        if (idx >= 0) {
            this.state.orders.splice(idx, 1, updated);
        }
    }

    _humanizeError(err) {
        return (
            err?.data?.message ||
            err?.message ||
            _t("Could not contact the server. Please retry.")
        );
    }

    // ─── Search ───────────────────────────────────────────────────────
    onSearchInput(ev) {
        this.state.searchQuery = ev.target.value || "";
    }

    async onSearchSubmit(ev) {
        ev?.preventDefault?.();
        await this._fetch();
    }

    async onClearSearch() {
        this.state.searchQuery = "";
        await this._fetch();
    }

    // ─── Workflow actions ─────────────────────────────────────────────
    async _runAction(order, methodName, actionKey) {
        if (this.state.busyOrderIds[order.id]) return;
        this.state.busyOrderIds[order.id] = actionKey;
        try {
            const updated = await this.orm.call(
                "laundry.order",
                methodName,
                [[order.id]]
            );
            this._replaceOrder(updated);
            this.notification.add(
                _t('Order %s updated.', updated?.name || order.name),
                { type: "success" }
            );
        } catch (err) {
            this.notification.add(this._humanizeError(err), { type: "danger" });
        } finally {
            delete this.state.busyOrderIds[order.id];
        }
    }

    onClickStartProcessing(order) {
        return this._runAction(order, "pos_action_start_processing", "start_processing");
    }

    onClickMarkReady(order) {
        return this._runAction(order, "pos_action_mark_ready", "mark_ready");
    }

    async onClickDeliver(order) {
        const confirmed = await ask(this.dialog, {
            title: _t("Deliver Order?"),
            body: _t(
                "Confirm delivery of %(name)s (%(items)s items, %(total)s).",
                {
                    name: order.name,
                    items: order.item_count,
                    total: this._fmt(order.amount_total),
                }
            ),
            confirmLabel: _t("Deliver"),
            cancelLabel: _t("Cancel"),
        });
        if (!confirmed) return;
        return this._runAction(order, "pos_action_deliver", "deliver");
    }

    /**
     * Hand off to the proven settle-due flow on the store. We close the
     * popup BEFORE the navigation kicks off — keeping the modal mounted
     * while routes change is the known cause of the white-screen race
     * the user reported. Toast first so the cashier always sees feedback,
     * even if the screen change is instantaneous.
     */
    async onClickCollectPayment(order) {
        this.notification.add(
            _t("Redirecting to payment for %(name)s — %(amount)s due.", {
                name: order.name,
                amount: this._fmt(order.amount_due),
            }),
            { type: "info" }
        );
        this.props.close();
        try {
            await this.pos.collectLaundryOrderPayment(this.props.partnerId);
        } catch (err) {
            // Re-surface server errors as a notification so the cashier
            // never gets stuck with no feedback.
            this.pos.notification.add(this._humanizeError(err), { type: "danger" });
        }
    }

    /**
     * Standard PDF Work Order via the existing report action.
     * The thermal-printer path is currently disabled (component
     * excluded from the bundle until re-validated). PDF is the
     * proven fallback — works without hardware-printer configuration.
     */
    async onClickPrintWorkOrder(order) {
        try {
            await this.pos.env.services.action.doAction(
                "laundry_management.action_report_laundry_work_order",
                { additionalContext: { active_ids: [order.id] } }
            );
        } catch (err) {
            this.notification.add(this._humanizeError(err), { type: "danger" });
        }
    }

    // ─── UI helpers ──────────────────────────────────────────────────
    stateBadge(order) {
        return WORKFLOW_BADGE[order.state] || WORKFLOW_BADGE.intake;
    }

    paymentBadge(order) {
        return PAYMENT_BADGE[order.payment_state] || PAYMENT_BADGE.paid;
    }

    formatDate(order) {
        if (!order.create_date) return "";
        const d = new Date(order.create_date.replace(" ", "T") + "Z");
        if (isNaN(d.getTime())) return order.create_date;
        return d.toLocaleString(undefined, {
            month: "short", day: "numeric",
            hour: "2-digit", minute: "2-digit",
        });
    }

    _fmt(amount) {
        try {
            return this.pos.env.utils.formatCurrency(amount || 0);
        } catch {
            const sym = this.pos.currency?.symbol || "";
            return `${(amount || 0).toFixed(2)} ${sym}`.trim();
        }
    }

    fmt(amount) {
        return this._fmt(amount);
    }

    isAllowed(order, key) {
        return (order.allowed_actions || []).includes(key);
    }

    isBusy(order, key) {
        return this.state.busyOrderIds[order.id] === key;
    }

    isOrderBusy(order) {
        return !!this.state.busyOrderIds[order.id];
    }

    get hasResults() {
        return !this.state.loading && !this.state.error && this.state.orders.length > 0;
    }

    get isEmpty() {
        return !this.state.loading && !this.state.error && this.state.orders.length === 0;
    }

    close() {
        this.props.close();
    }
}
