window.app = window.app || {};
window.app.modals = window.app.modals || {};

const recordsPerPage = 50;
const selectedBatchSize = 100;

const RECORDS_REQUEST_KEY = "listRelationPickerRecords";

const defaultSettings = {
    collection: "", // model, id or name
    selectedIds: [],
    maxSelect: 1,
    btnText: "Set selection",
    onselect: function(records) {},
};

/**
 * Opens a new records relation picker.
 *
 * @example
 *
 * ```js
 * app.modals.openRecordsPicker({
 *     collection:  "yourCollection",
 *     selectedIds: ["id1", "id2"],
 *     maxSelect:   1,
 *     onselect:    (records) => { ... }
 * })
 * ```
 *
 * @param {Object} settings
 */
window.app.modals.openRecordsPicker = function(settings = {}) {
    settings = Object.assign({}, defaultSettings, settings);

    const modal = recordsPickerModal(settings);

    document.body.appendChild(modal);

    app.modals.open(modal);
};

function recordsPickerModal(settings = defaultSettings) {
    let modal;

    const data = store({
        searchTerm: "",
        selected: [],

        preselected: [],
        isLoadingPreselected: false,

        records: [],
        isLoadingRecords: false,
        lastRecordsPage: 1,
        lastRecordsTotal: 0,

        get collection() {
            let idOrName = settings.collection;
            if (typeof settings.collection == "object" && settings.collection?.id) {
                idOrName = settings.collection?.id;
            }

            return app.store.collections.find((c) => c.id == idOrName || c.name == idOrName);
        },
        get isLoading() {
            return data.isLoadingPreselected || data.isLoadingRecords;
        },
        get canLoadMore() {
            return !data.isLoadingRecords && data.lastRecordsTotal == recordsPerPage;
        },
    });

    const watchers = [
        watch(
            () => [settings.collection, settings.selectedIds],
            () => {
                loadSelected();
            },
        ),

        // load initial records and reload on search
        watch(
            () => [data.collection, data.searchTerm],
            () => {
                loadRecords(true);
            },
        ),
    ];

    function close() {
        setTimeout(() => app.modals.close(modal), 0); // the popup may not be yet opened
    }

    async function loadSelected() {
        const selectedIds = app.utils.toArray(settings.selectedIds);

        const collectionId = settings.collection?.id || settings.collection;

        if (!collectionId || !selectedIds.length) {
            return;
        }

        data.isLoadingSelected = true;

        let loaded = [];

        // batch load all selected records to avoid filter length errors
        const loadIds = selectedIds.slice();
        const loadPromises = [];
        while (loadIds.length > 0) {
            const filters = [];
            const ids = loadIds.splice(0, selectedBatchSize);
            for (const id of ids) {
                filters.push(`id="${id}"`);
            }

            loadPromises.push(
                app.pb.collection(collectionId).getFullList({
                    requestKey: null,
                    filter: filters.join("||"),
                }),
            );
        }

        try {
            await Promise.all(loadPromises).then((values) => {
                loaded = loaded.concat(...values);
            });

            // preserve selected order
            const orderedSelected = [];
            for (const id of selectedIds) {
                const record = loaded.find((r) => r.id == id);
                if (record) {
                    orderedSelected.push(record);
                }
            }

            // add the ordered selected models to the list (if not already)
            if (!data.searchTerm.trim()) {
                data.records = app.utils.filterDuplicatesByKey(orderedSelected.concat(data.records));
            }

            data.selected = orderedSelected;
            data.isLoadingSelected = false;
        } catch (err) {
            if (!err.isAbort) {
                app.checkApiError(err);
                data.isLoadingSelected = false;
            }
        }
    }

    async function loadRecords(reset = false) {
        if (!data.collection?.id) {
            return;
        }

        if (reset) {
            resetList();

            if (!data.searchTerm.trim()) {
                // prepend the loaded selected items
                data.records = data.selected.slice();
            }
        }

        data.isLoadingRecords = true;

        try {
            const page = reset ? 1 : data.lastRecordsPage + 1;

            const fallbackSearchFields = app.utils.getAllCollectionIdentifiers(data.collection);

            let normalizedFilter = app.utils.normalizeSearchFilter(data.searchTerm, fallbackSearchFields) || "";

            const result = await app.pb.collection(data.collection.id).getList(page, recordsPerPage, {
                requestKey: RECORDS_REQUEST_KEY,
                filter: normalizedFilter,
                skipTotal: 1,
                sort: data.collection.type != "view" ? "-@rowid" : "",
            });

            data.lastRecordsPage = result.page;
            data.lastRecordsTotal = result.items.length;
            data.records = app.utils.filterDuplicatesByKey(data.records.concat(result.items));
            data.isLoadingRecords = false;
        } catch (err) {
            if (!err.isAbort) {
                data.isLoadingRecords = false;
                app.checkApiError(err);
            }
        }
    }

    function resetList() {
        app.pb.cancelRequest(RECORDS_REQUEST_KEY);
        data.isLoadingRecords = false;
        data.records = [];
        data.lastTotalRecords = 0;
        data.lastRecordsPage = 1;
    }

    function scrollHandler(e) {
        const offset = e.target.scrollHeight - e.target.clientHeight - e.target.scrollTop;

        if (offset <= 100 && data.canLoadMore) {
            loadRecords();
        }
    }

    function toggleSelected(record) {
        const index = data.selected.findIndex((r) => r.id == record.id);

        if (index >= 0) {
            data.selected.splice(index, 1);
        } else {
            const maxSelect = settings.maxSelect || 1;

            // clear last redundant elements (leaving place for the new selected)
            let toRemove = data.selected.length - maxSelect;
            while (toRemove >= 0) {
                data.selected.pop();
                toRemove--;
            }

            data.selected.push(record);
        }
    }

    function isSelected(record) {
        return data.selected.findIndex((r) => r.id == record.id) >= 0;
    }

    const documentEvents = {
        "record:save": (e) => {
            if (e.detail.collectionId != data.collection?.id) {
                return;
            }

            const selectedIndex = data.selected?.findIndex((r) => r.id == e.detail.id);
            if (selectedIndex >= 0) {
                data.selected[selectedIndex] = e.detail;
            }

            app.utils.pushOrReplaceObject(data.records, e.detail);
            loadRecords(true);
        },
        "record:delete": (e) => {
            if (
                // check both because for delete we don't know which one was assigned to
                e.detail.collectionId != data.collection?.id
                && e.detail.collectionName != data.collection?.name
            ) {
                return;
            }

            if (isSelected(e.detail)) {
                toggleSelected(e.detail);
            }

            app.utils.removeByKey(data.records, "id", e.detail.id);
            loadRecords(true);
        },
    };

    modal = t.div(
        {
            className: "modal popup lg records-picker-modal",
            onafterclose: (el) => {
                el.remove();
            },
            onmount: (el) => {
                for (let event in documentEvents) {
                    document.addEventListener(event, documentEvents[event]);
                }
            },
            onunmount: (el) => {
                watchers.forEach((w) => w?.unwatch());

                for (let event in documentEvents) {
                    document.removeEventListener(event, documentEvents[event]);
                }
            },
        },
        t.header(
            { className: "modal-header" },
            t.h6({ className: "collection-name" }, () => data.collection.name),
            app.components.recordsSearchbar({
                disabled: () => !data.collection?.id,
                collection: () => data.collection,
                value: () => data.searchTerm,
                onsubmit: (newFilter) => (data.searchTerm = newFilter),
            }),
            t.button(
                {
                    type: "button",
                    className: "btn circle transparent",
                    ariaLabel: app.attrs.tooltip("Add new record"),
                    onclick: () => {
                        app.modals.openRecordUpsert(data.collection);
                    },
                },
                t.i({ className: "ri-add-line txt-hint", ariaHidden: true }),
            ),
        ),
        t.div(
            { className: "modal-content", hidden: () => data.isLoadingCollection },
            t.div(
                {
                    className: "list records-picker-list",
                    onscroll: scrollHandler,
                    onresize: scrollHandler,
                },
                () => {
                    return data.records.map((record) => {
                        return t.div(
                            {
                                tabIndex: 0,
                                className: "list-item handle",
                                onclick: () => {
                                    toggleSelected(record);
                                    document.activeElement?.blur();
                                },
                            },
                            t.div(
                                { className: "content" },
                                t.span(
                                    { className: "state-icon" },
                                    t.i({
                                        ariaHidden: true,
                                        className: () =>
                                            isSelected(record)
                                                ? "ri-checkbox-circle-fill txt-success"
                                                : "ri-checkbox-blank-circle-line txt-disabled",
                                    }),
                                ),
                                () => app.components.recordSummary(record),
                            ),
                            t.div(
                                { className: "actions autohide" },
                                t.button(
                                    {
                                        className: "btn sm secondary transparent circle",
                                        ariaLabel: app.attrs.tooltip("Edit"),
                                        onclick: (e) => {
                                            e.stopPropagation();
                                            app.modals.openRecordUpsert(data.collection, record);
                                        },
                                    },
                                    t.i({ className: "ri-pencil-line", ariaHidden: true }),
                                ),
                            ),
                        );
                    });
                },
                // loader
                t.div(
                    {
                        className: "list-item",
                        hidden: () => !data.isLoading,
                    },
                    t.div({ className: "skeleton-loader" }),
                ),
                // no records
                t.div(
                    {
                        className: "list-item",
                        hidden: () => data.records.length || data.isLoading,
                    },
                    t.div(
                        { className: "content txt-hint" },
                        t.span({ className: "txt" }, "No records found."),
                        t.button({
                            type: "button",
                            className: "btn sm secondary",
                            textContent: "Clear search",
                            hidden: () => !data.searchTerm.trim().length,
                            onclick: () => {
                                data.searchTerm = "";
                            },
                        }),
                    ),
                ),
            ),
            t.div(
                { className: "block m-t-base" },
                t.p(
                    { className: "txt-bold" },
                    () => `Selected (${data.selected.length} of max ${settings.maxSelect || 1})`,
                ),
                t.span({ className: "txt-hint", hidden: () => data.selected }, "No selected records."),
                app.components.sortable({
                    className: "records-picker-selected-list",
                    data: () => data.selected,
                    dataItem: (record, i) => {
                        return t.div(
                            { rid: record, className: "label handle" },
                            () => app.components.recordSummary(record, [], true),
                            t.span(
                                {
                                    className: "link-hint",
                                    title: "Remove",
                                    role: "button",
                                    onclick: () => toggleSelected(record),
                                },
                                t.i({ className: "ri-close-line", ariaHidden: true }),
                            ),
                        );
                    },
                    onchange: (sortedList, fromIndex, toIndex) => {
                        data.selected = sortedList;
                    },
                }),
            ),
        ),
        t.footer(
            { className: "modal-footer" },
            t.button(
                {
                    type: "button",
                    className: "btn transparent m-r-auto",
                    onclick: () => close(),
                },
                t.span({ className: "txt" }, "Close"),
            ),
            // image thumb selector
            () => {
                if (!data.selectedFile?.name || !app.utils.hasImageExtension(data.selectedFile.name)) {
                    return;
                }

                const options = [
                    { value: "", label: "Original size" },
                    { value: "100x100", label: "100x100 thumb" },
                ];

                // find the related field and its thumbs
                const fileField = data.activeCollectionFileFields.find((f) => {
                    return data.selectedFile.record[f.name].includes(data.selectedFile.name);
                });
                const thumbs = app.utils.toArray(fileField.thumbs);
                for (let thumb of thumbs) {
                    options.push({
                        value: thumb,
                        label: `${thumb} thumb`,
                    });
                }

                return t.div(
                    { className: "record-file-picker-thumb-select" },
                    app.components.select({
                        required: true,
                        value: data.selectedFile.thumb || "",
                        options: options,
                        onchange: (opts) => {
                            data.selectedFile.thumb = opts?.[0].value;
                        },
                    }),
                );
            },
            // submit selected
            t.button(
                {
                    type: "button",
                    className: "btn expanded",
                    disabled: () => data.isLoadingCollection,
                    onclick: () => {
                        const selected = JSON.parse(JSON.stringify(data.selected));

                        if (settings.onselect && settings.onselect(selected) === false) {
                            return false;
                        }

                        close();
                    },
                },
                t.span({ className: "txt" }, settings.btnText || defaultSettings.btnText),
            ),
        ),
    );

    return modal;
}
