import {
    capitalizeFirstLetter,
    toRelativeStringFromTimespan,
    pluralize
} from './string-utils.mjs.js';
import { createLogger } from './logger.mjs.js';

const logger = createLogger('modules/core-utils');

export const throttle = (func, interval) => {
    let shouldFire = true;
    return () => {
        if (shouldFire) {
            func();
            shouldFire = false;
            setTimeout(() => {
                shouldFire = true;
            }, interval);
        }
    };
};

export const sanitizeHeaderMessageId = headerMessageId =>
    headerMessageId
        .replace('>', '')
        .replace('<', '')
        .trim();

export const sendConnectionMessageEx = async (
    openConnections,
    message,
    connectionName
) => {
    logger.info(`open connections?`, openConnections, 'send message', message);
    const connection = openConnections.find(con => con.name === connectionName);
    if (connection) {
        await connection.postMessage(message);
    } else {
        logger.warn(
            `No connection found in ${openConnections.length} open connections`
        );
    }
};

/**
 * Finds the currently running Thunderbird version
 * @returns
 */
export const findThunderbirdVersion = () => {
    // 
    const agent = window.navigator.userAgent;
    const version = (agent || '')
        .split('/')
        .pop()
        .split('.')
        .shift();
    return Number.parseInt(version) || 0;
};

/**
 * Checks if the currently executed Thunderird is below given version number
 * @param {number} version
 * @returns
 */
export const isThunderbirdBelowVersion = version =>
    findThunderbirdVersion() < (version || 86);

export const buildQueryInfoForMessageAndFolder = (messageDetails, folder) => {
    const { headerMessageId, author, subject: _ } = messageDetails;
    const queryInfo = {
        // 
        headerMessageId: sanitizeHeaderMessageId(headerMessageId),
        author: getMailAddress(author),
        folder: {
            accountId: folder.accountId,
            path: folder.path
        }
    };

    const thunderbirdVersion = findThunderbirdVersion();
    const isThunderbirdBelow86 = thunderbirdVersion < 86;

    if (isThunderbirdBelow86) {
        delete queryInfo.headerMessageId;
    }

    return queryInfo;
};

export const uniqueArray = (list, compare) => {
    return list.filter(
        compare || ((item, index, lst) => lst.indexOf(item) === index)
    );
};

/**
 * Checks if two given mindrs are the same bt `headerMessageId` and `guid`
 * @param {Mindr} mindrA A mindr
 * @param {Mindr} mindrB Another mindr
 * @returns {boolean} `true` if the mindrs are the same by `headerMessageId` and (internal) `guid`
 */
export const isSameMindr = (mindrA, mindrB) =>
    sanitizeHeaderMessageId(mindrA.headerMessageId) ===
        sanitizeHeaderMessageId(mindrB.headerMessageId) &&
    mindrA.guid === mindrB.guid;

export const createMailmindrId = scope =>
    `${scope}--${[Math.random(), Math.random(), Math.random()]
        .map(item => Math.round(item * 1000000).toString(16))
        .join('-')}`;

/**
 * Checks if a mindr has been executed
 * @param {Mindr} mindr A mindr
 * @returns {boolean}
 */
export const isExecuted = mindr => mindr.isExecuted;

/**
 * Returns true when a mindr is waiting for a reply
 * @param {Mindr} mindr
 * @returns Boolean
 */
export const hasReply = mindr =>
    mindr.isWaitingForReply &&
    mindr.metaData &&
    'string' === typeof mindr.metaData.replyHeaderMessageId;

export const showReminderForMindr = mindr =>
    String(mindr.remindMeMinutesBefore) !== '-1' ||
    mindr.action.showReminder !== false;

/**
 * Checks if a indr is overdue
 * @param {Mindr} mindr
 * @returns {boolean}
 */
export const isOverdue = mindr => {
    const now = Date.now();
    const due = mindr.due.getTime();
    return due <= now;
};

export const isActiveWithinLookAhead = (mindr, lookeahedInMinutes) => {
    const { due, remindMeMinutesBefore } = mindr;

    const now = Date.now();
    const dueTime = due.getTime();

    // 
    // 

    const remindMeAtDate = getReminderDateTime(mindr, lookeahedInMinutes);
    // 
    const remindMeAt = remindMeAtDate.getTime();
    // 
    // 
    // 
    // 
    // 
    // 

    // 
    // 
    const executedButOverdue = isExecuted(mindr) && now >= remindMeAt;
    const remindBeforeDue = now < dueTime && remindMeAt < now;

    logger.log(
        `>> reminder executedButOverdue (${executedButOverdue}) / remindBeforeDue (${remindBeforeDue})`
    );

    return remindBeforeDue || executedButOverdue;
};

