/**
 * @typedef {import('@converse/skeletor').Model} Model
 * @typedef {import('@converse/headless').RosterContact} RosterContact
 * @typedef {import('@converse/headless').RosterContacts} RosterContacts
 */
import { __ } from 'i18n';
import { _converse, api, converse, log, constants } from "@converse/headless";

const { Strophe } = converse.env;
const { STATUS_WEIGHTS } = constants;

export function removeContact (contact) {
    contact.removeFromRoster(
        () => contact.destroy(),
        (e) => {
            e && log.error(e);
            api.alert('error', __('Error'), [
                __('Sorry, there was an error while trying to remove %1$s as a contact.',
                contact.getDisplayName())
            ]);
        }
    );
}

export function highlightRosterItem (chatbox) {
    _converse.state.roster?.get(chatbox.get('jid'))?.trigger('highlight');
}

export function toggleGroup (ev, name) {
    ev?.preventDefault?.();
    const { roster } = _converse.state;
    const collapsed = roster.state.get('collapsed_groups');
    if (collapsed.includes(name)) {
        roster.state.save('collapsed_groups', collapsed.filter(n => n !== name));
    } else {
        roster.state.save('collapsed_groups', [...collapsed, name]);
    }
}

/**
 * @param {RosterContact} contact
 * @param {string} groupname
 * @returns {boolean}
 */
export function isContactFiltered (contact, groupname) {
    const filter = _converse.state.roster_filter;
    const type = filter.get('type');
    const q = (type === 'state') ?
        filter.get('state').toLowerCase() :
        filter.get('text').toLowerCase();

    if (!q) return false;

    if (type === 'state') {
        const sticky_groups = [_converse.labels.HEADER_REQUESTING_CONTACTS, _converse.labels.HEADER_UNREAD];
        if (sticky_groups.includes(groupname)) {
            // When filtering by chat state, we still want to
            // show sticky groups, even though they don't
            // match the state in question.
            return false;
        } else if (q === 'unread_messages') {
            return contact.get('num_unread') === 0;
        } else if (q === 'online') {
            return ["offline", "unavailable", "dnd", "away", "xa"].includes(contact.presence.get('show'));
        } else {
            return !contact.presence.get('show').includes(q);
        }
    } else if (type === 'items')  {
        return !contact.getFilterCriteria().includes(q);
    }
}

/**
 * @param {RosterContact} contact
 * @param {string} groupname
 * @param {Model} model
 * @returns {boolean}
 */
export function shouldShowContact (contact, groupname, model) {
    if (!model.get('filter_visible')) return true;

    const chat_status = contact.presence.get('show');
    if (api.settings.get('hide_offline_users') && chat_status === 'offline') {
        // If pending or requesting, show
        if ((contact.get('ask') === 'subscribe') ||
                (contact.get('subscription') === 'from') ||
                (contact.get('requesting') === true)) {
            return !isContactFiltered(contact, groupname);
        }
        return false;
    }
    return !isContactFiltered(contact, groupname);
}

export function shouldShowGroup (group, model) {
    if (!model.get('filter_visible')) return true;

    const filter = _converse.state.roster_filter;
    const type = filter.get('type');
    if (type === 'groups') {
        const q = filter.get('text')?.toLowerCase();
        if (!q) {
            return true;
        }
        if (!group.toLowerCase().includes(q)) {
            return false;
        }
    }
    return true;
}

export function populateContactsMap (contacts_map, contact) {
    if (contact.get('requesting')) {
        const name = /** @type {string} */(_converse.labels.HEADER_REQUESTING_CONTACTS);
        contacts_map[name] ? contacts_map[name].push(contact) : (contacts_map[name] = [contact]);
    } else {
        let contact_groups;
        if (api.settings.get('roster_groups')) {
            contact_groups = contact.get('groups');
            contact_groups = (contact_groups.length === 0) ? [_converse.labels.HEADER_UNGROUPED] : contact_groups;
        } else {
            if (contact.get('ask') === 'subscribe') {
                contact_groups = [_converse.labels.HEADER_PENDING_CONTACTS];
            } else {
                contact_groups = [_converse.labels.HEADER_CURRENT_CONTACTS];
            }
        }
        for (const name of contact_groups) {
            contacts_map[name] ? contacts_map[name].push(contact) : (contacts_map[name] = [contact]);
        }
    }
    if (contact.get('num_unread')) {
        const name = /** @type {string} */(_converse.labels.HEADER_UNREAD);
        contacts_map[name] ? contacts_map[name].push(contact) : (contacts_map[name] = [contact]);
    }
    return contacts_map;
}

