import { computed, effect, inject, Injectable, NgZone } from '@angular/core';
import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { BASE_HREF_FN, isJustOne, shareDistinctLast } from '@iqser/common-ui/lib/utils';
import {
    ManualRedactionEntryType,
    ManualRedactionEntryTypes,
    ManualRedactionEntryWrapper,
} from '@models/file/manual-redaction-entry.wrapper';
import { TranslateService } from '@ngx-translate/core';
import { Core } from '@pdftron/webviewer';
import { IHeaderElement, IManualRedactionEntry, User } from '@red/domain';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { PermissionsService } from '@services/permissions.service';
import { Roles } from '@users/roles';
import { UserPreferenceService } from '@users/user-preference.service';
import { NGXLogger } from 'ngx-logger';
import { Subject } from 'rxjs';
import { map, tap } from 'rxjs/operators';
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 { PdfViewer } from '../../pdf-viewer/services/pdf-viewer.service';
import { ViewerHeaderService } from '../../pdf-viewer/services/viewer-header.service';
import {
    ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED,
    AnnotationToolNames,
    HEADER_ITEMS_TO_TOGGLE,
    TEXT_POPUPS_TO_TOGGLE,
} from '../../pdf-viewer/utils/constants';
import { HeaderElements, HeaderElementType, TextPopups } from '../utils/constants';
import { toPosition } from '../utils/pdf-calculation.utils';
import { AnnotationActionsService } from './annotation-actions.service';
import { AnnotationsListingService } from './annotations-listing.service';
import { FileDataService } from './file-data.service';
import { FilePreviewStateService } from './file-preview-state.service';
import { ManualRedactionService } from './manual-redaction.service';
import { MultiSelectService } from './multi-select.service';
import { PdfAnnotationActionsService } from './pdf-annotation-actions.service';
import { ViewModeService } from './view-mode.service';
import Annotation = Core.Annotations.Annotation;
import Quad = Core.Math.Quad;