export const getParsedJsonOrDefaultValue = (value, defaultValue) => {
    try {
        return JSON.parse(value);
    } catch (exception) {
        logger.error(
            `getParsedJsonOrDefaultValue failed parsing and returns default value`,
            value,
            exception,
            'defaultValue: ',
            defaultValue
        );
        return defaultValue;
    }
};

/**
 * Checks if two timespans are the same
 * @param {MailmindrTimespan} timespanA A timespan value
 * @param {MailmindrTimespan} timespanB Another timespan
 * @returns {boolean}
 */
export const equalTimePresetValues = (timespanA, timespanB) =>
    timespanA &&
    timespanB &&
    timespanA.days === timespanB.days &&
    timespanA.hours === timespanB.hours &&
    timespanA.minutes === timespanB.minutes &&
    timespanA.isRelative === timespanB.isRelative &&
    timespanA.isGenerated === timespanB.isGenerated &&
    timespanA.isSelectable === timespanB.isSelectable;

/**
 * Checks wether two actions are equal
 * @param {MailmindrAction} someAction
 * @param {MailmindrAction} someOtherAction
 * @returns {boolean}
 */
export const equalActionPresetValues = (someAction, someOtherAction) =>
    someAction &&
    someOtherAction &&
    someAction.flag === someOtherAction.flag &&
    someAction.markUnread === someOtherAction.markUnread &&
    someAction.showReminder === someOtherAction.showReminder &&
    someAction.tagWithLabel === someOtherAction.tagWithLabel &&
    someAction.copyMessageTo === someOtherAction.copyMessageTo &&
    someAction.moveMessageTo === someOtherAction.moveMessageTo;

/**
 * Creates ann empty timespan with some dfeaults
 * @returns {MailmindrTimespan}
 */
export const createEmptyTimespan = (isRelative = true) => ({
    days: 0,
    hours: 0,
    minutes: 0,
    text: '',
    isRelative,
    isGenerated: false,
    isSelectable: true
});

/**
 * Creates an empty action initialized with some defaults
 * @returns {MailmindrAction}
 */
export const createEmptyActionTemplate = () => ({
    flag: false,
    markUnread: false,
    showReminder: false,
    tagWithLabel: undefined,
    copyMessageTo: undefined,
    moveMessageTo: undefined,
    isSystemAction: false,
    text: ''
});

/**
 * Creates a list (array) of timespans
 * with 7 days and 7 hours
 * @returns {Array<MailmindrTimespan>}
 */
export const createSystemTimespans = () => {
    let systemTimespans = [];

    const nonSelectablePreset = {
        ...createEmptyTimespan(),
        text: browser.i18n.getMessage('presets.time.userdefined'),
        isSelectable: false,
        isGenerated: true
    };
    systemTimespans.push(nonSelectablePreset);

    /* create a seven day lookahead */
    for (let days = 1; days <= 7; days++) {
        const dayPreset = {
            ...createEmptyTimespan(),
            days,
            isGenerated: true
        };

        dayPreset.text = capitalizeFirstLetter(
            toRelativeStringFromTimespan(dayPreset)
        );
        systemTimespans.push(dayPreset);
    }

    /* create a 6 hour window */
    for (let hours = 1; hours < 7; hours++) {
        const hourPreset = {
            ...createEmptyTimespan(true),
            hours,
            isGenerated: true
        };
        hourPreset.text = capitalizeFirstLetter(
            toRelativeStringFromTimespan(hourPreset)
        );
        systemTimespans.push(hourPreset);
    }

    return systemTimespans;
};

/**
 * Create an array of system actions:
 * - mark unread
 * - show dialog
 * - tag message with
 * ..
 * @returns {Array<MailmindrAction>} A list of default action presets
 */
export const createSystemActions = _options => {
    let actionTemplates = [];

    const markUnreadAction = {
        ...createEmptyActionTemplate(),
        markUnread: true,
        isSystemAction: true,
        text: browser.i18n.getMessage('presets.action.mark-unread')
    };
    actionTemplates.push(markUnreadAction);

    const flagAction = {
        ...createEmptyActionTemplate(),
        flag: true,
        isSystemAction: true,
        text: browser.i18n.getMessage('presets.action.flag')
    };
    actionTemplates.push(flagAction);

    const reminderOnlyAction = {
        ...createEmptyActionTemplate(),
        showReminder: true,
        isSystemAction: true,
        text: browser.i18n.getMessage('presets.action.reminder-only')
    };

    actionTemplates.push(reminderOnlyAction);

    return actionTemplates;
};

