/** @odoo-module **/
/* eslint-disable init-declarations */
/* Copyright 2022 Tecnativa - Carlos Roca
 * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */

import {patch} from "@web/core/utils/patch";
import {Domain} from "@web/core/domain";
import {serializeDate, serializeDateTime} from "@web/core/l10n/dates";
import {localization} from "@web/core/l10n/localization";
/* Redefine some methods of dates.js from web.static.src.utils to
add support to days and weeks periods */
import * as dates from "@web/search/utils/dates";

/**
 * Method used to calculate the quantity of days and weeks of the
 * actual year.
 */
function _getQtyOfCurrentYear(option) {
    const now = new Date();
    const startOfYear = new Date(now.getFullYear(), 0, 1);
    const endOfYear = new Date(now.getFullYear(), 11, 31);
    let millisecondsPerWeek = 0;
    if (option === "week") {
        millisecondsPerWeek = 604800000;
    } else if (option === "day") {
        millisecondsPerWeek = 86400000;
    } else {
        return;
    }
    const weeks = Math.ceil((endOfYear - startOfYear) / millisecondsPerWeek);
    return weeks;
}

Object.assign(dates.PER_YEAR, {
    week: _getQtyOfCurrentYear("week"),
    day: _getQtyOfCurrentYear("day"),
});

// This is needed to call the super functions on @web/search/utils/dates
const _getSetParam = dates.getSetParam;

