import {Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core';
import {v4} from 'uuid';
import {ApiService, ResourceNotAvailableOfflineError} from '../../../service/api.service';
import {EditorNodeContract} from '../../nodes/EditorNode.contract';
import {EditorService} from '../../../service/editor.service';
import {EditorComponent} from 'ngx-monaco-editor';

@Component({
    selector: 'editor-code',
    templateUrl: './code.component.html',
    styleUrls: ['./code.component.scss'],
})
export class CodeComponent extends EditorNodeContract implements OnInit {
    @Input() nodeId: string;
    @Input() editorUUID?: string;
    @ViewChild('theEditor') theEditor: EditorComponent;
    @ViewChild('editorContainer') editorContainer: ElementRef;
    public dirty = false;
    protected dbRecord: any = {};
    protected codeRefId!: string;
    public notAvailableOffline = false;
    public containerHeight = 540;

    public editorOptions = {
        theme: this.isDark() ? 'vs-dark' : 'vs',
        language: 'javascript',
        uri: v4(),
        readOnly: false,
        automaticLayout: true,
        scrollBeyondLastLine: false,
        scrollbar: {
            alwaysConsumeMouseWheel: false,
        },
    };

    public editorValue = '';
    public get readonly() {
        return !this.node || !this.editorService.canEdit();
    }

    public languageOptions: Array<string> = [
        'ABAP',
        'AES',
        'Apex',
        'AZCLI',
        'Bat',
        'C',
        'Cameligo',
        'Clojure',
        'CoffeeScript',
        'Cpp',
        'Csharp',
        'CSP',
        'CSS',
        'Dockerfile',
        'Fsharp',
        'Go',
        'GraphQL',
        'Handlebars',
        'HTML',
        'INI',
        'Java',
        'JavaScript',
        'JSON',
        'Kotlin',
        'LeSS',
        'Lua',
        'Markdown',
        'MiPS',
        'MSDAX',
        'MySQL',
        'Objective-C',
        'Pascal',
        'Pascaligo',
        'Perl',
        'pgSQL',
        'PHP',
        'Plaintext',
        'Postiats',
        'PowerQuery',
        'PowerShell',
        'Pug',
        'Python',
        'R',
        'Razor',
        'Redis',
        'RedShift',
        'RestructuredText',
        'Ruby',
        'Rust',
        'SB',
        'Scheme',
        'SCSS',
        'Shell',
        'SOL',
        'SQL',
        'St',
        'Swift',
        'TCL',
        'Twig',
        'TypeScript',
        'VB',
        'XML',
        'YAML',
    ];
    protected hadLoad = false;

    constructor(
        public editorService: EditorService,
        public readonly api: ApiService,
    ) { super(); }

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

    public isDirty(): boolean | Promise<boolean> {
        return this.dirty;
    }

    public needsSave(): boolean | Promise<boolean> {
        return this.dirty;
    }

    public writeChangesToNode(): void | Promise<void> {
        this.node.Value.Mode = 'code';
        this.node.Value.Value = this.codeRefId;
        this.node.value = this.codeRefId;
    }

    public needsLoad(): boolean | Promise<boolean> {
        return this.node && !this.hadLoad;
    }

    public performLoad(): void | Promise<void> {
        return new Promise((res, rej) => {
            if ( !this.node.Value ) {
                this.node.Value = {};
            }

            if ( !this.node.Value.Value && this.editorService.canEdit() ) {
                this.api.createCodium(this.page.UUID, this.node.UUID).then(data => {
                    this.dbRecord = data;
                    this.node.Value.Mode = 'code';
                    this.node.Value.Value = data.UUID;
                    this.node.value = data.UUID;
                    this.codeRefId = data.UUID;
                    this.editorOptions.readOnly = this.readonly;
                    this.onSelectChange(false);
                    this.hadLoad = true;
                    this.notAvailableOffline = false;
                    res();
                }).catch(rej);
            } else {
                this.api.getCodium(this.page.UUID, this.node.UUID, this.node.Value.Value).then(data => {
                    this.dbRecord = data;
                    this.initialValue = this.dbRecord.code;
                    this.editorValue = this.dbRecord.code;
                    this.editorOptions.language = this.dbRecord.Language;
                    this.codeRefId = this.node.Value.Value;
                    this.editorOptions.readOnly = this.readonly;
                    this.onSelectChange(false);
                    this.hadLoad = true;
                    this.notAvailableOffline = false;
                    res();
                }).catch(e => {
                    if ( e instanceof ResourceNotAvailableOfflineError ) {
                        this.notAvailableOffline = true;
                    } else {
                        rej(e);
                    }
                });
            }
        });
    }

    public performSave(): void | Promise<void> {
        if ( !this.editorService.canEdit() ) {
            return;
        }

        return new Promise((res, rej) => {
            this.dbRecord.code = this.editorValue;
            this.dbRecord.Language = this.editorOptions.language;

            this.api.saveCodium(this.page.UUID, this.node.UUID, this.node.Value.Value, this.dbRecord).then(data => {
                this.dbRecord = data;
                this.editorOptions.language = this.dbRecord.Language;
                this.editorValue = this.dbRecord.code;
                this.dirty = false;
                res();
            }).catch(rej);
        });
    }

    public performDelete(): void | Promise<void> {
        return this.api.deleteCodium(this.page.UUID, this.node.UUID, this.node.Value.Value);
    }

    ngOnInit() {
        this.editorService = this.editorService.getEditor(this.editorUUID);
        this.editorService.registerNodeEditor(this.nodeId, this).then(() => {
            this.editorOptions.readOnly = !this.editorService.canEdit();
        });
    }

    onMonacoEditorInit(editor) {
        let ignoreEvent = false;
        const updateHeight = () => {
            const contentHeight = Math.max(540, editor.getContentHeight());
            this.containerHeight = contentHeight;

            try {
                ignoreEvent = true;
                editor.layout({ width: this.editorContainer.nativeElement.offsetWidth, height: contentHeight });
            } finally {
                ignoreEvent = false;
            }
        };

        editor.onDidContentSizeChange(updateHeight);
        updateHeight();
    }

    public onEditorModelChange($event) {
        if ( this.editorValue !== this.dbRecord.code ) {
            this.dirty = true;
            this.editorService.triggerSave();
        }
    }

    public onSelectChange(updateDbRecord = true) {
        if ( updateDbRecord ) {
            this.dbRecord.Language = this.editorOptions.language;
            this.editorService.triggerSave();
            this.dirty = true;
        }

        this.editorOptions = {...this.editorOptions};
    }
}
