import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
import { IconButtonTypes, LoadingService } from '@iqser/common-ui';
import {
    Dictionary,
    DICTIONARY_TYPE_KEY_MAP,
    DictionaryEntryType,
    DictionaryEntryTypes,
    DictionaryType,
    Dossier,
    DossierTemplate,
} from '@red/domain';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { EditorComponent } from '@shared/components/editor/editor.component';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { saveAs } from 'file-saver';
import { Debounce, List } from '@iqser/common-ui/lib/utils';
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
import FindMatch = monaco.editor.FindMatch;
import { firstValueFrom } from 'rxjs';

const SMOOTH_SCROLL = 0;
const HELP_MODE_KEYS = {
    dictionary: 'dictionary_entity',
    'false-positive': 'false_recommendations_entity',
    'false-recommendations': 'false_recommendations_entity',
};

@Component({
    selector: 'redaction-dictionary-manager',
    templateUrl: './dictionary-manager.component.html',
    styleUrls: ['./dictionary-manager.component.scss'],
})
export class DictionaryManagerComponent implements OnChanges {
    private _searchDecorations: string[] = [];
    readonly #currentTab = window.location.href.split('/').pop();
    @Input() type: DictionaryType = 'dictionary';
    @Input() entityType?: string;
    @Input() withFloatingActions = true;
    @Input() filterByDossierTemplate = false;
    @Input() initialEntries: List;
    @Input() canEdit = false;
    @Input() canDownload = false;
    @Input() isLeavingPage = false;
    @Input() hint = false;
    @Input() selectedDictionaryType = 'dossier_redaction';
    @Input() activeEntryType: DictionaryEntryType = DictionaryEntryTypes.ENTRY;
    @Output() readonly saveDictionary = new EventEmitter<string[]>();
    @ViewChild(EditorComponent) readonly editor: EditorComponent;
    readonly iconButtonTypes = IconButtonTypes;
    currentMatch = 0;
    findMatches: FindMatch[] = [];
    diffEditorText = '';
    showDiffEditor = false;
    searchText = '';
    selectDossier = { dossierName: _('dictionary-overview.compare.select-dossier') } as Dossier;
    selectDictionary = {
        label: _('dictionary-overview.compare.select-dictionary'),
    } as Dictionary;
    #compareDictionary = this.selectDictionary;
    selectDossierTemplate = { name: _('dictionary-overview.compare.select-dossier-template') } as DossierTemplate;
    compare: false;
    dictionaries: List<Dictionary> = this.#dictionaries;

    constructor(
        private readonly _dictionaryService: DictionaryService,
        private readonly _dictionariesMapService: DictionariesMapService,
        protected readonly _loadingService: LoadingService,
        private readonly _changeRef: ChangeDetectorRef,
        readonly activeDossiersService: ActiveDossiersService,
        readonly dossierTemplatesService: DossierTemplatesService,
    ) {}

    private _dossierTemplate = this.selectDossierTemplate;

    get dossierTemplate() {
        return this._dossierTemplate;
    }

    set dossierTemplate(value: DossierTemplate) {
        this._dossierTemplate = value;
        this.dictionaries = this.#dictionaries;
        this.#compareDictionary = this.selectDictionary;
        this.showDiffEditor = false;
    }

    private _dossier = this.selectDossier;

    get dossier() {
        return this._dossier;
    }

