import { AsyncPipe, NgIf, NgTemplateOutlet } from '@angular/common';
import {
    ChangeDetectorRef,
    Component,
    computed,
    effect,
    ElementRef,
    HostListener,
    OnDestroy,
    OnInit,
    TemplateRef,
    untracked,
    viewChild,
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatIcon } from '@angular/material/icon';
import { MatTooltip } from '@angular/material/tooltip';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { EmptyStateComponent } from '@common-ui/empty-state';
import { RoundCheckboxComponent } from '@common-ui/inputs/round-checkbox/round-checkbox.component';
import {
    CapitalizePipe,
    CircleButtonComponent,
    CircleButtonTypes,
    getConfig,
    IconButtonComponent,
    IconButtonTypes,
    PreventDefaultDirective,
} from '@iqser/common-ui';
import { FilterService, INestedFilter, PopupFilterComponent } from '@iqser/common-ui/lib/filtering';
import { AutoUnsubscribe, Debounce, IqserEventTarget } from '@iqser/common-ui/lib/utils';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { ListItem } from '@models/file/list-item';
import { TranslateModule } from '@ngx-translate/core';
import { WorkflowFileStatuses } from '@red/domain';
import { workloadTranslations } from '@translations/workload-translations';
import { UserPreferenceService } from '@users/user-preference.service';
import { getLocalStorageDataByFileId } from '@utils/local-storage';
import { combineLatest, delay, Observable } from 'rxjs';
import { filter, map, tap } 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';
import { AnnotationActionsComponent } from '../annotation-actions/annotation-actions.component';
import { AnnotationsListComponent } from '../annotations-list/annotations-list.component';
import { PageExclusionComponent } from '../page-exclusion/page-exclusion.component';
import { PagesComponent } from '../pages/pages.component';
import { ReadonlyBannerComponent } from '../readonly-banner/readonly-banner.component';
import { DocumentInfoComponent } from '../document-info/document-info.component';
import { getLast } from '@utils/functions';

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'],
    standalone: true,
    imports: [
        IconButtonComponent,
        AnnotationsListComponent,
        PageExclusionComponent,
        NgIf,
        CircleButtonComponent,
        TranslateModule,
        PreventDefaultDirective,
        AsyncPipe,
        PopupFilterComponent,
        ReadonlyBannerComponent,
        RoundCheckboxComponent,
        AnnotationActionsComponent,
        MatTooltip,
        MatIcon,
        PagesComponent,
        EmptyStateComponent,
        CapitalizePipe,
        NgTemplateOutlet,
        DocumentInfoComponent,
    ],
})
export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, OnDestroy {
    readonly multiSelectTemplate = viewChild<TemplateRef<any>>('multiSelect');
    readonly annotationsList$: Observable<ListItem<AnnotationWrapper>[]>;
    readonly allPages = computed(() => Array.from({ length: this.state.file()?.numberOfPages }, (_x, i) => i + 1));
    protected readonly iconButtonTypes = IconButtonTypes;
    protected readonly circleButtonTypes = CircleButtonTypes;
    protected readonly filteredAnnotations$: Observable<Map<number, ListItem<AnnotationWrapper>[]>>;
    protected readonly title = computed(() =>
        this.viewModeService.isEarmarks() ? _('file-preview.tabs.highlights.label') : _('file-preview.tabs.annotations.label'),
    );
    protected readonly currentPageIsExcluded = computed(() => this.state.file().excludedPages.includes(this.pdf.currentPage()));
    protected readonly translations = workloadTranslations;
    protected readonly isDocumine = getConfig().IS_DOCUMINE;
    readonly showAnalysisDisabledBanner = computed(() => {
        const file = this.state.file();
        return this.isDocumine && file.excludedFromAutomaticAnalysis && file.workflowStatus !== WorkflowFileStatuses.APPROVED;
    });
    protected displayedAnnotations = new Map<number, AnnotationWrapper[]>();
    readonly activeAnnotations = computed(() => this.displayedAnnotations.get(this.pdf.currentPage()) || []);
    protected displayedPages: number[] = [];
    protected pagesPanelActive = true;
    protected enabledFilters = [];
    private readonly _annotationsElement = viewChild<ElementRef>('annotationsElement');
    private readonly _quickNavigationElement = viewChild<ElementRef>('quickNavigation');
    readonly #isIqserDevMode = this._userPreferenceService.isIqserDevMode;
    #displayedPagesChanged = false;

    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();

        effect(() => {
            this._scrollViews();
            this.scrollAnnotationsToPage(this.pdf.currentPage(), '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.filteredAnnotations$ = this._filteredAnnotations$;

        this.annotationsList$ = combineLatest([this.filteredAnnotations$, this.pdf.currentPage$]).pipe(
            map(([annotations, page]) => annotations.get(page)),
        );

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

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

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

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

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

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

    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 file = untracked(this.state.file);
            const showExcludePages = getLocalStorageDataByFileId(file?.id, 'show-exclude-pages') ?? false;
            if (showExcludePages) {
                this.excludedPagesService.show();
            }

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

    selectAllOnActivePage() {
        const activeAnnotations = untracked(this.activeAnnotations);
        this.listingService.selectAnnotations(activeAnnotations);
    }

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

    @HostListener('window:keyup', ['$event'])
    handleKeyEvent($event: KeyboardEvent): void {
        const multiSelectServiceInactive = untracked(this.multiSelectService.inactive);

        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 && multiSelectServiceInactive) {
                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 (multiSelectServiceInactive) {
                this.navigateAnnotations($event);
            }
        } else {
            this.#navigatePages($event);
        }

        this._changeDetectorRef.markForCheck();
    }

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

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

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

    scrollQuickNavigation(): void {
        const currentPage = untracked(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() {
        const file = untracked(this.state.file);
        this.pdf.navigateTo(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());
    }

    @Debounce(15)
    navigateAnnotations($event: KeyboardEvent) {
        const currentPage = untracked(this.pdf.currentPage);
        const activeAnnotations = untracked(this.activeAnnotations);
        if (!this._firstSelectedAnnotation || currentPage !== this._firstSelectedAnnotation.pageNumber) {
            if (this.displayedPages.indexOf(currentPage) !== -1) {
                // Displayed page has annotations
                return this.listingService.selectAnnotations(activeAnnotations ? 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(getLast(prevPageAnnotations));
        }

        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(getLast(prevPageAnnotations));
                    break;
                }
            }
        }
    }

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

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

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

        if (isRedacted) {
            annotations = annotations.filter(a => !a.isRemoved);
        }

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

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

        return this.displayedAnnotations;
    }

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

    #navigatePages($event: KeyboardEvent) {
        const currentPage = untracked(this.pdf.currentPage);
        const pageIdx = this.displayedPages.indexOf(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() {
        const currentPage = untracked(this.pdf.currentPage);
        let idx = 0;
        for (const page of this.displayedPages) {
            if (page > currentPage && this.displayedAnnotations.get(page)) {
                break;
            }
            ++idx;
        }
        return idx < this.displayedPages.length ? this.displayedPages[idx] : this.displayedPages[this.displayedPages.length - 1];
    }

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

    #scrollQuickNavigationToPage(page: number) {
        const quickNavigationElement = untracked(this._quickNavigationElement);
        if (quickNavigationElement) {
            const elements: HTMLElement[] = 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;
    }

    #scrollToFirstAnnotationPage(annotations: Map<number, ListItem<AnnotationWrapper>[]>) {
        const currentPage = untracked(this.pdf.currentPage);
        if (this.isDocumine && annotations.size && this.#displayedPagesChanged && !this.displayedPages.includes(currentPage)) {
            const page = annotations.keys().next().value;
            this.pdf.navigateTo(page);
        }
    }

    #setDisplayedPages(newDisplayedPages: number[]) {
        if (!this.#samePages(newDisplayedPages)) {
            this.displayedPages = newDisplayedPages;
            this.#displayedPagesChanged = true;
            return;
        }
        this.#displayedPagesChanged = false;
    }

    #samePages(newDisplayedPages: number[]) {
        return (
            this.displayedPages.length === newDisplayedPages.length &&
            this.displayedPages.every((value, index) => value === newDisplayedPages[index])
        );
    }

    @HostListener('click', ['$event'])
    clickInsideWorkloadView($event: MouseEvent) {
        $event?.stopPropagation();
    }

    @HostListener('document: click')
    clickOutsideWorkloadView() {
        if (this.multiSelectService.active() && !this._dialog.openDialogs.length) {
            this.multiSelectService.deactivate();
        }
    }
}