@Injectable()
export class PdfProxyService {
    readonly #convertPath = inject(BASE_HREF_FN);
    readonly #visibilityOffIcon = this.#convertPath('/assets/icons/general/visibility-off.svg');
    readonly #visibilityIcon = this.#convertPath('/assets/icons/general/visibility.svg');
    readonly #falsePositiveIcon = this.#convertPath('/assets/icons/general/pdftron-action-false-positive.svg');
    readonly #addRedactionIcon = this._iqserPermissionsService.has(Roles.getRss)
        ? this.#convertPath('/assets/icons/general/pdftron-action-add-annotation.svg')
        : this.#convertPath('/assets/icons/general/pdftron-action-add-redaction.svg');
    readonly #isDocumine = getConfig().IS_DOCUMINE;
    readonly #addHintIcon = this.#convertPath('/assets/icons/general/pdftron-action-add-hint.svg');
    readonly annotationSelected$ = this.#annotationSelected$;
    readonly manualAnnotationRequested$ = new Subject<ManualRedactionEntryWrapper>();
    readonly redactTextRequested$ = new Subject<ManualRedactionEntryWrapper>();
    readonly currentUser = getCurrentUser<User>();
    readonly pageChanged$ = this._pdf.pageChanged$.pipe(
        tap(() => this.#handleExcludedPageActions()),
        tap(() => this._pdf.resetAnnotationActions()),
        shareDistinctLast(),
    );
    readonly canPerformActions = computed(() => {
        const isStandard = this._viewModeService.isStandard();
        const isAllowed = this._permissionsService.canPerformAnnotationActions(this._state.file(), this._state.dossier());
        return isAllowed && isStandard;
    });

    constructor(
        private readonly _translateService: TranslateService,
        private readonly _manualRedactionService: ManualRedactionService,
        private readonly _annotationsActionsService: AnnotationActionsService,
        private readonly _ngZone: NgZone,
        private readonly _userPreferenceService: UserPreferenceService,
        private readonly _annotationDrawService: AnnotationDrawService,
        private readonly _pdfAnnotationActionsService: PdfAnnotationActionsService,
        private readonly _fileDataService: FileDataService,
        private readonly _viewerHeaderService: ViewerHeaderService,
        private readonly _viewModeService: ViewModeService,
        private readonly _permissionsService: PermissionsService,
        private readonly _documentViewer: REDDocumentViewer,
        private readonly _annotationManager: REDAnnotationManager,
        private readonly _pdf: PdfViewer,
        private readonly _logger: NGXLogger,
        private readonly _state: FilePreviewStateService,
        private readonly _multiSelectService: MultiSelectService,
        private readonly _listingService: AnnotationsListingService,
        private readonly _dictionariesMapService: DictionariesMapService,
        private readonly _iqserPermissionsService: IqserPermissionsService,
    ) {
        effect(
            () => {
                const canPerformActions = this.canPerformActions();
                this._pdf.isCompareMode();

                this.#configureTextPopup();
                this.#handleExcludedPageActions();

                if (canPerformActions) {
                    this.#enableActions();
                } else {
                    this.#disableActions();
                    this.#deactivateMultiSelect();
                }
            },
            { allowSignalWrites: true },
        );

        effect(() => {
            if (this._viewModeService.isRedacted()) {
                this._viewerHeaderService.enable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
            } else {
                this._viewerHeaderService.disable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
            }
        });
    }

    get #annotationSelected$() {
        return this._annotationManager.annotationSelected$.pipe(
            map(value => this.#processSelectedAnnotations(...value)),
            tap(annotations => this.handleAnnotationSelected(annotations)),
        );
    }

    handleAnnotationSelected(annotationIds: string[]) {
        this._listingService.setSelected(annotationIds.map(id => this._fileDataService.find(id)).filter(ann => ann !== undefined));
    }

    configureElements() {
        const hexColor = this._dictionariesMapService.get(this._state.dossierTemplateId, 'manual').hexColor;
        const color = this._annotationDrawService.convertColor(hexColor);
        this._documentViewer.setRectangleToolStyles(color);
    }

    #configureRectangleAnnotationPopup(annotation: Annotation) {
        if (!this._pdf.isCompareMode() || annotation.getPageNumber() % 2 === 1) {
            const addRectangleButton = {
                type: 'actionButton',
                dataElement: TextPopups.ADD_RECTANGLE,
                img: this.#addRedactionIcon,
                title: this.#getTitle(ManualRedactionEntryTypes.REDACTION),
                onClick: () => this._ngZone.run(() => this.#addRectangleManualRedaction()),
            };

            this._pdf.instance.UI.annotationPopup.add([addRectangleButton]);
        }
    }

    #addRectangleManualRedaction() {
        const activeAnnotation = this._annotationManager.selected[0];
        const activePage = activeAnnotation.getPageNumber();
        const quads = [this._annotationDrawService.annotationToQuads(activeAnnotation)];
        const manualRedactionEntry = this.#getManualRedaction({ [activePage]: quads });
        this.#cleanUpRectangleButtonState();

        this.manualAnnotationRequested$.next({ manualRedactionEntry, type: 'REDACTION' });
    }

