import { MarkupEntity, MarkupEntityConstructorArgs } from './MarkupEntity';
import { MarkupSheet2D } from './MarkupSheet2D';
import { MarkupProperty, MarkupStyle } from '../MarkupEntityTypes';
import { MergableObjectEventArgs, MergableObjectEventSource } from '../../MergableSet';
import { Color3, Color4, Vector3 } from '../../loader/babylonjs-import';

/**
 * Style for the markup text.
 */
export interface MarkupTextStyle {
    /** Background color for the markup text */
    backgroundColor: Color4;
}

/**
 * Class representing a markup text in the markup API.
 * WARNING: MarkupText does not sanitize any text passed to it, if you are using MarkupText in conjunction with Html2CanvasText make sure to use a sanitizer (for example DOMPurify).
 */
export class MarkupText extends MarkupEntity implements MarkupStyle<MarkupTextStyle> {
    style = { backgroundColor: new Color4(...Color3.White().asArray(), 1.0) };
    position: Vector3;
    normal: Vector3;
    scale: Vector3 | number;
    text: string;
    rotation = 0;

    /**
     * Do not use. Use {@link MarkupSheet2D.add} instead.
     * @hidden
     */
    public constructor({
        id,
        sheet,
        properties,
        style,
        position,
        normal,
        scale,
        text,
        rotation
    }: MarkupEntityConstructorArgs & {
        style: MarkupTextStyle;
        position: Vector3;
        normal: Vector3;
        scale: Vector3 | number;
        text: string;
        rotation: number;
    }) {
        super(id, sheet, properties);
        this.style = style;
        this.position = position;
        this.normal = normal;
        this.text = text;
        this.scale = scale;
        this.rotation = rotation;
    }

    /** @inheritDoc */
    copyTo(sheet: MarkupSheet2D): MarkupText {
        return sheet.add(
            MarkupText,
            {
                style: { backgroundColor: this.style.backgroundColor.clone() },
                position: this.position.clone(),
                normal: this.normal.clone(),
                scale: this.scale instanceof Vector3 ? this.scale.clone() : this.scale,
                text: this.text,
                rotation: this.rotation
            },
            new Map<string, MarkupProperty>(this.properties)
        );
    }

    /** @inheritDoc */
    onUpdate(o: MergableObjectEventArgs<MarkupText>): void {
        if (o.eventSource === MergableObjectEventSource.Remote && !o.conflictReason && o.remote) {
            this.copyFrom(o.remote);
        }

        super.onUpdate(o);
    }

    /**Copies data of other markup text into current */
    copyFrom(text: MarkupText): void {
        this.style = { backgroundColor: text.style.backgroundColor.clone() };
        this.position = text.position.clone();
        this.normal = text.normal.clone();
        this.scale = text.scale instanceof Vector3 ? text.scale.clone() : text.scale;
        this.text = text.text;
        this.rotation = text.rotation;
        this.properties.clear();
        text.properties.forEach((value: MarkupProperty, key: string) => {
            this.properties.set(key, value);
        });
    }

    /** @inheritDoc */
    clone(): MarkupText {
        const area = new MarkupText({
            id: this.id,
            sheet: this.sheet,
            properties: new Map<string, MarkupProperty>(this.properties),
            style: { backgroundColor: this.style.backgroundColor.clone() },
            position: this.position.clone(),
            normal: this.normal.clone(),
            scale: this.scale instanceof Vector3 ? this.scale.clone() : this.scale,
            text: this.text,
            rotation: this.rotation
        });
        return area;
    }

    /** @inheritDoc */
    isEqual(o: MarkupText): boolean {
        const epsilon = 0.01;
        return (
            o.id === this.id &&
            o.sheet.isEqual(this.sheet) &&
            o.properties.size === this.properties.size &&
            [...this.properties].every(([key, value]) => value === o.properties.get(key)) &&
            o.text === this.text &&
            o.position.equalsWithEpsilon(this.position, epsilon) &&
            o.normal.equalsWithEpsilon(this.normal, epsilon) &&
            o.rotation === this.rotation &&
            this.scalePropertiesEqual(o, epsilon)
        );
    }

    private scalePropertiesEqual(o: MarkupText, epsilon = 0.01): boolean {
        if (typeof this.scale !== typeof o.scale) {
            return false;
        } else if (typeof this.scale === 'number') {
            return this.scale === o.scale;
        }

        return this.scale.equalsWithEpsilon(o.scale as Vector3, epsilon);
    }
}
