"""
Widget - Base class and primitive widgets for imgui
Each widget gets TreeLike and path for data access
"""

from imgui_bundle import imgui
from imery.frontend.types import Visual
from imery.backend.types import TreeLike
from imery.types import DataPath, Object, EventHandler
from imery.dispatcher import Dispatcher
from imery.result import Result, Ok

from typing import Optional, Dict, Any, Union


class FieldValues(Object):
    """
    Helper class that implements the logic of handling (read/write) of field values of a widget
    The value can be static in the definition of the widget or obtained from the tree like data
    """
    def __init__(self, tree_like: TreeLike, data_path: DataPath, params: Optional[Dict]):
        self._tree_like = tree_like
        self._data_path = data_path
        self._params = params
        self._data_map = None

    def as_tree(self, data_path: DataPath, depth: int = 0):
        return {
            "data-path": str(self._data_path),
            "params": self._params,
            "tree-like": self._tree_like.as_tree,
            "data-map": self._data_map
        }


    def init(self) -> Result[None]:
        if not isinstance(self._params, dict):
            self._data_map = {}
            return Ok(None)
        self._data_map = self._params.get("data-map")
        if self._data_map:
            if not isinstance(self._data_map, dict):
                return Result.error("FieldValues.init: data-map should be dictionary")
        else:
            self._data_map = {}
        return Ok(None)

    def dispose(self) -> Result[None]:
        return Ok(None)

    def _resolve_metadata_path(self, key: str) -> Result[DataPath]:
        """
        Resolve metadata path for a field key using data-map if present.

        Args:
            key: Field name (e.g., "label")

        Returns:
            Result[DataPath]: Resolved path for metadata access
        """
        # Check if data-map exists and has mapping for this key
        if key in self._data_map:
            mapping = self._data_map[key]
            if not isinstance(mapping, str):
                return Result.error(f"FieldValues: _resolve_metadata_path: mapping value should be string, got {type(mapping)}")

            if mapping.startswith("/"):
                return Ok(DataPath(mapping))
            else: 
                return Ok(self._data_path / mapping)
        # No mapping - use key as metadata key at current data_path
        return Ok(self._data_path / key)

    def get(self, key: str, default_value: Any = None) -> Result[Any]:
        """
        Get field value - checks static params first, then dynamic from tree_like metadata.

        Args:
            key: Field name (e.g., "label")

        Returns:
            Result with field value, or Error if not found
        """
        # Handle string params: treat as "label" value
        if self._params and isinstance(self._params, str):
            if key == "label":
                return Ok(self._params)
            # For other keys, fall through to tree_like lookup

        # Check if key exists directly in params dict (static value)
        if self._params and isinstance(self._params, dict) and key in self._params:
            return Ok(self._params[key])

        # No static value - try to get from tree_like metadata
        if not self._tree_like:
            if default_value is None:
                return Result.error(f"FieldValues.get: no tree_like available and key '{key}' not in params")
            else:
                return Ok(default_value)

        res = self._resolve_metadata_path(key)
        if not res:
            return Result.error(f"FieldValues: get: Could not resolve metadata path for '{key}'", res)
        full_path = res.unwrapped

        # Get metadata value (path includes key as last component)
        res = self._tree_like.get(full_path)
        if not res:
            if default_value is None:
                return Result.error(f"FieldValues.get: failed to get '{key}' from tree_like at path '{full_path}'", res)
            else:
                return Ok(default_value)

        return Ok(res.unwrapped)

    def get_metadata(self) -> Result[Any]:
        """
        return the metadata view at current path
        """
        if self._tree_like is None:
            return self._params.copy() if not self._params is None else Ok(None)

        res = self._tree_like.get_metadata(self._data_path)
        if not res:
            return Result.error(f"FieldValues: get_metadata: no data found at {self._data_path}", res)
        metadata = res.unwrapped.copy()
        metadata.update(self._params)
        return Ok(metadata)



    def set(self, key: str, value: Any) -> Result[None]:
        """
        Set field value - writes to tree_like metadata.

        Args:
            key: Field name (e.g., "label")
            value: Value to set

        Returns:
            Result[None]
        """
        if not self._tree_like:
            return Result.error(f"FieldValues.set: no tree_like available")

        # Check if there's a data-map for this key
        if self._data_map and isinstance(self._data_map, dict) and key in self._data_map:
            # Use mapped value
            mapped = self._data_map[key]

            # Check if mapped value is an absolute path
            if isinstance(mapped, str) and mapped.startswith("/"):
                # Absolute path - use it directly
                full_path = DataPath(mapped)
            else:
                # Relative path or metadata key - build path
                node_path = self._data_path
                full_path = node_path / mapped
        else:
            # No mapping - resolve path normally
            path_res = self._resolve_metadata_path(key)
            if not path_res:
                return Result.error(f"FieldValues.set: failed to resolve path for key '{key}'", path_res)
            full_path = path_res.unwrapped


        # Set metadata value (path includes key as last component)
        res = self._tree_like.set(full_path, value)
        if not res:
            return Result.error(f"FieldValues.set: failed to set '{key}' at path '{full_path}'", res)

        return Ok(None)

    def add_child(self, path: DataPath, name: str, data: Any) -> Result[None]:
        """
        Add a child to the tree at the given path.

        Args:
            path: DataPath to the parent node
            name: Name of the new child
            data: Data for the new child

        Returns:
            Result[None]
        """
        if not self._tree_like:
            return Result.error(f"FieldValues.add_child: no tree_like available")

        res = self._tree_like.add_child(path, name, data)
        if not res:
            return Result.error(f"FieldValues.add_child: failed to add child '{name}' at path '{path}'", res)

        return Ok(None)