// Patch of functions defined before
patch(dates, {
    /*
     * Redefine function to avoid the exclusion of days and weeks.
     */
    constructDateDomain(
        referenceMoment,
        fieldName,
        fieldType,
        selectedOptionIds,
        comparisonOptionId
    ) {
        let plusParam;
        let selectedOptions;
        if (comparisonOptionId) {
            [plusParam, selectedOptions] = dates.getComparisonParams(
                referenceMoment,
                selectedOptionIds,
                comparisonOptionId
            );
        } else {
            selectedOptions = dates.getSelectedOptions(
                referenceMoment,
                selectedOptionIds
            );
        }
        const yearOptions = selectedOptions.year;
        const otherOptions = [
            ...(selectedOptions.quarter || []),
            ...(selectedOptions.month || []),
            ...(selectedOptions.day || []),
            ...(selectedOptions.week || []),
        ];
        dates.sortPeriodOptions(yearOptions);
        dates.sortPeriodOptions(otherOptions);
        const ranges = [];
        for (const yearOption of yearOptions) {
            const constructRangeParams = {
                referenceMoment,
                fieldName,
                fieldType,
                plusParam,
            };
            if (otherOptions.length) {
                for (const option of otherOptions) {
                    const year_param =
                        option.granularity === "week"
                            ? {weekYear: yearOption.setParam.year}
                            : yearOption.setParam;
                    const setParam = Object.assign(
                        {},
                        year_param,
                        option ? option.setParam : {}
                    );
                    const {granularity, custom_period} = option;
                    if (comparisonOptionId && custom_period.is_custom_period) {
                        custom_period.is_comparison = true;
                    }
                    const range = dates.constructDateRange(
                        Object.assign(
                            {granularity, custom_period, setParam},
                            constructRangeParams
                        )
                    );
                    ranges.push(range);
                }
            } else {
                const {granularity, custom_period, setParam} = yearOption;
                if (comparisonOptionId && custom_period.is_custom_period) {
                    custom_period.is_comparison = true;
                }
                const range = dates.constructDateRange(
                    Object.assign(
                        {granularity, custom_period, setParam},
                        constructRangeParams
                    )
                );
                ranges.push(range);
            }
        }
        const domain = Domain.combine(
            ranges.map((range) => range.domain),
            "OR"
        );
        const description = ranges.map((range) => range.description).join("/");
        return {domain, description};
    },
    constructDateRange(params) {
        const {
            referenceMoment,
            fieldName,
            fieldType,
            granularity,
            setParam,
            plusParam,
            custom_period,
        } = params;
        if ("quarter" in setParam) {
            // Luxon does not consider quarter key in setParam (like moment did)
            setParam.month = dates.QUARTERS[setParam.quarter].coveredMonths[0];
            delete setParam.quarter;
        }
        const date = referenceMoment.set(setParam).plus(plusParam || {});
        // Compute domain
        var leftDate = date.startOf(granularity);
        var rightDate = date.endOf(granularity);
        if (
            custom_period.is_custom_period &&
            parseInt(custom_period.last_period) !== 0
        ) {
            if (granularity === "day") {
                const plusParamReferenceMoment = referenceMoment.plus(plusParam || {});
                leftDate = leftDate.plus({days: 1});
                rightDate = plusParamReferenceMoment;
            } else {
                var customPlusParam = {};
                customPlusParam[granularity + "s"] = parseInt(
                    custom_period.last_period
                );
                rightDate = leftDate.plus(customPlusParam);
            }
        }
        let leftBound;
        let rightBound;
        if (fieldType === "date") {
            leftBound = serializeDate(leftDate);
            rightBound = serializeDate(rightDate);
        } else {
            leftBound = serializeDateTime(leftDate);
            rightBound = serializeDateTime(rightDate);
        }
        const domain = new Domain([
            "&",
            [fieldName, ">=", leftBound],
            [fieldName, "<=", rightBound],
        ]);
        // Compute description
        var description = "";
        if (custom_period.is_custom_period) {
            if (plusParam) {
                const key = Object.keys(plusParam)[0];
                description = "Previous " + Math.abs(plusParam[key]) + " " + key;
            } else {
                description =
                    "Last " + custom_period.last_period + " " + granularity + "s";
            }
        } else {
            var descriptions = [date.toFormat("yyyy")];
            const method = localization.direction === "rtl" ? "push" : "unshift";
            if (granularity === "month") {
                descriptions[method](date.toFormat("MMMM"));
            } else if (granularity === "quarter") {
                const quarter = date.quarter;
                descriptions[method](dates.QUARTERS[quarter].description.toString());
            } else if (granularity === "day") {
                descriptions[method](date.toFormat("dd MMMM"));
            } else if (granularity === "week") {
                descriptions[method](date.toFormat("'W'WW"));
            }
            description = descriptions.join(" ");
        }
        return {domain, description};
    },
    /*
     * Redefine function to allow process days and weeks.
     */
    getPeriodOptions(referenceMoment) {
        const options = [];
        const originalOptions = Object.values(dates.PERIOD_OPTIONS);
        for (const option of originalOptions) {
            const {id, groupNumber} = option;
            let description;
            let defaultYear;
            switch (option.granularity) {
                case "quarter":
                    description = option.description.toString();
                    defaultYear = referenceMoment.set(option.setParam).year;
                    break;
                case "day":
                case "week":
                case "month":
                case "year":
                    const date = referenceMoment.plus(option.plusParam);
                    description = option.description || date.toFormat(option.format);
                    defaultYear = date.year;
                    break;
            }
            const setParam = dates.getSetParam(option, referenceMoment);
            options.push({id, groupNumber, description, defaultYear, setParam});
        }
        const periodOptions = [];
        for (const option of options) {
            const {id, groupNumber, description, defaultYear} = option;
            const yearOption = options.find(
                (o) => o.setParam && o.setParam.year === defaultYear
            );
            periodOptions.push({
                id,
                groupNumber,
                description,
                defaultYearId: yearOption.id,
            });
        }
        return periodOptions;
    },
    /*
     * Add selection of month when week or day selected.
     *
     * @override
     */
    getSetParam(periodOption, referenceMoment) {
        if (periodOption.granularity === "day") {
            const date = referenceMoment.plus(periodOption.plusParam);
            return {
                day: date.day,
                month: date.month,
            };
        } else if (periodOption.granularity === "week") {
            const date = referenceMoment.plus(periodOption.plusParam);
            return {
                weekNumber: date.weekNumber,
            };
        }
        return _getSetParam(...arguments);
    },
    getSelectedOptions(referenceMoment, selectedOptionIds) {
        const selectedOptions = {year: []};
        for (const optionId of selectedOptionIds) {
            const option = dates.PERIOD_OPTIONS[optionId];
            const setParam = dates.getSetParam(option, referenceMoment);
            const custom_period = option.custom_period || {};
            const granularity = option.granularity;
            if (!selectedOptions[granularity]) {
                selectedOptions[granularity] = [];
            }
            selectedOptions[granularity].push({granularity, setParam, custom_period});
        }
        return selectedOptions;
    },
    /*
     * Add support to day and week options.
     *
     */
    getComparisonParams(referenceMoment, selectedOptionIds, comparisonOptionId) {
        const comparisonOption = dates.COMPARISON_OPTIONS[comparisonOptionId];
        const selectedOptions = dates.getSelectedOptions(
            referenceMoment,
            selectedOptionIds
        );
        if (comparisonOption.plusParam) {
            return [comparisonOption.plusParam, selectedOptions];
        }
        const plusParam = {};
        let globalGranularity = "year";
        if (selectedOptions.day) {
            globalGranularity = "day";
        } else if (selectedOptions.week) {
            globalGranularity = "week";
        } else if (selectedOptions.month) {
            globalGranularity = "month";
        } else if (selectedOptions.quarter) {
            globalGranularity = "quarter";
        }
        const granularityFactor = dates.PER_YEAR[globalGranularity];
        const years = selectedOptions.year.map((o) => o.setParam.year);
        const yearMin = Math.min(...years);
        const yearMax = Math.max(...years);
        let optionMin = 0;
        let optionMax = 0;
        if (selectedOptions.quarter) {
            const quarters = selectedOptions.quarter.map((o) => o.setParam.quarter);
            if (globalGranularity === "month") {
                delete selectedOptions.quarter;
                for (const quarter of quarters) {
                    for (const month of dates.QUARTERS[quarter].coveredMonths) {
                        const monthOption = selectedOptions.month.find(
                            (o) => o.setParam.month === month
                        );
                        if (!monthOption) {
                            selectedOptions.month.push({
                                setParam: {month},
                                granularity: "month",
                            });
                        }
                    }
                }
            } else {
                optionMin = Math.min(...quarters);
                optionMax = Math.max(...quarters);
            }
        }
        if (selectedOptions.month) {
            const months = selectedOptions.month.map((o) => o.setParam.month);
            optionMin = Math.min(...months);
            optionMax = Math.max(...months);
        }
        if (selectedOptions.week) {
            const weeks = selectedOptions.week.map((o) => o.setParam.weekNumber);
            optionMin = Math.min(...weeks);
            optionMax = Math.max(...weeks);
        }
        if (selectedOptions.day) {
            const days = selectedOptions.day.map((o) => o.setParam.day);
            optionMin = Math.min(...days);
            optionMax = Math.max(...days);
        }
        const num =
            -1 + granularityFactor * (yearMin - yearMax) + optionMin - optionMax;
        const key =
            globalGranularity === "year"
                ? "years"
                : globalGranularity === "month"
                ? "months"
                : globalGranularity === "week"
                ? "weeks"
                : globalGranularity === "day"
                ? "days"
                : "quarters";
        plusParam[key] = num;
        return [plusParam, selectedOptions];
    },
});
