import _converse from '../../shared/_converse.js';
import api from '../../shared/api/index.js';
import converse from '../../shared/api/public.js';
import log from '@converse/log';
import { MUC_ROLE_WEIGHTS } from './constants.js';
import { safeSave } from '../../utils/init.js';
import { CHATROOMS_TYPE } from '../../shared/constants.js';
import { getUnloadEvent } from '../../utils/session.js';

const { Strophe, sizzle, u } = converse.env;

/**
 * @returns {Promise<string|undefined>}
 */
export async function getDefaultMUCService() {
    let muc_service = api.settings.get('muc_domain') || _converse.session.get('default_muc_service');
    if (!muc_service) {
        const domain = _converse.session.get('domain');
        const items = await api.disco.entities.items(domain);
        for (const item of items) {
            if (await api.disco.features.has(Strophe.NS.MUC, item.get('jid'))) {
                muc_service = item.get('jid');
                _converse.session.save({ default_muc_service: muc_service });
                break;
            }
        }
    }
    return muc_service;
}

/**
 * @param {import('@converse/skeletor').Model} model
 */
export function isChatRoom(model) {
    return model?.get('type') === 'chatroom';
}

export function shouldCreateGroupchatMessage(attrs) {
    return attrs.nick && (u.shouldCreateMessage(attrs) || attrs.is_tombstone);
}

/**
 * @param {import('./occupant').default} occupant1
 * @param {import('./occupant').default} occupant2
 */
export function occupantsComparator(occupant1, occupant2) {
    const role1 = occupant1.get('role') || 'none';
    const role2 = occupant2.get('role') || 'none';
    if (MUC_ROLE_WEIGHTS[role1] === MUC_ROLE_WEIGHTS[role2]) {
        const nick1 = occupant1.getDisplayName().toLowerCase();
        const nick2 = occupant2.getDisplayName().toLowerCase();
        return nick1 < nick2 ? -1 : nick1 > nick2 ? 1 : 0;
    } else {
        return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1;
    }
}

export function registerDirectInvitationHandler() {
    api.connection.get().addHandler(
        /** @param {Element} message */
        (message) => {
            _converse.exports.onDirectMUCInvitation(message);
            return true;
        },
        'jabber:x:conference',
        'message'
    );
}

export function disconnectChatRooms() {
    /* When disconnecting, mark all groupchats as
     * disconnected, so that they will be properly entered again
     * when fetched from session storage.
     */
    return _converse.state.chatboxes
        .filter((m) => m.get('type') === CHATROOMS_TYPE)
        .forEach((m) => m.session.save({ 'connection_status': converse.ROOMSTATUS.DISCONNECTED }));
}

export async function onWindowStateChanged() {
    if (!document.hidden && api.connection.connected()) {
        const rooms = await api.rooms.get();
        rooms.forEach((room) => room.rejoinIfNecessary());
    }
}

/**
 * @param {Event} [event]
 */
export async function routeToRoom(event) {
    if (!location.hash.startsWith('#converse/room?jid=')) {
        return;
    }
    event?.preventDefault();
    const jid = location.hash.split('=').pop();
    if (!u.isValidMUCJID(jid)) {
        return log.warn(`invalid jid "${jid}" provided in url fragment`);
    }
    await api.waitUntil('roomsAutoJoined');
    if (api.settings.get('allow_bookmarks')) {
        await api.waitUntil('bookmarksInitialized');
    }
    api.rooms.open(jid, {}, true);
}

/**
 * Opens a groupchat, making sure that certain attributes
 * are correct, for example that the "type" is set to
 * "chatroom".
 * @param {string} jid
 * @param {Object} settings
 */
export async function openChatRoom(jid, settings) {
    settings.type = CHATROOMS_TYPE;
    settings.id = jid;
    const chatbox = await api.rooms.get(jid, settings, true);
    chatbox.maybeShow(true);
    return chatbox;
}

/**
 * A direct MUC invitation to join a groupchat has been received
 * See XEP-0249: Direct MUC invitations.
 * @method _converse.ChatRoom#onDirectMUCInvitation
 * @param {Element} message - The message stanza containing the invitation.
 */