export const createRemindMeMinutesBefore = () => {
    const remindMeMinutesBefore = [
        { minutes: 0, display: 0, unit: 'on-time' },
        { minutes: 5, display: 5, unit: 'minutes' },
        { minutes: 15, display: 15, unit: 'minutes' },
        { minutes: 30, display: 30, unit: 'minutes' },
        { minutes: 60, display: 1, unit: 'hours' },
        { minutes: 120, display: 2, unit: 'hours' },
        { minutes: 240, display: 4, unit: 'hours' },
        { minutes: -1, display: null, unit: 'no-reminder' }
    ];
    return remindMeMinutesBefore;
};

/**
 * Extracts the email address of an email author
 * @param {string} author The author of an email message, might be an email address or in format Firstname Surname <email@example.com>
 * @returns {string|null} An email address if one can be found, `null` otherwise
 */
export const getMailAddress = author => {
    if (!author) {
        logger.warn('author is empty');
        return null;
    }

    const tokenStartPos = author.indexOf('<');
    const tokenEndPos = author.indexOf('>');

    if (tokenStartPos > 0 && tokenStartPos < tokenEndPos) {
        const mayBeAddress = author.substring(tokenStartPos + 1, tokenEndPos);
        return mayBeAddress;
    }

    return author;
};

export const createMindrFromActionTemplate = async (
    /** @type {Mindr} */ mindrData
) => {
    const {
        guid,
        headerMessageId,
        notes,
        actionTemplate,
        due,
        remindMeMinutesBefore,
        metaData,
        isExecuted = false,
        isWaitingForReply = false
    } = mindrData;
    const {
        flag,
        markUnread,
        showReminder,
        tagWithLabel,
        copyMessageTo,
        moveMessageTo,
        isSystemAction
    } = actionTemplate;
    const action = {
        flag,
        markUnread,
        showReminder,
        tagWithLabel,
        copyMessageTo,
        moveMessageTo,
        isSystemAction
    };

    const mindrGuid = guid || createMailmindrId('mailmindr:mindr-guid');
    const modified = Date.now();

    return {
        guid: mindrGuid,
        headerMessageId,
        due,
        remindMeMinutesBefore,
        action,
        notes,
        metaData,
        isExecuted,
        modified,
        isWaitingForReply
    };
};

export const getFlatFolderList = async forAccount => {
    const rawAccounts = await await browser.accounts.list();
    const accounts = rawAccounts.filter(
        account => !Boolean(forAccount) || account.id === forAccount.id
    );
    const allFolders = [];

    const getNext = list => {
        const index = list.findIndex(item => !item.visited);
        return index < 0 ? null : { index, value: list[index] };
    };

    for await (let account of accounts) {
        allFolders.push({
            account,
            type: 'account',
            depth: 0,
            name: account.name
        });

        // 
        let folderList = (account.folders || []).map(item => ({
            visited: false,
            depth: 0,
            item
        }));

        // 
        let next = getNext(folderList);

        // 
        while (next) {
            // 
            const { index, value } = next;

            // 
            const {
                item: { folders, subFolders },
                depth
            } = value;

            // 
            const subfolders = (folders || subFolders || []).map(item => ({
                visited: false,
                item,
                depth: depth + 1
            }));

            // 
            folderList.splice(index + 1, 0, ...subfolders);

            // 
            value.visited = true;

            // 
            next = getNext(folderList);
        }

        // 
        for await (let option of folderList) {
            const { depth, item } = option;

            allFolders.push({
                type: 'folder',
                depth,
                name: item.name,
                folder: await localFolderToGenericFolder(item)
            });
        }
    }
    return allFolders;
};

/**
 *
 * @param {browser.folders.MailFolder} folder
 * @returns MailmindrGenericFolder
 */
export const localFolderToGenericFolder = async folder => {
    if (!folder) {
        return null;
    }

    const { accountId, name, path, type } = folder;
    if (!accountId) {
        return null;
    }

    const account = await browser.accounts.get(accountId);
    const identityEmailAddressList = account.identities.map(
        identity => identity.email
    );
    const identityEmailAddress = identityEmailAddressList[0];
    const genericFolder = { accountId, identityEmailAddress, name, path, type };

    return genericFolder;
};

/**
 *
 * @param {MailmindrGenericFolder} genericFolder
 * @returns browser.folders.MailFolder
 */
export const genericFolderToLocalFolder = async genericFolder => {
    const { accountId, identityEmailAddress, name, path, type } = genericFolder;
    if (identityEmailAddress) {
        const accounts = await browser.accounts.list();
        const account = accounts.find(account =>
            account.identities.find(
                identity => identity.email === identityEmailAddress
            )
        );

        // 
        if (account) {
            return { accountId: account.id, name, path, type };
        }
    }

    if (accountId) {
        // 
        return { accountId, name, path, type };
    }

    // 
    return null;
};

export const genericFoldersAreEqual = (a, b) =>
    a &&
    b &&
    (a.name || '') === (b.name || '') &&
    (a.path || '') === (b.path || '') &&
    (a.type || '') === (b.type || '') &&
    (a.identityEmailAddress || '') === (b.identityEmailAddress || '');

