# Source code for tabletree.src.api.feature

    # sphinx bug workaround for python 3.11. see grm:86663
    from __future__ import annotations

    import base64
    from collections.abc import Sequence
    from typing import Any, Self

    import frtobjs
    import geode as gd

    from ..lib.i18n import I18nResourcesWriter, I18nWord, is_i18n_word
    from ..lib.row import RowBuilder
    from ..lib.view_command import INVALID_PROPERTY_KEY, ConfigAccessor
    from .view import GeomElement, ViewKeySuffix
    from .view_util import (
        Arrow,
        Circle,
        HighlightColor,
        Point,
        Polygon,
        Polyline,
        Text,
    )

    class Feature:
        _RESERVED_KEYS = ["_value", "relatedElements", INVALID_PROPERTY_KEY]
        _builder: RowBuilder

        def __init__(self, name: str | I18nWord):
            """Create a feature.

            Args:
                name:
                    The name of the feature.
            """
            self._builder = RowBuilder()
            if isinstance(name, str):
                self._builder.set_name(name)
            else:
                self._builder.set_name_i18n(name)
                I18nResourcesWriter.get_instance().add_word(name)

        def register_geometry(
            self,
            key: str,
            geom: GeomElement,
            colors: (
                Sequence[HighlightColor.HighlightColorPreset | HighlightColor]
                | None
            ) = None,
        ) -> Self:
            """Register a geometry element and its results of measurements.

            Call this method within a frt session.

            Args:
                key:
                    The key for the geometry.
                geom:
                    The geometry to be registered.
                colors:
                    The color(s) of the geometry.
                    The number of colors must be
                    either one or the same as the number of points in a PointCloud.
            """
            self.register_geometries(key, [geom])
            if colors is not None:
                color_list: list[list[int] | ConfigAccessor] = []
                for color in colors:
                    if isinstance(color, HighlightColor):
                        color_list.append(color.config_accessor)
                    else:
                        color_list.append(
                            HighlightColor.preset_to_config_accessor(color)
                        )
                self._builder.set_resource(
                    f"{key}{ViewKeySuffix.colors_resource}", color_list
                )

            return self

        def register_geometries(  # noqa: C901
            self,
            key: str,
            geoms: Sequence[GeomElement],
        ) -> Self:
            """Register a set of geometry elements.

            Call this method within a frt session.

            Args:
                key:
                    The key for the geometries.
                geoms:
                    The geometries to be registered.
            """
            if key in Feature._RESERVED_KEYS:
                raise KeyError(f"{key} is reserved key for row")

            frtobjs_elems = [
                e
                for e in geoms
                if isinstance(e, frtobjs.Body | frtobjs.Face | frtobjs.Edge)
            ]

            perm_frtobjs_elems = [e for e in frtobjs_elems if not e.temporary]
            perm_frtobjs_elem_ids = [
                e.id.decode()
                for e in perm_frtobjs_elems
                if not isinstance(e, gd.FacetedEdge | gd.FacetedFace)
            ]
            if len(perm_frtobjs_elem_ids) > 0:
                self._builder.set_property(
                    f"{key}{ViewKeySuffix.elements_executable}", True
                )
                self._builder.set_property(key, perm_frtobjs_elem_ids)
                self._builder.set_related_elements(perm_frtobjs_elem_ids)

            temp_frtobjs_elems = [e for e in frtobjs_elems if e.temporary]
            polylines = (
                [
                    gd.tessellation(e)
                    for e in temp_frtobjs_elems
                    if isinstance(e, frtobjs.Edge)
                ]
                + [e for e in perm_frtobjs_elems if isinstance(e, gd.FacetedEdge)]
                + [e for e in geoms if isinstance(e, Polyline)]
            )
            if len(polylines) > 0:
                polyline_list = [
                    (
                        [list(node.poe) for node in poly.nodes]
                        if isinstance(poly, gd.FacetedEdge)
                        else poly.vertices
                    )
                    for poly in polylines
                ]
                self._builder.set_property(
                    f"{key}{ViewKeySuffix.polylines_executable}", True
                )
                self._builder.set_resource(
                    f"{key}{ViewKeySuffix.polylines_lengths_resource}",
                    polyline_list,
                )

            polygon_list = (
                [
                    faceted_f
                    for e in temp_frtobjs_elems
                    if isinstance(e, frtobjs.Body)
                    for faceted_f in gd.tessellation(e.faces)
                ]
                + [
                    faceted_f
                    for e in temp_frtobjs_elems
                    if isinstance(e, frtobjs.Face)
                    and not isinstance(e, gd.FacetedFace)
                    for faceted_f in gd.tessellation([e])
                ]
                + [e for e in perm_frtobjs_elems if isinstance(e, gd.FacetedFace)]
                + [e for e in geoms if isinstance(e, Polygon)]
            )
            if len(polygon_list) > 0:
                point_list = list[list[float]]()
                index_list = list[int]()
                for polygon in polygon_list:
                    ind_offset = len(point_list)
                    index_list += [
                        ind_offset + idx
                        for triangle in (
                            polygon.facets
                            if isinstance(polygon, gd.FacetedFace)
                            else polygon.facets
                        )
                        for idx in triangle
                    ]
                    point_list += (
                        [list(node.pos) for node in polygon.nodes]
                        if isinstance(polygon, gd.FacetedFace)
                        else polygon.vertices
                    )
                self._builder.set_property(
                    f"{key}{ViewKeySuffix.polygon_executable}", True
                )
                self._builder.set_resource(
                    f"{key}{ViewKeySuffix.polygon_points_resource}", point_list
                )
                self._builder.set_resource(
                    f"{key}{ViewKeySuffix.polygon_vertex_indices_resource}",
                    index_list,
                )

            arrows = [e for e in geoms if isinstance(e, Arrow)]
            if len(arrows) > 0:
                for arrow in arrows:
                    # Note: the property is overwritten by the last arrow.
                    self._builder.set_property(
                        f"{key}{ViewKeySuffix.arrow_executable}", True
                    ).set_property(
                        f"{key}{ViewKeySuffix.arrow_start}", arrow._tail
                    ).set_property(f"{key}{ViewKeySuffix.arrow_end}", arrow._head)
                    if arrow._double_headed:
                        self._builder.set_property(
                            f"{key}{ViewKeySuffix.arrow_double_headed}",
                            arrow._double_headed,
                        )
                    if arrow._reverse_headed:
                        self._builder.set_property(
                            f"{key}{ViewKeySuffix.arrow_head_reversed}",
                            arrow._reverse_headed,
                        )
                    if arrow._head_length is not None:
                        self._builder.set_property(
                            f"{key}{ViewKeySuffix.arrow_head_length}",
                            arrow._head_length,
                        )

            texts = [e for e in geoms if isinstance(e, Text)]
            if len(texts) > 0:
                for text in texts:
                    # Note: the property is overwritten by the last text.
                    self._builder.set_property(
                        f"{key}{ViewKeySuffix.text_executable}", True
                    )
                    self._builder.set_property(
                        f"{key}{ViewKeySuffix.text_content}", text._content
                    ).set_property(
                        f"{key}{ViewKeySuffix.text_position}", text._position
                    )
                    if text._width is not None:
                        self._builder.set_property(
                            f"{key}{ViewKeySuffix.text_letter_width}", text._width
                        )
                    if text._height is not None:
                        self._builder.set_property(
                            f"{key}{ViewKeySuffix.text_letter_height}",
                            text._height,
                        )

            circles = [e for e in geoms if isinstance(e, Circle)]
            if len(circles) > 0:
                for circle in circles:
                    # Note: the property is overwritten by the last circle.
                    self._builder.set_property(
                        f"{key}{ViewKeySuffix.circle_executale}", True
                    )
                    self._builder.set_property(
                        f"{key}{ViewKeySuffix.circle_center}", circle._center
                    )
                    self._builder.set_property(
                        f"{key}{ViewKeySuffix.circle_radius}", circle._r
                    )
                    self._builder.set_property(
                        f"{key}{ViewKeySuffix.circle_normal}", circle._normal
                    )

            point_cloud_list = [
                [
                    e
                    for e in elems
                    if isinstance(e, gd.PointOnFace | gd.PointOnEdge | Point)
                ]
                for elems in geoms
                if isinstance(elems, Sequence)
            ]
            point_cloud = [
                e
                for e in geoms
                if isinstance(e, gd.PointOnFace | gd.PointOnEdge | Point)
            ]
            if len(point_cloud) > 0:
                point_cloud_list += [point_cloud]
            if len(point_cloud_list) > 0:
                point_list = [
                    (
                        list(pt)
                        if isinstance(pt, gd.PointOnEdge | gd.PointOnFace)
                        else pt.pos
                    )
                    for point_cloud in point_cloud_list
                    for pt in point_cloud
                ]
                self._builder.set_property(
                    f"{key}{ViewKeySuffix.point_cloud_executable}", True
                )
                self._builder.set_resource(
                    f"{key}{ViewKeySuffix.point_cloud_points_resource}", point_list
                )

                text_list = [
                    p._text
                    for point_cloud in point_cloud_list
                    for p in point_cloud
                    if isinstance(p, Point) and p._text is not None
                ]
                if len(text_list) > 0:
                    self._builder.set_property(
                        f"{key}{ViewKeySuffix.point_cloud_texts}",
                        [
                            p._text or ""
                            for point_cloud in point_cloud_list
                            for p in point_cloud
                            if isinstance(p, Point)
                        ],
                    )

            return self

        def set_file_as_link_property(self, key: str, file_path: str) -> Self:
            """Set a file as a link property.

            Args:
                key:
                    The property key.
                file_path:
                    The path to the file.
            """
            self._builder.set_file_link(key, file_path)
            return self

        def set_image_as_property(self, key: str, image_path: str) -> Self:
            """Set an image as a property.

            Args:
                key:
                    The property key.
                image_path:
                    The path to the image file.
            """
            with open(image_path, mode="rb") as f:
                img_base64 = base64.b64encode(f.read()).decode("utf-8")
                self.set_property(key, img_base64)
            return self

        def set_property(
            self,
            key: str,
            value: Any,
            huge: bool | None = None,
        ) -> Self:
            """Set a new value to a user-defined property.

            Args:
                key:
                    The property key.
                value:
                    The new value to be set to the property.
                huge:
                    If true, data size optimization is enabled.
                    Set true if the value can be huge.
            """
            if key in Feature._RESERVED_KEYS:
                raise KeyError(f"{key} is reserved key for row")

            if huge:
                self._builder.set_resource(key, value)
            else:
                self._builder.set_property(key, value)
                if is_i18n_word(value):
                    # link or image property
                    I18nResourcesWriter.get_instance().add_word(value)
                elif (
                    not isinstance(value, str)
                    and isinstance(value, Sequence)
                    and len(value) >= 1
                    and isinstance(value[0], dict)
                ):
                    # dictionary property
                    for v in value:
                        for key in v:
                            if is_i18n_word(v[key]):
                                I18nResourcesWriter.get_instance().add_word(v[key])
            return self

        def _get_data(self) -> RowBuilder:
            """Obtain the data."""
            return self._builder