import {LitElement} from 'lit';

/**
 * Simplified Lit version of channelstream-connection
 * Replaces iron-ajax with native fetch()
 */
export class ChannelstreamConnection extends LitElement {
    static properties = {
        channels: {type: Array},
        username: {type: String},
        connectionId: {type: String},
        websocket: {type: Object},
        websocketUrl: {type: String},
        connectUrl: {type: String},
        disconnectUrl: {type: String},
        subscribeUrl: {type: String},
        unsubscribeUrl: {type: String},
        messageUrl: {type: String},
        longPollUrl: {type: String},
        shouldReconnect: {type: Boolean},
        heartbeats: {type: Boolean},
        increaseBounceIv: {type: Number},
        _currentBounceIv: {type: Number},
        useWebsocket: {type: Boolean},
        connected: {type: Boolean},
        channelsState: {type: Object},
        userState: {type: Object}
    };

    constructor() {
        super();
        this.channels = [];
        this.username = 'Anonymous';
        this.connectionId = '';
        this.websocket = null;
        this.websocketUrl = '';
        this.connectUrl = '';
        this.disconnectUrl = '';
        this.subscribeUrl = '';
        this.unsubscribeUrl = '';
        this.messageUrl = '';
        this.longPollUrl = '';
        this.shouldReconnect = true;
        this.heartbeats = true;
        this.increaseBounceIv = 2000;
        this._currentBounceIv = 0;
        this.useWebsocket = true;
        this.connected = false;
        this.channelsState = {};
        this.userState = {};

        // Mutators - simplified to just arrays
        this.mutators = {
            connect: [],
            message: [],
            subscribe: [],
            unsubscribe: [],
            disconnect: []
        };

        this._heartbeatInterval = null;
    }

    // No render needed - this component has no template
    render() {
        return null;
    }

    updated(changedProperties) {
        if (changedProperties.has('channels')) {
            this.dispatchEvent(new CustomEvent('channelstream-channels-changed', {
                detail: {value: this.channels}
            }));
        }
    }

    connectedCallback() {
        super.connectedCallback();

        // Set up self-event listeners (replaces Lit's 'listeners' property)
        this.addEventListener('channelstream-connected', this.startListening.bind(this));
        this.addEventListener('channelstream-connect-error', this.retryConnection.bind(this));
    }

