import { logLevel } from "./logLevel";

const perPage = 50;

export function logsList(logsSettings) {
    const data = store({
        logs: [],
        lastLoadCount: 0,
        lastPage: 1,
        bulkSelected: {},
        get canLoadMore() {
            return data.lastLoadCount >= perPage;
        },
        get totalSelected() {
            return Object.keys(data.bulkSelected).length;
        },
        get areAllSelected() {
            return data.logs.length && data.logs.length == data.totalSelected;
        },
        get paddedDefaultLogLevels() {
            const result = [];

            const sorted = Object.keys(app.utils.logLevels).sort();
            for (const level of sorted) {
                result.push(("" + level).padStart(2, " ") + ": " + app.utils.logLevels[level].label);
            }

            return result;
        },
    });

    // used as loose guard to prevent new logs to constantly push the old ones to later pages
    let loadStartDate;

    async function load(reset = false) {
        logsSettings.isListLoading = true;

        try {
            let page;
            if (reset) {
                page = 1;
                loadStartDate = new Date().toISOString().replace("T", " ");
            } else {
                page = data.lastPage + 1;
            }

            const normalizedFilter = (logsSettings.presets || []).concat(
                app.utils.normalizeSearchFilter(logsSettings.filter, ["level", "message", "data"]),
            );

            if (logsSettings.zoom?.min && logsSettings.zoom?.max) {
                const minDate = new Date(logsSettings.zoom.min * 1000);
                minDate.setSeconds(0);
                minDate.setMilliseconds(0);
                const min = app.utils.toRFC3339Datetime(minDate);

                const maxDate = new Date(logsSettings.zoom.max * 1000);
                maxDate.setSeconds(59);
                maxDate.setMilliseconds(999);
                const max = app.utils.toRFC3339Datetime(maxDate);

                normalizedFilter.push(`created >= "${min}" && created <= "${max}"`);
            } else {
                normalizedFilter.push(`created <= "${loadStartDate}"`);
            }

            const result = await app.pb.logs.getList(page, perPage, {
                skipTotal: 1,
                sort: "-@rowid",
                requestKey: "logs_list",
                filter: normalizedFilter
                    .filter(Boolean)
                    .map((f) => "(" + f + ")")
                    .join("&&"),
            });

            if (result.page == 1) {
                data.logs = [];
                data.bulkSelected = {};
            }

            data.lastPage = result.page;
            data.lastLoadCount = result.items.length;

            for (let i = 0; i < result.items.length; i++) {
                app.utils.pushOrReplaceObject(data.logs, result.items[i]);

                // yield to main
                if (i > 1 && i % 20 == 0) {
                    await new Promise((r) => setTimeout(r, 0));
                }
            }

            logsSettings.isListLoading = false;
            logsSettings.hasListItems = data.logs.length > 0;

            if (!logsSettings.isFirstLoadReady) {
                await new Promise((r) => setTimeout(r, 0));
                logsSettings.isFirstLoadReady = true;
            }
        } catch (err) {
            if (!err.isAbort) {
                logsSettings.isListLoading = false;
                app.checkApiError(err);
            }
        }
    }

    // note: allways assign a new object to trigger the getter's Object.keys
    function selectAll(state = true) {
        const selected = {};
        if (state) {
            for (let log of data.logs) {
                selected[log.id] = log;
            }
        }
        data.bulkSelected = selected;
    }

    function getLogPreviewKeys(log) {
        let keys = [];

        if (!log.data) {
            return keys;
        }

        if (log.data.type == "request") {
            const requestKeys = ["status", "execTime", "auth", "authId", "userIP"];
            for (let key of requestKeys) {
                if (typeof log.data[key] != "undefined") {
                    keys.push({ key });
                }
            }

            // add the referer if it is from a different source
            if (log.data.referer && !log.data.referer.includes(window.location.host)) {
                keys.push({ key: "referer" });
            }
        } else if (keys.length < 6) {
            // extract the first 6 keys (excluding the error, details and system truncate keys)
            const allKeys = Object.keys(log.data);
            for (const key of allKeys) {
                if (
                    keys.length < 6
                    && key != "error"
                    && key != "details"
                    && key != "__pb_truncated__"
                ) {
                    keys.push({ key });
                }
            }
        }

        // ensure that the error and detail keys are last
        if (log.data.error) {
            keys.push({ key: "error", label: "danger" });
        }
        if (log.data.details) {
            keys.push({ key: "details", label: "warning" });
        }

        return keys;
    }

    const dateFilenameRegex = /[-:\. ]/gi;

    function downloadSelected() {
        // extract the bulk selected log objects sorted desc
        const selected = Object.values(data.bulkSelected).sort((a, b) => {
            if (a.created < b.created) {
                return 1;
            }

            if (a.created > b.created) {
                return -1;
            }

            return 0;
        });

        if (!selected.length) {
            return; // nothing to download
        }

        if (selected.length == 1) {
            return app.utils.downloadJSON(
                selected[0],
                "log_" + selected[0].created.replace(" ", "T").replaceAll(dateFilenameRegex, "") + ".json",
            );
        }

        const to = selected[0].created.replace(" ", "T").replaceAll(dateFilenameRegex, "");
        const from = selected[selected.length - 1].created.replace(" ", "T").replaceAll(dateFilenameRegex, "");

        return app.utils.downloadJSON(selected, `${selected.length}_logs_${from}_to_${to}.json`);
    }

    const watchers = [];

    return t.div(
        {
            pbEvent: "logsList",
            className: "page-table-wrapper",
            onmount(el) {
                watchers.push(
                    // always reset zoom on filter or preset change
                    watch(
                        () => [logsSettings.filter, logsSettings.presets?.length],
                        () => {
                            logsSettings.zoom = {};
                        },
                    ),
                    watch(
                        () => [
                            logsSettings.reset,
                            logsSettings.filter,
                            logsSettings.presets?.length,
                            logsSettings.zoom?.min,
                            logsSettings.zoom?.max,
                        ],
                        () => {
                            load(true);

                            if (el) {
                                el.scrollTop = 0;
                            }
                        },
                    ),
                );
            },
            onunmount() {
                watchers.forEach((w) => w?.unwatch());
            },
        },
        t.table(
            { className: () => `logs-table ${data.logs?.length > perPage ? "optimize" : ""}` },
            t.thead(
                null,
                t.tr(
                    null,
                    t.th(
                        { className: "col-bulk-select" },
                        t.div(
                            {
                                className: "field",
                                hidden: () => logsSettings.isLoading,
                            },
                            t.input({
                                id: "logs_select_all",
                                type: "checkbox",
                                checked: () => data.areAllSelected,
                                onchange: (e) => selectAll(e.target.checked),
                            }),
                            t.label({ htmlFor: "logs_select_all" }),
                        ),
                        t.span({
                            className: "loader",
                            hidden: () => !logsSettings.isLoading,
                        }),
                    ),
                    t.th(
                        { className: "col-field-name-level" },
                        t.div(
                            { className: "inline-flex gap-5" },
                            t.i({ className: "ri-bookmark-line", ariaHidden: true }),
                            t.span({ textContent: "Level" }),
                            t.i({
                                className: "ri-information-line txt-sm link-hint",
                                ariaDescription: app.attrs.tooltip(
                                    () => "Default levels\n" + data.paddedDefaultLogLevels.join("\n"),
                                    "bottom",
                                    "code",
                                ),
                            }),
                        ),
                    ),
                    t.th(
                        { className: "col-field-name-message" },
                        t.div(
                            { className: "inline-flex gap-5" },
                            t.i({ className: "ri-file-list-2-line", ariaHidden: true }),
                            t.span({ textContent: "Message" }),
                        ),
                    ),
                    t.th(
                        { className: "col-field-type-date col-field-name-created" },
                        t.div(
                            { className: "inline-flex gap-5" },
                            t.i({ className: "ri-calendar-line", ariaHidden: true }),
                            t.span({ textContent: "Created" }),
                        ),
                    ),
                    t.th({ className: "col-meta" }),
                ),
            ),
            t.tbody(
                null,
                () => {
                    if (!data.logs?.length) {
                        return t.tr(
                            null,
                            t.td(
                                { colSpan: 99 },
                                () => {
                                    if (logsSettings.isListLoading) {
                                        return t.span({ className: "skeleton-loader" });
                                    }

                                    return t.div(
                                        { className: "sticky-content txt-center txt-hint" },
                                        t.div({ className: "txt-bold" }, "No logs found."),
                                        t.button(
                                            {
                                                hidden: () =>
                                                    logsSettings.filter?.length || app.utils.isEmpty(logsSettings.zoom),
                                                type: "button",
                                                className: "btn secondary expanded-lg m-t-10",
                                                onclick() {
                                                    logsSettings.zoom = {};
                                                },
                                            },
                                            t.span({ className: "txt" }, "Reset zoom"),
                                        ),
                                        t.button(
                                            {
                                                hidden: () => !logsSettings.filter?.length,
                                                type: "button",
                                                className: "btn secondary expanded-lg m-t-10",
                                                onclick() {
                                                    logsSettings.filter = "";
                                                },
                                            },
                                            t.span({ className: "txt" }, "Clear search"),
                                        ),
                                    );
                                },
                            ),
                        );
                    }

                    return data.logs.map((log) => {
                        return t.tr(
                            {
                                rid: log.id,
                                tabIndex: 0,
                                role: "button",
                                className: () => `handle ${log.data.type == "request" ? "log-request" : ""}`,
                                onclick: () => {
                                    logsSettings.activeLogIdOrModel = log;
                                },
                                onkeypress: (e) => {
                                    if (e.key == "Enter" || e.key == " ") {
                                        e.preventDefault();
                                        logsSettings.activeLogIdOrModel = log;
                                    }
                                },
                            },
                            () => {
                                return [
                                    t.td(
                                        {
                                            className: "col-bulk-select",
                                            onclick: (e) => e.stopPropagation(),
                                            onkeypress: (e) => e.stopPropagation(),
                                        },
                                        t.div(
                                            { className: "field" },
                                            t.input({
                                                id: "cb_" + log.id,
                                                type: "checkbox",
                                                checked: () => !!data.bulkSelected[log.id],
                                                onchange: (e) => {
                                                    let bulkSelected = Object.assign({}, data.bulkSelected);

                                                    // range select
                                                    if (e.target.__shiftKey) {
                                                        e.target.__shiftKey = false;

                                                        app.utils.bulkSelectRange(
                                                            data.logs,
                                                            bulkSelected,
                                                            log,
                                                            e.target.checked,
                                                        );
                                                    }

                                                    // toggle current log
                                                    if (e.target.checked) {
                                                        bulkSelected[log.id] = log;
                                                    } else {
                                                        delete bulkSelected[log.id];
                                                    }

                                                    // reassign to trigger the getter's Object.keys
                                                    data.bulkSelected = bulkSelected;
                                                },
                                            }),
                                            t.label({
                                                htmlFor: "cb_" + log.id,
                                                // workaround https://github.com/pocketbase/pocketbase/issues/7771
                                                onclick: (e) => {
                                                    e.preventDefault();
                                                    const input = document.getElementById(e.target.htmlFor);
                                                    if (input) {
                                                        input.__shiftKey = e.shiftKey;
                                                        input.click();
                                                    }
                                                },
                                            }),
                                        ),
                                    ),
                                    t.td({ className: "col-field-name-level" }, logLevel(log)),
                                    t.td(
                                        { className: "col-field-name-message" },
                                        t.div(
                                            { className: "content-primary" },
                                            () => app.utils.truncate(log.message, 1000),
                                        ),
                                        t.div(
                                            {
                                                className: "content-secondary",
                                            },
                                            () => {
                                                const labels = [];

                                                const keyItems = getLogPreviewKeys(log);
                                                for (const keyItem of keyItems) {
                                                    let value;
                                                    if (app.utils.logDataFormatters[keyItem.key]) {
                                                        value = app.utils.logDataFormatters[keyItem.key](log);
                                                    } else {
                                                        value = app.utils.displayValue(log.data[keyItem.key], 80);
                                                    }

                                                    labels.push(
                                                        t.span(
                                                            { className: `label sm ${keyItem.label || ""}` },
                                                            `${keyItem.key}: ${value}`,
                                                        ),
                                                    );
                                                }

                                                return labels;
                                            },
                                        ),
                                    ),
                                    t.td(
                                        {
                                            className: "col-field-type-date col-field-name-created",
                                        },
                                        app.components.formattedDate({
                                            value: () => log.created,
                                            short: false,
                                        }),
                                    ),
                                    t.td(
                                        { className: "col-meta" },
                                        t.i({ className: "ri-arrow-right-line", ariaHidden: true }),
                                    ),
                                ];
                            },
                        );
                    });
                },
                t.tr(
                    { hidden: () => !data.canLoadMore },
                    t.td(
                        { colSpan: 99 },
                        t.button(
                            {
                                className: () =>
                                    `btn lg secondary load-more-btn ${
                                        logsSettings.isListLoading ? "transparent loading" : ""
                                    }`,
                                disabled: () => logsSettings.isListLoading,
                                onclick: () => load(),
                            },
                            t.span({ className: "txt" }, "Load older"),
                        ),
                    ),
                ),
            ),
        ),
        t.div(
            { className: "bulkbar-wrapper" },
            t.div(
                {
                    hidden: () => data.totalSelected == 0,
                    className: "bulkbar logs-bulkbar",
                },
                t.span(
                    { className: "txt" },
                    "Selected ",
                    t.strong(null, () => data.totalSelected),
                    () => ` ${data.totalSelected == 1 ? "log" : "logs"}`,
                ),
                t.button(
                    {
                        type: "button",
                        className: "btn sm secondary pill m-r-auto",
                        onclick: () => selectAll(false),
                    },
                    t.span({ className: "txt" }, "Reset"),
                ),
                t.button(
                    {
                        type: "button",
                        className: "btn sm pill",
                        onclick: () => downloadSelected(),
                    },
                    t.i({ className: "ri-download-line", ariaHidden: true }),
                    t.span({ className: "txt" }, "JSON"),
                ),
            ),
        ),
    );
}