EVENT_COMMAND_NAMES = {"show", "add-data-child", "dispatch-event", "default"}

def _render_error_simple(error) -> Result[None]:
    """Display errors using bullet points recursively - fallback method"""
    print("_render_error_simple", error)
    def render_tree(obj, depth=0):
        """Recursively render tree structure with bullets"""
        if isinstance(obj, dict):
            for key, value in obj.items():
                if isinstance(value, (dict, list)):
                    imgui.bullet_text(f"{key}:")
                    imgui.indent()
                    render_tree(value, depth + 1)
                    imgui.unindent()
                else:
                    imgui.bullet_text(f"{key}: {value}")
        elif isinstance(obj, list):
            for i, item in enumerate(obj):
                if isinstance(item, (dict, list)):
                    imgui.bullet_text(f"[{i}]:")
                    imgui.indent()
                    render_tree(item, depth + 1)
                    imgui.unindent()
                else:
                    imgui.bullet_text(f"[{i}]: {item}")
        else:
            imgui.bullet_text(str(obj))

    imgui.text_colored(imgui.ImVec4(1.0, 0.0, 0.0, 1.0), "Errors:")
    imgui.separator()
    render_tree(error)
    print("_render_error_simple")
    return Ok(None)

class Widget(Visual, EventHandler):
    """Base class for all widgets"""

    def __init__(self, factory: "WidgetFactory", dispatcher: Dispatcher, namespace: str, tree_like: TreeLike, data_path: DataPath, params: Optional[Dict] = None):
        """
        Args:
            factory: WidgetFactory for creating nested widgets
            namespace: Current namespace (e.g., "demo_widgets")
            tree_like: TreeLike instance for data access (can be None)
            path: DataPath to data in the tree
            params: Widget parameters (dict or other type depending on widget)
        """
        super().__init__()
        self._factory = factory
        self._dispatcher = dispatcher
        self._namespace = namespace
        self._tree_like = tree_like
        self._data_path = data_path
        self._params = params
        self._style_color_count = 0
        self._style_var_count = 0
        self._metadata = None
        self._event_handlers = {}  # event_name -> list of command specs (lazy evaluated)
        self._body = None  # Body widget created from activated event
        self._is_body_activated = False # 
        self._should_create_body = False
        self._is_open = True
        self._last_error = None

        self._data_path = data_path

        self._field_values = None
        self._render_cycle = -1
        self._clicked = False


        # we collect errors from different step! there should be only one main error collected
        self._error_widget = None
        self._errors = []
        
        self._styles_pushed = False


    def init(self) -> Result[None]:
        """Initialize widget - override in subclasses if needed"""
        res = FieldValues.create(self._tree_like, self._data_path, self._params)
        if not res:
            return Result.error("Widget: failed to create FieldValues", res)
        self._field_values = res.unwrapped
        res = self._init_events()
        if not res:
            return Result.error("Widget: failed to initialize events", res)

        return Ok(None)

    @property
    def is_open(self):
        return self._is_open

    @property
    def is_empty(self) -> bool:
        """Check if widget has no content - override in subclasses"""
        return True

    @property
    def body(self) -> Result[None]:
        if not self._body:
            res = self._ensure_body()
            if not res:
                Result.error("Widget: body: could not create body")

    @property
    def field_values(self) -> FieldValues:
        return self._field_values

    def _init_events(self) -> Result[None]:
        """Parse and store event specifications from params"""
        if not isinstance(self._params, dict):
            return Ok(None)

        # List of supported event names
        event_names = ["on-active", "on-click", "on-double-click", "on-right-click", "on-hover", "on-error"]

        # Check each event in params
        for event_name in event_names:
            event_spec = self._params.get(event_name)
            if event_spec is not None:
                res = self._normalize_event_spec(event_name, event_spec)
                if not res:
                    return Result.error("Widget: _init_events: Could not init events", res)
                self._event_handlers[event_name] = res.unwrapped

        return Ok(None)

    def _evaluate_condition(self, condition, metadata: dict) -> Ok(bool):
        """
        Evaluate a condition against metadata.

        Condition can be:
        - list → implicit AND (all conditions must be true)
        - dict with single field: value → metadata[field] == value
        - dict with "and": [conditions] → all must be true
        - dict with "or": [conditions] → at least one must be true
        - dict with "not": condition → negate the condition

        Args:
            condition: Condition specification (list or dict)
            metadata: Metadata dict to check against

        Returns:
            bool: True if condition passes, False otherwise
        """
        # List → implicit AND
        # print(f"Widget: _evaluate_condition: {condition}")
        if isinstance(condition, str):
            if not condition in ["always", "never"]:
                return Result.error(f"Widget: _evaluate_condition: Unknown 'when' condition specified '{condition}'")
            if condition == "always":
                return Ok(True)
            return Ok(False)

        if isinstance(condition, list): # default is and of all conditions
            out = True
            for cond in condition:
                res = self._evaluate_condition(c, metadata)
                if not res:
                    return Result.error(f"Widget: _evaluate_condition: Failed to evaluate condition for '{cond}'")
                out = out and res.unwrapped
            return Ok(out)

        if not isinstance(condition, dict):
            return Ok(False)

        # Check for logical operators
        if "and" in condition:
            conditions = condition["and"]
            if isinstance(conditions, list):
                return all(self._evaluate_condition(c, metadata) for c in conditions)
            # Single condition (collapsed list)
            return self._evaluate_condition(conditions, metadata)

        if "or" in condition:
            conditions = condition["or"]
            if isinstance(conditions, list):
                return any(self._evaluate_condition(c, metadata) for c in conditions)
            # Single condition (collapsed list)
            return self._evaluate_condition(conditions, metadata)

        if "not" in condition:
            inner_condition = condition["not"]
            res = self._evaluate_condition(inner_condition, metadata)
            if not res:
                return Result.error(f"Widget: _evaluate_condition: Failed to evaluate condition for 'not': '{inner_condition}'")
            return Ok(not self.unwrapped)

        # Simple field comparison: {field: expected_value}
        # Should have exactly one key
        if len(condition) == 1:
            field_name, expected_value = next(iter(condition.items()))
            actual_value = metadata.get(field_name)
            return Ok(actual_value == expected_value)

        # Multiple keys but no logical operator → invalid, return False
        return Ok(False)

    def _normalize_event_spec_item(self, event_name, event_spec: Union[str, dict]) -> Result[dict]:

        if isinstance(event_spec, str):
            return Ok({
                      "command": event_spec,
                      "when": "always",
                      "data": None})
        if isinstance(event_spec, dict):
            when = event_spec.get("when", "always")
            action = None 
            for action_name in EVENT_COMMAND_NAMES:
                if action_name in event_spec:
                    if action is None:
                        action = action_name
                    else:
                        Result.error(f"both '{action_name}' and '{action}' is specified for {event_name}")
            if action is None:
                return Result.error(f"no known action specified for event: {event_name}, {event_spec}")
            data = event_spec.get(action)
            return Ok({
                      "command": action,
                      "when": when,
                      "data": data})

        return Result.error(f"Widget: _normalize_event_spec_item: unexpected type: {type(event_spec)}:, {event_spec}")



    def _normalize_event_spec(self, event_name: str, event_spec: Union[str, dict, list]) -> Result[list]:
        """
        Normalize event specification into a list of commands.

        Event spec can be:
        - string "default" → [{"command": "default"}]
        - string (other) → [{"command": "show", "what": string}]
        - dict with action keys (show/dispatch) → [{"command": "show"/"dispatch", ...}]
        - dict (other) → [{"command": "show", "what": dict}] (widget spec)
        - list → normalize each item

The event spec may have following layouts
action is the body (value) of the event. The body may be a string, a dict or a list of them
widgets:
    type: popup
    body:
    - menu-item:
        label: "add layer"
        on-click: default
or
widgets:
    type: popup
    body:
    - menu-item:
        label: "add layer"
        on-click:
            when: always
            add-data-child:
                name: "layer-new"
                metadata:
                    label: "new layer"
or
widgets:
    type: popup
    body:
    - menu-item:
        label: "add layer"
        on-click:
            when: always
            add-data-child:
                name: "layer-new"
                metadata:
                    label: "new layer"

        Returns:
            list: List of command dicts, each with "action", "what"/"message", optional "when"
        """
        if event_spec is None:
            return Ok([])
        # List → normalize each item
        if isinstance(event_spec, str):
            res = self._normalize_event_spec_item(event_name, event_spec)
            if not res:
                return Result.error("Widget: _normalize_event_spec: For '{event_name}', failed to normalize item {event_spec}:" , res)
            return Ok([res.unwrapped])
            
        if isinstance(event_spec, dict):
            res = self._normalize_event_spec_item(event_name, event_spec)
            if not res:
                return Result.error("Widget: _normalize_event_spec: For '{event_name}', failed to normalize item {event_spec}:" , res)
            return Ok([res.unwrapped])

        if isinstance(event_spec, list):
            output = []
            for action in event_spec:
                res = self._normalize_event_spec_item(event_name, action)
                if not res:
                    return Result.error("Widget: _normalize_event_spec: For '{event_name}', failed to normalize item {action}:" , res)
                output.append(res.unwrapped)
            return Ok(output)

        return Result.error(f"unexpected data for event '{event_name}', spec '{type(event_spec)}' {event_spec}")

    
    def _execute_event_command_add_data_child(self, event_name: str, command: str, data: Optional[Union[str, dict, list]] = None) -> Result[None]:
        print("adding child to data")
        if not isinstance(data, dict):
            return Result.error(f"Widget: _execute_event_command_dispatch_event: expected dict, got '{type(data)}'")
        # Get child name and data from command spec
        child_name = data.get("name")
        if not child_name:
            return Result.error(f"Widget: _execute_event_command_add_data_child: missing 'name' parameter")

        # Get data - can be metadata dict or any other data
        child_data = data.get("metadata")
        if child_data is None:
            return Result.error(f"Widget: _execute_event_command_add_data_child: missing 'metadata' or 'data' parameter")

        # Get target path (optional data-id, supports ".." for parent)
        data_id = data.get("data-id")
        if data_id:
            target_path = self._data_path / data_id
        else:
            target_path = self._data_path

        # Call add_child through field_values
        res = self._field_values.add_child(target_path, child_name, child_data)
        if not res:
            return Result.error(f"Widget: _execute_event_command_add_data_child: failed to add child '{child_name}' at '{target_path}'", res)
        return Ok(None)


    def _execute_event_command_show(self, event_name: str, command: str, data: Optional[Union[str, dict, list]] = None) -> Result[None]:
        res = self._create_widget_from_spec(data)
        if not res:
            return Result.error(f"Widget: _execute_event_command_show: failed to create widget from spec: {data}")
        widget = res.unwrapped
        res = widget.render()
        if not res:
            return Result.error(f"Widget: _execute_event_command_show: failed to render widget: {data}")
        return Ok(None)

    def _execute_event_command_default(self, event_name: str, command: str, data: Optional[Union[str, dict, list]] = None) -> Result[None]:
        return Ok(None)


    def _execute_event_command_dispatch_event(self, event_name: str, command: str, data: Optional[Union[str, dict, list]] = None) -> Result[None]:
        print("Widget: _execute_event_command_dispatch_event")
        return Result.error("Widget: _execute_event_command_dispatch_event: not implemented")


    def _execute_event_commands(self, event_name: str) -> Result[None]:
        print("Widget: _execute_event_command_dispatch_event")
        if event_name not in self._event_handlers:
            return Ok(None)
        commands = self._event_handlers[event_name]
        # print("_detect_and_execute_events", event_name, commands)
        for i, cmd_spec in enumerate(commands):
            if not isinstance(cmd_spec, dict):
                return Result.error(f"Widget: _execute_event_commands: expected dict, got '{type(cmd_spec)}'", cmd_spec)
            # Check condition if present
            if "when" in cmd_spec:
                condition = cmd_spec["when"]
                if self._metadata and not self._evaluate_condition(condition, self._metadata):
                    continue  # Condition not met, skip this command
            if "command" not in cmd_spec:
                return Result.error(f"'command' key not found in cmd_spec: {cmd_spec}")
            command = cmd_spec['command']
            method_name = f"_execute_event_command_{command.replace("-", "_")}"
            method = getattr(self, method_name)
            if method is None:
                return Result.error(f"'command' key not found in cmd_spec: {cmd_spec}")
            return method(event_name, command, cmd_spec["data"])

        return Ok(None)


    def _execute_event_commands_old(self, event_name: str) -> Result[None]:
        """
        Execute commands for an event.

        For each command:
        - Check "when" condition (if present)
        - Execute action: "show" or "dispatch"
        - Lazy create widgets on first execution

        Args:
            event_name: Name of the event ("clicked", "hovered", etc.)

        Returns:
            Result[None]
        """
        if event_name not in self._event_handlers:
            return Ok(None)

        commands = self._event_handlers[event_name]

        for i, cmd_spec in enumerate(commands):
            if not isinstance(cmd_spec, dict):
                continue


            # Get action (normalized format: {"action": "show"/"dispatch"/"default", "what": ...})
            action = cmd_spec.get("action")
            if action == "show":
                widget_spec = cmd_spec.get("what")
            elif action == "dispatch":
                message = cmd_spec.get("message") or cmd_spec.get("what")
            elif action == "default":
                # Default action - no additional data needed
                pass
            else:
                return Result.error(f"Unknown action in {event_name} event: {cmd_spec}")

            # Execute action
            if action == "default":
                # Default action: for "click" events, set "selected" field
                if event_name == "on-click":
                    set_res = self._field_values.set("selection", str(self._data_path))
                    if not set_res:
                        return Result.error(f"default action failed to set selected", set_res)
                # For other events, default does nothing

            elif action == "show":
                if "widget-instance" not in cmd_spec:
                    # Lazy create widget
                    res = self._create_widget_from_spec(widget_spec)
                    if not res:
                        return Result.error(f"Widget: Failed to create widget for {event_name} event", res)
                    cmd_spec["widget-instance"] = res.unwrapped

                # Render the widget
                widget = cmd_spec["widget-instance"]
                res = widget.render()
                if not res:
                    return Result.error(f"Failed to render widget for {event_name} event", res)

            elif action == "dispatch":
                # Placeholder for dispatch
                # TODO: Implement dispatch to dispatcher
                pass

            elif action == "add-data-child":
                # Get child name and data from command spec
                child_name = cmd_spec.get("name")
                if not child_name:
                    return Result.error(f"add-data-child: missing 'name' parameter")

                # Get data - can be metadata dict or any other data
                child_data = cmd_spec.get("metadata") or cmd_spec.get("data")
                if child_data is None:
                    return Result.error(f"add-data-child: missing 'metadata' or 'data' parameter")

                # Get target path (optional data-id, supports ".." for parent)
                data_id = cmd_spec.get("data-id")
                if data_id:
                    target_path = self._data_path / data_id
                else:
                    target_path = self._data_path

                # Call add_child through field_values
                res = self._field_values.add_child(target_path, child_name, child_data)
                if not res:
                    return Result.error(f"add-data-child: failed to add child '{child_name}' at '{target_path}'", res)

            elif action == "add-data-key-value":
                #TODO
                pass

        return Ok(None)


    def _create_widget_from_spec(self, widget_spec) -> Result["Widget"]:
        """
        Create a widget from a specification.

        Args:
            widget_spec: String (widget name), dict (inline widget), or list (composite)

        Returns:
            Result[Widget]: Created widget instance
        """

        print("Widget: _create_widget_from_spec", widget_spec)
        # String → widget reference
        if isinstance(widget_spec, str):
            widget_name = widget_spec
            if '.' not in widget_name and self._namespace:
                full_widget_name = f"{self._namespace}.{widget_name}"
            else:
                full_widget_name = widget_spec
            return self._factory.create_widget(full_widget_name, self._tree_like, self._data_path)

        # Dict or list → composite - use factory to avoid circular import
        if isinstance(widget_spec, (dict, list)):
            print("Widget: _create_widget_from_spec 2", widget_spec)
            params = {"type": "composite", "body": [widget_spec] if isinstance(widget_spec, dict) else widget_spec}
            # Use full widget name with namespace to preserve context
            full_widget_name = f"{self._namespace}.composite" if self._namespace else "composite"
            res = self._factory.create_widget(full_widget_name, self._tree_like, self._data_path, params)
            if not res:
                print("ERROR", res)
                return Result.error("Widget: _create_widget_from_spec: could not create widget", res)
            else:
                print("OK!", res.unwrapped)
                return Ok(res.unwrapped)

        return Result.error(f"Invalid widget spec type: {type(widget_spec)}")

    def _prepare_render(self) -> Result[None]:
        """Prepare for rendering: load metadata and label"""
        # Get metadata first if we have a data tree
        if self._tree_like:
            # Determine metadata path: path / data-id if data-id present, otherwise path
            if isinstance(self._params, dict) and "data-id" in self._params:
                metadata_path = self._data_path / self._params["data-id"]
            else:
                metadata_path = self._data_path

            # Get metadata
            res = self._tree_like.get_metadata(metadata_path)
            if not res:
                return Result.error(f"_prepare_render: failed to get metadata at path '{metadata_path}'", res)
            self._metadata = res.unwrapped


        return Ok(None)

    def _apply_style_dict(self, style_dict: dict) -> Result[None]:
        """Apply a single style dictionary (colors and vars)"""
        for style_name, style_value in style_dict.items():
            # Convert hyphenated name to underscore for enum lookup
            enum_name = style_name.replace("-", "_")

            # Try to find it as a color first
            try:
                color_enum = getattr(imgui.Col_, enum_name)
                # Convert list to ImVec4 if needed
                if isinstance(style_value, list):
                    if len(style_value) == 4:
                        color = imgui.ImVec4(style_value[0], style_value[1], style_value[2], style_value[3])
                    else:
                        return Result.error(f"Style color '{style_name}' requires 4 components, got {len(style_value)}")
                else:
                    return Result.error(f"Style color '{style_name}' must be a list")

                imgui.push_style_color(color_enum, color)
                self._style_color_count += 1
                continue
            except AttributeError:
                pass  # Not a color, try style var

            # Try to find it as a style var
            try:
                var_enum = getattr(imgui.StyleVar_, enum_name)
                # Convert list to ImVec2 if needed, otherwise use scalar
                if isinstance(style_value, list):
                    if len(style_value) == 2:
                        vec = imgui.ImVec2(style_value[0], style_value[1])
                        imgui.push_style_var(var_enum, vec)
                    else:
                        return Result.error(f"Style var '{style_name}' requires 1 or 2 components, got {len(style_value)}")
                else:
                    # Scalar value
                    imgui.push_style_var(var_enum, float(style_value))

                self._style_var_count += 1
                continue
            except AttributeError:
                return Result.error(f"Unknown style attribute '{style_name}'")

        return Ok(None)

    def _push_styles(self) -> Result[None]:
        """Push styles before rendering"""
        if not isinstance(self._params, dict):
            return Ok(None)

        # Apply default style first
        style = self._params.get("style")
        if style and isinstance(style, dict):
            res = self._apply_style_dict(style)
            if not res:
                return Result.error("_push_styles: failed to apply default style", res)

        # Apply style-mapping based on metadata conditions
        style_mapping = self._params.get("style-mapping")
        if style_mapping and self._metadata:
            # style-mapping can be:
            # 1. Dict (old format): {field_name: style_dict, ...} - backward compatible
            # 2. List (new format): [{when: condition, style: style_dict}, ...]

            if isinstance(style_mapping, dict):
                # Old format: dict keys are field names to check
                for field_name, field_style in style_mapping.items():
                    # Simple field name - check if exists and truthy
                    condition = {field_name: True}

                    # Evaluate condition
                    if self._evaluate_condition(condition, self._metadata):
                        # Apply the style for this condition
                        if isinstance(field_style, dict):
                            res = self._apply_style_dict(field_style)
                            if not res:
                                return Result.error(f"_push_styles: failed to apply style-mapping for '{field_name}'", res)

            elif isinstance(style_mapping, list):
                # New format: list of {when: condition, style: style_dict}
                for mapping_entry in style_mapping:
                    if not isinstance(mapping_entry, dict):
                        continue

                    condition = mapping_entry.get("when")
                    entry_style = mapping_entry.get("style")

                    if condition is None or entry_style is None:
                        continue

                    # Evaluate condition
                    if self._evaluate_condition(condition, self._metadata):
                        # Apply the style
                        if isinstance(entry_style, dict):
                            res = self._apply_style_dict(entry_style)
                            if not res:
                                return Result.error(f"_push_styles: failed to apply style-mapping for condition", res)

        self._styles_pushed = True
        return Ok(None)

    def _pop_styles(self) -> Result[None]:
        """Pop styles after rendering - called by subclasses"""
        if not self._styles_pushed:
            return Ok(True)
        if self._style_color_count > 0:
            imgui.pop_style_color(self._style_color_count)
            self._style_color_count = 0

        if self._style_var_count > 0:
            imgui.pop_style_var(self._style_var_count)
            self._style_var_count = 0

        self._styles_pushed = False
        return Ok(None)

    def _pre_render_head(self) -> Result[None]:
        """Render widget core - must be implemented by subclasses
        """
        return Result.error("Widget: _pre_render_head() not implemented")

    def _post_render_head(self) -> Result[None]:
        """Widget-specific post-render cleanup - override in subclasses if needed

        Examples:
            TreeNode: imgui.tree_pop() if widget was opened
            Menu: imgui.end_menu() if widget was opened
            Indent: imgui.unindent() always
        """
        return Ok(None)

    def _ensure_body(self) -> Result[None]:
        # print("Widget: _ensure_body")
        if isinstance(self._params, dict) and "body" in self._params and self._body is None:
            activated_spec = self._params["body"]
            res = self._create_widget_from_spec(activated_spec)
            if not res:
                return Result.error("_prepare_render: failed to create activated widget", res)
            self._body = res.unwrapped
        return Ok(None)


    def _render_errors(self) -> Result[None]:
        """Display all collected errors as a tree view"""
        if len(self._errors) == 0:
            return Ok(None)

        errors_tree = Result.error("Error:", self._errors).as_tree
        import pprint
        pprint.pp(errors_tree)
        # Create error widget only once (persist across render cycles)
        if self._error_widget is None:
            print("Widget: _render_errors: creating error widget")
            errors_tree = Result.error("Error:", self._errors).as_tree


            from imery.backend.simple_data_tree import SimpleDataTree
            # Convert errors list to tree structure
            res = SimpleDataTree.create(errors_tree)
            if not res:
                errors = self._errors.copy()
                errors.append(res)
                return _render_error_simple(Result.error("Widget: _render_errors: failed to create SimpleDataTree from errors", errors).as_tree)

            error_tree = res.unwrapped
            res = self._factory.create_widget("builtin.tree-view", error_tree, DataPath("/"))
            if not res:
                errors = self._errors.copy()
                errors.append(res)
                return _render_error_simple(Result.error("Widget: _render_errors: failed to create 'tree-view' for errors", errors).as_tree)

            self._error_widget = res.unwrapped

        # Render the persisted error widget every frame
        res = self._error_widget.render()
        if not res:
            errors = self._errors.copy()
            errors.append(res)
            return _render_error_simple(Result.error("Widget: _render_errors: failed to render 'tree-view' for errors", errors).as_tree)

        return Ok(None)

    def _handle_error(self, error) -> Result[Any]:
        print("Widget: _handle_error", error)
        if not error:
            self._errors.append(error)
        return error

    def render(self) -> Result[None]:
        """Unified render flow for all widgets"""
        # Prepare: load metadata and label
        # print("Widget: render 000", self)

        self._render_cycle = self._render_cycle + 1
        res = self._prepare_render()
        if not res:
            self._handle_error(Result.error("Widget: render: prepare_render failed", res))

        # Push styles
        if not self._errors:
            res = self._push_styles()
            if not res:
                self._handle_error(Result.error("Widget: render: _push_styles failed", res))

        # print("Widget: render 010", self)
        # First Render the "head", in case of Button, the button is itself the head
        # and the widget activated is the body, even if rendered in the "same window" or in popup or modal
        if not self._errors:
            res = self._pre_render_head()
            if not res:
                self._handle_error(Result.error(f"Widget: render: _pre_render_head failed for class: {str(type(self))}", res))

        if not self._errors:
            res = self._detect_and_execute_events()
            if not res:
                self._handle_error(Result.error("Widget: render: detect_and_execute_events failed", res))

        if not self._errors:
            if self._body is None and (self._is_body_activated or self._should_create_body):
                res = self._ensure_body()
                if not res:
                    self._handle_error(Result.error("render: ensure_body failed", res))

        # print("Widget: render 1", self)
        # print("Widget: render 020", self)

        if not self._errors and self._is_body_activated:
            if self._body:
                res = self._body.render()
                if not res:
                    self._handle_error(Result.error("Widget: render: body.render failed", res))

        # Widget-specific post-render (cleanup like tree_pop, end_menu, etc.)
        # Must be called whenever body is activated, even if body widget doesn't exist yet
        res = self._post_render_head()
        if not res:
            self._handle_error(Result.error("Widget: render: _post_render_head failed", res))


        # Pop styles - always try to clean up
        res = self._pop_styles()
        if not res:
            self._handle_error(Result.error("render: _pop_styles failed", res))

        if self._body and not self._body.is_open:
            # This is mainly used for the Button-Popup logic
            self._is_body_activated = False
            self._body = None

        return self._render_errors()


    def _detect_and_execute_events(self) -> Result[None]:
        """Detect ImGui events and execute corresponding event handlers"""
        # activated event - triggered by widget return value
        if self._is_body_activated and "on-active" in self._event_handlers:
            res = self._execute_event_commands("on-active")
            if not res:
                return Result.error("Failed to execute activated event", res)

        # clicked event - left mouse button
        # TODO ... each widget should implement the manipulation of the self._clicked!
        # likely only tree-node reacts on is_item_clicked
        if (imgui.is_item_clicked(0)) and "on-click" in self._event_handlers:
            print("item clicked")
            res = self._execute_event_commands("on-click")
            if not res:
                return Result.error("Failed to execute 'on-click' commands", res)

        # right-clicked event - right mouse button
        if imgui.is_item_clicked(1) and "on-right-click" in self._event_handlers:
            res = self._execute_event_commands("on-right-click")
            if not res:
                return Result.error("Failed to execute 'on-right-click' commands", res)

        # double-clicked event
        if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0) and "on-double-click" in self._event_handlers:
            res = self._execute_event_commands("on-double-click")
            if not res:
                return Result.error("Failed to execute 'on-double-click' commands", res)

        # hovered event
        if imgui.is_item_hovered() and "on-hover" in self._event_handlers:
            res = self._execute_event_commands("on-hover")
            if not res:
                return Result.error("Failed to execute 'on-hover' commands", res)

        # hovered event
        if not self._last_error is None and "on-error" in self._event_handlers:
            res = self._execute_event_commands("on-error")
            if not res:
                return Result.error("Failed to execute 'on-error' commands", res)

        return Ok(None)

    def dispose(self) -> Result[None]:
        """Cleanup widget resources"""
        return Ok(None)
    
    def _close(self) -> Result[None]:
        return Result.error("Widget: _close: Not implemented")

    def handle_event(self, event):
        pass
