/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Annotation
 */
import { Point3d, Transform, YawPitchRollAngles } from "@itwin/core-geometry";
import { TextBlock } from "./TextBlock";
/**
 * Represents a formatted block of text positioned in 2d or 3d space.
 * [TextAnnotation2d]($backend) and [TextAnnotation3d]($backend) elements store a single TextAnnotation from which their geometric representation is generated.
 * Other types of elements may store multiple TextAnnotations, positioned relative to one another.
 * The annotation's position and orientation relative to the host element's [Placement]($common) is determined as follows:
 * - First, a bounding box is computed enclosing the contents of the [[textBlock].
 * - Then, an "anchor point" is computed based on the bounding box and the [[anchor]] property. The anchor point can be at one of the four corners of the box, in the middle of one of its four
 * edges, or in the center of the box.
 * - The [[orientation]] is applied to rotate the box around the anchor point.
 * - Finally, the [[offset]] is added to the anchor point to apply translation.
 * @see [appendTextAnnotationGeometry]($backend) to construct the geometry and append it to an [[ElementGeometry.Builder]].
 * @beta
 */
export class TextAnnotation {
    /** The rotation of the annotation.
     * @note When defining an annotation for a [TextAnnotation2d]($backend), only the `yaw` component (rotation around the Z axis) is used.
     */
    orientation;
    /** The formatted document. */
    textBlock;
    /** Describes how to compute the [[textBlock]]'s anchor point. */
    anchor;
    /** An offset applied to the anchor point that can be used to position annotations within the same geometry stream relative to one another. */
    offset;
    /** The leaders of the text annotation. */
    leaders;
    constructor(offset, angles, textBlock, anchor, leaders) {
        this.offset = offset;
        this.orientation = angles;
        this.textBlock = textBlock;
        this.anchor = anchor;
        this.leaders = leaders;
    }
    /** Creates a new TextAnnotation. */
    static create(args) {
        const offset = args?.offset ?? new Point3d();
        const angles = args?.orientation ?? new YawPitchRollAngles();
        const textBlock = args?.textBlock ?? TextBlock.create();
        const anchor = args?.anchor ?? { vertical: "top", horizontal: "left" };
        const leaders = args?.leaders ?? undefined;
        return new TextAnnotation(offset, angles, textBlock, anchor, leaders);
    }
    /**
     * Creates a new TextAnnotation instance from its JSON representation.
     */
    static fromJSON(props) {
        return TextAnnotation.create({
            offset: props?.offset ? Point3d.fromJSON(props.offset) : undefined,
            orientation: props?.orientation ? YawPitchRollAngles.fromJSON(props.orientation) : undefined,
            textBlock: props?.textBlock ? TextBlock.create(props.textBlock) : undefined,
            anchor: props?.anchor ? { ...props.anchor } : undefined,
            leaders: props?.leaders ? props.leaders.map((leader) => ({
                startPoint: Point3d.fromJSON(leader.startPoint),
                attachment: leader.attachment,
                styleOverrides: leader.styleOverrides ?? undefined,
                intermediatePoints: leader.intermediatePoints ? leader.intermediatePoints.map((point) => Point3d.fromJSON(point)) : undefined,
            })) : undefined,
        });
    }
    /**
     * Converts this annotation to its JSON representation.
     */
    toJSON() {
        const props = {};
        // Even if the text block is empty, we want to record its style ID and overrides, e.g.,
        // so the user can pick up where they left off editing it next time.
        props.textBlock = this.textBlock.toJSON();
        if (!this.offset.isZero) {
            props.offset = this.offset.toJSON();
        }
        if (!this.orientation.isIdentity()) {
            props.orientation = this.orientation.toJSON();
        }
        if (this.anchor.vertical !== "top" || this.anchor.horizontal !== "left") {
            props.anchor = { ...this.anchor };
        }
        props.leaders = this.leaders?.map((leader) => ({
            startPoint: leader.startPoint.toJSON(),
            attachment: leader.attachment,
            styleOverrides: leader.styleOverrides ?? undefined,
            intermediatePoints: leader.intermediatePoints ? leader.intermediatePoints.map((point) => point.toJSON()) : undefined,
        })) ?? undefined;
        return props;
    }
    /** Compute the transform that positions and orients this annotation relative to its anchor point, based on the [[textBlock]]'s computed bounding box.
     * The anchor point is computed as specified by this annotation's [[anchor]] setting. For example, if the text block is anchored
     * at the bottom left, then the transform will be relative to the bottom-left corner of `textBlockExtents`.
     * The text block will be rotated around the fixed anchor point according to [[orientation]], then translated by [[offset]].
     * The anchor point will coincide with (0, 0, 0) unless an [[offset]] is present.
     * If a scale factor is specified, the transform will also scale the annotation by that factor. Usually, this should come from the [[Drawing]] containing the annotation.
     * @param boundingBox A box fully containing the [[textBlock]]. This range should include the margins.
     * @param scaleFactor A factor by which to scale the annotation. Default: 1 (no scaling).
     * @see [[computeAnchorPoint]] to compute the transform's anchor point.
     * @see [computeLayoutTextBlockResult]($backend) to lay out a `TextBlock`.
     */
    computeTransform(boundingBox, scaleFactor = 1) {
        const anchorPt = this.computeAnchorPoint(boundingBox);
        const matrix = this.orientation.toMatrix3d();
        const transform = Transform.createIdentity();
        const translation = Transform.createTranslation(this.offset.minus(anchorPt));
        const scaleTransform = Transform.createScaleAboutPoint(anchorPt, scaleFactor);
        const rotation = Transform.createFixedPointAndMatrix(anchorPt, matrix);
        transform.multiplyTransformTransform(translation, transform);
        transform.multiplyTransformTransform(scaleTransform, transform);
        transform.multiplyTransformTransform(rotation, transform);
        return transform;
    }
    /** Compute the anchor point of this annotation as specified by [[anchor]].
     * @param boundingBox A box fully containing the [[textBlock]].
     * @see [[computeTransform]] to compute the transform relative to the anchor point.
     */
    computeAnchorPoint(boundingBox) {
        let x = boundingBox.low.x;
        let y = boundingBox.high.y;
        switch (this.anchor.horizontal) {
            case "center":
                x += boundingBox.xLength() / 2;
                break;
            case "right":
                x += boundingBox.xLength();
                break;
        }
        switch (this.anchor.vertical) {
            case "middle":
                y -= boundingBox.yLength() / 2;
                break;
            case "bottom":
                y -= boundingBox.yLength();
                break;
        }
        return new Point3d(x, y, 0);
    }
    /** Returns true if the leaders of this annotation are equal to the leaders of `other`. */
    areLeadersEqual(leadersA, leadersB) {
        if (leadersA === leadersB)
            return true;
        if (!leadersA || !leadersB || leadersA.length !== leadersB.length)
            return false;
        for (let i = 0; i < leadersA.length; ++i) {
            const a = leadersA[i];
            const b = leadersB[i];
            if (!a.startPoint.isAlmostEqual(b.startPoint))
                return false;
            if (JSON.stringify(a.attachment) !== JSON.stringify(b.attachment))
                return false;
            if (JSON.stringify(a.styleOverrides) !== JSON.stringify(b.styleOverrides))
                return false;
            const pointsA = a.intermediatePoints ?? [];
            const pointsB = b.intermediatePoints ?? [];
            if (pointsA.length !== pointsB.length)
                return false;
            for (let j = 0; j < pointsA.length; ++j) {
                if (!pointsA[j].isAlmostEqual(pointsB[j]))
                    return false;
            }
        }
        return true;
    }
    /** Returns true if this annotation is logically equivalent to `other`. */
    equals(other) {
        if (this.anchor.horizontal !== other.anchor.horizontal ||
            this.anchor.vertical !== other.anchor.vertical ||
            !this.orientation.isAlmostEqual(other.orientation) ||
            !this.offset.isAlmostEqual(other.offset) ||
            !this.textBlock.equals(other.textBlock))
            return false;
        return this.areLeadersEqual(this.leaders, other.leaders);
    }
}
//# sourceMappingURL=TextAnnotation.js.map