import { Connector } from './connector';

import { AblyChannel, AblyPrivateChannel, AblyPresenceChannel, AblyAuth } from './../channel';
import { AblyRealtime, TokenDetails } from '../../typings/ably';
import { toBase64UrlEncoded } from '../channel/ably/utils';

/**
 * This class creates a connector to Ably.
 */
export class AblyConnector extends Connector {

    /**
     * The laravel-echo library version.
     */
    static LIB_VERSION = '1.0.6';
    
    /**
     * The Ably instance.
     */
    ably: AblyRealtime;

    /**
     * All of the subscribed channel names.
     */
    channels: Record<string, AblyChannel> = {};

    /**
     * Auth instance containing all explicit channel authz ops.
     */
    ablyAuth: AblyAuth;

    /**
     * Create a fresh Ably connection.
     */
    connect(): void {
        if (typeof this.options.client !== 'undefined') {
            this.ably = this.options.client;
        } else {
            this.ablyAuth = new AblyAuth(this, this.options);
            if (!this.options.agents) {
                this.options.agents = {};
            }
            this.options.agents['laravel-echo'] = AblyConnector.LIB_VERSION;
            this.ably = new Ably.Realtime({ ...this.ablyAuth.options, ...this.options });
            this.ablyAuth.enableAuthorizeBeforeChannelAttach();
            this.ablyAuth.allowReconnectOnUserLogin()
        }
    }

    /**
     * Listen for an event on a channel instance.
     */
    listen(name: string, event: string, callback: Function): AblyChannel {
        return this.channel(name).listen(event, callback);
    }

    /**
     * Get a channel instance by name.
     */
    channel(name: string): AblyChannel {
        const prefixedName = `public:${name}`; // adding public as a ably namespace prefix
        if (!this.channels[prefixedName]) {
            this.channels[prefixedName] = new AblyChannel(this.ably, prefixedName, this.options);
        }

        return this.channels[prefixedName];
    }

    /**
     * Get a private channel instance by name.
     */
    privateChannel(name: string): AblyPrivateChannel {
        const prefixedName = `private:${name}`; // adding private as a ably namespace prefix
        if (!this.channels[prefixedName]) {
            this.channels[prefixedName] = new AblyPrivateChannel(this.ably, prefixedName, this.options, this.ablyAuth);
        }

        return this.channels[prefixedName] as AblyPrivateChannel;
    }

    /**
     * Get a presence channel instance by name.
     */
    presenceChannel(name: string): AblyPresenceChannel {
        const prefixedName = `presence:${name}`; // adding presence as a ably namespace prefix
        if (!this.channels[prefixedName]) {
            this.channels[prefixedName] = new AblyPresenceChannel(this.ably, prefixedName, this.options, this.ablyAuth);
        }

        return this.channels[prefixedName] as AblyPresenceChannel;
    }

    /**
     * Leave the given channel, as well as its private and presence variants.
     */
    leave(name: string): void {
        let channels = [`public:${name}`, `private:${name}`, `presence:${name}`];

        channels.forEach((name: string, index: number) => {
            this.leaveChannel(name);
        });
    }

    /**
     * Leave the given channel.
     */
    leaveChannel(name: string): void {
        if (name.indexOf('public:') !== 0 && name.indexOf('private:') !== 0 && name.indexOf('presence:') !== 0) {
            throw new Error(
                `Error leaving ${name}, name should be prefixed with either "public:", "private:" or "presence:"`
            );
        }
        if (this.channels[name]) {
            this.channels[name].unsubscribe();
            this.ablyAuth.setExpired(name);
            delete this.channels[name];
        }
    }

    /**
     * Get the socket ID for the connection.
     * For ably, returns base64 url encoded json with keys {connectionKey, clientId}
     */
    socketId(): string {
        let socketIdObject = {
            connectionKey : this.ably.connection.key,
            clientId : this.ably.auth.clientId ?? null,
        }
        return toBase64UrlEncoded(JSON.stringify(socketIdObject));
    }

    /**
     * Disconnect Ably connection.
     */
    disconnect(): void {
        this.ably.close();
    }
}