    set dossier(dossier: Dossier) {
        this._dossier = dossier;

        if (dossier.dossierName === this.selectDossier.dossierName) {
            this.showDiffEditor = false;
            this.diffEditorText = '';
            return;
        }

        this.#onDossierChanged(dossier.dossierTemplateId, dossier.id).then(entries => {
            this.diffEditorText = entries;
            this.showDiffEditor = true;
        });
    }

    get compareDictionary() {
        return this.#compareDictionary;
    }

    set compareDictionary(dictionary: Dictionary) {
        this._loadingService.start();
        this.#compareDictionary = dictionary;

        if (dictionary.label === this.selectDictionary.label) {
            this.showDiffEditor = false;
            this.diffEditorText = '';
            return;
        }
        const entries: List =
            this.#compareDictionary.getEntries(this.type) ??
            this._dictionariesMapService.get(this.#compareDictionary.dossierTemplateId, this.#compareDictionary.type).getEntries(this.type);

        if (entries.length) {
            this.diffEditorText = this.#toString([...entries]);
            this.showDiffEditor = true;
            return;
        }

        this._dictionaryService
            .getForType(this.#compareDictionary.dossierTemplateId, this.#compareDictionary.type)
            .then(values => {
                this.#compareDictionary.setEntries([...values[DICTIONARY_TYPE_KEY_MAP[this.type]]] ?? [], this.type);
                this.diffEditorText = this.#toString([...(this.#compareDictionary.getEntries(this.type) as string[])]);
                this.showDiffEditor = true;
                this._changeRef.markForCheck();
                this._loadingService.stop();
            })
            .catch(() => this.#compareDictionary.setEntries([], this.type));
    }

    get dossierTemplateIsNotSelected() {
        return this.filterByDossierTemplate && this._dossierTemplate.name === this.selectDossierTemplate.name;
    }

    get optionNotSelected() {
        if (this.filterByDossierTemplate) {
            return this.selectDictionary.label === this.#compareDictionary.label;
        }
        return this.dossier.dossierName === this.selectDossier.dossierName;
    }

    get helpModeKey(): string {
        return HELP_MODE_KEYS[this.#currentTab];
    }

    get #dictionaries(): Dictionary[] {
        if (!this._dossierTemplate || this._dossierTemplate.name === this.selectDossierTemplate.name) {
            return;
        }
        return this._dictionariesMapService.get(this.dossierTemplate?.dossierTemplateId).filter(dict => !dict.virtual);
    }

    download(): void {
        const content = this.editor.currentEntries.join('\n');
        const blob = new Blob([content], {
            type: 'text/plain;charset=utf-8',
        });
        saveAs(blob, `${this.entityType}-${this.type}.txt`);
    }

    revert() {
        this.editor?.revert();
        this.searchText = '';
        this.searchChanged('');
    }

    @Debounce()
    searchChanged(text: string) {
        this.findMatches = this._getMatches(text.toLowerCase());
        this._applySearchDecorations();

        this.currentMatch = 0;
        this.nextSearchMatch();
    }

    nextSearchMatch(): void {
        if (this.findMatches.length <= 0) {
            return;
        }

        this.currentMatch = this.currentMatch < this.findMatches.length ? this.currentMatch + 1 : 1;
        this._scrollToCurrentMatch();
    }

    previousSearchMatch(): void {
        if (this.findMatches.length <= 0) {
            return;
        }

        this.currentMatch = this.currentMatch > 1 ? this.currentMatch - 1 : this.findMatches.length;
        this._scrollToCurrentMatch();
    }

    ngOnChanges(changes: SimpleChanges): void {
        if ((changes.selectedDictionaryType || changes.activeEntryType) && this._dossier?.dossierTemplateId && this.dossier?.dossierId) {
            this.#onDossierChanged(this._dossier.dossierTemplateId, this._dossier.dossierId).then(entries => {
                this.diffEditorText = entries;
                this.showDiffEditor = true;
            });
        }
        if (changes.selectedDictionaryType) {
            this.searchText = '';
        }
    }

    private _applySearchDecorations() {
        this._searchDecorations = this.editor.codeEditor?.deltaDecorations(this._searchDecorations, []) || [];

        const decorations = this.findMatches.map(match => this._getSearchDecoration(match));

        this._searchDecorations = this.editor.codeEditor?.deltaDecorations(this._searchDecorations, decorations) || [];
    }

    private _getMatches(text: string): FindMatch[] {
        const model = this.editor.codeEditor?.getModel();
        return model?.findMatches(text, false, false, false, null, false) || [];
    }

    private _getSearchDecoration(match: FindMatch): IModelDeltaDecoration {
        return { range: match.range, options: { inlineClassName: 'search-marker' } };
    }

    private _scrollToCurrentMatch(): void {
        const range = this.findMatches[this.currentMatch - 1].range;

        this.editor.codeEditor.setSelection(range);
        this.editor.codeEditor.revealLineInCenter(range.startLineNumber, SMOOTH_SCROLL);
    }

    async #onDossierChanged(dossierTemplateId: string, dossierId?: string) {
        const dictionary = (
            await firstValueFrom(
                this._dictionaryService.loadDictionaryEntriesByType([this.selectedDictionaryType], dossierTemplateId, dossierId),
            )
        )[0];
        const activeEntries =
            this.activeEntryType === DictionaryEntryTypes.ENTRY || this.hint
                ? [...dictionary.entries]
                : this.activeEntryType === DictionaryEntryTypes.FALSE_POSITIVE
                ? [...dictionary.falsePositiveEntries]
                : [...dictionary.falseRecommendationEntries];
        return this.#toString(activeEntries);
    }

    #toString(entries: string[]): string {
        entries.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'accent' }));
        return entries.join('\n');
    }
}
