import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, OnInit, signal } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { RulesService } from '../../../services/rules.service';
import { firstValueFrom } from 'rxjs';
import { DOSSIER_TEMPLATE_ID, DroolsKeywords, IRules } from '@red/domain';
import { EditorThemeService } from '@services/editor-theme.service';
import { ComponentCanDeactivate } from '@guards/can-deactivate.guard';
import { Debounce, getParam } from '@iqser/common-ui/lib/utils';
import { ActivatedRoute } from '@angular/router';
import { rulesScreenTranslations } from '../../../translations/rules-screen-translations';
import ICodeEditor = monaco.editor.ICodeEditor;
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;

interface SyntaxError {
    line: number;
    column: number;
    message: string;
}

@Component({
    templateUrl: './rules-screen.component.html',
    styleUrls: ['./rules-screen.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
    readonly translations = rulesScreenTranslations;

    readonly iconButtonTypes = IconButtonTypes;
    readonly editorOptions: IStandaloneEditorConstructionOptions = {
        theme: 'vs',
        language: 'java',
        automaticLayout: true,
        readOnly: !this.permissionsService.canEditRules(),
        glyphMargin: true,
    };
    initialLines: string[] = [];
    currentLines: string[] = [];
    isLeaving = false;
    readonly type: IRules['ruleFileType'];
    private _codeEditor: ICodeEditor;
    private _decorations: string[] = [];
    readonly #errorGlyphs = signal<string[]>([]);
    readonly numberOfErrors = computed(() => this.#errorGlyphs().length);
    readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);

    set isLeavingPage(isLeaving: boolean) {
        this.isLeaving = isLeaving;
        this._changeDetectorRef.detectChanges();
    }

    get changed(): boolean {
        return this.currentLines.toString() !== this.initialLines.toString();
    }

    get codeEditorText() {
        return this.currentLines.join('\n');
    }

    set codeEditorText($event: any) {
        this.currentLines = $event.split('\n');
        this.codeEditorTextChanged();
        this._closeProblemsView();
    }

    constructor(
        readonly permissionsService: PermissionsService,
        private readonly _rulesService: RulesService,
        private readonly _changeDetectorRef: ChangeDetectorRef,
        private readonly _toaster: Toaster,
        private readonly _loadingService: LoadingService,
        private readonly _editorThemeService: EditorThemeService,
        private readonly _route: ActivatedRoute,
    ) {
        this.type = this._route.snapshot.data.type;
    }

    async ngOnInit() {
        await this._initialize();
    }

    async onCodeEditorInit(editor: ICodeEditor): Promise<void> {
        this._codeEditor = editor;
        for (const theme of this._editorThemeService.themes) {
            (window as any).monaco.editor.defineTheme(theme, this._editorThemeService.configurations[theme]);
        }
        (window as any).monaco.editor.setTheme(this._editorThemeService.getTheme(true));
        await this._configureSyntaxHighlighting();
        this._changeDetectorRef.detectChanges();
    }

    @Debounce()
    codeEditorTextChanged() {
        const newDecorations = this.currentLines.filter(entry => this._isNew(entry)).map(entry => this._makeDecorationFor(entry));
        this._decorations = this._codeEditor.deltaDecorations(this._decorations, newDecorations);
    }

    goToErrors() {
        this._codeEditor.trigger(null, 'editor.action.marker.next', null);
    }

    async save(): Promise<void> {
        this._loadingService.start();
        this._removeErrorMarkers();
        await firstValueFrom(
            this._rulesService.uploadRules({
                rules: this._getValue(),
                dossierTemplateId: this.#dossierTemplateId,
                ruleFileType: this.type,
            }),
        ).then(
            async () => {
                await this._initialize();
                this._toaster.success(rulesScreenTranslations[this.type]['success-generic']);
            },
            error => {
                const errors = error.error as SyntaxError[] | undefined;
                this._drawErrorMarkers(errors);
                this._loadingService.stop();
                this._toaster.success(rulesScreenTranslations[this.type]['error-generic']);
            },
        );
    }

    revert(): void {
        this.currentLines = this.initialLines;
        this._decorations = this._codeEditor?.deltaDecorations(this._decorations, []) || [];
        this._removeErrorMarkers();
        this._changeDetectorRef.detectChanges();
        this._loadingService.stop();
    }

    private _getValue(): string {
        this._codeEditor.getModel().setEOL(monaco.editor.EndOfLineSequence.LF);
        return this._codeEditor.getModel().getValue();
    }

    private async _configureSyntaxHighlighting() {
        const languages = (window as any).monaco.languages.getLanguages();
        const javaLang = languages.find(l => l.id === 'java');
        const { language: config } = await javaLang.loader();
        DroolsKeywords.forEach(k => {
            if (!config.keywords.includes(k)) {
                config.keywords.push(k);
            }
        });

        // Update tokenizer to match keywords with hyphens (originally /[a-zA-Z_$][\w$]*/)
        config.tokenizer.root[1][0] = /[a-zA-Z_$][\w$-]*/;

        monaco.languages.registerCompletionItemProvider('java', {
            provideCompletionItems: (model, position) => {
                const word = model.getWordUntilPosition(position);
                const range = {
                    startLineNumber: position.lineNumber,
                    endLineNumber: position.lineNumber,
                    startColumn: word.startColumn,
                    endColumn: word.endColumn,
                };
                const suggestions = config.keywords.map(k => ({
                    label: k,
                    kind: monaco.languages.CompletionItemKind.Keyword,
                    insertText: k,
                    range,
                }));
                return { suggestions };
            },
        });
    }

    private _isNew(entry: string): boolean {
        return this.initialLines.indexOf(entry) < 0 && entry?.trim().length > 0;
    }

    private _makeDecorationFor(entry: string): IModelDeltaDecoration {
        const line = this.currentLines.indexOf(entry) + 1;

        return {
            range: new monaco.Range(line, 1, line, 1),
            options: { isWholeLine: true, className: 'changed-row-marker' },
        } as IModelDeltaDecoration;
    }

    private _drawErrorMarkers(errors: SyntaxError[] | undefined) {
        const model = this._codeEditor?.getModel();
        if (!model || !errors?.length) {
            return;
        }
        const markers = [];
        const glyphs = [];
        errors
            .filter(e => e.line > 0)
            .forEach(e => {
                const endColumn = model.getLineLength(e.line) + 1;
                markers.push({
                    message: e.message,
                    severity: monaco.MarkerSeverity.Error,
                    startLineNumber: e.line,
                    startColumn: e.column,
                    endLineNumber: e.line,
                    endColumn,
                });
                glyphs.push({
                    range: new monaco.Range(e.line, e.column, e.line, endColumn),
                    options: {
                        glyphMarginClassName: 'error-glyph-margin',
                    },
                });
            });
        this.#errorGlyphs.set(this._codeEditor.deltaDecorations(this.#errorGlyphs(), glyphs));
        (window as any).monaco.editor.setModelMarkers(model, model.id, markers);
    }

    private _closeProblemsView(): void {
        this._codeEditor.trigger(null, 'closeMarkersNavigation', null);
    }

    private _removeErrorMarkers(): void {
        const model = this._codeEditor?.getModel();
        if (!model) {
            return;
        }
        (window as any).monaco.editor.setModelMarkers(model, model.id, []);
        this.#errorGlyphs.set(this._codeEditor.deltaDecorations(this.#errorGlyphs(), []) || []);
        this._closeProblemsView();
    }

    private async _initialize() {
        this._loadingService.start();
        await firstValueFrom(this._rulesService.download(this.#dossierTemplateId, this.type)).then(
            rules => {
                this.currentLines = this.initialLines = rules.rules.split('\n');
                this.revert();
            },
            () => this._loadingService.stop(),
        );
    }
}