    /**
     * Helper method for fetch requests
     */
    async _fetchJSON(url, options = {}) {
        const {body, headers, ...fetchOptions} = options;

        const response = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                ...headers
            },
            body: body ? JSON.stringify(body) : undefined,
            ...fetchOptions
        });

        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        return response.json();
    }

    /**
     * Connects user and fetches connection id from the server
     */
    async connect() {
        const body = {
            username: this.username,
            channels: this.channels
        };

        // Apply mutators
        for (let mutator of this.mutators.connect) {
            mutator({body});
        }

        try {
            const response = await this._fetchJSON(this.connectUrl, {body});
            this._handleConnect({detail: {response}});
        } catch (error) {
            this._handleConnectError({detail: {error}});
        }
    }

    /**
     * Subscribes user to channels
     */
    async subscribe(channels) {
        const body = {
            channels: channels,
            conn_id: this.connectionId
        };

        // Apply mutators
        for (let mutator of this.mutators.subscribe) {
            mutator({body});
        }

        if (body.channels.length) {
            try {
                const response = await this._fetchJSON(this.subscribeUrl, {body});
                this._handleSubscribe({detail: {response}});
            } catch (error) {
                this.dispatchEvent(new CustomEvent('channelstream-subscribe-error', {detail: error}));
            }
        }
    }

    /**
     * Unsubscribes user from channels
     */
    async unsubscribe(channels) {
        const body = {
            channels: channels,
            conn_id: this.connectionId
        };

        // Apply mutators
        for (let mutator of this.mutators.unsubscribe) {
            mutator({body});
        }

        try {
            const response = await this._fetchJSON(this.unsubscribeUrl, {body});
            this._handleUnsubscribe({detail: {response}});
        } catch (error) {
            this.dispatchEvent(new CustomEvent('channelstream-unsubscribe-error', {detail: error}));
        }
    }

    /**
     * Calculates list of channels to add user to
     */
    calculateSubscribe(channels) {
        const currentlySubscribed = this.channels;
        const toSubscribe = [];
        for (let i = 0; i < channels.length; i++) {
            if (currentlySubscribed.indexOf(channels[i]) === -1) {
                toSubscribe.push(channels[i]);
            }
        }
        return toSubscribe;
    }

    /**
     * Calculates list of channels to remove user from
     */
    calculateUnsubscribe(channels) {
        const currentlySubscribed = this.channels;
        const toUnsubscribe = [];
        for (let i = 0; i < channels.length; i++) {
            if (currentlySubscribed.indexOf(channels[i]) !== -1) {
                toUnsubscribe.push(channels[i]);
            }
        }
        return toUnsubscribe;
    }

    /**
     * Marks the connection as expired
     */
    async disconnect() {
        const params = new URLSearchParams({conn_id: this.connectionId});

        // Apply mutators
        for (let mutator of this.mutators.disconnect) {
            mutator({params});
        }

        try {
            const response = await fetch(`${this.disconnectUrl}?${params}`);
            const data = await response.json();
            this._handleDisconnect({detail: {response: data}});
        } catch (error) {
        }

        // Disconnect existing connection
        this.closeConnection();
    }

    /**
     * Sends a message to the server
     */
    async message(messageData) {
        const body = messageData;

        // Apply mutators
        for (let mutator of this.mutators.message) {
            mutator({body});
        }

        try {
            const response = await this._fetchJSON(this.messageUrl, {body});
            this._handleMessage({detail: {response}});
        } catch (error) {
            this._handleMessageError({detail: {error}});
        }
    }

    /**
     * Opens long lived connection (websocket/longpoll)
     */
    startListening(event) {
        this.dispatchEvent(new CustomEvent('start-listening', {detail: {}}));

        if (this.useWebsocket) {
            this.useWebsocket = window.WebSocket ? true : false;
        }

        if (this.useWebsocket) {
            this.openWebsocket();
        } else {
            this.openLongPoll();
        }
    }

    /**
     * Opens websocket connection
     */
    openWebsocket() {
        const url = this.websocketUrl + '?conn_id=' + this.connectionId;
        this.websocket = new WebSocket(url);
        this.websocket.onopen = this._handleListenOpen.bind(this);
        this.websocket.onclose = this._handleListenCloseEvent.bind(this);
        this.websocket.onerror = this._handleListenErrorEvent.bind(this);
        this.websocket.onmessage = this._handleListenMessageEvent.bind(this);
    }

    /**
     * Opens long-poll connection
     */
    async openLongPoll() {
        const url = this.longPollUrl + '?conn_id=' + this.connectionId;

        try {
            this._handleListenOpen();
            const response = await fetch(url);
            const text = await response.text();
            this._handleListenMessageEvent({data: text});
        } catch (error) {
            this._handleListenError({detail: {error}});
        }
    }

    /**
     * Retries connect() call with increasing interval
     */
    retryConnection() {
        if (!this.shouldReconnect) {
            return;
        }

        if (this._currentBounceIv < 60000) {
            this._currentBounceIv = this._currentBounceIv + this.increaseBounceIv;
        } else {
            this._currentBounceIv = 60000;
        }

        setTimeout(this.connect.bind(this), this._currentBounceIv);
    }

    /**
     * Closes listening connection
     */
    closeConnection() {
        if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
            this.websocket.onclose = null;
            this.websocket.onerror = null;
            this.websocket.close();
        }

        if (this._heartbeatInterval) {
            clearInterval(this._heartbeatInterval);
            this._heartbeatInterval = null;
        }

        this.connected = false;
    }

    /**
     * Add mutator function
     */
    addMutator(type, func) {
        this.mutators[type].push(func);
    }

    /**
     * Create heartbeat interval
     */
    createHeartBeats() {
        if (!this._heartbeatInterval && this.websocket !== null && this.heartbeats) {
            this._heartbeatInterval = setInterval(this._sendHeartBeat.bind(this), 10000);
        }
    }

    /**
     * Send heartbeat
     */
    _sendHeartBeat() {
        if (this.websocket && this.websocket.readyState === WebSocket.OPEN && this.heartbeats) {
            this.websocket.send(JSON.stringify({type: 'heartbeat'}));
        }
    }

    // Event handlers
    _handleListenOpen(event) {
        this.connected = true;
        this.dispatchEvent(new CustomEvent('channelstream-listen-opened', {detail: event}));
        this.createHeartBeats();
    }

    _handleListenError(event) {
        this.connected = false;
        this.retryConnection();
    }

    _handleConnectError(event) {
        this.connected = false;
        this.dispatchEvent(new CustomEvent('channelstream-connect-error', {detail: event.detail}));
    }

    _handleListenMessageEvent(event) {
        let data = null;

        // From long-poll (fetch)
        if (event.detail && event.detail.response) {
            data = JSON.parse(event.detail.response);
            setTimeout(this.openLongPoll.bind(this), 0);
        }
        // From websocket
        else if (event.data) {
            data = JSON.parse(event.data);
        }

        if (data) {
            this.dispatchEvent(new CustomEvent('channelstream-listen-message', {detail: data}));
        }
    }

    _handleListenCloseEvent(event) {
        this.connected = false;
        this.dispatchEvent(new CustomEvent('channelstream-listen-closed', {detail: event}));
        this.retryConnection();
    }

    _handleListenErrorEvent(event) {
        this.connected = false;
        this.dispatchEvent(new CustomEvent('channelstream-listen-error', {detail: {}}));
    }

    _handleConnect(event) {
        this._currentBounceIv = 0;
        this.connectionId = event.detail.response.conn_id;
        this.dispatchEvent(new CustomEvent('channelstream-connected', {detail: event.detail.response}));
    }

    _handleDisconnect(event) {
        this.connected = false;
        this.dispatchEvent(new CustomEvent('channelstream-disconnected', {detail: {}}));
    }

    _handleMessage(event) {
        this.dispatchEvent(new CustomEvent('channelstream-message-sent', {detail: event.detail.response}));
    }

    _handleMessageError(event) {
        this.dispatchEvent(new CustomEvent('channelstream-message-error', {detail: event.detail}));
    }

    _handleSubscribe(event) {
        this.dispatchEvent(new CustomEvent('channelstream-subscribed', {detail: event.detail.response}));
    }

    _handleUnsubscribe(event) {
        this.dispatchEvent(new CustomEvent('channelstream-unsubscribed', {detail: event.detail.response}));
    }

    // Polymer compatibility methods - keep these for rhodecode-app
    push(path, value) {
        if (path === 'channels') {
            this.channels = [...this.channels, value];
        }
    }

    set(path, value) {
        if (typeof path === 'string') {
            this[path] = value;
            this.requestUpdate(path);
        } else if (Array.isArray(path)) {
            // Handle nested paths like ['channelsState', key] or ['userState', key]
            if (path.length === 2 && path[0] === 'channelsState') {
                this.channelsState = {...this.channelsState, [path[1]]: value};
            } else if (path.length === 2 && path[0] === 'userState') {
                this.userState = {...this.userState, [path[1]]: value};
            }
        }
    }
}

customElements.define('channelstream-connection', ChannelstreamConnection);
