import { inject, Injectable, signal } from '@angular/core';
import type { List } from '@iqser/common-ui/lib/utils';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Core } from '@pdftron/webviewer';
import { getLast } from '@utils/functions';
import { NGXLogger } from 'ngx-logger';
import { Subject } from 'rxjs';
import { AnnotationToolNames } from '../utils/constants';
import { asList, getId, isStringOrWrapper } from '../utils/functions';
import { AnnotationPredicate, DeleteAnnotationsOptions } from '../utils/types';
import AnnotationManager = Core.AnnotationManager;
import Annotation = Core.Annotations.Annotation;

const MODIFY_ACTION = 'modify';
const RESIZE_OPTION = 'resize';
const MOVE_OPTION = 'move';

@Injectable()
export class REDAnnotationManager {
    readonly #hidden = signal(new Set<string>());
    #manager: AnnotationManager;
    readonly #logger = inject(NGXLogger);
    readonly #annotationSelected$ = new Subject<[Annotation[], string]>();
    readonly annotationSelected$ = this.#annotationSelected$.asObservable();
    resizingAnnotationId?: string = undefined;
    annotationHasBeenResized?: boolean = false;
    readonly hidden = this.#hidden.asReadonly();

    get selected() {
        return this.#manager.getSelectedAnnotations();
    }

    get annotations() {
        return this.#get();
    }

    addToHidden(value: string) {
        this.#hidden.mutate(set => set.add(value));
    }

    removeFromHidden(value: string) {
        this.#hidden.mutate(set => set.delete(value));
    }

    init(annotationManager: AnnotationManager) {
        this.#manager = annotationManager;
        this.#listenForAnnotationSelected();
        this.#autoSelectRectangleAfterCreation();
    }

    isHidden(annotationId: string) {
        return this.hidden().has(annotationId);
    }

    delete(annotations?: List | List<AnnotationWrapper> | string | AnnotationWrapper) {
        const items = isStringOrWrapper(annotations) ? [this.get(annotations)] : this.get(annotations);
        if (!items.length) {
            return;
        }
        const options: DeleteAnnotationsOptions = { force: true };
        this.#manager.deleteAnnotations(items, options);
    }

    get(annotation: AnnotationWrapper | string): Annotation;

    get(annotations: List | List<AnnotationWrapper>): Annotation[];

    get(predicate?: AnnotationPredicate): Annotation[];

    get(argument?: AnnotationPredicate | List<AnnotationWrapper> | List | AnnotationWrapper | string): Annotation | Annotation[] {
        if (isStringOrWrapper(argument)) {
            return this.#getById(argument);
        }

        const isList = argument instanceof Array;
        if (isList) {
            return this.#getByIds(argument);
        }

        const isNotPredicate = typeof argument !== 'function';
        if (isNotPredicate) {
            return this.#getById(argument);
        }

        return this.#get(argument);
    }

    deselect(annotation: string | AnnotationWrapper): void;
    deselect(annotations?: List | List<AnnotationWrapper>): void;
    deselect(argument?: string | AnnotationWrapper | List | List<AnnotationWrapper>) {
        if (!argument) {
            this.#logger.info('[ANNOTATIONS] Deselect all');
            return this.#manager.deselectAllAnnotations();
        }

        this.#logger.info('[ANNOTATIONS] Deselecting: ', argument);
        const ann = isStringOrWrapper(argument) ? [this.#getById(argument)] : this.#getByIds(argument);
        this.#manager.deselectAnnotations(ann);
    }

    hide(annotations: Annotation[]): void {
        this.#manager.hideAnnotations(annotations);
    }

    show(annotations: Annotation[]): void {
        this.#manager.showAnnotations(annotations);
    }

    jumpAndSelect(annotations: List | List<AnnotationWrapper>) {
        const annotationsFromViewer = this.get(annotations);

        this.#manager.jumpToAnnotation(getLast(annotationsFromViewer));
        this.#manager.selectAnnotations(annotationsFromViewer);
    }

    select(annotations: Annotation | Annotation[]) {
        this.#manager.selectAnnotations(asList(annotations));
    }

    redraw(annotations: Annotation | Annotation[]) {
        annotations = asList(annotations);
        annotations.forEach(annotation => this.#manager.redrawAnnotation(annotation));
    }

    add(annotations: Annotation | Annotation[]) {
        try {
            annotations = asList(annotations);
            this.#manager.addAnnotations(annotations, { imported: true });
            return this.#manager.drawAnnotationsFromList(annotations);
        } catch (e) {
            console.log(e);
        }
    }

    showHidden() {
        const hidden = this.#getByIds([...this.hidden().values()]);
        this.show(hidden);
    }

    #listenForAnnotationSelected() {
        this.#manager.addEventListener('annotationSelected', (annotations: Annotation[], action: string) => {
            this.#logger.info('[PDF] Annotation selected: ', annotations, action);
            this.#annotationSelected$.next([annotations, action]);
        });
    }

    #getById(annotation: AnnotationWrapper | string) {
        return this.#manager.getAnnotationById(getId(annotation));
    }

    #autoSelectRectangleAfterCreation() {
        this.#manager.addEventListener('annotationChanged', (annotations: Annotation[], action: string, options) => {
            this.#logger.info('[ANNOTATIONS] Annotations changed: ', annotations, action, options);

            if (action === MODIFY_ACTION && (options.source === RESIZE_OPTION || options.source === MOVE_OPTION)) {
                this.annotationHasBeenResized = true;
                this.#annotationSelected$.next([annotations, action]);
            }

            // when a rectangle is drawn,
            // it returns one annotation with tool name 'AnnotationCreateRectangle;
            // this will auto select rectangle after drawing
            const annotation = annotations.length === 1 ? annotations[0] : undefined;
            if (annotation && annotation.ToolName === AnnotationToolNames.AnnotationCreateRectangle) {
                this.#manager.selectAnnotation(annotation);
                annotation.disableRotationControl();
            }
        });
    }

    #getByIds(annotations: List | List<AnnotationWrapper>) {
        return annotations.map((item: string | AnnotationWrapper) => this.#getById(item)).filter(a => !!a);
    }

    #get(predicate?: AnnotationPredicate) {
        const annotations = this.#manager.getAnnotationsList();
        return predicate ? annotations.filter(predicate) : annotations;
    }
}
