import { Component, computed, effect, HostListener, Input, OnDestroy } from '@angular/core';
import { Dossier, File, FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
import {
    BaseFormComponent,
    CircleButtonComponent,
    DynamicInputComponent,
    HelpModeService,
    ListingService,
    Toaster,
} from '@iqser/common-ui';
import { PermissionsService } from '@services/permissions.service';
import { AbstractControl, FormBuilder, FormsModule, ReactiveFormsModule, UntypedFormGroup, ValidatorFn } from '@angular/forms';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { firstValueFrom, Subscription } from 'rxjs';
import { FilesService } from '@services/files/files.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import dayjs from 'dayjs';
import { NavigationEnd, Router } from '@angular/router';
import { filter, map, tap } from 'rxjs/operators';
import { ConfigService } from '../../config.service';
import { Debounce } from '@iqser/common-ui/lib/utils';
import { AsyncPipe, DatePipe, NgClass, NgIf, NgTemplateOutlet } from '@angular/common';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatIconModule } from '@angular/material/icon';

@Component({
    selector: 'redaction-file-attribute [fileAttribute] [file] [dossier]',
    templateUrl: './file-attribute.component.html',
    styleUrls: ['./file-attribute.component.scss'],
    standalone: true,
    imports: [
        NgIf,
        NgClass,
        MatTooltipModule,
        MatIconModule,
        DatePipe,
        AsyncPipe,
        FormsModule,
        ReactiveFormsModule,
        DynamicInputComponent,
        CircleButtonComponent,
        NgTemplateOutlet,
    ],
})
export class FileAttributeComponent extends BaseFormComponent implements OnDestroy {
    readonly #subscriptions = new Subscription();
    readonly #shouldClose = computed(
        () =>
            (this.fileAttributesService.isEditingFileAttribute() &&
                this.file &&
                this.fileAttribute &&
                (this.fileAttribute.id !== this.fileAttributesService.openAttributeEdit() ||
                    this.file.fileId !== this.fileAttributesService.fileEdit())) ||
            !this.fileAttributesService.openAttributeEdits().length,
    );

    isInEditMode = false;
    closedDatepicker = true;

    @Input() fileAttribute!: IFileAttributeConfig;
    @Input() file!: File;
    @Input() dossier!: Dossier;
    @Input() fileNameColumn = false;

    constructor(
        router: Router,
        private readonly _toaster: Toaster,
        private readonly _formBuilder: FormBuilder,
        private readonly _filesService: FilesService,
        readonly permissionsService: PermissionsService,
        private readonly _listingService: ListingService<File>,
        readonly fileAttributesService: FileAttributesService,
        readonly helpModeService: HelpModeService,
        readonly configService: ConfigService,
    ) {
        super();
        const sub = router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => this.close());
        this.#subscriptions.add(sub);

        const sub2 = this._listingService.selectedLength$.pipe(
            map(selectedLength => !!selectedLength),
            tap(() => this.close()),
        );
        this.#subscriptions.add(sub2.subscribe());

        effect(
            () => {
                if (this.#shouldClose()) {
                    this.close();
                }
            },
            { allowSignalWrites: true },
        );
    }

    get isDate(): boolean {
        return this.fileAttribute.type === FileAttributeConfigTypes.DATE;
    }

    get fileAttributeValue(): string {
        return this.file.fileAttributes.attributeIdToValue[this.fileAttribute.id];
    }

    @Debounce(60)
    @HostListener('document:click', ['$event'])
    clickOutside($event: MouseEvent) {
        const clickCalendarCell = ($event.target as HTMLElement).classList?.contains('mat-calendar-body-cell-content');
        if (this.isDate) {
            this.#focusOnEditInput();
        }

        if (this.isInEditMode && this.closedDatepicker && !clickCalendarCell) {
            this.close();
        }
    }

    handleClick($event: MouseEvent) {
        $event.stopPropagation();
    }

    ngOnDestroy() {
        this.#subscriptions.unsubscribe();
    }

    async editFileAttribute($event: MouseEvent): Promise<void> {
        if (
            !this.file.isInitialProcessing &&
            this.permissionsService.canEditFileAttributes(this.file, this.dossier) &&
            !this.isInEditMode
        ) {
            $event.stopPropagation();
            this.fileAttributesService.openAttributeEdits.mutate(value =>
                value.push({ attribute: this.fileAttribute.id, file: this.file.id }),
            );
            this.#toggleEdit();
            this.fileAttributesService.setFileEdit(this.file.fileId);
            this.fileAttributesService.setOpenAttributeEdit(this.fileAttribute.id);
        }
    }

    async save() {
        const rawFormValue = this.form.getRawValue();
        const fileAttrValue = rawFormValue[this.fileAttribute.id];
        const attributeIdToValue = {
            ...this.#getForm().getRawValue(),
            [this.fileAttribute.id]: this.#formatAttributeValue(fileAttrValue),
        };
        try {
            await firstValueFrom(
                this.fileAttributesService.setFileAttributes({ attributeIdToValue }, this.file.dossierId, this.file.fileId),
            );
            await this._filesService.reload(this.file.dossierId, this.file);
            this.initialFormValue = rawFormValue;
            this._toaster.success(_('file-attribute.update.success'));
        } catch (e) {
            this._toaster.error(_('file-attribute.update.error'));
        }

        this.close();
    }

    close(): void {
        if (this.isInEditMode) {
            this.form = this.#getForm();
            this.#toggleEdit();
            this.fileAttributesService.openAttributeEdits.mutate(value => {
                for (let index = 0; index < value.length; index++) {
                    if (JSON.stringify(value[index]) === JSON.stringify({ attribute: this.fileAttribute.id, file: this.file.id })) {
                        value.splice(index, 1);
                    }
                }
            });
        }
    }

    #initFileAttributes() {
        const configs = this.fileAttributesService.getFileAttributeConfig(this.file.dossierTemplateId).fileAttributeConfigs;
        configs.forEach(config => {
            if (!this.file.fileAttributes.attributeIdToValue[config.id]) {
                this.file.fileAttributes.attributeIdToValue[config.id] = null;
            }
        });
    }

    #getForm(): UntypedFormGroup {
        const config = {};
        const fileAttributes = this.file.fileAttributes.attributeIdToValue;
        Object.keys(fileAttributes).forEach(key => {
            const attrValue = fileAttributes[key];
            config[key] = [dayjs(attrValue, 'YYYY-MM-DD', true).isValid() ? dayjs(attrValue).toDate() : attrValue];
        });
        return this._formBuilder.group(config, {
            validators: [this.#checkEmptyInput(), this.#checkDate()],
        });
    }

    #checkEmptyInput(): ValidatorFn {
        return (control: AbstractControl) =>
            (!this.isDate && !control.get(this.fileAttribute.id)?.value?.trim().length && !this.fileAttributeValue) ||
            control.get(this.fileAttribute.id)?.value === this.fileAttributeValue
                ? { emptyString: true }
                : null;
    }

    #checkDate(): ValidatorFn {
        return (control: AbstractControl) => {
            const expr = new RegExp('(0?[1-9]|[12][0-9]|3[01])(/|.)(0?[1-9]|1[12])(/|.)\\d{2}');
            return this.isDate
                ? (!expr.test(control.get(this.fileAttribute.id)?.value) && control.get(this.fileAttribute.id)?.value?.length) ||
                  this.#formatAttributeValue(control.get(this.fileAttribute.id)?.value) === this.fileAttributeValue
                    ? { invalidDate: true }
                    : null
                : null;
        };
    }

    #formatAttributeValue(attrValue) {
        return this.isDate ? attrValue && dayjs(attrValue).format('YYYY-MM-DD') : attrValue.trim().replaceAll(/\s\s+/g, ' ');
    }

    #toggleEdit(): void {
        if (!this.isInEditMode) {
            this.#initFileAttributes();
            this.form = this.#getForm();
            this.initialFormValue = this.form.getRawValue();
        }

        this.isInEditMode = !this.isInEditMode;

        if (this.isInEditMode) {
            this.#focusOnEditInput();
        }
    }

    #focusOnEditInput(): void {
        setTimeout(() => {
            const input = document.getElementById(this.fileAttribute.id) as HTMLInputElement;
            const end = input.value.length;
            input?.setSelectionRange(end, end);
            input?.focus();
        }, 100);
    }
}
