"""
Composition Builder - Combines components into complete video compositions.

Manages the timeline, layering, and sequencing of video components.

## Dynamic Builder Methods

All component builder methods (add_line_chart, add_code_block, etc.) are
dynamically registered at runtime by discover_components() and
register_all_builders() in src/chuk_motion/components/__init__.py.

Each component directory (e.g., src/chuk_motion/components/charts/LineChart/)
contains a builder.py file with an add_to_composition() function. These
functions are automatically discovered and converted into methods on the
CompositionBuilder class.

This design ensures:
- Single source of truth: Component logic lives only in component/builder.py
- Automatic discovery: New components are automatically available
- Consistency: All components follow the same pattern
- Maintainability: Update one place, not multiple files

Usage:
    from chuk_motion.generator.composition_builder import CompositionBuilder
    from chuk_motion.components import register_all_builders

    # Register all component builder methods
    register_all_builders(CompositionBuilder)

    # Create composition and use dynamically added methods
    builder = CompositionBuilder(fps=30)
    builder.add_title_scene(text="Hello", duration_seconds=3.0)
    builder.add_line_chart(data=[[0,10],[1,20]], start_time=3.0)
"""

import logging
from dataclasses import dataclass, field
from typing import Any

logger = logging.getLogger(__name__)


def snake_to_camel(snake_str: str) -> str:
    """Convert snake_case to camelCase."""
    components = snake_str.split("_")
    return components[0] + "".join(x.title() for x in components[1:])


@dataclass
class ComponentInstance:
    """Represents an instance of a component in the timeline."""

    component_type: str  # TitleScene, LowerThird, etc.
    start_frame: int
    duration_frames: int
    props: dict[str, Any] = field(default_factory=dict)
    layer: int = 0  # Higher layers render on top


