import {Component, HostListener, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {EditorNodeContract} from '../EditorNode.contract';
import {EditorService} from '../../../service/editor.service';
import {FlitterSocketConnection, FlitterSocketServerClientTransaction} from '../../../flitter-socket';
import {ApiService} from '../../../service/api.service';
import {debug} from '../../../utility';
import {EditingUserSelect, MutationBroadcast, WysiwygComponent} from '../../wysiwyg/wysiwyg.component';

@Component({
    selector: 'editor-norm',
    templateUrl: './norm.component.html',
    styleUrls: ['./norm.component.scss'],
})
export class NormComponent extends EditorNodeContract implements OnInit, OnDestroy {
    @ViewChild('editable') editable;
    @ViewChild('wysiwygComponent') wysiwygComponent: WysiwygComponent;
    @Input() nodeId: string;
    @Input() editorUUID?: string;

    public initialValue = 'Double-click to edit...';
    protected savedValue = 'Double-click to edit...';
    public contents = '';
    private dirtyOverride = false;
    private editorGroupSocket?: FlitterSocketConnection;

    public editorGroupUsers: Array<{uuid: string, uid: string, display: string, color: string}> = [];
    public editorGroupId?: string;
    public editingUserSelections: Array<EditingUserSelect> = [];

    public requestSelectionRefresh = () => this.refreshRemoteSelections();

    constructor(
        public editorService: EditorService,
        public readonly api: ApiService,
    ) {
        super();
        this.contents = this.initialValue;
        this.savedValue = this.initialValue;

        console.log('Norm editor component', this);
    }

    public isDark() {
        return document.body.classList.contains('dark');
    }

    public isDirty(): boolean | Promise<boolean> {
        return this.dirtyOverride || this.contents !== this.savedValue;
    }

    public get isReadonly(): boolean {
        return !this.editorService.canEdit();
    }

    public writeChangesToNode(): void | Promise<void> {
        this.nodeRec.value = this.contents;
        this.savedValue = this.contents;
    }

    ngOnInit() {
        this.editorService = this.editorService.getEditor(this.editorUUID);
        this.editorService.registerNodeEditor(this.nodeId, this).then(() => {
            if ( !this.node.Value ) {
                this.node.Value = {};
            }

            if ( this.node.Value.Value ) {
                this.initialValue = this.node.Value.Value;
                this.savedValue = this.node.Value.Value;
            }

            this.contents = this.initialValue;
        });
    }

    ngOnDestroy() {
        debug('ngOnDestroy in Norm editor component');
        if ( this.editorGroupSocket && this.editorGroupId ) {
            debug('Closing editor socket...');
            this.editorGroupSocket.socket.close();
        }
    }

    onContentsChanged(contents: string) {
        if ( this.contents !== contents ) {
            this.contents = contents;
            this.editorService.triggerSave();
        }
    }

    public needsLoad(): boolean | Promise<boolean> {
        return true;
    }

    public async performLoad(): Promise<void> {
        // This is called after the Node record has been loaded.
        return;

        // FIXME need to find a consistent way of doing this on prod/development
        // FIXME Probably make use of the systemBase, but allow overriding it in the environment
        // const url = this.api._build_url('socket/norm-editor');
        const url = 'ws://localhost:8000/api/v1/socket/norm-editor';
        debug(`Norm editor socket URL: ${url}`);

        const socket = new FlitterSocketConnection(url);
        socket.controller(this);

        await socket.on_open();
        debug('Connected to norm editor socket', socket);

        const [transaction2, socket2, { editor_group_id }] = await socket.asyncRequest('join_editor_group', {
            NodeId: this.node.UUID,
            PageId: this.page.UUID,
        });

        this.editorGroupSocket = socket;

        const [transaction3, socket3, users = []] = await socket.asyncRequest('get_editor_group_users', { editor_group_id });
        if ( Array.isArray(users) ) {
            this.editorGroupUsers = users;
        }

        this.editorGroupId = editor_group_id;
        await this.refreshRemoteSelections();
    }

    setEditorGroupUsers(transaction: FlitterSocketServerClientTransaction, socket: any) {
        if ( Array.isArray(transaction?.incoming?.users) ) {
            this.editorGroupUsers = transaction.incoming.users;
            debug('Refreshed norm editor group users.');
            transaction.resolve();
        }
    }

    setEditorGroupSelections(transaction: FlitterSocketServerClientTransaction, socket: any) {
        if ( Array.isArray(transaction?.incoming?.selections) ) {
            debug('Got selections', transaction.incoming.selections);
            this.editingUserSelections = transaction.incoming.selections;
            transaction.resolve();
        }
    }

    applyRemoteContentMutation(transaction: FlitterSocketServerClientTransaction, socket: any) {
        if ( this.wysiwygComponent && transaction?.incoming?.mutation ) {
            this.wysiwygComponent.applyRemoteContentMutation(transaction.incoming.mutation);
        }
    }

    async onSelectionChanged(selection: { path: string, offset: number }) {
        if ( this.editorGroupSocket && this.editorGroupId ) {
            await this.editorGroupSocket.asyncRequest('set_member_selection', {
                editor_group_id: this.editorGroupId,
                selection,
            });
        }
    }

    async onContentsMutated(data: MutationBroadcast) {
        if ( this.editorGroupSocket && this.editorGroupId ) {
            await this.editorGroupSocket.asyncRequest('broadcast_content_mutation', {
                editor_group_id: this.editorGroupId,
                data,
            });
        }
    }

    async refreshRemoteSelections() {
        if ( this.editorGroupSocket && this.editorGroupId ) {
            const [
                transaction, _, data
            ] = await this.editorGroupSocket.asyncRequest('get_selections', {
                editor_group_id: this.editorGroupId,
            });

            if ( Array.isArray(data?.selections) ) {
                this.editingUserSelections = data.selections;
            }
        }
    }
}
