import {
    AfterViewInit,
    ChangeDetectorRef,
    Component,
    computed,
    effect,
    ElementRef,
    HostListener,
    NgZone,
    OnDestroy,
    OnInit,
    TemplateRef,
    ViewChild,
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ComponentCanDeactivate } from '@guards/can-deactivate.guard';
import {
    CircleButtonTypes,
    ConfirmOption,
    ConfirmOptions,
    CustomError,
    ErrorService,
    getConfig,
    HelpModeService,
    IConfirmationDialogData,
    IqserDialog,
    IqserPermissionsService,
    isIqserDevMode,
    LoadingService,
    Toaster,
} from '@iqser/common-ui';
import { copyLocalStorageFiltersValues, FilterService, NestedFilter, processFilters } from '@iqser/common-ui/lib/filtering';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
import { AutoUnsubscribe, Bind, bool, Debounce, List, OnAttach, OnDetach } from '@iqser/common-ui/lib/utils';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { ManualRedactionEntryTypes, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
import { Dictionary, File, ViewModes } from '@red/domain';
import { ConfigService } from '@services/config.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { DossiersService } from '@services/dossiers/dossiers.service';
import { FileManagementService } from '@services/files/file-management.service';
import { FilesMapService } from '@services/files/files-map.service';
import { FilesService } from '@services/files/files.service';
import { PermissionsService } from '@services/permissions.service';
import { ReanalysisService } from '@services/reanalysis.service';
import { Roles } from '@users/roles';
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
import { saveAs } from 'file-saver';
import JSZip from 'jszip';
import { NGXLogger } from 'ngx-logger';
import { combineLatest, first, firstValueFrom, Observable, of, pairwise } from 'rxjs';
import { catchError, filter, map, startWith, switchMap, tap } from 'rxjs/operators';
import { byId, byPage, download, handleFilterDelta, hasChanges } from '../../utils';
import { AnnotationDrawService } from '../pdf-viewer/services/annotation-draw.service';
import { REDAnnotationManager } from '../pdf-viewer/services/annotation-manager.service';
import { REDDocumentViewer } from '../pdf-viewer/services/document-viewer.service';
import { PageRotationService } from '../pdf-viewer/services/page-rotation.service';
import { PdfViewer } from '../pdf-viewer/services/pdf-viewer.service';
import { ReadableRedactionsService } from '../pdf-viewer/services/readable-redactions.service';
import { ViewerHeaderService } from '../pdf-viewer/services/viewer-header.service';
import { ROTATION_ACTION_BUTTONS, ViewerEvents } from '../pdf-viewer/utils/constants';
import { AddHintDialogComponent } from './dialogs/add-hint-dialog/add-hint-dialog.component';
import { AddAnnotationDialogComponent } from './dialogs/docu-mine/add-annotation-dialog/add-annotation-dialog.component';
import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component';
import { filePreviewScreenProviders } from './file-preview-providers';
import { AnnotationProcessingService } from './services/annotation-processing.service';
import { AnnotationsListingService } from './services/annotations-listing.service';
import { FileDataService } from './services/file-data.service';
import { FilePreviewDialogService } from './services/file-preview-dialog.service';
import { FilePreviewStateService } from './services/file-preview-state.service';
import { ManualRedactionService } from './services/manual-redaction.service';
import { PdfProxyService } from './services/pdf-proxy.service';
import { SkippedService } from './services/skipped.service';
import { StampService } from './services/stamp.service';
import { TablesService } from './services/tables.service';
import { ViewModeService } from './services/view-mode.service';
import { ALL_HOTKEYS, TextPopups } from './utils/constants';
import { RedactTextData } from './utils/dialog-types';

const textActions = [TextPopups.REDACT_TEXT, TextPopups.ADD_HINT, TextPopups.ADD_FALSE_POSITIVE];

@Component({
    templateUrl: './file-preview-screen.component.html',
    styleUrls: ['./file-preview-screen.component.scss'],
    providers: filePreviewScreenProviders,
})
export class FilePreviewScreenComponent
    extends AutoUnsubscribe
    implements AfterViewInit, OnInit, OnDestroy, OnAttach, OnDetach, ComponentCanDeactivate
{
    readonly circleButtonTypes = CircleButtonTypes;
    readonly roles = Roles;
    fullScreen = false;
    readonly fileId = this.state.fileId;
    readonly dossierId = this.state.dossierId;
    readonly lastAssignee = computed(() => this.getLastAssignee());
    width: number;
    readonly isIqserDevMode = isIqserDevMode();
    @ViewChild('annotationFilterTemplate', {
        read: TemplateRef,
        static: false,
    })
    private readonly _filterTemplate: TemplateRef<unknown>;
    #loadAllAnnotationsEnabled = false;
    @ViewChild('actionsWrapper', { static: false }) private readonly _actionsWrapper: ElementRef;
    readonly #isDocumine = getConfig().IS_DOCUMINE;

    get changed() {
        return this._pageRotationService.hasRotations();
    }

    get #earmarks$() {
        const isEarmarksViewMode$ = this._viewModeService.viewMode$.pipe(filter(() => this._viewModeService.isEarmarks()));

        const earmarks$ = isEarmarksViewMode$.pipe(
            tap(() => this._loadingService.start()),
            switchMap(() => this._fileDataService.loadEarmarks()),
            tap(() => this.updateViewMode().then(() => this._loadingService.stop())),
        );

        const currentPageIfEarmarksView$ = combineLatest([this.pdf.currentPage$, this._viewModeService.viewMode$]).pipe(
            filter(() => this._viewModeService.isEarmarks()),
            map(([page]) => page),
        );

        const currentPageEarmarks$ = combineLatest([currentPageIfEarmarksView$, earmarks$]).pipe(
            map(([page, earmarks]) => earmarks.get(page)),
        );

        return currentPageEarmarks$.pipe(
            map(earmarks => [earmarks, this._skippedService.hideSkipped(), this.state.dossierTemplateId] as const),
            tap(args => this._annotationDrawService.draw(...args)),
        );
    }

    constructor(
        readonly pdf: PdfViewer,
        readonly state: FilePreviewStateService,
        readonly permissionsService: PermissionsService,
        readonly userPreferenceService: UserPreferenceService,
        readonly pdfProxyService: PdfProxyService,
        readonly configService: ConfigService,
        private readonly _iqserPermissionsService: IqserPermissionsService,
        private readonly _listingService: AnnotationsListingService,
        private readonly _router: Router,
        private readonly _ngZone: NgZone,
        private readonly _logger: NGXLogger,
        private readonly _annotationManager: REDAnnotationManager,
        private readonly _errorService: ErrorService,
        private readonly _filterService: FilterService,
        private readonly _loadingService: LoadingService,
        private readonly _filesMapService: FilesMapService,
        private readonly _dossiersService: DossiersService,
        private readonly _skippedService: SkippedService,
        private readonly _fileDataService: FileDataService,
        private readonly _viewModeService: ViewModeService,
        private readonly _documentViewer: REDDocumentViewer,
        private readonly _changeRef: ChangeDetectorRef,
        private readonly _tenantsService: TenantsService,
        private readonly _dialogService: FilePreviewDialogService,
        private readonly _iqserDialog: IqserDialog,
        private readonly _pageRotationService: PageRotationService,
        private readonly _viewerHeaderService: ViewerHeaderService,
        private readonly _annotationDrawService: AnnotationDrawService,
        private readonly _annotationProcessingService: AnnotationProcessingService,
        private readonly _stampService: StampService,
        private readonly _reanalysisService: ReanalysisService,
        private readonly _toaster: Toaster,
        private readonly _manualRedactionService: ManualRedactionService,
        private readonly _filesService: FilesService,
        private readonly _fileManagementService: FileManagementService,
        private readonly _readableRedactionsService: ReadableRedactionsService,
        private readonly _helpModeService: HelpModeService,
        private readonly _dossierTemplatesService: DossierTemplatesService,
        private readonly _dialog: MatDialog,
        private readonly _tablesService: TablesService,
    ) {
        super();
        effect(() => {
            const file = this.state.file();
            this._fileDataService.loadAnnotations(file).then();
        });

        effect(
            () => {
                if (this._documentViewer.loaded()) {
                    this._pageRotationService.clearRotations();
                    this._viewerHeaderService.disable(ROTATION_ACTION_BUTTONS);
                    this.viewerReady().then();
                }
            },
            { allowSignalWrites: true },
        );

        effect(() => {
            this.state.shouldUpdate();
            if (this._documentViewer.pageComplete()) {
                this.#setExcludedPageStyles();
            }
        });

        effect(() => {
            if (this._viewModeService.viewMode()) {
                this.updateViewMode().then();
                this._logger.info('[PDF] Stamp pdf');
                this._stampService.stampPDF().then();
            }
        });
    }

    getLastAssignee() {
        const { isApproved, lastReviewer, lastApprover } = this.state.file();
        const isRss = this._iqserPermissionsService.has(this.roles.getRss);
        return isApproved ? (isRss ? lastReviewer : lastApprover) : lastReviewer;
    }

    deleteEarmarksOnViewChange$() {
        const isChangingFromEarmarksViewMode$ = this._viewModeService.viewMode$.pipe(
            pairwise(),
            filter(([oldViewMode]) => oldViewMode === ViewModes.TEXT_HIGHLIGHTS),
        );

        return isChangingFromEarmarksViewMode$.pipe(
            map(() => this._fileDataService.earmarks().get(this.pdf.currentPage()) ?? []),
            map(earmarks => this.deleteAnnotations(earmarks, [])),
        );
    }

    async save() {
        await this._pageRotationService.applyRotation();
        this._viewerHeaderService.disable(ROTATION_ACTION_BUTTONS);
    }

    async updateViewMode(): Promise<void> {
        this._logger.info(`[PDF] Update ${this._viewModeService.viewMode()} view mode`);

        const annotations = this._annotationManager.get(a => bool(a.getCustomData('redact-manager')));
        const redactions = annotations.filter(a => bool(a.getCustomData('redaction')));

        switch (this._viewModeService.viewMode()) {
            case ViewModes.STANDARD: {
                const wrappers = this._fileDataService.annotations();
                const ocrAnnotationIds = wrappers.filter(a => a.isOCR).map(a => a.id);
                const standardEntries = annotations
                    .filter(a => !bool(a.getCustomData('changeLogRemoved')) && !this._annotationManager.isHidden(a.Id))
                    .filter(a => !ocrAnnotationIds.includes(a.Id));
                const nonStandardEntries = annotations.filter(
                    a => bool(a.getCustomData('changeLogRemoved')) || this._annotationManager.isHidden(a.Id),
                );
                this._readableRedactionsService.setAnnotationsColor(standardEntries, 'annotationColor');
                this._readableRedactionsService.setAnnotationsOpacity(standardEntries, true);
                this._annotationManager.show(standardEntries);
                this._annotationManager.hide(nonStandardEntries);
                break;
            }
            case ViewModes.DELTA: {
                const changeLogEntries = annotations.filter(a => bool(a.getCustomData('changeLog')));
                const nonChangeLogEntries = annotations.filter(a => !bool(a.getCustomData('changeLog')));
                this._readableRedactionsService.setAnnotationsColor(redactions, 'annotationColor');
                this._readableRedactionsService.setAnnotationsOpacity(changeLogEntries, true);
                this._annotationManager.show(changeLogEntries);
                this._annotationManager.hide(nonChangeLogEntries);
                break;
            }
            case ViewModes.REDACTED: {
                const nonRedactionEntries = annotations.filter(
                    a => !bool(a.getCustomData('redaction')) || bool(a.getCustomData('changeLogRemoved')),
                );
                this._readableRedactionsService.setAnnotationsColor(redactions, 'redactionColor');
                this._readableRedactionsService.setAnnotationsOpacity(redactions);
                this._annotationManager.show(redactions);
                this._annotationManager.hide(nonRedactionEntries);

                break;
            }
            case ViewModes.TEXT_HIGHLIGHTS: {
                this._annotationManager.hide(annotations);
            }
        }

        this._logger.info('[PDF] Rebuild filters');
        this.#rebuildFilters();
        this._logger.info('[PDF] Update done');
    }

    ngOnDetach() {
        this._viewerHeaderService.resetCompareButtons();
        this._viewerHeaderService.enableLoadAllAnnotations(); // Reset the button state (since the viewer is reused between files)
        super.ngOnDetach();
        document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
        this._changeRef.markForCheck();
    }

    ngOnDestroy() {
        document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
        super.ngOnDestroy();
    }

    @Bind()
    fullscreenListener() {
        if (!document.fullscreenElement) {
            this.fullScreen = false;
        }
    }

    async ngOnAttach(previousRoute: ActivatedRouteSnapshot) {
        if (!this.state.file().canBeOpened) {
            return this.#navigateToDossier();
        }

        this._viewModeService.switchToStandard();

        await this.ngOnInit();
        this._viewerHeaderService.updateElements();
        const page = previousRoute.queryParams.page ?? '1';
        await this.#updateQueryParamsPage(Number(page));
        await this.viewerReady(page);
    }

    async ngOnInit(): Promise<void> {
        const file = this.state.file();

        if (!file) {
            return this.#handleDeletedFile();
        }

        this._loadingService.start();
        this.userPreferenceService.saveLastOpenedFileForDossier(this.dossierId, this.fileId).then();
        this.#subscribeToFileUpdates();

        if (file?.analysisRequired && !file.excludedFromAutomaticAnalysis) {
            await this._reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true });
        }

        this.pdfProxyService.configureElements();
        this.#restoreOldFilters();
        document.documentElement.addEventListener('fullscreenchange', this.fullscreenListener);

        this.#openRssDialogIfDefault();
        this._viewerHeaderService.resetLayers();
    }

    ngAfterViewInit() {
        const _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
            this._updateItemWidth(entries[0]);
        });
        _observer.observe(this._actionsWrapper.nativeElement);
    }

    openManualAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
        const file = this.state.file();

        this._dialogService.openDialog(
            'manualAnnotation',
            { manualRedactionEntryWrapper, dossierId: this.dossierId, file },
            (result: { annotations: ManualRedactionEntryWrapper[]; dictionary?: Dictionary }) => {
                const selectedAnnotations = this._annotationManager.selected;
                if (selectedAnnotations.length > 0) {
                    this._annotationManager.delete([selectedAnnotations[0].Id]);
                }

                const add$ = this._manualRedactionService.addAnnotation(
                    result.annotations.map(w => w.manualRedactionEntry).filter(e => e.positions[0].page <= file.numberOfPages),
                    this.dossierId,
                    this.fileId,
                    { dictionaryLabel: result.dictionary?.label },
                );

                const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file)));
                return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined))));
            },
        );
    }

    toggleFullScreen() {
        this.fullScreen = !this.fullScreen;
        if (this.fullScreen) {
            this.#openFullScreen();
        } else {
            this.closeFullScreen();
        }
    }

    @HostListener('document:keyup', ['$event'])
    handleKeyEvent($event: KeyboardEvent) {
        if (this._router.url.indexOf('/file/') < 0) {
            return;
        }

        if (!ALL_HOTKEYS.includes($event.key) || this._dialog.openDialogs.length) {
            return;
        }

        if (['Escape'].includes($event.key)) {
            $event.preventDefault();
            this.fullScreen = false;
            this.closeFullScreen();
            this._changeRef.markForCheck();
        }

        if (!$event.ctrlKey && !$event.metaKey && ['f', 'F'].includes($event.key)) {
            // if you type in an input, don't toggle full-screen
            if ($event.target instanceof HTMLInputElement || $event.target instanceof HTMLTextAreaElement) {
                return;
            }
            this.toggleFullScreen();
            return;
        }

        if (['h', 'H'].includes($event.key)) {
            if ($event.target instanceof HTMLInputElement || $event.target instanceof HTMLTextAreaElement) {
                return;
            }
            this._ngZone.run(() => {
                window.focus();
                this._helpModeService.activateHelpMode(false);
            });
            return;
        }
    }

    async viewerReady(pageNumber?: string) {
        if (pageNumber) {
            const file = this.state.file();
            let page = parseInt(pageNumber, 10);

            if (page < 1 || Number.isNaN(page)) {
                page = 1;
                await this.#updateQueryParamsPage(page);
            } else if (page > file.numberOfPages) {
                page = file.numberOfPages;
            }

            setTimeout(() => {
                this.pdf.navigateTo(page);
            }, 300);
        }

        this._loadingService.stop();
        this._changeRef.markForCheck();
    }

    closeFullScreen() {
        if (!!document.fullscreenElement && document.exitFullscreen) {
            document.exitFullscreen().then();
        }
    }

    async downloadOriginalFile({ cacheIdentifier, dossierId, fileId, filename }: File) {
        const originalFile = this._fileManagementService.downloadOriginal(dossierId, fileId, 'response', cacheIdentifier);
        download(await firstValueFrom(originalFile), filename);
    }

    openRSSView() {
        this._dialogService.openDialog('rss', { file: this.state.file() });
    }

    loadAnnotations$() {
        const annotations$ = this._fileDataService.annotations$.pipe(
            startWith([] as AnnotationWrapper[]),
            pairwise(),
            tap(annotations => this.deleteAnnotations(...annotations)),
        );

        const currentPageIfNotHighlightsView$ = combineLatest([this.pdf.currentPage$, this._viewModeService.viewMode$]).pipe(
            filter(([, viewMode]) => viewMode !== ViewModes.TEXT_HIGHLIGHTS),
            map(([page]) => page),
        );

        const currentPageAnnotations$ = combineLatest([currentPageIfNotHighlightsView$, annotations$]).pipe(
            map(([page, [oldAnnotations, newAnnotations]]) =>
                this.#loadAllAnnotationsEnabled
                    ? ([oldAnnotations, newAnnotations] as const)
                    : ([oldAnnotations.filter(byPage(page)), newAnnotations.filter(byPage(page))] as const),
            ),
        );

        return combineLatest([currentPageAnnotations$, this._documentViewer.loaded$]).pipe(
            filter(([, loaded]) => loaded),
            map(([annotations]) => annotations),
            tap(([oldA, newA]) => this.drawChangedAnnotations(oldA, newA)?.then(() => this.updateViewMode())),
            tap(([, newAnnotations]) => this.#highlightSelectedAnnotations(newAnnotations)),
        );
    }

    deleteAnnotations(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) {
        const annotationsToDelete = oldAnnotations.filter(oldAnnotation => {
            const newAnnotation = newAnnotations.find(byId(oldAnnotation.id));
            return newAnnotation ? hasChanges(oldAnnotation, newAnnotation) : true;
        });

        this._logger.info('[ANNOTATIONS] To delete: ', annotationsToDelete);
        this._annotationManager.delete(annotationsToDelete);
    }

    drawChangedAnnotations(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) {
        const annotationsToDraw = this.#getAnnotationsToDraw(oldAnnotations, newAnnotations);
        this._logger.info('[ANNOTATIONS] To draw: ', annotationsToDraw);
        this._annotationManager.delete(annotationsToDraw);
        return this.#cleanupAndRedrawAnnotations(annotationsToDraw);
    }

    async getTables() {
        this._loadingService.start();

        const currentPage = this.pdf.currentPage();
        const tables = await this._tablesService.get(this.state.dossierId, this.state.fileId, this.pdf.currentPage());
        await this._annotationDrawService.drawTables(tables, currentPage, this.state.dossierTemplateId);

        const filename = this.state.file().filename;
        const zip = new JSZip();

        tables.forEach((t, index) => {
            const blob = new Blob([atob(t.csvAsBytes)], {
                type: 'text/csv;charset=utf-8',
            });
            zip.file(filename + '_page' + currentPage + '_table' + (index + 1) + '.csv', blob);
        });

        saveAs(await zip.generateAsync({ type: 'blob' }), filename + '_tables.zip');

        this._loadingService.stop();
    }

    async #openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
        const file = this.state.file();

        const data = this.#getRedactTextDialogData(manualRedactionEntryWrapper, file);
        const result = await this.#getRedactTextDialog(data).result();

        if (!result) {
            return;
        }

        const hint = manualRedactionEntryWrapper.type === ManualRedactionEntryTypes.HINT;
        const add$ = this._manualRedactionService.addAnnotation([result.redaction], this.dossierId, this.fileId, {
            hint,
            dictionaryLabel: result.dictionary?.label,
        });

        const addAndReload$ = add$.pipe(
            tap(() => this._documentViewer.clearSelection()),
            switchMap(() => this._filesService.reload(this.dossierId, file)),
        );
        return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined))));
    }

    @Debounce(30)
    private _updateItemWidth(entry: ResizeObserverEntry): void {
        this.width = entry.contentRect.width;
        this._changeRef.detectChanges();
    }

    #getAnnotationsToDraw(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) {
        const currentPage = this.pdf.currentPage();
        const currentPageAnnotations = this._annotationManager.get(a => a.getPageNumber() === currentPage);
        const existingAnnotations = [];
        for (const annotation of currentPageAnnotations) {
            const oldAnnotation = oldAnnotations.find(byId(annotation.Id));
            if (oldAnnotation) {
                existingAnnotations.push(oldAnnotation);
                continue;
            }

            const newAnnotation = newAnnotations.find(byId(annotation.Id));
            if (newAnnotation) {
                existingAnnotations.push(newAnnotation);
            }
        }

        if (existingAnnotations.length > 0) {
            return this.#findAnnotationsToDraw(newAnnotations, oldAnnotations, existingAnnotations);
        }
        return newAnnotations;
    }

    #rebuildFilters() {
        const startTime = new Date().getTime();

        const annotationFilters = this._annotationProcessingService.getAnnotationFilter();
        const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters;
        this._filterService.addFilterGroup({
            slug: 'primaryFilters',
            filterTemplate: this._filterTemplate,
            filters: processFilters(primaryFilters, annotationFilters),
        });
        const secondaryFilters = this._filterService.getGroup('secondaryFilters')?.filters;
        const secondaryAnnotationFilters = this._annotationProcessingService.secondaryAnnotationFilters;
        this._filterService.addFilterGroup({
            slug: 'secondaryFilters',
            filterTemplate: this._filterTemplate,
            filters: processFilters(secondaryFilters, secondaryAnnotationFilters),
        });

        this._logger.info(`[FILTERS] Rebuild time: ${new Date().getTime() - startTime} ms`);
    }

    async #updateQueryParamsPage(page: number): Promise<void> {
        const extras: NavigationExtras = {
            queryParams: { page },
            queryParamsHandling: 'merge',
            replaceUrl: true,
        };
        await this._router.navigate([], extras);

        this._changeRef.markForCheck();
    }

    #findAnnotationsToDraw(
        newAnnotations: AnnotationWrapper[],
        oldAnnotations: AnnotationWrapper[],
        existingAnnotations: AnnotationWrapper[],
    ) {
        function selectToDrawIfDoesNotExist(newAnnotation: AnnotationWrapper) {
            return !existingAnnotations.some(byId(newAnnotation.id));
        }

        return newAnnotations.filter(newAnnotation => {
            const oldAnnotation = oldAnnotations.find(byId(newAnnotation.id));
            if (!oldAnnotation || !hasChanges(oldAnnotation, newAnnotation)) {
                return selectToDrawIfDoesNotExist(newAnnotation);
            }

            return true;
        });
    }

    #setExcludedPageStyles() {
        const file = this._filesMapService.get(this.dossierId, this.fileId);
        const iframeDoc = this.pdf.instance.UI.iframeWindow.document;
        const currentPage = this.pdf.currentPage();
        const elementId = `pageWidgetContainer${currentPage}`;
        const pageContainer = iframeDoc.getElementById(elementId);
        if (pageContainer) {
            if (file.excludedPages.includes(currentPage)) {
                pageContainer.classList.add('excluded-page');
            } else {
                pageContainer.classList.remove('excluded-page');
            }
        }
    }

    #annotationsExceedingThresholdWarning(annotations: AnnotationWrapper[]): Observable<readonly [boolean, AnnotationWrapper[]]> {
        const data = {
            question: _('load-all-annotations-threshold-exceeded'),
            checkboxes: [
                {
                    label: _('load-all-annotations-threshold-exceeded-checkbox'),
                    value: false,
                },
            ],
            checkboxesValidation: false,
            translateParams: {
                threshold: this.configService.values.ANNOTATIONS_THRESHOLD,
            },
        } as IConfirmationDialogData;

        const ref = this._dialogService.openDialog('confirm', data);
        return ref.afterClosed().pipe(
            switchMap(async (result: ConfirmOption) => {
                const doNotShowWarningAgain = result === ConfirmOptions.SECOND_CONFIRM;
                if (doNotShowWarningAgain) {
                    await this.userPreferenceService.save(PreferencesKeys.loadAllAnnotationsWarning, 'true');
                    await this.userPreferenceService.reload();
                }
                const validOptions: number[] = [ConfirmOptions.CONFIRM, ConfirmOptions.SECOND_CONFIRM];
                const shouldLoad = validOptions.includes(result);
                return [shouldLoad, annotations] as const;
            }),
        );
    }

    #subscribeToFileUpdates(): void {
        this.addActiveScreenSubscription = this.loadAnnotations$().subscribe();

        this.addActiveScreenSubscription = this._dossiersService
            .getEntityDeleted$(this.dossierId)
            .pipe(tap(() => this.#handleDeletedDossier()))
            .subscribe();

        this.addActiveScreenSubscription = this._filesMapService
            .watchDeleted$(this.fileId)
            .pipe(tap(() => this.#handleDeletedFile()))
            .subscribe();

        this.addActiveScreenSubscription = this._documentViewer.keyUp$.subscribe($event => {
            this.handleKeyEvent($event);
        });

        this.addActiveScreenSubscription = this.#earmarks$.subscribe();
        this.addActiveScreenSubscription = this.deleteEarmarksOnViewChange$().subscribe();

        this.addActiveScreenSubscription = this.state.blob$
            .pipe(
                tap(() => this._errorService.clear()),
                switchMap(blob => this.pdf.loadDocument(blob, this.state.file(), () => this.state.reloadBlob())),
            )
            .subscribe();

        this.addActiveScreenSubscription = this.pdfProxyService.manualAnnotationRequested$.subscribe($event => {
            this.openManualAnnotationDialog($event);
        });

        this.addActiveScreenSubscription = this.pdfProxyService.redactTextRequested$.subscribe($event => {
            this.#openRedactTextDialog($event).then();
        });

        this.addActiveScreenSubscription = this.pdfProxyService.pageChanged$.subscribe(page =>
            this._ngZone.run(() => this.#updateQueryParamsPage(page)),
        );

        this.addActiveScreenSubscription = this._viewerHeaderService.toggleLoadAnnotations$.subscribe();

        this.addActiveScreenSubscription = this.pdfProxyService.annotationSelected$.subscribe();

        this.addActiveScreenSubscription = this._viewerHeaderService.events$
            .pipe(
                filter(event => event.type === ViewerEvents.LOAD_ALL_ANNOTATIONS),
                switchMap(() => {
                    // TODO: this switchMap is ugly, to be refactored
                    const annotations = this._fileDataService.annotations();
                    const showWarning = !this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning);
                    const annotationsExceedThreshold = annotations.length >= this.configService.values.ANNOTATIONS_THRESHOLD;

                    if (annotationsExceedThreshold && showWarning) {
                        return this.#annotationsExceedingThresholdWarning(annotations);
                    }

                    return of([true, annotations] as const);
                }),
                filter(([confirmed]) => confirmed),
                map(([, annotations]) => {
                    this.#loadAllAnnotationsEnabled = true;
                    this.drawChangedAnnotations([], annotations).then(() => {
                        this._toaster.success(_('load-all-annotations-success'));
                        this._viewerHeaderService.disableLoadAllAnnotations();
                    });
                }),
            )
            .subscribe();

        this.addActiveScreenSubscription = this._readableRedactionsService.active$.pipe(map(() => this.updateViewMode())).subscribe();

        this.addActiveScreenSubscription = combineLatest([this._viewModeService.viewMode$, this.state.file$, this._documentViewer.loaded$])
            .pipe(
                map(([viewMode, file]) => {
                    if (viewMode === 'REDACTED' && !this._readableRedactionsService.active) {
                        this._readableRedactionsService.setCustomDrawHandler();
                    } else {
                        this._readableRedactionsService.restoreDraw();
                    }
                    return ['STANDARD', 'TEXT_HIGHLIGHTS'].includes(viewMode) && this.permissionsService.canRotatePage(file);
                }),
                tap(canRotate =>
                    canRotate ? this._viewerHeaderService.enableRotationButtons() : this._viewerHeaderService.disableRotationButtons(),
                ),
            )
            .subscribe();

        const selectedTextEffect = this._documentViewer.selectedText$.pipe(
            tap(selectedText => {
                const canPerformActions = this.pdfProxyService.canPerformActions();
                const isCurrentPageExcluded = this.state.file().isPageExcluded(this.pdf.currentPage());

                if ((selectedText.length > 2 || this._isJapaneseString(selectedText)) && canPerformActions && !isCurrentPageExcluded) {
                    this.pdf.enable(textActions);
                } else {
                    this.pdf.disable(textActions);
                }
            }),
        );
        this.addActiveScreenSubscription = selectedTextEffect.subscribe();
    }

    #handleDeletedDossier(): void {
        const error = new CustomError(
            _('error.deleted-entity.file-dossier.label'),
            _('error.deleted-entity.file-dossier.action'),
            'iqser:expand',
        );
        this._errorService.set(error);
    }

    #handleDeletedFile(): void {
        const error = new CustomError(
            _('error.deleted-entity.file.label'),
            _('error.deleted-entity.file.action'),
            'iqser:expand',
            null,
            () => this.#navigateToDossier(),
        );
        this._errorService.set(error);
    }

    #cleanupAndRedrawAnnotations(newAnnotations: List<AnnotationWrapper>) {
        if (!newAnnotations.length) {
            return undefined;
        }

        const currentFilters = this._filterService.getGroup('primaryFilters')?.filters || [];
        this.#rebuildFilters();

        if (currentFilters) {
            setTimeout(() => {
                this.#handleDeltaAnnotationFilters(currentFilters);
            }, 100);
        }

        return this._annotationDrawService.draw(newAnnotations, this._skippedService.hideSkipped(), this.state.dossierTemplateId);
    }

    #handleDeltaAnnotationFilters(currentFilters: NestedFilter[]) {
        const primaryFilterGroup = this._filterService.getGroup('primaryFilters');
        const primaryFilters = primaryFilterGroup.filters;
        const secondaryFilters = this._filterService.getGroup('secondaryFilters').filters;
        const hasAnyFilterSet = [...primaryFilters, ...secondaryFilters].some(f => f.checked || f.indeterminate);
        const autoExpandFilters = this.userPreferenceService.getAutoExpandFiltersOnActions();
        const isReviewer = this.permissionsService.isFileAssignee(this.state.file());
        const shouldExpandFilters = hasAnyFilterSet && autoExpandFilters && isReviewer;

        if (!shouldExpandFilters) {
            return;
        }

        const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter();

        handleFilterDelta(currentFilters, newPageSpecificFilters, primaryFilters);
        this._filterService.addFilterGroup({
            ...primaryFilterGroup,
            filters: primaryFilters,
        });
    }

    #openFullScreen() {
        const documentElement = document.documentElement;
        if (documentElement.requestFullscreen) {
            documentElement.requestFullscreen().then();
        }
    }

    #navigateToDossier() {
        this._logger.info('Navigating to ', this.state.dossier().dossierName);
        return this._router.navigate([`/${this._tenantsService.activeTenantId}${this.state.dossier().routerLink}`]);
    }

    #highlightSelectedAnnotations(newAnnotations: AnnotationWrapper[]) {
        const annotationsIds = newAnnotations.map(annotation => annotation.id);
        const selected = this._listingService.selected.filter(a => annotationsIds.includes(a.id));
        const annotations = this._annotationManager.get(selected);
        this._annotationManager.select(annotations);
    }

    private _isJapaneseString(text: string) {
        return text.match(/[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf]/);
    }

    #restoreOldFilters() {
        combineLatest([
            this._filterService.getGroup$('primaryFilters').pipe(first(filterGroup => !!filterGroup?.filters.length)),
            this._filterService.getGroup$('secondaryFilters').pipe(first(secondaryFilters => !!secondaryFilters?.filters.length)),
        ]).subscribe(([primaryFilters, secondaryFilters]) => {
            const localStorageFiltersString = localStorage.getItem('workload-filters') ?? '{}';
            const localStorageFilters = JSON.parse(localStorageFiltersString)[this.fileId];
            if (localStorageFilters) {
                copyLocalStorageFiltersValues(primaryFilters.filters, localStorageFilters.primaryFilters);
                copyLocalStorageFiltersValues(secondaryFilters.filters, localStorageFilters.secondaryFilters);
            }
        });
    }

    #openRssDialogIfDefault() {
        if (this.permissionsService.canViewRssDialog() && this.userPreferenceService.getOpenScmDialogByDefault()) {
            this.openRSSView();
        }
    }

    #getRedactTextDialog(data: RedactTextData) {
        if (this.#isDocumine) {
            return this._iqserDialog.openDefault(AddAnnotationDialogComponent, { data });
        }

        const hint = data.manualRedactionEntryWrapper.type === ManualRedactionEntryTypes.HINT;
        if (hint) {
            return this._iqserDialog.openDefault(AddHintDialogComponent, { data });
        }
        return this._iqserDialog.openDefault(RedactTextDialogComponent, { data });
    }

    #getRedactTextDialogData(manualRedactionEntryWrapper: ManualRedactionEntryWrapper, file: File): RedactTextData {
        const dossierTemplate = this._dossierTemplatesService.find(this.state.dossierTemplateId);
        const isApprover = this.permissionsService.isApprover(this.state.dossier());
        const applyDictionaryUpdatesToAllDossiersByDefault = dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault;

        return {
            manualRedactionEntryWrapper,
            dossierId: this.dossierId,
            file,
            applyToAllDossiers: isApprover ? applyDictionaryUpdatesToAllDossiersByDefault : false,
            isApprover,
        };
    }
}
