import { ChangeDetectorRef, Component, computed, effect, ElementRef, HostListener, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { CircleButtonTypes, getConfig, IconButtonTypes } from '@iqser/common-ui';
import { FilterService, INestedFilter } from '@iqser/common-ui/lib/filtering';
import { AutoUnsubscribe, bool, Debounce, IqserEventTarget } from '@iqser/common-ui/lib/utils';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { ListItem } from '@models/file/list-item';
import { UserPreferenceService } from '@users/user-preference.service';
import { getLocalStorageDataByFileId } from '@utils/local-storage';
import { combineLatest, delay, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import scrollIntoView from 'scroll-into-view-if-needed';
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 { AnnotationProcessingService } from '../../services/annotation-processing.service';
import { AnnotationsListingService } from '../../services/annotations-listing.service';
import { DocumentInfoService } from '../../services/document-info.service';
import { ExcludedPagesService } from '../../services/excluded-pages.service';
import { FileDataService } from '../../services/file-data.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { MultiSelectService } from '../../services/multi-select.service';
import { SkippedService } from '../../services/skipped.service';
import { ViewModeService } from '../../services/view-mode.service';

const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];

@Component({
    selector: 'redaction-file-workload',
    templateUrl: './file-workload.component.html',
    styleUrls: ['./file-workload.component.scss'],
})
export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, OnDestroy {
    @ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef;
    @ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef;
    readonly #isDocumine = getConfig().IS_DOCUMINE;
    readonly #isIqserDevMode = this._userPreferenceService.isIqserDevMode;
    readonly iconButtonTypes = IconButtonTypes;
    readonly circleButtonTypes = CircleButtonTypes;
    displayedAnnotations = new Map<number, AnnotationWrapper[]>();
    displayedPages: number[] = [];
    pagesPanelActive = true;
    readonly displayedAnnotations$: Observable<Map<number, ListItem<AnnotationWrapper>[]>>;
    readonly title = computed(() =>
        this.viewModeService.isEarmarks() ? _('file-preview.tabs.highlights.label') : _('file-preview.tabs.annotations.label'),
    );
    readonly currentPageIsExcluded = computed(() => this.state.file().excludedPages.includes(this.pdf.currentPage()));

    constructor(
        readonly filterService: FilterService,
        readonly skippedService: SkippedService,
        private readonly _dialog: MatDialog,
        readonly state: FilePreviewStateService,
        readonly pdf: PdfViewer,
        readonly fileDataService: FileDataService,
        readonly viewModeService: ViewModeService,
        readonly multiSelectService: MultiSelectService,
        readonly annotationManager: REDAnnotationManager,
        private readonly _documentViewer: REDDocumentViewer,
        readonly documentInfoService: DocumentInfoService,
        readonly listingService: AnnotationsListingService,
        readonly excludedPagesService: ExcludedPagesService,
        private readonly _changeDetectorRef: ChangeDetectorRef,
        private readonly _annotationProcessingService: AnnotationProcessingService,
        private readonly _pageRotationService: PageRotationService,
        private readonly _userPreferenceService: UserPreferenceService,
    ) {
        super();

        // TODO: ngOnDetach is not called here, so we need to unsubscribe manually
        this.addActiveScreenSubscription = this.pdf.currentPage$.subscribe(pageNumber => {
            this._scrollViews();
            this.scrollAnnotationsToPage(pageNumber, 'always');
        });

        this.addActiveScreenSubscription = this.listingService.selected$.subscribe(annotationIds => {
            if (annotationIds.length > 0) {
                this.pagesPanelActive = false;
            }
            this.scrollToSelectedAnnotation();
        });

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

        this.displayedAnnotations$ = this._displayedAnnotations$;

        effect(() => {
            if (this.multiSelectService.inactive()) {
                this.annotationManager.deselect();
            }
        });

        effect(() => {
            this.viewModeService.viewMode();
            this._scrollViews();
        });

        effect(
            () => {
                if (this.excludedPagesService.shown()) {
                    this.#disableMultiSelectAndDocumentInfo();
                }
            },
            { allowSignalWrites: true },
        );
    }

    get activeAnnotations(): AnnotationWrapper[] {
        return this.displayedAnnotations.get(this.pdf.currentPage()) || [];
    }

    private get _firstSelectedAnnotation() {
        return this.listingService.selected.length ? this.listingService.selected[0] : null;
    }

    private get _displayedAnnotations$(): Observable<Map<number, ListItem<AnnotationWrapper>[]>> {
        const primary$ = this.filterService.getFilterModels$('primaryFilters');
        const secondary$ = this.filterService.getFilterModels$('secondaryFilters');

        return combineLatest([
            this.fileDataService.all$,
            primary$,
            secondary$,
            this.listingService.selected$,
            this._pageRotationService.rotations$,
        ]).pipe(
            delay(0),
            map(([annotations, primary, secondary]) => this.#filterAnnotations(annotations, primary, secondary)),
            map(annotations => this.#mapListItemsFromAnnotationWrapperArray(annotations)),
        );
    }

    get #allPages() {
        return Array.from({ length: this.state.file()?.numberOfPages }, (_x, i) => i + 1);
    }

    private static _scrollToFirstElement(elements: HTMLElement[], mode: 'always' | 'if-needed' = 'if-needed') {
        if (elements.length > 0) {
            scrollIntoView(elements[0], {
                behavior: 'smooth',
                scrollMode: mode,
                block: 'start',
                inline: 'start',
            });
        }
    }

    ngOnInit(): void {
        setTimeout(() => {
            const showExcludePages = getLocalStorageDataByFileId(this.state.file()?.id, 'show-exclude-pages') ?? false;
            if (showExcludePages) {
                this.excludedPagesService.show();
            }

            const showDocumentInfo = getLocalStorageDataByFileId(this.state.file()?.id, 'show-document-info') ?? false;
            if (showDocumentInfo) {
                this.documentInfoService.show();
            }
        });
    }

    selectAllOnActivePage() {
        this.listingService.selectAnnotations(this.activeAnnotations);
    }

    deselectAllOnActivePage(): void {
        this.listingService.deselect(this.activeAnnotations);
        this.annotationManager.deselect(this.activeAnnotations);
    }

    @HostListener('window:keyup', ['$event'])
    handleKeyEvent($event: KeyboardEvent): void {
        if (
            !ALL_HOTKEY_ARRAY.includes($event.key) ||
            this._dialog.openDialogs.length ||
            ($event.target as IqserEventTarget).localName === 'input'
        ) {
            return;
        }

        if (!$event.metaKey && !$event.ctrlKey && $event.key === 'ArrowLeft') {
            this.pagesPanelActive = true;
            this._changeDetectorRef.markForCheck();
            return;
        }

        if (!$event.metaKey && !$event.ctrlKey && $event.key === 'ArrowRight') {
            this.pagesPanelActive = false;
            // if we activated annotationsPanel -
            // select first annotation from this page in case there is no
            // selected annotation on this page and not in multi select mode
            if (!this.pagesPanelActive && this.multiSelectService.inactive()) {
                this._documentViewer.clearSelection();
                this.#selectFirstAnnotationOnCurrentPageIfNecessary();
            }
            this._changeDetectorRef.markForCheck();
            return;
        }

        if (!this.pagesPanelActive) {
            // Disable annotation navigation in multi select mode
            // => TODO: maybe implement selection on enter?
            if (this.multiSelectService.inactive()) {
                this.navigateAnnotations($event);
            }
        } else {
            this.#navigatePages($event);
        }

        this._changeDetectorRef.markForCheck();
    }

    scrollAnnotations(): void {
        const currentPage = this.pdf.currentPage();
        if (this._firstSelectedAnnotation?.pageNumber === currentPage) {
            return;
        }
        this.scrollAnnotationsToPage(currentPage, 'always');
    }

    scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed'): void {
        if (this._annotationsElement) {
            const elements: HTMLElement[] = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`);
            FileWorkloadComponent._scrollToFirstElement(elements, mode);
        }
    }

    @Debounce()
    scrollToSelectedAnnotation(): void {
        if (this.listingService.selected.length === 0 || !this._annotationsElement) {
            return;
        }
        const elements: HTMLElement[] = this._annotationsElement.nativeElement.querySelectorAll(
            `[annotation-id="${this._firstSelectedAnnotation?.id}"]`,
        );
        FileWorkloadComponent._scrollToFirstElement(elements);
    }

    scrollQuickNavigation(): void {
        const currentPage = this.pdf.currentPage();
        let quickNavPageIndex = this.displayedPages.findIndex(p => p >= currentPage);
        if (quickNavPageIndex === -1 || this.displayedPages[quickNavPageIndex] !== currentPage) {
            quickNavPageIndex = Math.max(0, quickNavPageIndex - 1);
        }
        this.#scrollQuickNavigationToPage(this.displayedPages[quickNavPageIndex]);
    }

    scrollQuickNavFirst(): void {
        this.pdf.navigateTo(1);
    }

    scrollQuickNavLast() {
        this.pdf.navigateTo(this.state.file().numberOfPages);
    }

    preventKeyDefault($event: KeyboardEvent): void {
        if (COMMAND_KEY_ARRAY.includes($event.key) && !(($event.target as any).localName === 'input')) {
            $event.preventDefault();
        }
    }

    jumpToPreviousWithAnnotations(): void {
        this.pdf.navigateTo(this.#prevPageWithAnnotations());
    }

    jumpToNextWithAnnotations(): void {
        this.pdf.navigateTo(this.#nextPageWithAnnotations());
    }

    navigateAnnotations($event: KeyboardEvent) {
        const currentPage = this.pdf.currentPage();
        if (!this._firstSelectedAnnotation || currentPage !== this._firstSelectedAnnotation.pageNumber) {
            if (this.displayedPages.indexOf(currentPage) !== -1) {
                // Displayed page has annotations
                return this.listingService.selectAnnotations(this.activeAnnotations ? this.activeAnnotations[0] : null);
            }
            // Displayed page doesn't have annotations
            if ($event.key === 'ArrowDown') {
                const nextPage = this.#nextPageWithAnnotations();

                return this.listingService.selectAnnotations(this.displayedAnnotations.get(nextPage)[0]);
            }

            const prevPage = this.#prevPageWithAnnotations();
            const prevPageAnnotations = this.displayedAnnotations.get(prevPage);

            return this.listingService.selectAnnotations(prevPageAnnotations[prevPageAnnotations.length - 1]);
        }

        const page = this._firstSelectedAnnotation.pageNumber;
        const pageIdx = this.displayedPages.indexOf(page);
        const nextPageIdx = pageIdx + 1;
        const previousPageIdx = pageIdx - 1;
        const annotationsOnPage = this.displayedAnnotations.get(page);
        const idx = annotationsOnPage.findIndex(a => a.id === this._firstSelectedAnnotation.id);

        if ($event.key === 'ArrowDown') {
            if (idx + 1 !== annotationsOnPage.length) {
                // If not last item in page
                this.listingService.selectAnnotations(annotationsOnPage[idx + 1]);
            } else if (nextPageIdx < this.displayedPages.length) {
                // If not last page
                for (let i = nextPageIdx; i < this.displayedPages.length; i++) {
                    const nextPageAnnotations = this.displayedAnnotations.get(this.displayedPages[i]);
                    if (nextPageAnnotations) {
                        this.listingService.selectAnnotations(nextPageAnnotations[0]);
                        break;
                    }
                }
            }
            return;
        }

        if (idx !== 0) {
            // If not first item in page
            return this.listingService.selectAnnotations(annotationsOnPage[idx - 1]);
        }

        if (pageIdx) {
            // If not first page
            for (let i = previousPageIdx; i >= 0; i--) {
                const prevPageAnnotations = this.displayedAnnotations.get(this.displayedPages[i]);
                if (prevPageAnnotations) {
                    this.listingService.selectAnnotations(prevPageAnnotations[prevPageAnnotations.length - 1]);
                    break;
                }
            }
        }
    }

    @Debounce(0)
    private _scrollViews() {
        this.scrollQuickNavigation();
        this.scrollAnnotations();
    }

    #disableMultiSelectAndDocumentInfo(): void {
        this.multiSelectService.deactivate();
        this.documentInfoService.hide();
    }

    #filterAnnotations(
        annotations: AnnotationWrapper[],
        primary: INestedFilter[],
        secondary: INestedFilter[] = [],
    ): Map<number, AnnotationWrapper[]> {
        const onlyPageWithAnnotations = this.viewModeService.onlyPagesWithAnnotations();
        if (!primary || primary.length === 0) {
            this.displayedPages = onlyPageWithAnnotations ? [] : this.#allPages;
            return;
        }

        if (this.viewModeService.isRedacted()) {
            annotations = annotations.filter(a => !bool(a.isChangeLogRemoved));
        }

        if (this.#isDocumine && !this.#isIqserDevMode) {
            annotations = annotations.filter(a => !a.isOCR);
        }

        this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(annotations, primary, secondary);
        const pagesThatDisplayAnnotations = [...this.displayedAnnotations.keys()];
        const enabledFilters = this.filterService.enabledFlatFilters;
        if (enabledFilters.some(f => f.id === 'pages-without-annotations')) {
            if (enabledFilters.length === 1 && !onlyPageWithAnnotations) {
                this.displayedPages = this.#allPages.filter(page => !pagesThatDisplayAnnotations.includes(page));
            } else {
                this.displayedPages = [];
            }
            this.displayedAnnotations.clear();
        } else if (enabledFilters.length || onlyPageWithAnnotations) {
            this.displayedPages = pagesThatDisplayAnnotations;
        } else {
            this.displayedPages = this.#allPages;
        }
        this.displayedPages.sort((a, b) => a - b);

        return this.displayedAnnotations;
    }

    #selectFirstAnnotationOnCurrentPageIfNecessary() {
        const currentPage = this.pdf.currentPage();
        if (
            (!this._firstSelectedAnnotation || currentPage !== this._firstSelectedAnnotation.pageNumber) &&
            this.displayedPages.indexOf(currentPage) >= 0 &&
            this.activeAnnotations.length > 0
        ) {
            this.listingService.selectAnnotations(this.activeAnnotations[0]);
        }
    }

    #navigatePages($event: KeyboardEvent) {
        const pageIdx = this.displayedPages.indexOf(this.pdf.currentPage());

        if ($event.key !== 'ArrowDown') {
            if (pageIdx === -1) {
                // If active page doesn't have annotations
                const prevPage = this.#prevPageWithAnnotations();
                if (prevPage) {
                    this.pdf.navigateTo(prevPage);
                }
                return;
            }
            // If active page has annotations
            if (pageIdx !== 0) {
                this.pdf.navigateTo(this.displayedPages[pageIdx - 1]);
            }
            return;
        }
        if (pageIdx === -1) {
            // If active page doesn't have annotations
            const nextPage = this.#nextPageWithAnnotations();
            if (nextPage) {
                this.pdf.navigateTo(nextPage);
            }
            return;
        }
        // If active page has annotations
        if (pageIdx !== this.displayedPages.length - 1) {
            this.pdf.navigateTo(this.displayedPages[pageIdx + 1]);
        }
    }

    #nextPageWithAnnotations() {
        let idx = 0;
        for (const page of this.displayedPages) {
            if (page > this.pdf.currentPage() && this.displayedAnnotations.get(page)) {
                break;
            }
            ++idx;
        }
        return idx < this.displayedPages.length ? this.displayedPages[idx] : this.displayedPages[this.displayedPages.length - 1];
    }

    #prevPageWithAnnotations() {
        let idx = this.displayedPages.length - 1;
        const reverseDisplayedPages = [...this.displayedPages].reverse();
        for (const page of reverseDisplayedPages) {
            if (page < this.pdf.currentPage() && this.displayedAnnotations.get(page)) {
                break;
            }
            --idx;
        }
        return idx >= 0 ? this.displayedPages[idx] : this.displayedPages[0];
    }

    #scrollQuickNavigationToPage(page: number) {
        if (this._quickNavigationElement) {
            const elements: HTMLElement[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`);
            FileWorkloadComponent._scrollToFirstElement(elements);
        }
    }

    #mapListItemsFromAnnotationWrapperArray(annotations: Map<number, AnnotationWrapper[]>) {
        const listItemsMap = new Map<number, ListItem<AnnotationWrapper>[]>();
        if (!annotations) {
            return listItemsMap;
        }
        [...annotations.keys()].forEach(key => {
            const newValue = annotations.get(key).map(annotation => ({
                item: annotation,
                isSelected: this.listingService.isSelected(annotation),
            }));
            listItemsMap.set(key, newValue);
        });
        return listItemsMap;
    }
}