class CompositionBuilder:
    """Builds complete video compositions from components."""

    # Class-level registry for component-specific renderers
    _component_renderers: dict = {}

    def __init__(
        self, fps: int = 30, width: int = 1920, height: int = 1080, transparent: bool = False
    ):
        """
        Initialize composition builder.

        Args:
            fps: Frames per second (default: 30)
            width: Video width in pixels (default: 1920)
            height: Video height in pixels (default: 1080)
            transparent: Use transparent background (default: False)
        """
        self.fps = fps
        self.width = width
        self.height = height
        self.components: list[ComponentInstance] = []
        self.theme = "tech"
        self.transparent = transparent

    def seconds_to_frames(self, seconds: float) -> int:
        """Convert seconds to frames."""
        return int(seconds * self.fps)

    def frames_to_seconds(self, frames: int) -> float:
        """Convert frames to seconds."""
        return frames / self.fps

    # =========================================================================
    # COMPONENT BUILDER METHODS ARE DYNAMICALLY ADDED
    # =========================================================================
    #
    # All add_* methods (add_line_chart, add_code_block, etc.) are dynamically
    # registered at runtime by the register_all_builders() function in
    # src/chuk_motion/components/__init__.py
    #
    # This function discovers all components and automatically creates builder
    # methods from each component's builder.py file, ensuring:
    #   - Single source of truth (no duplication)
    #   - Automatic discovery of new components
    #   - Consistency across all components
    #
    # See: src/chuk_motion/components/__init__.py:138-164
    # =========================================================================

    def _get_next_start_frame(self) -> int:
        """Get the start frame for the next sequential component."""
        if not self.components:
            return 0

        # Find the last component on layer 0 (main content)
        layer_0_components = [c for c in self.components if c.layer == 0]
        if not layer_0_components:
            return 0

        last = max(layer_0_components, key=lambda c: c.start_frame + c.duration_frames)
        return last.start_frame + last.duration_frames

    def get_total_duration_frames(self) -> int:
        """Get total duration of the composition in frames."""
        if not self.components:
            return 0
        return max(c.start_frame + c.duration_frames for c in self.components)

    def get_total_duration_seconds(self) -> float:
        """Get total duration of the composition in seconds."""
        return self.frames_to_seconds(self.get_total_duration_frames())

    def generate_composition_tsx(self) -> str:
        """
        Generate the main VideoComposition.tsx component.

        Returns:
            TSX code for the complete composition
        """
        # Sort components by layer (lower layers first)
        sorted_components = sorted(self.components, key=lambda c: c.layer)

        # Find all nested children to exclude from top-level rendering
        nested_children = self._find_nested_children(sorted_components)

        # Generate import statements (recursively find all component types)
        unique_types = self._find_all_component_types(sorted_components)
        imports = "\n".join(
            [
                f"import {{ {comp_type} }} from './components/{comp_type}';"
                for comp_type in sorted(unique_types)
            ]
        )

        # Generate component JSX (only top-level components)
        components_jsx = []
        for comp in sorted_components:
            # Skip if this component is a child of another component
            if id(comp) in nested_children:
                continue

            jsx = self._render_component_jsx(comp, indent=6)
            components_jsx.append(jsx)

        components_jsx_str = "\n".join(components_jsx)

        # Background color: transparent or black
        background_color = "transparent" if self.transparent else "#000"

        # Generate complete composition
        tsx = f"""import React from 'react';
import {{ AbsoluteFill }} from 'remotion';
{imports}

interface VideoCompositionProps {{
  theme: string;
}}

export const VideoComposition: React.FC<VideoCompositionProps> = ({{ theme }}) => {{
  return (
    <AbsoluteFill style={{{{ backgroundColor: '{background_color}' }}}}>
{components_jsx_str}
    </AbsoluteFill>
  );
}};
"""
        return tsx

    def _find_all_component_types(self, components: list[ComponentInstance]) -> set:
        """Recursively find all component types including nested children."""
        types = set()

        def collect_types(comp):
            types.add(comp.component_type)

            # Check for nested children
            layout_types = [
                "Grid",
                "Container",
                "SplitScreen",
                "ThreeColumnLayout",
                "ThreeRowLayout",
                "ThreeByThreeGrid",
                "AsymmetricLayout",
                "OverTheShoulder",
                "DialogueFrame",
                "StackedReaction",
                "HUDStyle",
                "PerformanceMultiCam",
                "FocusStrip",
                "PiP",
                "Vertical",
                "Timeline",
                "Mosaic",
                # Frame components with content prop
                "BrowserFrame",
                "DeviceFrame",
                "Terminal",
                # Transition components
                "PixelTransition",
                "LayoutTransition",
                # Animation wrapper components
                "LayoutEntrance",
                "PanelCascade",
                # Overlay components with content
                "TextOverlay",
                "LowerThird",
                "SubscribeButton",
            ]

            if comp.component_type in layout_types:
                children = comp.props.get("children")
                if isinstance(children, list):
                    for child in children:
                        if isinstance(child, ComponentInstance):
                            collect_types(child)
                elif isinstance(children, ComponentInstance):
                    collect_types(children)

                # For SplitScreen and ThreeColumn/ThreeRow layouts
                for key in ["left", "right", "top", "bottom", "center", "middle"]:
                    child = comp.props.get(key)
                    if isinstance(child, ComponentInstance):
                        collect_types(child)

                # For specialized layouts
                specialized_keys = [
                    "mainFeed",
                    "demo1",
                    "demo2",
                    "overlay",  # AsymmetricLayout (old)
                    "main",
                    "top_side",
                    "bottom_side",  # AsymmetricLayout (new)
                    "hostView",
                    "screenContent",  # OverTheShoulder
                    "screen_content",
                    "shoulder_overlay",  # OverTheShoulder (new)
                    "characterA",
                    "characterB",  # DialogueFrame
                    "left_speaker",
                    "center_content",
                    "right_speaker",  # DialogueFrame (new)
                    "originalClip",
                    "reactorFace",  # StackedReaction
                    "original_content",
                    "reaction_content",  # StackedReaction (new)
                    "gameplay",
                    "webcam",
                    "chatOverlay",  # HUDStyle
                    "main_content",
                    "top_left",
                    "top_right",
                    "bottom_left",
                    "bottom_right",  # HUDStyle (new)
                    "frontCam",
                    "overheadCam",
                    "handCam",
                    "detailCam",  # PerformanceMultiCam
                    "primary_cam",
                    "secondary_cams",  # PerformanceMultiCam (new)
                    "hostStrip",
                    "backgroundContent",  # FocusStrip
                    "focus_content",  # FocusStrip (new)
                    "mainContent",
                    "pipContent",  # PiPLayout
                    "topContent",
                    "bottomContent",
                    "captionBar",  # VerticalLayout
                    "milestones",
                    "clips",  # TimelineLayout, MosaicLayout
                    "content",  # Container, Frames, Overlays, Animations
                    "items",  # PanelCascade - array of ComponentInstance
                    "panels",  # Legacy
                    "leftPanel",
                    "rightPanel",
                    "topPanel",
                    "bottomPanel",  # SplitScreen
                    "firstContent",
                    "secondContent",  # PixelTransition
                    "before",
                    "after",  # LayoutTransition
                ]
                for key in specialized_keys:
                    child = comp.props.get(key)
                    if isinstance(child, ComponentInstance):
                        collect_types(child)
                    # Also handle arrays of ComponentInstances
                    elif isinstance(child, list):
                        for item in child:
                            if isinstance(item, ComponentInstance):
                                collect_types(item)

        for comp in components:
            collect_types(comp)

        return types

    def _find_nested_children(self, components: list[ComponentInstance]) -> set:
        """Find all components that are children of layout components."""
        nested = set()
        layout_types = [
            "Grid",
            "Container",
            "SplitScreen",
            "ThreeColumnLayout",
            "ThreeRowLayout",
            "ThreeByThreeGrid",
            "AsymmetricLayout",
            "OverTheShoulder",
            "DialogueFrame",
            "StackedReaction",
            "PixelTransition",
            "LayoutTransition",
            "HUDStyle",
            "PerformanceMultiCam",
            "FocusStrip",
            "PiP",
            "Vertical",
            "Timeline",
            "Mosaic",
            # Animation wrappers
            "LayoutEntrance",
            "PanelCascade",
        ]

        for comp in components:
            if comp.component_type in layout_types:
                # Get children from props
                children = comp.props.get("children")
                if isinstance(children, list):
                    for child in children:
                        if isinstance(child, ComponentInstance):
                            nested.add(id(child))
                elif isinstance(children, ComponentInstance):
                    nested.add(id(children))

                # For SplitScreen and ThreeColumn/ThreeRow layouts
                for key in ["left", "right", "top", "bottom", "center", "middle"]:
                    child = comp.props.get(key)
                    if isinstance(child, ComponentInstance):
                        nested.add(id(child))

                # For specialized layouts
                specialized_keys = [
                    "mainFeed",
                    "demo1",
                    "demo2",
                    "overlay",  # AsymmetricLayout (old)
                    "main",
                    "top_side",
                    "bottom_side",  # AsymmetricLayout (new)
                    "hostView",
                    "screenContent",  # OverTheShoulder
                    "screen_content",
                    "shoulder_overlay",  # OverTheShoulder (new)
                    "characterA",
                    "characterB",  # DialogueFrame
                    "left_speaker",
                    "center_content",
                    "right_speaker",  # DialogueFrame (new)
                    "originalClip",
                    "reactorFace",  # StackedReaction
                    "original_content",
                    "reaction_content",  # StackedReaction (new)
                    "gameplay",
                    "webcam",
                    "chatOverlay",  # HUDStyle
                    "main_content",
                    "top_left",
                    "top_right",
                    "bottom_left",
                    "bottom_right",  # HUDStyle (new)
                    "frontCam",
                    "overheadCam",
                    "handCam",
                    "detailCam",  # PerformanceMultiCam
                    "primary_cam",
                    "secondary_cams",  # PerformanceMultiCam (new)
                    "hostStrip",
                    "backgroundContent",  # FocusStrip
                    "focus_content",  # FocusStrip (new)
                    "mainContent",
                    "pipContent",  # PiPLayout
                    "topContent",
                    "bottomContent",
                    "captionBar",  # VerticalLayout
                    "milestones",
                    "clips",  # TimelineLayout, MosaicLayout
                    "content",  # Container, Frames, Overlays, Animations
                    "panels",  # PanelCascade
                    "items",  # PanelCascade (new)
                    "leftPanel",
                    "rightPanel",
                    "topPanel",
                    "bottomPanel",  # SplitScreen
                    "firstContent",
                    "secondContent",  # PixelTransition
                    "before",
                    "after",  # LayoutTransition
                ]
                for key in specialized_keys:
                    child = comp.props.get(key)
                    if isinstance(child, ComponentInstance):
                        nested.add(id(child))
                    # Also handle arrays
                    elif isinstance(child, list):
                        for item in child:
                            if isinstance(item, ComponentInstance):
                                nested.add(id(item))
        return nested

    def _render_component_jsx(self, comp: ComponentInstance, indent: int = 0) -> str:
        """Render a component as JSX, including nested children."""
        # Try component-specific custom renderer FIRST (highest priority)
        custom_jsx = self._try_custom_renderer(comp, indent)
        if custom_jsx is not None:
            return custom_jsx

        # Check if this is a layout component with children
        layout_types = [
            "Grid",
            "Container",
            "SplitScreen",
            "ThreeColumnLayout",
            "ThreeRowLayout",
            "ThreeByThreeGrid",
            "AsymmetricLayout",
            "OverTheShoulder",
            "DialogueFrame",
            "StackedReaction",
            "HUDStyle",
            "PerformanceMultiCam",
            "FocusStrip",
            "PiP",
            "Vertical",
            "Timeline",
            "Mosaic",
            # Frame components with content prop
            "BrowserFrame",
            "DeviceFrame",
            "Terminal",
            # Transition components
            "PixelTransition",
            "LayoutTransition",
            # Animation components with content
            "LayoutEntrance",
            "PanelCascade",
            # Overlay components with content
            "TextOverlay",
            "LowerThird",
            "SubscribeButton",
        ]
        has_children = comp.component_type in layout_types

        if has_children:
            return self._render_layout_component(comp, indent)
        else:
            return self._render_simple_component(comp, indent)

    def _render_simple_component(self, comp: ComponentInstance, indent: int) -> str:
        """Render a simple component without children."""
        spaces = " " * indent

        # Format props (exclude children-related props)
        # Convert snake_case keys to camelCase for TypeScript
        props_lines = []
        for key, value in comp.props.items():
            if key not in ["children", "left", "right", "top", "bottom"] and value is not None:
                camel_key = snake_to_camel(key)
                props_lines.append(f"{spaces}  {camel_key}={self._format_prop_value(value)}")
        props_str = "\n".join(props_lines) if props_lines else ""

        if props_str:
            return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{props_str}
{spaces}/>"""
        else:
            return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{spaces}/>"""

    def _render_layout_component(self, comp: ComponentInstance, indent: int) -> str:
        """Render a layout component with nested children."""
        spaces = " " * indent

        # Format non-children props
        # Exclude child component props from regular props (using snake_case names to match templates)
        exclude_keys = [
            "children",
            "left",
            "right",
            "top",
            "bottom",
            "center",
            "middle",
            # AsymmetricLayout
            "main",
            "top_side",
            "bottom_side",
            # OverTheShoulder
            "screen_content",
            "shoulder_overlay",
            # DialogueFrame
            "left_speaker",
            "right_speaker",
            "center_content",
            # StackedReaction
            "original_content",
            "reaction_content",
            # HUDStyle
            "main_content",
            "top_left",
            "top_right",
            "bottom_left",
            "bottom_right",
            # PerformanceMultiCam
            "primary_cam",
            "secondary_cams",
            # FocusStrip
            "focus_content",
            # PiP
            "mainContent",
            "pipContent",
            # Vertical
            "topContent",
            "bottomContent",
            "captionBar",
            # Timeline / Mosaic
            "clips",
            # Container, Overlays, Animations
            "content",
            # PanelCascade
            "panels",
            # PixelTransition / LayoutTransition
            "firstContent",
            "secondContent",
            "before",
            "after",
        ]
        props_lines = []
        for key, value in comp.props.items():
            if key not in exclude_keys and value is not None:
                camel_key = snake_to_camel(key)
                props_lines.append(f"{spaces}  {camel_key}={self._format_prop_value(value)}")
        props_str = "\n".join(props_lines) if props_lines else ""

        # Render children based on component type
        if comp.component_type == "Grid":
            children = comp.props.get("children", [])
            if isinstance(children, list):
                children_jsx = []
                for child in children:
                    if isinstance(child, ComponentInstance):
                        child_jsx = self._render_component_jsx(child, indent + 4)
                        children_jsx.append(child_jsx)
                # Join with commas for JSX array
                children_str = ",\n".join(children_jsx)
            else:
                children_str = ""

            if props_str:
                return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{props_str}
{spaces}>
{spaces}  {{[
{children_str}
{spaces}  ]}}
{spaces}</{comp.component_type}>"""
            else:
                return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{spaces}>
{spaces}  {{[
{children_str}
{spaces}  ]}}
{spaces}</{comp.component_type}>"""

        elif comp.component_type == "Container":
            children = comp.props.get("children")
            # Handle single child or array of children
            if isinstance(children, ComponentInstance):
                child_jsx = self._render_component_jsx(children, indent + 4)
            elif isinstance(children, list) and len(children) > 0:
                # If array with single item, render it directly
                if len(children) == 1 and isinstance(children[0], ComponentInstance):
                    child_jsx = self._render_component_jsx(children[0], indent + 4)
                else:
                    # Multiple children - render them all
                    children_jsxs = []
                    for child_item in children:
                        if isinstance(child_item, ComponentInstance):
                            children_jsxs.append(self._render_component_jsx(child_item, indent + 4))
                    child_jsx = "\n".join(children_jsxs)
            else:
                child_jsx = ""

            if props_str:
                return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{props_str}
{spaces}>
{child_jsx}
{spaces}</{comp.component_type}>"""
            else:
                return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{spaces}>
{child_jsx}
{spaces}</{comp.component_type}>"""

        elif comp.component_type == "SplitScreen":
            # Render left/right or top/bottom based on orientation
            orientation = comp.props.get("orientation", "horizontal")
            if orientation == "horizontal":
                left = comp.props.get("left")
                right = comp.props.get("right")
                left_jsx = (
                    self._render_component_jsx(left, indent + 4)
                    if isinstance(left, ComponentInstance)
                    else ""
                )
                right_jsx = (
                    self._render_component_jsx(right, indent + 4)
                    if isinstance(right, ComponentInstance)
                    else ""
                )

                if props_str:
                    return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{props_str}
{spaces}  left={{
{left_jsx}
{spaces}  }}
{spaces}  right={{
{right_jsx}
{spaces}  }}
{spaces}/>"""
                else:
                    return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{spaces}  left={{
{left_jsx}
{spaces}  }}
{spaces}  right={{
{right_jsx}
{spaces}  }}
{spaces}/>"""
            else:  # vertical
                top = comp.props.get("top")
                bottom = comp.props.get("bottom")
                top_jsx = (
                    self._render_component_jsx(top, indent + 4)
                    if isinstance(top, ComponentInstance)
                    else ""
                )
                bottom_jsx = (
                    self._render_component_jsx(bottom, indent + 4)
                    if isinstance(bottom, ComponentInstance)
                    else ""
                )

                if props_str:
                    return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{props_str}
{spaces}  top={{
{top_jsx}
{spaces}  }}
{spaces}  bottom={{
{bottom_jsx}
{spaces}  }}
{spaces}/>"""
                else:
                    return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{spaces}  top={{
{top_jsx}
{spaces}  }}
{spaces}  bottom={{
{bottom_jsx}
{spaces}  }}
{spaces}/>"""

        # Handle specialized layouts (OverTheShoulder, DialogueFrame, ThreeColumn, ThreeRow, Asymmetric, etc.)
        elif comp.component_type in [
            "OverTheShoulder",
            "DialogueFrame",
            "StackedReaction",
            "HUDStyle",
            "PerformanceMultiCam",
            "FocusStrip",
            "ThreeColumnLayout",
            "ThreeRowLayout",
            "AsymmetricLayout",
            "ThreeByThreeGrid",
            "PiP",
            "Vertical",
            "Timeline",
            "Mosaic",
        ]:
            # Map layout types to their prop keys (using snake_case names to match templates)
            layout_prop_keys = {
                "OverTheShoulder": ["screen_content", "shoulder_overlay"],
                "DialogueFrame": ["left_speaker", "center_content", "right_speaker"],
                "StackedReaction": ["original_content", "reaction_content"],
                "HUDStyle": [
                    "main_content",
                    "top_left",
                    "top_right",
                    "bottom_left",
                    "bottom_right",
                ],
                "PerformanceMultiCam": ["primary_cam", "secondary_cams"],
                "FocusStrip": ["main_content", "focus_content"],
                "ThreeColumnLayout": ["left", "center", "right"],
                "ThreeRowLayout": ["top", "middle", "bottom"],
                "AsymmetricLayout": ["main", "top_side", "bottom_side"],
                "ThreeByThreeGrid": ["children"],
                "PiP": ["mainContent", "pipContent"],
                "Vertical": ["top", "bottom"],
                "Timeline": ["main_content"],
                "Mosaic": ["clips"],
            }

            prop_keys = layout_prop_keys.get(comp.component_type, [])
            child_props = []

            for key in prop_keys:
                child = comp.props.get(key)
                if isinstance(child, ComponentInstance):
                    child_jsx = self._render_component_jsx(child, indent + 4)
                    child_props.append(f"{spaces}  {key}={{\n{child_jsx}\n{spaces}  }}")
                elif isinstance(child, list):
                    # Handle array of children (e.g., ThreeByThreeGrid, secondary_cams, clips)
                    children_jsx = []
                    for child_item in child:
                        if isinstance(child_item, ComponentInstance):
                            # Special handling for Mosaic clips - wrap in {content: ...} object
                            if comp.component_type == "Mosaic" and key == "clips":
                                # Render child inline within the content object
                                child_jsx = self._render_component_jsx(
                                    child_item, indent + 6
                                ).strip()
                                children_jsx.append(
                                    f"{spaces}    {{\n{spaces}      content: {child_jsx}\n{spaces}    }}"
                                )
                            else:
                                child_jsx = self._render_component_jsx(child_item, indent + 4)
                                children_jsx.append(child_jsx)
                    children_str = ",\n".join(children_jsx)
                    child_props.append(f"{spaces}  {key}={{[\n{children_str}\n{spaces}  ]}}")
                elif child is None:
                    # Only add undefined for optional child props
                    pass  # Don't render undefined props

            children_str = "\n".join(child_props)

            if props_str:
                return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{props_str}
{children_str}
{spaces}/>"""
            else:
                return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{children_str}
{spaces}/>"""

        # Handle frame components (BrowserFrame, DeviceFrame, Terminal)
        elif comp.component_type in ["BrowserFrame", "DeviceFrame", "Terminal"]:
            content = comp.props.get("content")
            if isinstance(content, ComponentInstance):
                content_jsx = self._render_component_jsx(content, indent + 4)
                if props_str:
                    return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{props_str}
{spaces}  content={{
{content_jsx}
{spaces}  }}
{spaces}/>"""
                else:
                    return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{spaces}  content={{
{content_jsx}
{spaces}  }}
{spaces}/>"""
            else:
                # No content, render as simple component
                return self._render_simple_component(comp, indent)

        # Handle overlay and animation components with content
        elif comp.component_type in [
            "TextOverlay",
            "LowerThird",
            "SubscribeButton",
            "LayoutEntrance",
        ]:
            content = comp.props.get("content")
            if isinstance(content, ComponentInstance):
                content_jsx = self._render_component_jsx(content, indent + 4)
                if props_str:
                    return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{props_str}
{spaces}>
{content_jsx}
{spaces}</{comp.component_type}>"""
                else:
                    return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{spaces}>
{content_jsx}
{spaces}</{comp.component_type}>"""
            else:
                # No content, render as simple component
                return self._render_simple_component(comp, indent)

        # Handle PanelCascade with panels array
        elif comp.component_type == "PanelCascade":
            panels = comp.props.get("panels", [])
            if isinstance(panels, list):
                panels_jsx = []
                for panel in panels:
                    if isinstance(panel, ComponentInstance):
                        panel_jsx = self._render_component_jsx(panel, indent + 4)
                        panels_jsx.append(panel_jsx)
                panels_str = ",\n".join(panels_jsx)

                if props_str:
                    return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{props_str}
{spaces}  panels={{[
{panels_str}
{spaces}  ]}}
{spaces}/>"""
                else:
                    return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{spaces}  panels={{[
{panels_str}
{spaces}  ]}}
{spaces}/>"""
            else:
                return self._render_simple_component(comp, indent)

        # Handle LayoutTransition with before/after
        elif comp.component_type == "LayoutTransition":
            before = comp.props.get("before")
            after = comp.props.get("after")

            before_jsx = (
                self._render_component_jsx(before, indent + 4)
                if isinstance(before, ComponentInstance)
                else ""
            )
            after_jsx = (
                self._render_component_jsx(after, indent + 4)
                if isinstance(after, ComponentInstance)
                else ""
            )

            if props_str:
                return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{props_str}
{spaces}  before={{
{before_jsx}
{spaces}  }}
{spaces}  after={{
{after_jsx}
{spaces}  }}
{spaces}/>"""
            else:
                return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{spaces}  before={{
{before_jsx}
{spaces}  }}
{spaces}  after={{
{after_jsx}
{spaces}  }}
{spaces}/>"""

        # Handle transition components (PixelTransition)
        elif comp.component_type == "PixelTransition":
            first_content = comp.props.get("firstContent")
            second_content = comp.props.get("secondContent")

            first_jsx = (
                self._render_component_jsx(first_content, indent + 4)
                if isinstance(first_content, ComponentInstance)
                else ""
            )
            second_jsx = (
                self._render_component_jsx(second_content, indent + 4)
                if isinstance(second_content, ComponentInstance)
                else ""
            )

            if props_str:
                return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{props_str}
{spaces}  firstContent={{
{first_jsx}
{spaces}  }}
{spaces}  secondContent={{
{second_jsx}
{spaces}  }}
{spaces}/>"""
            else:
                return f"""{spaces}<{comp.component_type}
{spaces}  startFrame={{{comp.start_frame}}}
{spaces}  durationInFrames={{{comp.duration_frames}}}
{spaces}  firstContent={{
{first_jsx}
{spaces}  }}
{spaces}  secondContent={{
{second_jsx}
{spaces}  }}
{spaces}/>"""

        # Fallback to default rendering
        return self._render_simple_component(comp, indent)

    def _try_custom_renderer(self, comp: ComponentInstance, indent: int) -> str | None:
        """Try to use a component-specific custom renderer if available."""
        # Check if this component has a custom renderer
        renderer = self._component_renderers.get(comp.component_type)
        if renderer:
            try:
                return renderer(
                    comp,
                    self._render_component_jsx,
                    indent,
                    snake_to_camel,
                    self._format_prop_value,
                )
            except Exception as e:
                logger.warning(f"Custom renderer for {comp.component_type} failed: {e}")
                return None
        return None

    def _serialize_value(self, value: Any) -> Any:
        """Recursively serialize values to JSON-compatible types."""
        from dataclasses import asdict, is_dataclass

        from pydantic import BaseModel

        if isinstance(value, BaseModel):
            # Recursively serialize Pydantic models using model_dump
            try:
                dumped = value.model_dump(mode="python")
                # Recursively process the dumped dict in case it contains more BaseModels
                return self._serialize_value(dumped)
            except Exception as e:
                logger.warning(f"Could not serialize {type(value).__name__}: {e}")
                # Fallback: try to convert to dict manually
                return str(value)
        elif is_dataclass(value) and not isinstance(value, type):
            # Handle dataclasses (like ComponentInstance)
            dumped = asdict(value)
            # Recursively process the dumped dict
            return self._serialize_value(dumped)
        elif isinstance(value, dict):
            # Recursively serialize dict values
            return {k: self._serialize_value(v) for k, v in value.items()}
        elif isinstance(value, list):
            # Recursively serialize list items
            return [self._serialize_value(item) for item in value]
        elif isinstance(value, tuple):
            # Convert tuples to lists
            return [self._serialize_value(item) for item in value]
        else:
            # Return primitive types as-is
            return value

    def _format_prop_value(self, value: Any) -> str:
        """Format a prop value for JSX."""
        import json

        if isinstance(value, str):
            # Use template literals for strings (supports multiline and quotes)
            # Escape backticks and ${} in the string
            escaped = value.replace("\\", "\\\\").replace("`", "\\`").replace("${", "\\${")
            return "{`" + escaped + "`}"
        elif isinstance(value, bool):
            return "{" + str(value).lower() + "}"
        elif isinstance(value, (int, float)):
            return "{" + str(value) + "}"
        elif isinstance(value, (dict, list)) or hasattr(value, "model_dump"):
            # Serialize complex types (dicts, lists, Pydantic models)
            serialized = self._serialize_value(value)
            try:
                return "{" + json.dumps(serialized) + "}"
            except TypeError:
                # Debug: log what failed
                logger.debug(f"Failed to serialize value of type {type(value)}")
                logger.debug(f"Serialized type: {type(serialized)}")
                logger.debug(f"Serialized value: {serialized}")
                raise
        else:
            return f"{{{value}}}"

    def to_dict(self) -> dict[str, Any]:
        """
        Export composition as dictionary.

        Returns:
            Dictionary representation of the composition
        """
        return {
            "fps": self.fps,
            "width": self.width,
            "height": self.height,
            "theme": self.theme,
            "duration_frames": self.get_total_duration_frames(),
            "duration_seconds": self.get_total_duration_seconds(),
            "components": [
                {
                    "type": c.component_type,
                    "start_frame": c.start_frame,
                    "duration_frames": c.duration_frames,
                    "start_time": self.frames_to_seconds(c.start_frame),
                    "duration": self.frames_to_seconds(c.duration_frames),
                    "layer": c.layer,
                    "props": c.props,
                }
                for c in self.components
            ],
        }