export const getPostPoneItems = () => {
    const minutesBeforeDueDate = [15, 10, 5, 0];
    const minutes = [5, 10, 15, 30, 45];
    const hours = [1, 2, 4];

    const result = [
        ...minutesBeforeDueDate.map(item => ({
            caption: pluralize(
                item,
                'mailmindr.utils.core.plural.beforestart.minutes'
            ),
            value: item,
            unit: 'minutes',
            type: 'beforeStart'
        })),
        {
            type: 'separator'
        },
        ...minutes.map(item => ({
            caption: pluralize(item, 'mailmindr.utils.core.plural.minutes'),
            value: item,
            unit: 'minutes',
            type: 'postpone'
        })),
        ...hours.map(item => ({
            caption: pluralize(item, 'mailmindr.utils.core.plural.hours'),
            value: item,
            unit: 'hours',
            type: 'postpone'
        }))
    ];

    return result;
};

export const getReminderDateTime = (mindr, lookeahedInMinutes) => {
    const { due, remindMeMinutesBefore } = mindr;
    const dueTime = due.getTime();
    const minutesBefore = remindMeMinutesBefore; // Math.max(remindMeMinutesBefore, 0);
    // 
    const reminderLookahead = (minutesBefore || lookeahedInMinutes) * 60 * 1000;
    const remindMeAt = dueTime - reminderLookahead;

    return new Date(remindMeAt);
};

/**
 *
 * @param {Mindr[]} mindrList
 * @returns {MindrActiveOverdueList}
 */
export const getActiveAndOverdueMindrs = (mindrList, _) => {
    /** @type {MindrActiveOverdueList} */
    const seed = {
        active: [],
        mindrs: [],
        overdue: []
    };
    return mindrList.reduce((collector, currentMindr) => {
        // 
        // 
        // 
        // 
        // 
        // 

        if (isOverdue(currentMindr)) {
            // 
            collector.overdue.push(currentMindr);
        }

        // 
        // 
        if (isActiveWithinLookAhead(currentMindr, 0)) {
            // 
            // 
            collector.active.push(currentMindr);
        }

        // 
        collector.mindrs.push(currentMindr);

        return collector;
    }, seed);
};

/**
 *
 * @param {string} identityMailAddress
 * @param {MailmindrSettings} settings
 * @returns MailmindrGenericFolder | null
 */
export const getGenericIceboxFolderForIdentityOrNull = async (
    identityMailAddress,
    settings
) => {
    const iceboxFolderSettings = (settings?.iceboxFolders || []).find(
        iceBoxSetting =>
            iceBoxSetting.accountIdentity ===
            (identityMailAddress || 'localFolders')
    );

    const isIceboxFolderSet = Boolean(iceboxFolderSettings?.folder);
    const isDefaultIceboxFolderSet = Boolean(
        settings?.defaultIceboxFolder?.folder
    );

    logger.info('icebox folder available?', {
        identityMailAddress,
        iceboxFolderSettings,
        settings
    });

    if (isIceboxFolderSet) {
        return await localFolderToGenericFolder(iceboxFolderSettings.folder);
    }

    if (isDefaultIceboxFolderSet) {
        return await localFolderToGenericFolder(
            settings.defaultIceboxFolder.folder
        );
    }

    return null;
};

/**
 *
 * @param {Mindr} mindr
 * @param { { readonly snoozeTimeMinutes: number; readonly correlationId: string; } } param1
 * @returns {Mindr}
 */
export const snoozeMindr = (
    mindr,
    { snoozeTimeMinutes, correlationId = '<correlationId not set>' }
) => {
    const /** @type {EditableMindr} */ modifiedMindr = structuredClone(mindr);
    const { due, isExecuted, remindMeMinutesBefore } = modifiedMindr;

    const dueTime = due.getTime();
    const now = Date.now();

    // 
    if (dueTime < now) {
        const { remindMeMinutesBefore } = modifiedMindr;

        if (isExecuted) {
            // 
            // 
            // 

            if (modifiedMindr.remindMeMinutesBefore < 0) {
                modifiedMindr.action.showReminder = false;
            }

            modifiedMindr.remindMeMinutesBefore =
                // 
                -1 *
                Math.floor(
                    (now - dueTime + snoozeTimeMinutes * 60 * 1000) / 60 / 1000
                );
        } else {
            // 

            modifiedMindr.due = new Date(
                Date.now() + snoozeTimeMinutes * 60 * 1000
            );
        }
    } else {
        console.warn(`mindr.remindMeMinutesBefore is not modified`);
        modifiedMindr.due = new Date(
            Date.now() + remindMeMinutesBefore * 60 * 1000
        );
    }

    return modifiedMindr;
};