    #cleanUpRectangleButtonState() {
        this._viewerHeaderService.disable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]);
        this._viewerHeaderService.enable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]);
    }

    #configureTextPopup() {
        if (this._state.file().isApproved) {
            this._pdf.configureTextPopups([]);
        }

        const popups: IHeaderElement[] = [];
        // Adding directly to the false-positive dict is only available in dev-mode
        if (this._userPreferenceService.isIqserDevMode) {
            popups.push({
                type: 'actionButton',
                dataElement: TextPopups.ADD_FALSE_POSITIVE,
                img: this.#falsePositiveIcon,
                title: this.#getTitle(ManualRedactionEntryTypes.FALSE_POSITIVE),
                onClick: () => this._ngZone.run(() => this.#addManualRedactionOfType(ManualRedactionEntryTypes.FALSE_POSITIVE)),
            });
        }

        if (this._iqserPermissionsService.has(Roles.redactions.write)) {
            popups.push({
                type: 'actionButton',
                dataElement: TextPopups.REDACT_TEXT,
                img: this.#addRedactionIcon,
                title: this.#getTitle(ManualRedactionEntryTypes.REDACT),
                onClick: () => this._ngZone.run(() => this.#redactText(ManualRedactionEntryTypes.REDACT)),
            });

            if (!this.#isDocumine) {
                popups.push({
                    type: 'actionButton',
                    dataElement: TextPopups.ADD_HINT,
                    img: this.#addHintIcon,
                    title: this.#getTitle(ManualRedactionEntryTypes.HINT),
                    onClick: () => this._ngZone.run(() => this.#redactText(ManualRedactionEntryTypes.HINT)),
                });
            }
        }

        this._pdf.configureTextPopups(popups);
    }

    #addManualRedactionOfType(type: ManualRedactionEntryType) {
        const selectedQuads: Record<string, Quad[]> = this._pdf.documentViewer.getSelectedTextQuads();
        const text = this._documentViewer.selectedText;
        const manualRedactionEntry = this.#getManualRedaction(selectedQuads, text, true);
        this.manualAnnotationRequested$.next({ manualRedactionEntry, type });
    }

    #redactText(type: ManualRedactionEntryType) {
        const selectedQuads: Record<string, Quad[]> = this._pdf.documentViewer.getSelectedTextQuads();
        const text = this._documentViewer.selectedText;
        const manualRedactionEntry = this.#getManualRedaction(selectedQuads, text, true);
        this.redactTextRequested$.next({ manualRedactionEntry, type });
    }

    #handleExcludedPageActions() {
        const isCurrentPageExcluded = this._state.file().isPageExcluded(this._pdf.currentPage());
        if (!isCurrentPageExcluded) {
            return;
        }

        let textPopupElementsToDisable = [...TEXT_POPUPS_TO_TOGGLE];
        let headerElementsToDisable = [...HEADER_ITEMS_TO_TOGGLE];

        if (this.canPerformActions()) {
            this._pdf.enable(ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED);
            this._viewerHeaderService.enable(ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED as HeaderElementType[]);

            textPopupElementsToDisable = textPopupElementsToDisable.filter(
                element => !ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED.includes(element),
            );
            headerElementsToDisable = headerElementsToDisable.filter(element => !ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED.includes(element));
        }

        this._pdf.disable(textPopupElementsToDisable);
        this._viewerHeaderService.disable(headerElementsToDisable);
    }

    #disableActions() {
        this._pdf.instance.UI.disableTools([AnnotationToolNames.AnnotationCreateRectangle]);
        this._pdf.disable(TEXT_POPUPS_TO_TOGGLE);
        this._viewerHeaderService.disable(HEADER_ITEMS_TO_TOGGLE);
    }

    #enableActions() {
        this._pdf.instance.UI.enableTools([AnnotationToolNames.AnnotationCreateRectangle]);
        this._pdf.enable(TEXT_POPUPS_TO_TOGGLE);
        this._viewerHeaderService.enable(HEADER_ITEMS_TO_TOGGLE);

        if (this._documentViewer.selectedText.length > 2) {
            this._pdf.enable([TextPopups.REDACT_TEXT, TextPopups.ADD_HINT, TextPopups.ADD_FALSE_POSITIVE]);
        }
    }

    #getManualRedaction(quads: Record<string, Quad[]>, text?: string, convertQuads = false): IManualRedactionEntry {
        const entry: IManualRedactionEntry = { positions: [] };

        for (const key of Object.keys(quads)) {
            for (const quad of quads[key]) {
                const page = parseInt(key, 10);
                const pageHeight = this._documentViewer.getHeight(page);
                entry.positions.push(toPosition(page, pageHeight, convertQuads ? this._pdf.translateQuad(page, quad) : quad));
            }
        }

        entry.value = text;
        entry.rectangle = !text;
        return entry;
    }

    #deactivateMultiSelect() {
        this._logger.info('[PDF] Deactivating multi-select');
        this._multiSelectService.deactivate();
        this._annotationManager.deselect();
        this.handleAnnotationSelected([]);
    }

    #processSelectedAnnotations(annotations: Annotation[], action: string) {
        let nextAnnotations: Annotation[];
        this._pdf.resetAnnotationActions();

        if (action === 'deselected') {
            this.#cancelResizeIfIsResizing(annotations);
            // Remove deselected annotations from selected list
            nextAnnotations = this._annotationManager.selected.filter(ann => !annotations.some(a => a.Id === ann.Id));
            this._pdf.disable(TextPopups.ADD_RECTANGLE);
            const currentPage = this._pdf.currentPage();

            if (nextAnnotations.some(a => a.getPageNumber() === currentPage)) {
                this.#configureAnnotationSpecificActions(nextAnnotations);
            }

            return nextAnnotations.map(ann => ann.Id);
        }

        if (!this._multiSelectService.enabled()) {
            // Only choose the last selected annotation, to bypass viewer multi select
            nextAnnotations = annotations;
            const notSelected = this._fileDataService.all.filter(wrapper => !nextAnnotations.some(ann => ann.Id === wrapper.id));
            this._annotationManager.deselect(notSelected);
        } else {
            // Get selected annotations from the manager, no intervention needed
            nextAnnotations = this._annotationManager.selected;
        }

        this.#configureAnnotationSpecificActions(nextAnnotations);

        if (!(annotations.length === 1 && annotations[0].ReadOnly)) {
            this._pdf.enable(TextPopups.ADD_RECTANGLE);
        } else {
            this._pdf.disable(TextPopups.ADD_RECTANGLE);
        }

        return nextAnnotations.map(ann => ann.Id);
    }

    #cancelResizeIfIsResizing(annotations: Annotation[]) {
        if (!isJustOne(annotations)) {
            return;
        }

        const annotation = annotations[0];
        const isResizing = this._annotationManager.resizingAnnotationId === annotation.Id;

        if (!isResizing) {
            return;
        }

        // When resizing a rectangle, the original annotation is removed and triggers a deselect event and a new one is created
        // So we want to skip the first deselect event.
        const wrapper = this._fileDataService.find(annotation.Id);
        if (!wrapper) {
            // It might be a recommendation,
            // which is automatically converted to redaction when resized,
            // so the original annotation(wrapper) is removed
            this._annotationManager.resizingAnnotationId = undefined;
            return;
        }

        if (
            (wrapper.rectangle || wrapper.isImage || wrapper.imported) &&
            annotation.ToolName !== AnnotationToolNames.AnnotationCreateRectangle
        ) {
            return;
        }

        this._annotationsActionsService.cancelResize(wrapper).then();
    }

    #configureAnnotationSpecificActions(viewerAnnotations: Annotation[]) {
        if (!this.canPerformActions()) {
            return;
        }

        const annotationWrappers = viewerAnnotations.map(va => this._fileDataService.find(va.Id)).filter(va => !!va);
        if (annotationWrappers.length === 0) {
            return this.#configureRectangleAnnotationPopup(viewerAnnotations[0]);
        }

        // Add hide action as last item
        const allAreImage = annotationWrappers.reduce((acc, next) => acc && next.isImage, true);
        if (allAreImage && !this._annotationManager.resizingAnnotationId) {
            const allAreVisible = viewerAnnotations.reduce((acc, next) => next.isVisible() && acc, true);

            const visibilityButton = {
                type: 'actionButton',
                img: allAreVisible ? this.#visibilityOffIcon : this.#visibilityIcon,
                title: this._translateService.instant(`annotation-actions.${allAreVisible ? 'hide' : 'show'}`),
                onClick: () => {
                    this._ngZone.run(() => {
                        if (allAreVisible) {
                            this._annotationManager.hide(viewerAnnotations);
                            this._annotationManager.addToHidden(viewerAnnotations[0].Id);
                        } else {
                            this._annotationManager.show(viewerAnnotations);
                            this._annotationManager.removeFromHidden(viewerAnnotations[0].Id);
                        }
                        this._annotationManager.deselect();
                    });
                },
            };

            this._pdf.instance.UI.annotationPopup.add([visibilityButton]);
        }

        const actions = this._pdfAnnotationActionsService.get(annotationWrappers);
        this._pdf.instance.UI.annotationPopup.add(actions);
    }

    #getTitle(type: ManualRedactionEntryType) {
        return this._translateService.instant(this._manualRedactionService.getTitle(type));
    }
}