export async function onDirectMUCInvitation(message) {
    const x_el = sizzle('x[xmlns="jabber:x:conference"]', message).pop(),
        from = Strophe.getBareJidFromJid(message.getAttribute('from')),
        room_jid = x_el.getAttribute('jid'),
        reason = x_el.getAttribute('reason');

    let result;
    if (api.settings.get('auto_join_on_invite')) {
        result = true;
    } else {
        // Invite request might come from someone not your roster list
        const contact = _converse.state.roster.get(from)?.getDisplayName() ?? from;

        /**
         * *Hook* which is used to gather confirmation whether a direct MUC
         * invitation should be accepted or not.
         *
         * It's meant for consumers of `@converse/headless` to subscribe to
         * this hook and then ask the user to confirm.
         *
         * @event _converse#confirmDirectMUCInvitation
         */
        result = await api.hook('confirmDirectMUCInvitation', { contact, reason, jid: room_jid }, false);
    }

    if (result) {
        const chatroom = await openChatRoom(room_jid, { password: x_el.getAttribute('password') });
        if (chatroom.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) {
            _converse.state.chatboxes.get(room_jid).rejoin();
        }
    }
}

export function getDefaultMUCNickname() {
    // XXX: if anything changes here, update the docs for the
    // locked_muc_nickname setting.
    const { profile } = _converse.state;
    if (!profile) {
        log.error('Called getDefaultMUCNickname before statusInitialized has been fired.');
        return '';
    }
    const nick = profile.getNickname();
    if (nick) {
        return nick;
    } else if (api.settings.get('muc_nickname_from_jid')) {
        const bare_jid = _converse.session.get('bare_jid');
        return Strophe.unescapeNode(Strophe.getNodeFromJid(bare_jid));
    }
}

/**
 * Determines info message visibility based on
 * muc_show_info_messages configuration setting
 * @param {import('./types').MUCStatusCode} code
 * @memberOf _converse
 */
export function isInfoVisible(code) {
    const info_messages = api.settings.get('muc_show_info_messages');
    if (info_messages.includes(code)) {
        return true;
    }
    return false;
}

/**
 * Automatically join groupchats, based on the
 * "auto_join_rooms" configuration setting, which is an array
 * of strings (groupchat JIDs) or objects (with groupchat JID and other settings).
 */
export async function autoJoinRooms() {
    await Promise.all(
        api.settings.get('auto_join_rooms').map((muc) => {
            if (typeof muc === 'string') {
                if (_converse.state.chatboxes.where({ 'jid': muc }).length) {
                    return Promise.resolve();
                }
                return api.rooms.open(muc);
            } else if (muc instanceof Object) {
                return api.rooms.open(muc.jid, { ...muc });
            } else {
                log.error('Invalid muc criteria specified for "auto_join_rooms"');
                return Promise.resolve();
            }
        })
    );
    /**
     * Triggered once any rooms that have been configured to be automatically joined,
     * specified via the _`auto_join_rooms` setting, have been entered.
     * @event _converse#roomsAutoJoined
     * @example _converse.api.listen.on('roomsAutoJoined', () => { ... });
     * @example _converse.api.waitUntil('roomsAutoJoined').then(() => { ... });
     */
    api.trigger('roomsAutoJoined');
}

export function onAddClientFeatures() {
    api.disco.own.features.add(Strophe.NS.MUC);

    if (api.settings.get('allow_muc_invitations')) {
        api.disco.own.features.add('jabber:x:conference'); // Invites
    }
}

export function onBeforeTearDown() {
    _converse.state.chatboxes
        .where({ 'type': CHATROOMS_TYPE })
        .forEach((muc) => safeSave(muc.session, { 'connection_status': converse.ROOMSTATUS.DISCONNECTED }));
}

export function onStatusInitialized() {
    window.addEventListener(getUnloadEvent(), () => {
        const using_websocket = api.connection.isType('websocket');
        if (using_websocket && (!api.settings.get('enable_smacks') || !_converse.session.get('smacks_stream_id'))) {
            // For non-SMACKS websocket connections, or non-resumeable
            // connections, we disconnect all chatrooms when the page unloads.
            // See issue #1111
            disconnectChatRooms();
        }
    });
}

export function onBeforeResourceBinding() {
    api.connection.get().addHandler(
        /** @param {Element} stanza */
        (stanza) => {
            const muc_jid = Strophe.getBareJidFromJid(stanza.getAttribute('from'));
            if (!_converse.state.chatboxes.get(muc_jid)) {
                api.waitUntil('chatBoxesFetched').then(async () => {
                    const muc = _converse.state.chatboxes.get(muc_jid);
                    if (muc) {
                        await muc.initialized;
                        muc.message_handler.run(stanza);
                    }
                });
            }
            return true;
        },
        null,
        'message',
        'groupchat'
    );
}
