import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, ValidatorFn, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import * as Papa from 'papaparse';
import { firstValueFrom, Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { IconButtonTypes, ListingComponent, listingProvidersFactory, TableColumnConfig, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FileAttributeConfig, FileAttributeConfigTypes, FileAttributeEncodingTypes, IField, IFileAttributesConfig } from '@red/domain';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { fileAttributeEncodingTypesTranslations } from '@translations/file-attribute-encoding-types-translations';
import { trackByFactory } from '@iqser/common-ui/lib/utils';

export interface IFileAttributesCSVImportData {
    readonly csv: File;
    readonly dossierTemplateId: string;
    readonly existingConfiguration: IFileAttributesConfig;
}

@Component({
    templateUrl: './file-attributes-csv-import-dialog.component.html',
    styleUrls: ['./file-attributes-csv-import-dialog.component.scss'],
    providers: listingProvidersFactory(),
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FileAttributesCsvImportDialogComponent extends ListingComponent<IField> {
    readonly iconButtonTypes = IconButtonTypes;
    readonly tableColumnConfigs: TableColumnConfig<IField>[] = [];
    parseResult: { data: any[]; errors: any[]; meta: any; fields: IField[] };
    hoveredColumn: string;
    activeFields: IField[] = [];
    readonly form: UntypedFormGroup;
    isSearchOpen = false;
    previewExpanded = true;
    filteredKeyOptions: Observable<string[]>;
    keepPreview = false;
    columnSample = [];
    initialParseConfig: { delimiter?: string; encoding?: string } = {};
    readonly encodingTypeOptions = Object.keys(FileAttributeEncodingTypes);
    readonly translations = fileAttributeEncodingTypesTranslations;
    readonly tableHeaderLabel = '';
    readonly trackBy = trackByFactory();

    constructor(
        private readonly _toaster: Toaster,
        private readonly _formBuilder: UntypedFormBuilder,
        readonly dialogRef: MatDialogRef<FileAttributesCsvImportDialogComponent, boolean>,
        private readonly _fileAttributesService: FileAttributesService,
        @Inject(MAT_DIALOG_DATA) readonly data: IFileAttributesCSVImportData,
    ) {
        super();
        this.form = this._getForm();
        this.readFile();
    }

    get changedParseConfig(): boolean {
        return (
            this.initialParseConfig.delimiter !== this.form.get('delimiter').value ||
            this.initialParseConfig.encoding !== this.form.get('encoding').value
        );
    }

    readFile(updateDelimiter: boolean = true) {
        const reader = new FileReader();
        reader.addEventListener('load', event => {
            const parsedCsv = <any>event.target.result?.toString().trim();

            const hasMoreThanOneLine = parsedCsv.indexOf('\n') > 0 && parsedCsv.substring(parsedCsv.indexOf('\n')).length > 1;
            this.parseResult = Papa.parse(parsedCsv, {
                header: hasMoreThanOneLine,
                delimiter: this.form.get('delimiter').value,
            });

            if (updateDelimiter) {
                this.form.patchValue({ delimiter: this.parseResult.meta.delimiter });
            }

            if (!hasMoreThanOneLine) {
                this.parseResult.meta.fields = this.parseResult.data[0];
                this.parseResult.data = [];
            }

            // Filter duplicate columns
            if (this.parseResult?.data?.length) {
                this.parseResult.meta.fields = Object.keys(this.parseResult.data[0]);
            }

            this.entitiesService.setEntities(this.parseResult.meta.fields.map(field => this._buildAttribute(field)));
            this.activeFields = [];

            for (const entity of this.allEntities) {
                const existing = this.data.existingConfiguration.fileAttributeConfigs?.find(a => a.csvColumnHeader === entity.csvColumn);
                if (existing) {
                    entity.id = existing.id;
                    entity.name = existing.label;
                    entity.type = existing.type;
                    entity.readonly = !existing.editable;
                    entity.primaryAttribute = existing.primaryAttribute;
                    this.toggleFieldActive(entity);
                }
            }

            this.filteredKeyOptions = this.form.get('filenameMappingColumnHeaderName').valueChanges.pipe(
                startWith(this.form.get('filenameMappingColumnHeaderName').value as string),
                map((value: string) =>
                    this.allEntities
                        .filter(field => field.csvColumn.toLowerCase().indexOf(value.toLowerCase()) !== -1)
                        .map(field => field.csvColumn),
                ),
            );

            if (
                this.data.existingConfiguration &&
                this.allEntities.find(entity => entity.csvColumn === this.data.existingConfiguration.filenameMappingColumnHeaderName)
            ) {
                this.form.patchValue({
                    filenameMappingColumnHeaderName: this.data.existingConfiguration.filenameMappingColumnHeaderName,
                });
            }

            this.initialParseConfig = {
                delimiter: this.form.get('delimiter').value,
                encoding: this.form.get('encoding').value,
            };
        });
        reader.readAsText(this.data.csv, this._mapEncoding(this.form.get('encoding').value));
    }

    getSample(csvColumn: string) {
        return this.parseResult?.data?.length ? this.parseResult?.data[0][csvColumn] : '';
    }

    getEntries(csvColumn: string) {
        if (this.parseResult?.data) {
            let count = 0;
            for (const entry of this.parseResult.data) {
                if (entry[csvColumn]) {
                    count++;
                }
            }
            return count;
        }

        return 0;
    }

    isActive(field: IField): boolean {
        return !!this.activeFields.find(f => f.id === field.id);
    }

    toggleFieldActive(field: IField) {
        if (!this.isActive(field)) {
            this.activeFields = [...this.activeFields, { ...field, searchKey: field.csvColumn }];
        } else {
            this.activeFields = this.activeFields.filter(f => f.id !== field.id);
        }
    }

    activateAll() {
        this.activeFields = this.allEntities.map(item => ({ ...item, searchKey: item.csvColumn }));
    }

    deactivateAll() {
        this.activeFields = [];
    }

    async save() {
        const newPrimary = !!this.activeFields.find(attr => attr.primaryAttribute);
        let fileAttributeConfigs = this.data.existingConfiguration.fileAttributeConfigs ?? [];
        if (newPrimary) {
            fileAttributeConfigs = fileAttributeConfigs.map(attr => new FileAttributeConfig({ ...attr, primaryAttribute: false }));
        }

        const fileAttributes: IFileAttributesConfig = {
            ...this.form.getRawValue(),
            fileAttributeConfigs: [
                ...fileAttributeConfigs.filter(a => !this.allEntities.find(entity => entity.csvColumn === a.csvColumnHeader)),
                ...this.activeFields.map(field => ({
                    id: field.id,
                    csvColumnHeader: field.csvColumn,
                    editable: !field.readonly,
                    label: field.name,
                    type: field.type,
                    primaryAttribute: field.primaryAttribute,
                    displayedInFileList: null,
                    filterable: null,
                })),
            ],
        };

        try {
            await firstValueFrom(this._fileAttributesService.setFileAttributeConfig(fileAttributes, this.data.dossierTemplateId));
            this._toaster.success(_('file-attributes-csv-import.save.success'), { params: { count: this.activeFields.length } });
        } catch (e) {
            this._toaster.error(_('file-attributes-csv-import.save.error'));
        }

        this.dialogRef.close(true);
    }

    setHoveredColumn(column?: string) {
        setTimeout(() => {
            if (this.keepPreview && !column) {
                return;
            }
            this.hoveredColumn = column;

            if (!column) {
                this.columnSample = [];
            } else {
                this.columnSample = this.parseResult.data.filter(row => !!row[column]).map(row => row[column]);
            }
        }, 0);
    }

    private _getForm(): UntypedFormGroup {
        return this._formBuilder.group({
            filenameMappingColumnHeaderName: ['', [Validators.required, this._autocompleteStringValidator()]],
            delimiter: [undefined, Validators.required],
            encoding: [this.data.existingConfiguration?.encoding, Validators.required],
        });
    }

    private _autocompleteStringValidator(): ValidatorFn {
        return (control: AbstractControl): { [key: string]: any } | null => {
            if ((this.parseResult?.meta?.fields || []).indexOf(control.value) !== -1) {
                return null; /* valid option selected */
            }
            return { invalidAutocompleteString: { value: control.value } };
        };
    }

    private _buildAttribute(csvColumn: string): IField {
        const sample = this.getSample(csvColumn);
        const isNumber = sample && !isNaN(sample);
        return {
            id: btoa(unescape(encodeURIComponent(csvColumn))),
            csvColumn,
            name: csvColumn,
            searchKey: csvColumn,
            type: isNumber ? FileAttributeConfigTypes.NUMBER : FileAttributeConfigTypes.TEXT,
            readonly: false,
            primaryAttribute: false,
        };
    }

    private _mapEncoding(encoding: string) {
        if (!encoding) {
            return 'UTF-8';
        }
        if (encoding.toLowerCase() === 'utf-8' || encoding.toLowerCase() === 'utf_8') {
            return 'UTF-8';
        }
        if (encoding.toLowerCase() === 'ascii') {
            return 'ascii';
        }
        if (encoding.toLowerCase().startsWith('iso')) {
            return 'ISO-8859-1';
        }
        return 'UTF-8';
    }
}
