import { Annotation, AnnotationView } from "./annotation";
import { Model } from "../../model";
import { AreaVisuals } from "./area_visuals";
import { auto_ranged } from "../ranges/data_range1d";
import * as mixins from "../../core/property_mixins";
import { CoordinateUnits } from "../../core/enums";
import { min as amin } from "../../core/util/array";
import { BBox, empty } from "../../core/util/bbox";
import { Signal } from "../../core/signaling";
import { clamp } from "../../core/util/math";
import { assert } from "../../core/util/assert";
import { values } from "../../core/util/object";
import { BorderRadius } from "../common/kinds";
import * as Box from "../common/box_kinds";
import { round_rect } from "../common/painting";
import * as resolve from "../common/resolve";
import { Node } from "../coordinates/node";
import { Coordinate } from "../coordinates/coordinate";
export const EDGE_TOLERANCE = 2.5;
const { abs } = Math;
export class BoxInteractionHandles extends Model {
    static __name__ = "BoxInteractionHandles";
    constructor(attrs) {
        super(attrs);
    }
    static {
        this.define(({ Ref, Nullable }) => ({
            all: [Ref(AreaVisuals)],
            move: [Nullable(Ref(AreaVisuals)), null],
            resize: [Nullable(Ref(AreaVisuals)), null],
            sides: [Nullable(Ref(AreaVisuals)), null],
            corners: [Nullable(Ref(AreaVisuals)), null],
            left: [Nullable(Ref(AreaVisuals)), null],
            right: [Nullable(Ref(AreaVisuals)), null],
            top: [Nullable(Ref(AreaVisuals)), null],
            bottom: [Nullable(Ref(AreaVisuals)), null],
            top_left: [Nullable(Ref(AreaVisuals)), null],
            top_right: [Nullable(Ref(AreaVisuals)), null],
            bottom_left: [Nullable(Ref(AreaVisuals)), null],
            bottom_right: [Nullable(Ref(AreaVisuals)), null],
        }));
    }
}
const DEFAULT_HANDLES = () => {
    return new BoxInteractionHandles({
        all: new AreaVisuals({
            fill_color: "white",
            fill_alpha: 1.0,
            line_color: "black",
            line_alpha: 1.0,
            hover_fill_color: "lightgray",
            hover_fill_alpha: 1.0,
        }),
    });
};
export class BoxAnnotationView extends AnnotationView {
    static __name__ = "BoxAnnotationView";
    _bbox = new BBox();
    get bbox() {
        return this._bbox;
    }
    _handles;
    _handles_views = {};
    initialize() {
        super.initialize();
        this._update_handles();
    }
    _update_handles() {
        const { editable, use_handles, handles } = this.model;
        if (editable && use_handles) {
            const { movable, resizable } = this;
            const common = {
                visible: true,
                resizable: "none",
                left_units: "canvas",
                right_units: "canvas",
                top_units: "canvas",
                bottom_units: "canvas",
                level: this.model.level,
            };
            function attrs_of(source) {
                return {
                    ...mixins.attrs_of(source, "", mixins.Line, true),
                    ...mixins.attrs_of(source, "", mixins.Fill, true),
                    ...mixins.attrs_of(source, "", mixins.Hatch, true),
                    ...mixins.attrs_of(source, "hover_", mixins.Line, true),
                    ...mixins.attrs_of(source, "hover_", mixins.Fill, true),
                    ...mixins.attrs_of(source, "hover_", mixins.Hatch, true),
                };
            }
            const h = handles;
            const attrs = {
                area: attrs_of(h.move ?? h.all),
                left: attrs_of(h.left ?? h.sides ?? h.resize ?? h.all),
                right: attrs_of(h.right ?? h.sides ?? h.resize ?? h.all),
                top: attrs_of(h.top ?? h.sides ?? h.resize ?? h.all),
                bottom: attrs_of(h.bottom ?? h.sides ?? h.resize ?? h.all),
                top_left: attrs_of(h.top_left ?? h.corners ?? h.resize ?? h.all),
                top_right: attrs_of(h.top_right ?? h.corners ?? h.resize ?? h.all),
                bottom_left: attrs_of(h.bottom_left ?? h.corners ?? h.resize ?? h.all),
                bottom_right: attrs_of(h.bottom_right ?? h.corners ?? h.resize ?? h.all),
            };
            const { tl_cursor, tr_cursor, bl_cursor, br_cursor, ew_cursor, ns_cursor, } = this.model;
            this._handles = {
                area: movable ? new BoxAnnotation({ ...common, ...attrs.area, movable: this.model.movable }) : null,
                left: resizable.left ? new BoxAnnotation({ ...common, ...attrs.left, in_cursor: ew_cursor }) : null,
                right: resizable.right ? new BoxAnnotation({ ...common, ...attrs.right, in_cursor: ew_cursor }) : null,
                top: resizable.top ? new BoxAnnotation({ ...common, ...attrs.top, in_cursor: ns_cursor }) : null,
                bottom: resizable.bottom ? new BoxAnnotation({ ...common, ...attrs.bottom, in_cursor: ns_cursor }) : null,
                top_left: resizable.top_left ? new BoxAnnotation({ ...common, ...attrs.top_left, in_cursor: tl_cursor }) : null,
                top_right: resizable.top_right ? new BoxAnnotation({ ...common, ...attrs.top_right, in_cursor: tr_cursor }) : null,
                bottom_left: resizable.bottom_left ? new BoxAnnotation({ ...common, ...attrs.bottom_left, in_cursor: bl_cursor }) : null,
                bottom_right: resizable.bottom_right ? new BoxAnnotation({ ...common, ...attrs.bottom_right, in_cursor: br_cursor }) : null,
            };
        }
        else {
            this._handles = {
                area: null,
                left: null,
                right: null,
                top: null,
                bottom: null,
                top_left: null,
                top_right: null,
                bottom_left: null,
                bottom_right: null,
            };
        }
    }
    get computed_renderers() {
        return [...super.computed_renderers, ...values(this._handles).filter((handle) => handle != null)];
    }
    connect_signals() {
        super.connect_signals();
        const { editable, use_handles, handles, resizable, movable } = this.model.properties;
        this.on_change([editable, use_handles, handles, resizable, movable], async () => {
            this._update_handles();
            await this._update_renderers();
        });
        this.connect(this.model.change, () => this.request_paint());
    }
    async _build_renderers() {
        const build_result = await super._build_renderers();
        const get = (handle) => {
            return handle != null ? this._renderer_views.get(handle) : undefined;
        };
        this._handles_views = {
            area: get(this._handles.area),
            left: get(this._handles.left),
            right: get(this._handles.right),
            top: get(this._handles.top),
            bottom: get(this._handles.bottom),
            top_left: get(this._handles.top_left),
            top_right: get(this._handles.top_right),
            bottom_left: get(this._handles.bottom_left),
            bottom_right: get(this._handles.bottom_right),
        };
        return build_result;
    }
    [auto_ranged] = true;
    bounds() {
        const { left, left_units, right, right_units, top, top_units, bottom, bottom_units, } = this.model;
        const left_ok = left_units == "data" && !(left instanceof Coordinate);
        const right_ok = right_units == "data" && !(right instanceof Coordinate);
        const top_ok = top_units == "data" && !(top instanceof Coordinate);
        const bottom_ok = bottom_units == "data" && !(bottom instanceof Coordinate);
        const [x0, x1] = (() => {
            if (left_ok && right_ok) {
                return left <= right ? [left, right] : [right, left];
            }
            else if (left_ok) {
                return [left, left];
            }
            else if (right_ok) {
                return [right, right];
            }
            else {
                return [NaN, NaN];
            }
        })();
        const [y0, y1] = (() => {
            if (top_ok && bottom_ok) {
                return top <= bottom ? [top, bottom] : [bottom, top];
            }
            else if (top_ok) {
                return [top, top];
            }
            else if (bottom_ok) {
                return [bottom, bottom];
            }
            else {
                return [NaN, NaN];
            }
        })();
        return { x0, x1, y0, y1 };
    }
    log_bounds() {
        return empty();
    }
    get mappers() {
        function mapper(units, scale, view, canvas) {
            switch (units) {
                case "canvas": return canvas;
                case "screen": return view;
                case "data": return scale;
            }
        }
        const overlay = this.model;
        const { x_scale, y_scale } = this.coordinates;
        const { x_view, y_view } = this.plot_view.frame.bbox;
        const { x_screen, y_screen } = this.plot_view.canvas.bbox;
        const lrtb = {
            left: mapper(overlay.left_units, x_scale, x_view, x_screen),
            right: mapper(overlay.right_units, x_scale, x_view, x_screen),
            top: mapper(overlay.top_units, y_scale, y_view, y_screen),
            bottom: mapper(overlay.bottom_units, y_scale, y_view, y_screen),
        };
        return lrtb;
    }
    get border_radius() {
        return resolve.border_radius(this.model.border_radius);
    }
    compute_geometry() {
        super.compute_geometry();
        const bbox = (() => {
            const compute = (dim, value, mapper) => {
                return value instanceof Coordinate ? this.resolve_as_scalar(value, dim) : mapper.compute(value);
            };
            const { left, right, top, bottom } = this.model;
            const { mappers } = this;
            return BBox.from_lrtb({
                left: compute("x", left, mappers.left),
                right: compute("x", right, mappers.right),
                top: compute("y", top, mappers.top),
                bottom: compute("y", bottom, mappers.bottom),
            });
        })();
        this._bbox = bbox;
        const width = 10;
        const height = 10;
        function update(renderer, bbox) {
            const { left, right, top, bottom } = bbox;
            renderer?.setv({ left, right, top, bottom }, { silent: true });
        }
        update(this._handles.area, new BBox({ ...bbox.center, width, height, origin: "center" }));
        update(this._handles.left, new BBox({ ...bbox.center_left, width, height, origin: "center" }));
        update(this._handles.right, new BBox({ ...bbox.center_right, width, height, origin: "center" }));
        update(this._handles.top, new BBox({ ...bbox.top_center, width, height, origin: "center" }));
        update(this._handles.bottom, new BBox({ ...bbox.bottom_center, width, height, origin: "center" }));
        update(this._handles.top_left, new BBox({ ...bbox.top_left, width, height, origin: "center" }));
        update(this._handles.top_right, new BBox({ ...bbox.top_right, width, height, origin: "center" }));
        update(this._handles.bottom_left, new BBox({ ...bbox.bottom_left, width, height, origin: "center" }));
        update(this._handles.bottom_right, new BBox({ ...bbox.bottom_right, width, height, origin: "center" }));
    }
    _paint() {
        if (!this.bbox.is_valid) {
            return;
        }
        const { _is_hovered, visuals } = this;
        const fill = _is_hovered && visuals.hover_fill.doit ? visuals.hover_fill : visuals.fill;
        const hatch = _is_hovered && visuals.hover_hatch.doit ? visuals.hover_hatch : visuals.hatch;
        const line = _is_hovered && visuals.hover_line.doit ? visuals.hover_line : visuals.line;
        const { ctx } = this.layer;
        ctx.save();
        const { inverted } = this.model;
        if (!inverted) {
            ctx.beginPath();
            round_rect(ctx, this.bbox, this.border_radius);
            fill.apply(ctx);
            hatch.apply(ctx);
            line.apply(ctx);
        }
        else {
            ctx.beginPath();
            const parent = this.layout ?? this.plot_view.frame;
            const { x, y, width, height } = parent.bbox;
            ctx.rect(x, y, width, height);
            round_rect(ctx, this.bbox, this.border_radius);
            fill.apply(ctx, "evenodd");
            hatch.apply(ctx, "evenodd");
            ctx.beginPath();
            round_rect(ctx, this.bbox, this.border_radius);
            line.apply(ctx);
        }
        ctx.restore();
    }
    interactive_bbox() {
        const tolerance = this.model.line_width + EDGE_TOLERANCE;
        return this.bbox.grow_by(tolerance);
    }
    interactive_hit(sx, sy) {
        if (!this.model.visible) {
            return false;
        }
        const bbox = this.interactive_bbox();
        return bbox.contains(sx, sy);
    }
    _hit_test(sx, sy) {
        const { left, right, bottom, top } = this.bbox;
        const tolerance = Math.max(EDGE_TOLERANCE, this.model.line_width / 2);
        const dl = abs(left - sx);
        const dr = abs(right - sx);
        const dt = abs(top - sy);
        const db = abs(bottom - sy);
        const hits = {
            left: dl < tolerance && dl < dr,
            right: dr < tolerance && dr < dl,
            top: dt < tolerance && dt < db,
            bottom: db < tolerance && db < dt,
        };
        const hittable = this._hittable();
        const hits_handle = (hit_target, condition) => {
            if (!hittable[hit_target]) {
                return false;
            }
            const handle = this._handles_views[hit_target];
            if (handle != null) {
                return handle.bbox.contains(sx, sy);
            }
            else {
                return condition;
            }
        };
        if (hits_handle("top_left", hits.top && hits.left)) {
            return "top_left";
        }
        if (hits_handle("top_right", hits.top && hits.right)) {
            return "top_right";
        }
        if (hits_handle("bottom_left", hits.bottom && hits.left)) {
            return "bottom_left";
        }
        if (hits_handle("bottom_right", hits.bottom && hits.right)) {
            return "bottom_right";
        }
        if (hits_handle("left", hits.left)) {
            return "left";
        }
        if (hits_handle("right", hits.right)) {
            return "right";
        }
        if (hits_handle("top", hits.top)) {
            return "top";
        }
        if (hits_handle("bottom", hits.bottom)) {
            return "bottom";
        }
        if (hits_handle("area", this.bbox.contains(sx, sy))) {
            return "area";
        }
        return null;
    }
    get resizable() {
        const { resizable } = this.model;
        const left = resizable == "left" || resizable == "x" || resizable == "all";
        const right = resizable == "right" || resizable == "x" || resizable == "all";
        const top = resizable == "top" || resizable == "y" || resizable == "all";
        const bottom = resizable == "bottom" || resizable == "y" || resizable == "all";
        return {
            left,
            right,
            top,
            bottom,
            top_left: top && left,
            top_right: top && right,
            bottom_left: bottom && left,
            bottom_right: bottom && right,
        };
    }
    get movable() {
        return this.model.movable != "none";
    }
    _hittable() {
        const { left, right, top, bottom } = this.resizable;
        return {
            top_left: top && left,
            top_right: top && right,
            bottom_left: bottom && left,
            bottom_right: bottom && right,
            left,
            right,
            top,
            bottom,
            area: this.movable,
        };
    }
    _can_hit(target) {
        const { left, right, top, bottom } = this.resizable;
        switch (target) {
            case "top_left": return top && left;
            case "top_right": return top && right;
            case "bottom_left": return bottom && left;
            case "bottom_right": return bottom && right;
            case "left": return left;
            case "right": return right;
            case "top": return top;
            case "bottom": return bottom;
            case "area": return this.movable;
        }
    }
    _pan_state = null;
    on_pan_start(ev) {
        if (this.model.visible && this.model.editable) {
            const { sx, sy } = ev;
            const target = this._hit_test(sx, sy);
            if (target != null && this._can_hit(target)) {
                this._pan_state = {
                    bbox: this.bbox.clone(),
                    target,
                };
                this.model.pan.emit(["pan:start", ev.modifiers]);
                return true;
            }
        }
        return false;
    }
    on_pan(ev) {
        assert(this._pan_state != null);
        const { mappers } = this;
        const resolve = (dim, limit, mapper) => {
            if (limit instanceof Coordinate) {
                return this.resolve_as_scalar(limit, dim);
            }
            else if (limit == null) {
                return NaN;
            }
            else {
                return mapper.compute(limit);
            }
        };
        const slimits = BBox.from_lrtb({
            left: resolve("x", this.model.left_limit, mappers.left),
            right: resolve("x", this.model.right_limit, mappers.right),
            top: resolve("y", this.model.top_limit, mappers.top),
            bottom: resolve("y", this.model.bottom_limit, mappers.bottom),
        });
        const [dl, dr, dt, db] = (() => {
            const { dx, dy } = ev;
            const { target } = this._pan_state;
            const { symmetric } = this.model;
            const [Dx, Dy] = symmetric ? [-dx, -dy] : [0, 0];
            switch (target) {
                // corners
                case "top_left": return [dx, Dx, dy, Dy];
                case "top_right": return [Dx, dx, dy, Dy];
                case "bottom_left": return [dx, Dx, Dy, dy];
                case "bottom_right": return [Dx, dx, Dy, dy];
                // edges
                case "left": return [dx, Dx, 0, 0];
                case "right": return [Dx, dx, 0, 0];
                case "top": return [0, 0, dy, Dy];
                case "bottom": return [0, 0, Dy, dy];
                // area
                case "area": {
                    switch (this.model.movable) {
                        case "both": return [dx, dx, dy, dy];
                        case "x": return [dx, dx, 0, 0];
                        case "y": return [0, 0, dy, dy];
                        case "none": return [0, 0, 0, 0];
                    }
                }
            }
        })();
        const slrtb = (() => {
            const min = (a, b) => amin([a, b]);
            const sgn = (v) => v < 0 ? -1 : (v > 0 ? 1 : 0);
            const { bbox } = this._pan_state;
            let { left, right, left_sign, right_sign } = (() => {
                const left = bbox.left + dl;
                const right = bbox.right + dr;
                const left_sign = sgn(dl);
                const right_sign = sgn(dr);
                if (left <= right) {
                    return { left, right, left_sign, right_sign };
                }
                else {
                    return { left: right, right: left, left_sign: right_sign, right_sign: left_sign };
                }
            })();
            let { top, bottom, top_sign, bottom_sign } = (() => {
                const top = bbox.top + dt;
                const bottom = bbox.bottom + db;
                const top_sign = sgn(dt);
                const bottom_sign = sgn(db);
                if (top <= bottom) {
                    return { top, bottom, top_sign, bottom_sign };
                }
                else {
                    return { top: bottom, bottom: top, top_sign: bottom_sign, bottom_sign: top_sign };
                }
            })();
            const Dl = left - slimits.left;
            const Dr = slimits.right - right;
            const Dx = min(Dl < 0 ? Dl : NaN, Dr < 0 ? Dr : NaN);
            if (isFinite(Dx) && Dx < 0) {
                left += -left_sign * (-Dx);
                right += -right_sign * (-Dx);
            }
            const Dt = top - slimits.top;
            const Db = slimits.bottom - bottom;
            const Dy = min(Dt < 0 ? Dt : NaN, Db < 0 ? Db : NaN);
            if (isFinite(Dy) && Dy < 0) {
                top += -top_sign * (-Dy);
                bottom += -bottom_sign * (-Dy);
            }
            return BBox.from_lrtb({ left, right, top, bottom });
        })();
        const { min_width, min_height, max_width, max_height } = this.model;
        const { left, right, top, bottom } = this.model;
        const lrtb = {
            left: mappers.left.invert(slrtb.left),
            right: mappers.right.invert(slrtb.right),
            top: mappers.top.invert(slrtb.top),
            bottom: mappers.bottom.invert(slrtb.bottom),
        };
        if (0 < min_width || max_width < Infinity) {
            if (dl != 0 && dr == 0) {
                const min_left = lrtb.right - max_width;
                const max_left = lrtb.right - min_width;
                lrtb.left = clamp(lrtb.left, min_left, max_left);
            }
            else if (dl == 0 && dr != 0) {
                const min_right = lrtb.left + min_width;
                const max_right = lrtb.left + max_width;
                lrtb.right = clamp(lrtb.right, min_right, max_right);
            }
        }
        if (0 < min_height || max_height < Infinity) {
            if (dt != 0 && db == 0) {
                const min_top = lrtb.bottom + max_height;
                const max_top = lrtb.bottom + min_height;
                lrtb.top = clamp(lrtb.top, min_top, max_top);
            }
            else if (dt == 0 && db != 0) {
                const min_bottom = lrtb.top - min_height;
                const max_bottom = lrtb.top - max_height;
                lrtb.bottom = clamp(lrtb.bottom, min_bottom, max_bottom);
            }
        }
        const computed_lrtb = (() => {
            return {
                left: left instanceof Coordinate ? left : lrtb.left,
                right: right instanceof Coordinate ? right : lrtb.right,
                top: top instanceof Coordinate ? top : lrtb.top,
                bottom: bottom instanceof Coordinate ? bottom : lrtb.bottom,
            };
        })();
        this.model.update(computed_lrtb);
        this.model.pan.emit(["pan", ev.modifiers]);
    }
    on_pan_end(ev) {
        this._pan_state = null;
        this.model.pan.emit(["pan:end", ev.modifiers]);
    }
    _pinch_state = null;
    on_pinch_start(ev) {
        if (this.model.visible && this.model.editable && this.model.resizable != "none") {
            const { sx, sy } = ev;
            if (this.bbox.contains(sx, sy)) {
                this._pinch_state = {
                    bbox: this.bbox.clone(),
                };
                this.model.pan.emit(["pan:start", ev.modifiers]); // TODO: pinch signal
                return true;
            }
        }
        return false;
    }
    on_pinch(ev) {
        assert(this._pinch_state != null);
        const slrtb = (() => {
            const { scale } = ev;
            const { bbox } = this._pinch_state;
            const { left, top, right, bottom, width, height } = bbox;
            const dw = width * (scale - 1);
            const dh = height * (scale - 1);
            const { resizable } = this;
            const dl = resizable.left ? -dw / 2 : 0;
            const dr = resizable.right ? dw / 2 : 0;
            const dt = resizable.top ? -dh / 2 : 0;
            const db = resizable.bottom ? dh / 2 : 0;
            return BBox.from_lrtb({
                left: left + dl,
                right: right + dr,
                top: top + dt,
                bottom: bottom + db,
            });
        })();
        const lrtb = (() => {
            const { left, right, top, bottom } = this.model;
            const { mappers } = this;
            return {
                left: left instanceof Coordinate ? left : mappers.left.invert(slrtb.left),
                right: right instanceof Coordinate ? right : mappers.right.invert(slrtb.right),
                top: top instanceof Coordinate ? top : mappers.top.invert(slrtb.top),
                bottom: bottom instanceof Coordinate ? bottom : mappers.bottom.invert(slrtb.bottom),
            };
        })();
        this.model.update(lrtb);
        this.model.pan.emit(["pan", ev.modifiers]);
    }
    on_pinch_end(ev) {
        this._pinch_state = null;
        this.model.pan.emit(["pan:end", ev.modifiers]);
    }
    get _has_hover() {
        const { hover_line, hover_fill, hover_hatch } = this.visuals;
        return hover_line.doit || hover_fill.doit || hover_hatch.doit;
    }
    _is_hovered = false;
    on_enter(_ev) {
        const { _has_hover } = this;
        if (_has_hover) {
            this._is_hovered = true;
            this.request_paint();
        }
        return _has_hover;
    }
    on_move(_ev) { }
    on_leave(_ev) {
        if (this._has_hover) {
            this._is_hovered = false;
            this.request_paint();
        }
    }
    cursor(sx, sy) {
        const target = this._pan_state?.target ?? this._hit_test(sx, sy);
        if (target == null || !this._can_hit(target)) {
            return null;
        }
        const { tl_cursor, tr_cursor, bl_cursor, br_cursor, ew_cursor, ns_cursor, in_cursor, } = this.model;
        switch (target) {
            case "top_left": return this._handles.top_left == null ? tl_cursor : null;
            case "top_right": return this._handles.top_right == null ? tr_cursor : null;
            case "bottom_left": return this._handles.bottom_left == null ? bl_cursor : null;
            case "bottom_right": return this._handles.bottom_right == null ? br_cursor : null;
            case "left": return this._handles.left == null ? ew_cursor : null;
            case "right": return this._handles.right == null ? ew_cursor : null;
            case "top": return this._handles.top == null ? ns_cursor : null;
            case "bottom": return this._handles.bottom == null ? ns_cursor : null;
            case "area": {
                if (this._handles.area == null) {
                    switch (this.model.movable) {
                        case "both": return in_cursor;
                        case "x": return ew_cursor;
                        case "y": return ns_cursor;
                        case "none": return null;
                    }
                }
                else {
                    return null;
                }
            }
        }
    }
}
export class BoxAnnotation extends Annotation {
    static __name__ = "BoxAnnotation";
    constructor(attrs) {
        super(attrs);
    }
    clone(attrs) {
        return super.clone(attrs);
    }
    static {
        this.prototype.default_view = BoxAnnotationView;
        this.mixins([
            mixins.Line,
            mixins.Fill,
            mixins.Hatch,
            ["hover_", mixins.Line],
            ["hover_", mixins.Fill],
            ["hover_", mixins.Hatch],
        ]);
        this.define(({ Bool, Float, Ref, Or, NonNegative, Positive }) => ({
            top: [Or(Float, Ref(Coordinate)), () => new Node({ target: "frame", symbol: "top" })],
            bottom: [Or(Float, Ref(Coordinate)), () => new Node({ target: "frame", symbol: "bottom" })],
            left: [Or(Float, Ref(Coordinate)), () => new Node({ target: "frame", symbol: "left" })],
            right: [Or(Float, Ref(Coordinate)), () => new Node({ target: "frame", symbol: "right" })],
            top_units: [CoordinateUnits, "data"],
            bottom_units: [CoordinateUnits, "data"],
            left_units: [CoordinateUnits, "data"],
            right_units: [CoordinateUnits, "data"],
            top_limit: [Box.Limit, null],
            bottom_limit: [Box.Limit, null],
            left_limit: [Box.Limit, null],
            right_limit: [Box.Limit, null],
            min_width: [NonNegative(Float), 0],
            min_height: [NonNegative(Float), 0],
            max_width: [Positive(Float), Infinity],
            max_height: [Positive(Float), Infinity],
            border_radius: [BorderRadius, 0],
            editable: [Bool, false],
            resizable: [Box.Resizable, "all"],
            movable: [Box.Movable, "both"],
            symmetric: [Bool, false],
            use_handles: [Bool, false],
            handles: [Ref(BoxInteractionHandles), DEFAULT_HANDLES],
            inverted: [Bool, false],
        }));
        this.internal(({ Str }) => ({
            tl_cursor: [Str, "nwse-resize"],
            tr_cursor: [Str, "nesw-resize"],
            bl_cursor: [Str, "nesw-resize"],
            br_cursor: [Str, "nwse-resize"],
            ew_cursor: [Str, "ew-resize"],
            ns_cursor: [Str, "ns-resize"],
            in_cursor: [Str, "move"],
        }));
        this.override({
            fill_color: "#fff9ba",
            fill_alpha: 0.4,
            line_color: "#cccccc",
            line_alpha: 0.3,
            hover_fill_color: null,
            hover_fill_alpha: 0.4,
            hover_line_color: null,
            hover_line_alpha: 0.3,
        });
    }
    pan = new Signal(this, "pan");
    update({ left, right, top, bottom }) {
        this.setv({ left, right, top, bottom, visible: true });
    }
    clear() {
        this.visible = false;
    }
    nodes = (() => {
        const known = new Map();
        const node = (symbol) => {
            let node = known.get(symbol);
            if (node === undefined) {
                known.set(symbol, node = new Node({ target: this, symbol }));
            }
            return node;
        };
        return {
            get left() { return node("left"); },
            get right() { return node("right"); },
            get top() { return node("top"); },
            get bottom() { return node("bottom"); },
            get top_left() { return node("top_left"); },
            get top_center() { return node("top_center"); },
            get top_right() { return node("top_right"); },
            get center_left() { return node("center_left"); },
            get center() { return node("center"); },
            get center_right() { return node("center_right"); },
            get bottom_left() { return node("bottom_left"); },
            get bottom_center() { return node("bottom_center"); },
            get bottom_right() { return node("bottom_right"); },
            get width() { return node("width"); },
            get height() { return node("height"); },
        };
    })();
}
//# sourceMappingURL=box_annotation.js.map