export function contactsComparator (contact1, contact2) {
    const status1 = contact1.presence.get('show') || 'offline';
    const status2 = contact2.presence.get('show') || 'offline';
    if (STATUS_WEIGHTS[status1] === STATUS_WEIGHTS[status2]) {
        const name1 = (contact1.getDisplayName()).toLowerCase();
        const name2 = (contact2.getDisplayName()).toLowerCase();
        return name1 < name2 ? -1 : (name1 > name2? 1 : 0);
    } else  {
        return STATUS_WEIGHTS[status1] < STATUS_WEIGHTS[status2] ? -1 : 1;
    }
}

export function groupsComparator (a, b) {
    const HEADER_WEIGHTS = {};
    const {
        HEADER_UNREAD,
        HEADER_REQUESTING_CONTACTS,
        HEADER_CURRENT_CONTACTS,
        HEADER_UNGROUPED,
        HEADER_PENDING_CONTACTS,
    } = _converse.labels;

    HEADER_WEIGHTS[HEADER_UNREAD] = 0;
    HEADER_WEIGHTS[HEADER_REQUESTING_CONTACTS] = 1;
    HEADER_WEIGHTS[HEADER_CURRENT_CONTACTS]    = 2;
    HEADER_WEIGHTS[HEADER_UNGROUPED]           = 3;
    HEADER_WEIGHTS[HEADER_PENDING_CONTACTS]    = 4;

    const WEIGHTS =  HEADER_WEIGHTS;
    const special_groups = Object.keys(HEADER_WEIGHTS);
    const a_is_special = special_groups.includes(a);
    const b_is_special = special_groups.includes(b);
    if (!a_is_special && !b_is_special ) {
        return a.toLowerCase() < b.toLowerCase() ? -1 : (a.toLowerCase() > b.toLowerCase() ? 1 : 0);
    } else if (a_is_special && b_is_special) {
        return WEIGHTS[a] < WEIGHTS[b] ? -1 : (WEIGHTS[a] > WEIGHTS[b] ? 1 : 0);
    } else if (!a_is_special && b_is_special) {
        const a_header = HEADER_CURRENT_CONTACTS;
        return WEIGHTS[a_header] < WEIGHTS[b] ? -1 : (WEIGHTS[a_header] > WEIGHTS[b] ? 1 : 0);
    } else if (a_is_special && !b_is_special) {
        const b_header = HEADER_CURRENT_CONTACTS;
        return WEIGHTS[a] < WEIGHTS[b_header] ? -1 : (WEIGHTS[a] > WEIGHTS[b_header] ? 1 : 0);
    }
}

export function getGroupsAutoCompleteList () {
    const roster = /** @type {RosterContacts} */(_converse.state.roster);
    const groups = roster.reduce((groups, contact) => groups.concat(contact.get('groups')), []);
    return [...new Set(groups.filter(i => i))];
}

export function getJIDsAutoCompleteList () {
    const roster = /** @type {RosterContacts} */(_converse.state.roster);
    return [...new Set(roster.map(item => Strophe.getDomainFromJid(item.get('jid'))))];
}

/**
 * @param {string} query
 */
export async function getNamesAutoCompleteList (query) {
    const options = {
        'mode': /** @type {RequestMode} */('cors'),
        'headers': {
            'Accept': 'text/json'
        }
    };
    const url = `${api.settings.get('xhr_user_search_url')}q=${encodeURIComponent(query)}`;
    let response;
    try {
        response = await fetch(url, options);
    } catch (e) {
        log.error(`Failed to fetch names for query "${query}"`);
        log.error(e);
        return [];
    }

    const json = response.json;
    if (!Array.isArray(json)) {
        log.error(`Invalid JSON returned"`);
        return [];
    }
    return json.map(i => ({'label': i.fullname || i.jid, 'value': i.jid}));
}
