"""BIOS-style TUI for SmartTools using urwid (with mouse support)."""

import urwid
from typing import Optional, List, Callable

from .tool import (
    Tool, ToolArgument, PromptStep, CodeStep, Step,
    list_tools, load_tool, save_tool, delete_tool, tool_exists, validate_tool_name
)
from .providers import Provider, load_providers, add_provider, delete_provider, get_provider


# Color palette - BIOS style with 3D button effects
PALETTE = [
    ('body', 'white', 'dark blue'),
    ('header', 'white', 'dark red', 'bold'),
    ('footer', 'black', 'light gray'),
    # Button colors - raised 3D effect
    ('button', 'black', 'light gray'),
    ('button_focus', 'white', 'dark red', 'bold'),
    ('button_highlight', 'white', 'light gray'),      # Top/left edge (light)
    ('button_shadow', 'dark gray', 'light gray'),     # Bottom/right edge (dark)
    ('button_pressed', 'black', 'dark gray'),         # Pressed state
    # Edit fields
    ('edit', 'black', 'light gray'),
    ('edit_focus', 'black', 'yellow'),
    # List items
    ('listbox', 'black', 'light gray'),
    ('listbox_focus', 'white', 'dark red'),
    # Dialog
    ('dialog', 'black', 'light gray'),
    ('dialog_border', 'white', 'dark blue'),
    # Text styles
    ('label', 'yellow', 'dark blue', 'bold'),
    ('error', 'white', 'dark red', 'bold'),
    ('success', 'light green', 'dark blue', 'bold'),
    # 3D shadow elements
    ('shadow', 'black', 'black'),
    ('shadow_edge', 'dark gray', 'dark blue'),
]


class SelectableText(urwid.WidgetWrap):
    """A selectable text widget for list items."""

    def __init__(self, text, value=None, on_select=None):
        self.value = value
        self.on_select = on_select
        self.text_widget = urwid.Text(text)
        display = urwid.AttrMap(self.text_widget, 'listbox', 'listbox_focus')
        super().__init__(display)

    def selectable(self):
        return True

    def keypress(self, size, key):
        if key == 'enter' and self.on_select:
            self.on_select(self.value)
            return None
        return key

    def mouse_event(self, size, event, button, col, row, focus):
        if event == 'mouse press' and button == 1 and self.on_select:
            self.on_select(self.value)
            return True
        return False


class Button3D(urwid.WidgetWrap):
    """A 3D-style button using box-drawing characters for depth.

    Creates a raised button effect like DOS/BIOS interfaces:
    ┌──────────┐
    │  Label   │▄
    └──────────┘█

    When focused, colors change to show selection.
    """

    signals = ['click']

    def __init__(self, label, on_press=None, user_data=None):
        self.label = label
        self.on_press = on_press
        self.user_data = user_data
        self._pressed = False

        # Build the 3D button structure
        self._build_widget()
        super().__init__(self._widget)

    def _build_widget(self):
        """Build the 3D button widget structure."""
        label = self.label
        width = len(label) + 4  # Padding inside button

        # Button face with border
        # Top edge: ┌────┐
        top = '┌' + '─' * (width - 2) + '┐'
        # Middle: │ Label │ with shadow
        middle_text = '│ ' + label + ' │'
        # Bottom edge: └────┘ with shadow
        bottom = '└' + '─' * (width - 2) + '┘'

        # Shadow characters (right and bottom)
        shadow_right = '▄'
        shadow_bottom = '█'

        # Create the rows
        top_row = urwid.Text(top + ' ')  # Space for shadow alignment
        middle_row = urwid.Columns([
            ('pack', urwid.Text(middle_text)),
            ('pack', urwid.Text(('shadow_edge', shadow_right))),
        ])
        bottom_row = urwid.Columns([
            ('pack', urwid.Text(bottom)),
            ('pack', urwid.Text(('shadow_edge', shadow_right))),
        ])
        shadow_row = urwid.Text(('shadow_edge', ' ' + shadow_bottom * (width - 1)))

        # Stack them
        pile = urwid.Pile([
            top_row,
            middle_row,
            bottom_row,
            shadow_row,
        ])

        self._widget = urwid.AttrMap(pile, 'button', 'button_focus')

    def selectable(self):
        return True

    def keypress(self, size, key):
        if key == 'enter':
            self._activate()
            return None
        return key

    def mouse_event(self, size, event, button, col, row, focus):
        if button == 1:
            if event == 'mouse press':
                self._pressed = True
                return True
            elif event == 'mouse release' and self._pressed:
                self._pressed = False
                self._activate()
                return True
        return False

    def _activate(self):
        """Trigger the button callback."""
        if self.on_press:
            self.on_press(self.user_data)
        self._emit('click')


class Button3DCompact(urwid.WidgetWrap):
    """A compact 3D button that fits on a single line with shadow effect.

    Creates a subtle 3D effect:  [ Label ]▌

    Better for inline use where vertical space is limited.
    """

    signals = ['click']

    def __init__(self, label, on_press=None, user_data=None):
        self.label = label
        self.on_press = on_press
        self.user_data = user_data

        # Build compact button: ▐ Label ▌ with shadow
        # Using block characters for edges
        button_text = urwid.Text([
            ('button_highlight', '▐'),
            ('button', f' {label} '),
            ('button_shadow', '▌'),
            ('shadow_edge', '▄'),
        ])

        self._widget = urwid.AttrMap(button_text, None, {
            'button': 'button_focus',
            'button_highlight': 'button_focus',
            'button_shadow': 'button_focus',
        })
        super().__init__(self._widget)

    def selectable(self):
        return True

    def keypress(self, size, key):
        if key == 'enter':
            self._activate()
            return None
        return key

    def mouse_event(self, size, event, button, col, row, focus):
        if button == 1 and event == 'mouse release':
            self._activate()
            return True
        return False

    def _activate(self):
        if self.on_press:
            self.on_press(self.user_data)
        self._emit('click')


class ClickableButton(urwid.WidgetWrap):
    """A button that responds to mouse clicks (legacy wrapper)."""

    def __init__(self, label, on_press=None, user_data=None):
        self.on_press = on_press
        self.user_data = user_data
        button = urwid.Button(label)
        if on_press:
            urwid.connect_signal(button, 'click', self._handle_click)
        display = urwid.AttrMap(button, 'button', 'button_focus')
        super().__init__(display)

    def _handle_click(self, button):
        if self.on_press:
            self.on_press(self.user_data)


class SelectableToolItem(urwid.WidgetWrap):
    """A selectable tool item that maintains selection state."""

    def __init__(self, name, on_select=None):
        self.name = name
        self.on_select = on_select
        self._selected = False
        self.text_widget = urwid.Text(f"  {name}  ")
        self.attr_map = urwid.AttrMap(self.text_widget, 'listbox', 'listbox_focus')
        super().__init__(self.attr_map)

    def selectable(self):
        return True

    def set_selected(self, selected):
        """Set whether this item is the selected tool."""
        self._selected = selected
        if self._selected:
            self.attr_map.set_attr_map({None: 'listbox_focus'})
        else:
            self.attr_map.set_attr_map({None: 'listbox'})

    def keypress(self, size, key):
        if key == 'enter' and self.on_select:
            self.on_select(self.name)
            return None
        return key

    def mouse_event(self, size, event, button, col, row, focus):
        if event == 'mouse press' and button == 1:
            # Single click just selects/focuses - don't call on_select
            # on_select is only called on Enter key (to edit)
            return True
        return False


class ToolListBox(urwid.ListBox):
    """A ListBox that keeps arrow keys internal and passes Tab out."""

    def __init__(self, body, on_focus_change=None):
        super().__init__(body)
        self.on_focus_change = on_focus_change
        self._last_focus = None

    def keypress(self, size, key):
        if key in ('up', 'down'):
            # Handle arrow keys internally - navigate within list
            result = super().keypress(size, key)
            # Check if focus changed
            self._check_focus_change()
            return result
        elif key == 'tab':
            # Pass tab out to parent for focus cycling
            return key
        elif key == 'shift tab':
            return key
        else:
            return super().keypress(size, key)

    def _check_focus_change(self):
        """Check if focus changed and notify callback."""
        try:
            current = self.focus
            if current is not self._last_focus:
                self._last_focus = current
                if self.on_focus_change and isinstance(current, SelectableToolItem):
                    self.on_focus_change(current.name)
        except (IndexError, TypeError):
            pass

    def render(self, size, focus=False):
        # Check focus on render too (for initial display)
        if focus:
            self._check_focus_change()
        return super().render(size, focus)


class TabCyclePile(urwid.Pile):
    """A Pile that uses Tab/Shift-Tab to cycle between specific positions.

    Args:
        widget_list: List of widgets (same as urwid.Pile)
        tab_positions: List of indices in the pile that Tab should cycle between.
                       Default is [0] (only first position).
    """

    def __init__(self, widget_list, tab_positions=None):
        super().__init__(widget_list)
        # Positions in the pile that Tab should cycle between
        self.tab_positions = tab_positions or [0]
        self._current_tab_idx = 0

    def keypress(self, size, key):
        if key == 'tab':
            # Move to next tab position
            self._current_tab_idx = (self._current_tab_idx + 1) % len(self.tab_positions)
            self.focus_position = self.tab_positions[self._current_tab_idx]
            return None
        elif key == 'shift tab':
            # Move to previous tab position
            self._current_tab_idx = (self._current_tab_idx - 1) % len(self.tab_positions)
            self.focus_position = self.tab_positions[self._current_tab_idx]
            return None
        else:
            return super().keypress(size, key)


class TabPassEdit(urwid.Edit):
    """A multiline Edit that passes Tab through for focus cycling instead of inserting tabs."""

    def keypress(self, size, key):
        if key in ('tab', 'shift tab'):
            # Pass Tab through to parent for focus cycling
            return key
        return super().keypress(size, key)


class UndoableEdit(urwid.Edit):
    """A multiline Edit with undo/redo support.

    Features:
    - Undo with Alt+U (up to 50 states)
    - Redo with Alt+R
    - Tab passes through for focus cycling
    """

    MAX_UNDO = 50  # Maximum undo history size

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._undo_stack = []  # List of (text, cursor_pos) tuples
        self._redo_stack = []

    def keypress(self, size, key):
        if key in ('tab', 'shift tab'):
            return key

        # Handle undo (Alt+U or meta u)
        if key in ('meta u', 'alt u'):
            self._undo()
            return None

        # Handle redo (Alt+R or meta r)
        if key in ('meta r', 'alt r'):
            self._redo()
            return None

        # Save current state BEFORE the edit for undo
        old_text = self.edit_text
        old_pos = self.edit_pos

        # Let the parent handle the keypress
        result = super().keypress(size, key)

        # If text changed, save the old state to undo stack
        if self.edit_text != old_text:
            self._save_undo_state(old_text, old_pos)
            self._redo_stack.clear()  # Clear redo on new edit

        return result

    def _save_undo_state(self, text, pos):
        """Save state to undo stack."""
        # Don't save duplicate states
        if self._undo_stack and self._undo_stack[-1][0] == text:
            return
        if len(self._undo_stack) >= self.MAX_UNDO:
            self._undo_stack.pop(0)
        self._undo_stack.append((text, pos))

    def _undo(self):
        """Restore previous state from undo stack."""
        if not self._undo_stack:
            return

        # Save current state to redo stack
        self._redo_stack.append((self.edit_text, self.edit_pos))

        # Restore previous state
        text, pos = self._undo_stack.pop()
        self.set_edit_text(text)
        self.set_edit_pos(min(pos, len(text)))

    def _redo(self):
        """Restore state from redo stack."""
        if not self._redo_stack:
            return

        # Save current state to undo stack
        self._undo_stack.append((self.edit_text, self.edit_pos))

        # Restore redo state
        text, pos = self._redo_stack.pop()
        self.set_edit_text(text)
        self.set_edit_pos(min(pos, len(text)))


class DOSScrollBar(urwid.WidgetWrap):
    """A DOS-style scrollbar with arrow buttons at top and bottom.

    Renders a scrollbar on the right side of the wrapped widget with:
    - ▲ arrow at top (click to scroll up)
    - ░ track with █ thumb showing scroll position
    - ▼ arrow at bottom (click to scroll down)

    Click zones (expanded to last 2 columns for easier clicking):
    - Top 25%: scroll up 3 lines
    - Bottom 25%: scroll down 3 lines
    - Middle: page up/down based on which half clicked
    """

    def __init__(self, widget):
        self._wrapped = widget
        # Create a columns layout: content on left, scrollbar on right
        super().__init__(widget)

    def render(self, size, focus=False):
        maxcol, maxrow = size

        # Render the wrapped widget with one less column for scrollbar
        content_size = (maxcol - 1, maxrow)
        content_canvas = self._wrapped.render(content_size, focus)

        # Build the scrollbar column
        scrollbar_chars = []

        # Up arrow at top
        scrollbar_chars.append('▲')

        # Calculate thumb position
        if maxrow > 2:
            track_height = maxrow - 2  # Minus the two arrow buttons

            # Get scroll position info from wrapped widget
            try:
                if hasattr(self._wrapped, 'rows_max'):
                    rows_max = self._wrapped.rows_max(content_size)
                    scroll_pos = self._wrapped.get_scrollpos(content_size)
                else:
                    rows_max = maxrow
                    scroll_pos = 0

                if rows_max > maxrow:
                    # Calculate thumb position within track
                    thumb_pos = int((scroll_pos / (rows_max - maxrow)) * (track_height - 1))
                    thumb_pos = max(0, min(thumb_pos, track_height - 1))
                else:
                    thumb_pos = 0
            except (AttributeError, TypeError, ZeroDivisionError):
                thumb_pos = 0

            # Build track with thumb
            for i in range(track_height):
                if i == thumb_pos:
                    scrollbar_chars.append('█')  # Thumb
                else:
                    scrollbar_chars.append('░')  # Track

        # Down arrow at bottom
        scrollbar_chars.append('▼')

        # Create scrollbar canvas
        scrollbar_text = '\n'.join(scrollbar_chars[:maxrow])
        scrollbar_canvas = urwid.Text(scrollbar_text).render((1,))

        # Combine canvases
        combined = urwid.CanvasJoin([
            (content_canvas, None, focus, content_size[0]),
            (scrollbar_canvas, None, False, 1),
        ])

        return combined

    def keypress(self, size, key):
        maxcol, maxrow = size
        content_size = (maxcol - 1, maxrow)
        return self._wrapped.keypress(content_size, key)

    def mouse_event(self, size, event, button, col, row, focus):
        maxcol, maxrow = size
        content_size = (maxcol - 1, maxrow)

        # Expand clickable area - last 2 columns count as scrollbar
        if col >= maxcol - 2:
            if button == 1 and event == 'mouse press':
                # Top 25% of scrollbar = scroll up
                if row < maxrow // 4:
                    for _ in range(3):
                        self._wrapped.keypress(content_size, 'up')
                    self._invalidate()
                    return True
                # Bottom 25% of scrollbar = scroll down
                elif row >= maxrow - (maxrow // 4):
                    for _ in range(3):
                        self._wrapped.keypress(content_size, 'down')
                    self._invalidate()
                    return True
                # Middle = page up/down based on which half
                elif row < maxrow // 2:
                    for _ in range(maxrow // 2):
                        self._wrapped.keypress(content_size, 'up')
                    self._invalidate()
                    return True
                else:
                    for _ in range(maxrow // 2):
                        self._wrapped.keypress(content_size, 'down')
                    self._invalidate()
                    return True

            # Handle mouse wheel on scrollbar
            if button == 4:  # Scroll up
                for _ in range(3):
                    self._wrapped.keypress(content_size, 'up')
                self._invalidate()
                return True
            elif button == 5:  # Scroll down
                for _ in range(3):
                    self._wrapped.keypress(content_size, 'down')
                self._invalidate()
                return True

            return True  # Consume other scrollbar clicks

        # Pass to wrapped widget
        return self._wrapped.mouse_event(content_size, event, button, col, row, focus)

    def selectable(self):
        return self._wrapped.selectable()

    def sizing(self):
        return frozenset([urwid.Sizing.BOX])


class ToolBuilderLayout(urwid.WidgetWrap):
    """Custom layout for tool builder that handles Tab cycling across all sections."""

    def __init__(self, left_box, args_box, steps_box, args_section, steps_section, bottom_buttons, on_cancel=None):
        self._current_section = 0
        self.on_cancel = on_cancel

        # Store references to LineBoxes for title highlighting
        self.left_box = left_box
        self.args_box = args_box
        self.steps_box = steps_box

        # Build visual layout: left column and right column side by side
        right_pile = urwid.Pile([
            ('weight', 1, args_section),
            ('pack', urwid.Divider()),
            ('weight', 1, steps_section),
        ])

        columns = urwid.Columns([
            ('weight', 1, left_box),
            ('weight', 1, right_pile),
        ], dividechars=1)

        main_pile = urwid.Pile([
            ('weight', 1, columns),
            ('pack', urwid.Divider()),
            ('pack', bottom_buttons),
        ])

        super().__init__(main_pile)

        # Set initial highlight
        self._update_section_titles()

    def keypress(self, size, key):
        if key == 'tab':
            self._current_section = (self._current_section + 1) % 4
            self._focus_section(self._current_section)
            self._update_section_titles()
            return None
        elif key == 'shift tab':
            self._current_section = (self._current_section - 1) % 4
            self._focus_section(self._current_section)
            self._update_section_titles()
            return None
        elif key == 'esc':
            # Go back to main menu instead of exiting
            if self.on_cancel:
                self.on_cancel(None)
            return None
        else:
            return super().keypress(size, key)

    def mouse_event(self, size, event, button, col, row, focus):
        # Let the parent handle the mouse event first
        result = super().mouse_event(size, event, button, col, row, focus)

        # After mouse click, detect which section has focus and update titles
        if event == 'mouse press':
            self._detect_current_section()
            self._update_section_titles()

        return result

    def _detect_current_section(self):
        """Detect which section currently has focus based on widget hierarchy."""
        main_pile = self._w

        # Check if bottom buttons have focus (position 2)
        if main_pile.focus_position == 2:
            self._current_section = 3
            return

        # Focus is on columns (position 0)
        columns = main_pile.contents[0][0]

        if columns.focus_position == 0:
            # Left box (Tool Info)
            self._current_section = 0
        else:
            # Right pile
            right_pile = columns.contents[1][0]
            if right_pile.focus_position == 0:
                # Args section
                self._current_section = 1
            else:
                # Steps section
                self._current_section = 2

    def _update_section_titles(self):
        """Update section titles to highlight the current one with markers."""
        # Section 0 = Tool Info, Section 1 = Arguments, Section 2 = Steps, Section 3 = buttons
        if self._current_section == 0:
            self.left_box.set_title('[ Tool Info ]')
            self.args_box.set_title('Arguments')
            self.steps_box.set_title('Execution Steps')
        elif self._current_section == 1:
            self.left_box.set_title('Tool Info')
            self.args_box.set_title('[ Arguments ]')
            self.steps_box.set_title('Execution Steps')
        elif self._current_section == 2:
            self.left_box.set_title('Tool Info')
            self.args_box.set_title('Arguments')
            self.steps_box.set_title('[ Execution Steps ]')
        else:
            # Buttons focused - no section highlighted
            self.left_box.set_title('Tool Info')
            self.args_box.set_title('Arguments')
            self.steps_box.set_title('Execution Steps')

    def _focus_section(self, section_idx):
        """Set focus to the specified section."""
        # Get the main pile
        main_pile = self._w

        if section_idx == 0:
            # Tool Info (left box) - focus columns, then left
            main_pile.focus_position = 0  # columns
            columns = main_pile.contents[0][0]
            columns.focus_position = 0  # left box
        elif section_idx == 1:
            # Arguments section - focus columns, then right, then args
            main_pile.focus_position = 0  # columns
            columns = main_pile.contents[0][0]
            columns.focus_position = 1  # right pile
            right_pile = columns.contents[1][0]
            right_pile.focus_position = 0  # args section
        elif section_idx == 2:
            # Steps section - focus columns, then right, then steps
            main_pile.focus_position = 0  # columns
            columns = main_pile.contents[0][0]
            columns.focus_position = 1  # right pile
            right_pile = columns.contents[1][0]
            right_pile.focus_position = 2  # steps section (after divider)
        elif section_idx == 3:
            # Save/Cancel buttons
            main_pile.focus_position = 2  # bottom buttons (after divider)


class Dialog(urwid.WidgetWrap):
    """A dialog box overlay with 3D-style buttons."""

    def __init__(self, title, body, buttons, width=60, height=None):
        # Title
        title_widget = urwid.Text(('header', f' {title} '), align='center')

        # Buttons row - use 3D compact buttons for dialog actions
        button_widgets = []
        for label, callback in buttons:
            btn = Button3DCompact(label, callback)
            button_widgets.append(btn)
        buttons_row = urwid.Columns([('pack', b) for b in button_widgets], dividechars=2)
        buttons_centered = urwid.Padding(buttons_row, align='center', width='pack')

        # Check if body is a box widget
        # ListBox is always a box widget. For Piles with weighted items,
        # check if it ONLY supports BOX sizing (not FLOW).
        is_box_widget = isinstance(body, (urwid.ListBox, urwid.Scrollable, urwid.ScrollBar))
        if not is_box_widget:
            try:
                sizing = body.sizing()
                # Box widget if it ONLY supports BOX sizing
                is_box_widget = sizing == frozenset({urwid.Sizing.BOX})
            except (AttributeError, TypeError):
                pass

        if is_box_widget:
            # Box widget - use directly with weight
            pile = urwid.Pile([
                ('pack', title_widget),
                ('pack', urwid.Divider('─')),
                ('weight', 1, body),
                ('pack', urwid.Divider('─')),
                ('pack', buttons_centered),
            ])
        else:
            # Flow widget - wrap in Filler
            body_padded = urwid.Padding(body, left=1, right=1)
            body_filled = urwid.Filler(body_padded, valign='top')
            pile = urwid.Pile([
                ('pack', title_widget),
                ('pack', urwid.Divider('─')),
                body_filled,
                ('pack', urwid.Divider('─')),
                ('pack', buttons_centered),
            ])

        # Box it
        box = urwid.LineBox(pile, title='', title_align='center')
        box = urwid.AttrMap(box, 'dialog')

        super().__init__(box)


class SmartToolsUI:
    """Urwid-based UI for SmartTools with mouse support."""

    def __init__(self):
        self.loop = None
        self.main_widget = None
        self.overlay_stack = []

    def run(self):
        """Run the UI."""
        self.show_main_menu()
        self.loop = urwid.MainLoop(
            self.main_widget,
            palette=PALETTE,
            unhandled_input=self.handle_input,
            handle_mouse=True  # Enable mouse support!
        )
        self.loop.run()

    def handle_input(self, key):
        """Handle global key input."""
        if key in ('q', 'Q', 'esc'):
            if self.overlay_stack:
                self.close_overlay()
            else:
                raise urwid.ExitMainLoop()

    def refresh(self):
        """Refresh the display."""
        if self.loop:
            self.loop.draw_screen()

    def set_main(self, widget):
        """Set the main widget."""
        self.main_widget = urwid.AttrMap(widget, 'body')
        if self.loop:
            self.loop.widget = self.main_widget

    def show_overlay(self, dialog, width=60, height=20):
        """Show a dialog overlay."""
        overlay = urwid.Overlay(
            dialog,
            self.main_widget,
            align='center', width=width,
            valign='middle', height=height,
        )
        self.overlay_stack.append(self.main_widget)
        self.main_widget = overlay
        if self.loop:
            self.loop.widget = self.main_widget

    def close_overlay(self):
        """Close the current overlay."""
        if self.overlay_stack:
            self.main_widget = self.overlay_stack.pop()
            if self.loop:
                self.loop.widget = self.main_widget

    def message_box(self, title: str, message: str, callback=None):
        """Show a message box."""
        def on_ok(_):
            self.close_overlay()
            if callback:
                callback()

        body = urwid.Text(message)
        dialog = Dialog(title, body, [("OK", on_ok)], width=50)
        self.show_overlay(dialog, width=52, height=min(10 + message.count('\n'), 20))

    def yes_no(self, title: str, message: str, on_yes=None, on_no=None):
        """Show a yes/no dialog."""
        def handle_yes(_):
            self.close_overlay()
            if on_yes:
                on_yes()

        def handle_no(_):
            self.close_overlay()
            if on_no:
                on_no()

        body = urwid.Text(message)
        dialog = Dialog(title, body, [("Yes", handle_yes), ("No", handle_no)], width=50)
        self.show_overlay(dialog, width=52, height=10)

    def input_dialog(self, title: str, prompt: str, initial: str, callback: Callable[[str], None]):
        """Show an input dialog."""
        edit = urwid.Edit(('label', f"{prompt}: "), initial)
        edit = urwid.AttrMap(edit, 'edit', 'edit_focus')

        def on_ok(_):
            value = edit.base_widget.edit_text
            self.close_overlay()
            callback(value)

        def on_cancel(_):
            self.close_overlay()

        body = urwid.Filler(edit, valign='top')
        dialog = Dialog(title, body, [("OK", on_ok), ("Cancel", on_cancel)], width=50)
        self.show_overlay(dialog, width=52, height=8)

    # ==================== Main Menu ====================

    def show_main_menu(self):
        """Show the main menu with tool list and info panel."""
        self._selected_tool_name = None
        self._refresh_main_menu()

    def _refresh_main_menu(self):
        """Refresh the main menu display."""
        from collections import defaultdict
        from .tool import DEFAULT_CATEGORIES

        tools = list_tools()
        self._tools_list = tools

        # Group tools by category
        tools_by_category = defaultdict(list)
        for name in tools:
            tool = load_tool(name)
            category = tool.category if tool else "Other"
            tools_by_category[category].append(name)

        # Build tool list with category headers
        tool_items = []

        # Show categories in defined order, then any custom ones
        all_categories = list(DEFAULT_CATEGORIES)
        for cat in tools_by_category:
            if cat not in all_categories:
                all_categories.append(cat)

        for category in all_categories:
            if category in tools_by_category and tools_by_category[category]:
                # Category header (non-selectable)
                header = urwid.AttrMap(
                    urwid.Text(f"─── {category} ───"),
                    'label'
                )
                tool_items.append(header)

                # Tools in this category
                for name in sorted(tools_by_category[category]):
                    item = SelectableToolItem(name, on_select=self._on_tool_select)
                    tool_items.append(item)

        if not tools:
            tool_items.append(urwid.Text(('label', "  (no tools - click Create to add one)  ")))

        self._tool_walker = urwid.SimpleFocusListWalker(tool_items)
        tool_listbox = ToolListBox(self._tool_walker, on_focus_change=self._on_tool_focus)
        tool_box = urwid.LineBox(tool_listbox, title='Tools')

        # Action buttons - Tab navigates here from tool list (3D style)
        create_btn = Button3DCompact("Create", lambda _: self._create_tool_before_selected())
        edit_btn = Button3DCompact("Edit", lambda _: self._edit_selected_tool())
        delete_btn = Button3DCompact("Delete", lambda _: self._delete_selected_tool())
        test_btn = Button3DCompact("Test", lambda _: self._test_selected_tool())
        providers_btn = Button3DCompact("Providers", lambda _: self.manage_providers())

        buttons_row = urwid.Columns([
            ('pack', create_btn),
            ('pack', urwid.Text(" ")),
            ('pack', edit_btn),
            ('pack', urwid.Text(" ")),
            ('pack', delete_btn),
            ('pack', urwid.Text(" ")),
            ('pack', test_btn),
            ('pack', urwid.Text("  ")),
            ('pack', providers_btn),
        ])
        buttons_padded = urwid.Padding(buttons_row, align='left', left=1)

        # Info panel - shows details of selected tool (not focusable)
        self._info_name = urwid.Text("")
        self._info_desc = urwid.Text("")
        self._info_args = urwid.Text("")
        self._info_steps = urwid.Text("")
        self._info_output = urwid.Text("")

        info_content = urwid.Pile([
            self._info_name,
            self._info_desc,
            urwid.Divider(),
            self._info_args,
            urwid.Divider(),
            self._info_steps,
            urwid.Divider(),
            self._info_output,
        ])
        info_filler = urwid.Filler(info_content, valign='top')
        info_box = urwid.LineBox(info_filler, title='Tool Info')

        # Exit button at bottom (3D style)
        exit_btn = Button3DCompact("EXIT", lambda _: self.exit_app())
        exit_centered = urwid.Padding(exit_btn, align='center', width=12)

        # Use a custom Pile that handles Tab to cycle between tool list and buttons
        self._main_pile = TabCyclePile([
            ('weight', 1, tool_box),
            ('pack', buttons_padded),
            ('pack', urwid.Divider('─')),
            ('weight', 2, info_box),
            ('pack', urwid.Divider()),
            ('pack', exit_centered),
        ], tab_positions=[0, 1, 5])  # Tool list, buttons row, exit button

        # Header
        header = urwid.Text(('header', ' SmartTools Manager '), align='center')

        # Footer
        footer = urwid.Text(('footer', ' Arrow:Navigate list | Tab:Jump to buttons | Enter/Click:Select | Q:Quit '), align='center')

        frame = urwid.Frame(self._main_pile, header=header, footer=footer)
        self.set_main(frame)

        # Update info for first tool if any
        if tools:
            self._on_tool_focus(tools[0])

    def _create_tool_before_selected(self):
        """Create a new tool (will appear in list based on name sorting)."""
        self.create_tool()

    def _on_tool_focus(self, name):
        """Called when a tool is focused/highlighted."""
        self._selected_tool_name = name

        # Update selection state on all tool items
        if hasattr(self, '_tool_walker'):
            for item in self._tool_walker:
                if isinstance(item, SelectableToolItem):
                    item.set_selected(item.name == name)

        tool = load_tool(name)

        if tool:
            self._info_name.set_text(('label', f"Name: {tool.name}"))
            self._info_desc.set_text(f"Description: {tool.description or '(none)'}")

            if tool.arguments:
                args_text = "Arguments:\n"
                for arg in tool.arguments:
                    default = f" = {arg.default}" if arg.default else ""
                    args_text += f"  {arg.flag} -> {{{arg.variable}}}{default}\n"
            else:
                args_text = "Arguments: (none)"
            self._info_args.set_text(args_text.rstrip())

            if tool.steps:
                steps_text = "Execution Steps:\n"
                for i, step in enumerate(tool.steps):
                    if isinstance(step, PromptStep):
                        steps_text += f"  {i+1}. PROMPT [{step.provider}] -> {{{step.output_var}}}\n"
                    else:
                        steps_text += f"  {i+1}. CODE -> {{{step.output_var}}}\n"
            else:
                steps_text = "Execution Steps: (none)"
            self._info_steps.set_text(steps_text.rstrip())

            self._info_output.set_text(f"Output: {tool.output}")
        else:
            self._info_name.set_text("")
            self._info_desc.set_text("")
            self._info_args.set_text("")
            self._info_steps.set_text("")
            self._info_output.set_text("")

    def _on_tool_select(self, name):
        """Called when a tool is selected (Enter/double-click)."""
        # Edit the tool on select
        tool = load_tool(name)
        if tool:
            self.tool_builder(tool)

    def _edit_selected_tool(self):
        """Edit the currently selected tool."""
        if self._selected_tool_name:
            tool = load_tool(self._selected_tool_name)
            if tool:
                self.tool_builder(tool)
        else:
            self.message_box("Edit", "No tool selected.")

    def _delete_selected_tool(self):
        """Delete the currently selected tool."""
        if self._selected_tool_name:
            name = self._selected_tool_name
            def do_delete():
                delete_tool(name)
                self._selected_tool_name = None
                self.message_box("Deleted", f"Tool '{name}' deleted.", self._refresh_main_menu)
            self.yes_no("Confirm", f"Delete tool '{name}'?", on_yes=do_delete)
        else:
            self.message_box("Delete", "No tool selected.")

    def _test_selected_tool(self):
        """Test the currently selected tool."""
        if self._selected_tool_name:
            tool = load_tool(self._selected_tool_name)
            if tool:
                self._test_tool(tool)
        else:
            self.message_box("Test", "No tool selected.")

    def exit_app(self):
        """Exit the application."""
        raise urwid.ExitMainLoop()


    # ==================== Tool Builder ====================

    def create_tool(self):
        """Create a new tool."""
        self.tool_builder(None)

    def tool_builder(self, existing: Optional[Tool]):
        """Main tool builder interface."""
        is_edit = existing is not None

        # Initialize tool
        if existing:
            tool = Tool(
                name=existing.name,
                description=existing.description,
                arguments=list(existing.arguments),
                steps=list(existing.steps),
                output=existing.output
            )
        else:
            tool = Tool(name="", description="", arguments=[], steps=[], output="{input}")

        # Store references for callbacks
        self._current_tool = tool
        self._is_edit = is_edit
        self._selected_arg_idx = None
        self._selected_step_idx = None

        self._show_tool_builder()

    def _save_tool_fields(self):
        """Save current edit field values to the tool object."""
        if not hasattr(self, '_name_edit') or not hasattr(self, '_current_tool'):
            return

        tool = self._current_tool

        # Save name (only if it's an edit widget, not a text label)
        if not self._is_edit and hasattr(self._name_edit, 'base_widget'):
            name_edit = self._name_edit.base_widget if hasattr(self._name_edit, 'base_widget') else self._name_edit
            if hasattr(name_edit, 'edit_text'):
                tool.name = name_edit.edit_text.strip()

        # Save description
        if hasattr(self, '_desc_edit') and hasattr(self._desc_edit, 'base_widget'):
            tool.description = self._desc_edit.base_widget.edit_text.strip()

        # Save output
        if hasattr(self, '_output_edit') and hasattr(self._output_edit, 'base_widget'):
            tool.output = self._output_edit.base_widget.edit_text.strip()

    def _show_tool_builder(self):
        """Render the tool builder screen."""
        from .tool import DEFAULT_CATEGORIES

        tool = self._current_tool

        # Create edit widgets
        if self._is_edit:
            name_widget = urwid.Text(('label', f"Name: {tool.name}"))
        else:
            name_widget = urwid.AttrMap(urwid.Edit(('label', "Name: "), tool.name), 'edit', 'edit_focus')
        self._name_edit = name_widget

        self._desc_edit = urwid.AttrMap(urwid.Edit(('label', "Desc: "), tool.description), 'edit', 'edit_focus')
        self._output_edit = urwid.AttrMap(urwid.Edit(('label', "Output: "), tool.output), 'edit', 'edit_focus')

        # Category selector
        self._selected_category = [tool.category or "Other"]
        category_btn_text = urwid.Text(self._selected_category[0])
        category_btn = urwid.AttrMap(
            urwid.Padding(category_btn_text, left=1, right=1),
            'edit', 'edit_focus'
        )

        def show_category_dropdown(_):
            """Show category selection popup."""
            def select_category(cat):
                def callback(_):
                    self._selected_category[0] = cat
                    category_btn_text.set_text(cat)
                    tool.category = cat
                    self.close_overlay()
                return callback

            items = []
            for cat in DEFAULT_CATEGORIES:
                btn = urwid.Button(cat, on_press=select_category(cat))
                items.append(urwid.AttrMap(btn, 'button', 'button_focus'))

            listbox = urwid.ListBox(urwid.SimpleFocusListWalker(items))
            popup = Dialog("Select Category", listbox, [])
            self.show_overlay(popup, width=30, height=len(DEFAULT_CATEGORIES) + 4)

        category_select_btn = Button3DCompact("▼", on_press=show_category_dropdown)

        category_row = urwid.Columns([
            ('pack', urwid.Text(('label', "Category: "))),
            ('weight', 1, category_btn),
            ('pack', urwid.Text(" ")),
            ('pack', category_select_btn),
        ])

        # Left column - fields
        left_pile = urwid.Pile([
            ('pack', name_widget),
            ('pack', urwid.Divider()),
            ('pack', self._desc_edit),
            ('pack', urwid.Divider()),
            ('pack', category_row),
            ('pack', urwid.Divider()),
            ('pack', self._output_edit),
        ])
        left_box = urwid.LineBox(urwid.Filler(left_pile, valign='top'), title='Tool Info')

        # Arguments list
        arg_items = []
        for i, arg in enumerate(tool.arguments):
            text = f"{arg.flag} -> {{{arg.variable}}}"
            item = SelectableToolItem(text, on_select=lambda n, idx=i: self._on_arg_activate(idx))
            item.name = i  # Store index
            arg_items.append(item)
        if not arg_items:
            arg_items.append(urwid.Text(('label', "  (none)  ")))

        self._arg_walker = urwid.SimpleFocusListWalker(arg_items)
        args_listbox = ToolListBox(self._arg_walker, on_focus_change=self._on_arg_focus)
        args_box = urwid.LineBox(args_listbox, title='Arguments')

        # Argument buttons
        arg_add_btn = ClickableButton("Add", lambda _: self._add_argument_dialog())
        arg_edit_btn = ClickableButton("Edit", lambda _: self._edit_selected_arg())
        arg_del_btn = ClickableButton("Delete", lambda _: self._delete_selected_arg())
        arg_buttons = urwid.Columns([
            ('pack', arg_add_btn),
            ('pack', urwid.Text(" ")),
            ('pack', arg_edit_btn),
            ('pack', urwid.Text(" ")),
            ('pack', arg_del_btn),
        ])
        arg_buttons_padded = urwid.Padding(arg_buttons, align='left', left=1)

        # Args section (list + buttons)
        args_section = urwid.Pile([
            ('weight', 1, args_box),
            ('pack', arg_buttons_padded),
        ])

        # Steps list
        step_items = []
        for i, step in enumerate(tool.steps):
            if isinstance(step, PromptStep):
                text = f"P:{step.provider} -> {{{step.output_var}}}"
            else:
                text = f"C: -> {{{step.output_var}}}"
            item = SelectableToolItem(text, on_select=lambda n, idx=i: self._on_step_activate(idx))
            item.name = i  # Store index
            step_items.append(item)
        if not step_items:
            step_items.append(urwid.Text(('label', "  (none)  ")))

        self._step_walker = urwid.SimpleFocusListWalker(step_items)
        steps_listbox = ToolListBox(self._step_walker, on_focus_change=self._on_step_focus)
        steps_box = urwid.LineBox(steps_listbox, title='Execution Steps')

        # Step buttons
        step_add_btn = ClickableButton("Add", lambda _: self._add_step_choice())
        step_edit_btn = ClickableButton("Edit", lambda _: self._edit_selected_step())
        step_del_btn = ClickableButton("Delete", lambda _: self._delete_selected_step())
        step_buttons = urwid.Columns([
            ('pack', step_add_btn),
            ('pack', urwid.Text(" ")),
            ('pack', step_edit_btn),
            ('pack', urwid.Text(" ")),
            ('pack', step_del_btn),
        ])
        step_buttons_padded = urwid.Padding(step_buttons, align='left', left=1)

        # Steps section (list + buttons)
        steps_section = urwid.Pile([
            ('weight', 1, steps_box),
            ('pack', step_buttons_padded),
        ])

        # Save/Cancel buttons
        save_btn = ClickableButton("Save", self._on_save_tool)
        cancel_btn = ClickableButton("Cancel", self._on_cancel_tool)
        bottom_buttons = urwid.Columns([
            ('pack', save_btn),
            ('pack', urwid.Text("  ")),
            ('pack', cancel_btn),
        ], dividechars=1)
        bottom_buttons_centered = urwid.Padding(bottom_buttons, align='center', width='pack')

        # Use ToolBuilderLayout for proper Tab cycling
        # Pass LineBoxes for title highlighting and on_cancel for Escape key
        body = ToolBuilderLayout(
            left_box, args_box, steps_box,
            args_section, steps_section, bottom_buttons_centered,
            on_cancel=self._on_cancel_tool
        )

        # Frame
        title = f"Edit Tool: {tool.name}" if self._is_edit and tool.name else "New Tool"
        header = urwid.Text(('header', f' {title} '), align='center')
        footer = urwid.Text(('footer', ' Arrow:Navigate | Tab:Next section | Enter/Click:Select | Esc:Cancel '), align='center')

        frame = urwid.Frame(body, header=header, footer=footer)
        self.set_main(frame)

        # Set initial selection
        if tool.arguments:
            self._selected_arg_idx = 0
            self._on_arg_focus(0)
        if tool.steps:
            self._selected_step_idx = 0
            self._on_step_focus(0)

    def _on_arg_focus(self, idx):
        """Called when an argument is focused."""
        if isinstance(idx, int):
            self._selected_arg_idx = idx
            # Update selection display
            if hasattr(self, '_arg_walker'):
                for i, item in enumerate(self._arg_walker):
                    if isinstance(item, SelectableToolItem):
                        item.set_selected(i == idx)

    def _on_arg_activate(self, idx):
        """Called when an argument is activated (Enter/click)."""
        self._selected_arg_idx = idx
        self._edit_argument_at(idx)

    def _on_step_focus(self, idx):
        """Called when a step is focused."""
        if isinstance(idx, int):
            self._selected_step_idx = idx
            # Update selection display
            if hasattr(self, '_step_walker'):
                for i, item in enumerate(self._step_walker):
                    if isinstance(item, SelectableToolItem):
                        item.set_selected(i == idx)

    def _on_step_activate(self, idx):
        """Called when a step is activated (Enter/click)."""
        self._selected_step_idx = idx
        self._edit_step_at(idx)

    def _edit_selected_arg(self):
        """Edit the currently selected argument."""
        if self._selected_arg_idx is not None and self._selected_arg_idx < len(self._current_tool.arguments):
            self._edit_argument_at(self._selected_arg_idx)
        else:
            self.message_box("Edit", "No argument selected.")

    def _delete_selected_arg(self):
        """Delete the currently selected argument."""
        if self._selected_arg_idx is not None and self._selected_arg_idx < len(self._current_tool.arguments):
            idx = self._selected_arg_idx
            arg = self._current_tool.arguments[idx]
            def do_delete():
                self._save_tool_fields()
                self._current_tool.arguments.pop(idx)
                self._selected_arg_idx = None
                self._show_tool_builder()
            self.yes_no("Delete", f"Delete argument {arg.flag}?", on_yes=do_delete)
        else:
            self.message_box("Delete", "No argument selected.")

    def _edit_selected_step(self):
        """Edit the currently selected step."""
        if self._selected_step_idx is not None and self._selected_step_idx < len(self._current_tool.steps):
            self._edit_step_at(self._selected_step_idx)
        else:
            self.message_box("Edit", "No step selected.")

    def _delete_selected_step(self):
        """Delete the currently selected step."""
        if self._selected_step_idx is not None and self._selected_step_idx < len(self._current_tool.steps):
            idx = self._selected_step_idx
            def do_delete():
                self._save_tool_fields()
                self._current_tool.steps.pop(idx)
                self._selected_step_idx = None
                self._show_tool_builder()
            self.yes_no("Delete", f"Delete step {idx + 1}?", on_yes=do_delete)
        else:
            self.message_box("Delete", "No step selected.")

    def _edit_argument_at(self, idx):
        """Edit argument at index."""
        self._do_edit_argument(idx)

    def _edit_step_at(self, idx):
        """Edit step at index - opens the appropriate dialog based on step type."""
        # Save current field values before showing dialog
        self._save_tool_fields()

        step = self._current_tool.steps[idx]
        if isinstance(step, PromptStep):
            self._add_prompt_dialog(step, idx)
        else:
            self._add_code_dialog(step, idx)

    def _add_argument_dialog(self):
        """Show add argument dialog."""
        # Save current field values before showing dialog
        self._save_tool_fields()

        flag_edit = urwid.Edit(('label', "Flag: "), "--")
        var_edit = urwid.Edit(('label', "Variable: "), "")
        default_edit = urwid.Edit(('label', "Default: "), "")

        def on_ok(_):
            flag = flag_edit.edit_text.strip()
            var = var_edit.edit_text.strip()
            default = default_edit.edit_text.strip() or None

            if not flag:
                return
            if not var:
                var = flag.lstrip("-").replace("-", "_")

            self._current_tool.arguments.append(ToolArgument(
                flag=flag, variable=var, default=default, description=""
            ))
            self.close_overlay()
            self._show_tool_builder()

        def on_cancel(_):
            self.close_overlay()

        body = urwid.Pile([
            urwid.AttrMap(flag_edit, 'edit', 'edit_focus'),
            urwid.Divider(),
            urwid.AttrMap(var_edit, 'edit', 'edit_focus'),
            urwid.Divider(),
            urwid.AttrMap(default_edit, 'edit', 'edit_focus'),
        ])

        dialog = Dialog("Add Argument", body, [("OK", on_ok), ("Cancel", on_cancel)])
        self.show_overlay(dialog, width=50, height=14)

    def _do_edit_argument(self, idx):
        """Edit an existing argument."""
        # Save current field values before showing dialog
        self._save_tool_fields()

        arg = self._current_tool.arguments[idx]

        flag_edit = urwid.Edit(('label', "Flag: "), arg.flag)
        var_edit = urwid.Edit(('label', "Variable: "), arg.variable)
        default_edit = urwid.Edit(('label', "Default: "), arg.default or "")

        def on_ok(_):
            arg.flag = flag_edit.edit_text.strip()
            arg.variable = var_edit.edit_text.strip()
            arg.default = default_edit.edit_text.strip() or None
            self.close_overlay()
            self._show_tool_builder()

        def on_cancel(_):
            self.close_overlay()

        body = urwid.Pile([
            urwid.AttrMap(flag_edit, 'edit', 'edit_focus'),
            urwid.Divider(),
            urwid.AttrMap(var_edit, 'edit', 'edit_focus'),
            urwid.Divider(),
            urwid.AttrMap(default_edit, 'edit', 'edit_focus'),
        ])

        dialog = Dialog("Edit Argument", body, [("OK", on_ok), ("Cancel", on_cancel)])
        self.show_overlay(dialog, width=50, height=14)

    def _add_step_choice(self):
        """Choose step type to add."""
        # Save current field values before showing dialog
        self._save_tool_fields()

        def on_prompt(_):
            self.close_overlay()
            # Defer dialog opening to avoid overlay rendering issues
            if self.loop:
                self.loop.set_alarm_in(0, lambda loop, data: self._add_prompt_dialog())
            else:
                self._add_prompt_dialog()

        def on_code(_):
            self.close_overlay()
            # Defer dialog opening to avoid overlay rendering issues
            if self.loop:
                self.loop.set_alarm_in(0, lambda loop, data: self._add_code_dialog())
            else:
                self._add_code_dialog()

        def on_cancel(_):
            self.close_overlay()

        body = urwid.Text("Choose step type:")
        dialog = Dialog("Add Step", body, [("Prompt", on_prompt), ("Code", on_code), ("Cancel", on_cancel)])
        self.show_overlay(dialog, width=45, height=9)

    def _get_available_vars(self, up_to=-1):
        """Get available variables."""
        tool = self._current_tool
        variables = ["input"]
        for arg in tool.arguments:
            variables.append(arg.variable)
        if up_to == -1:
            up_to = len(tool.steps)
        for i, step in enumerate(tool.steps):
            if i >= up_to:
                break
            variables.append(step.output_var)
        return variables

    def _add_prompt_dialog(self, existing=None, idx=-1):
        """Add/edit prompt step with provider dropdown and multiline prompt."""
        from .tool import get_tools_dir

        providers = load_providers()
        provider_names = [p.name for p in providers]
        if not provider_names:
            provider_names = ["mock"]
        current_provider = existing.provider if existing else provider_names[0]

        # Provider selector state
        selected_provider = [current_provider]  # Use list to allow mutation in closures

        # Provider dropdown button
        provider_btn_text = urwid.Text(current_provider)
        provider_btn = urwid.AttrMap(
            urwid.Padding(provider_btn_text, left=1, right=1),
            'edit', 'edit_focus'
        )

        def show_provider_dropdown(_):
            """Show provider selection popup with descriptions."""
            # Build provider lookup for descriptions
            provider_lookup = {p.name: p.description for p in providers}

            # Description display (updates on focus change)
            desc_text = urwid.Text("")
            desc_box = urwid.AttrMap(
                urwid.Padding(desc_text, left=1, right=1),
                'label'
            )

            def update_description(name):
                """Update the description text for the focused provider."""
                desc = provider_lookup.get(name, "")
                desc_text.set_text(('label', desc if desc else "No description"))

            def select_provider(name):
                def callback(_):
                    selected_provider[0] = name
                    provider_btn_text.set_text(name)
                    self.close_overlay()
                return callback

            # Create focusable buttons that update description on focus
            class DescriptiveButton(urwid.Button):
                def __init__(self, name, desc_callback):
                    super().__init__(name, on_press=select_provider(name))
                    self._name = name
                    self._desc_callback = desc_callback

                def render(self, size, focus=False):
                    if focus:
                        self._desc_callback(self._name)
                    return super().render(size, focus)

            items = []
            for name in provider_names:
                # Show short hint inline: "name | short_desc"
                short_desc = provider_lookup.get(name, "")
                # Extract just the key info (after the timing)
                if "|" in short_desc:
                    short_desc = short_desc.split("|", 1)[1].strip()[:20]
                else:
                    short_desc = short_desc[:20]

                label = f"{name:<18} {short_desc}"
                btn = DescriptiveButton(name, update_description)
                btn.set_label(label)
                items.append(urwid.AttrMap(btn, 'button', 'button_focus'))

            # Set initial description
            update_description(provider_names[0])

            listbox = urwid.ListBox(urwid.SimpleFocusListWalker(items))

            # Combine listbox with description footer
            body = urwid.Pile([
                ('weight', 1, listbox),
                ('pack', urwid.Divider('─')),
                ('pack', desc_box),
            ])

            popup = Dialog("Select Provider", body, [])
            self.show_overlay(popup, width=50, height=min(len(provider_names) + 6, 16))

        provider_select_btn = Button3DCompact("▼", on_press=show_provider_dropdown)

        # File input for external prompt
        default_file = existing.prompt_file if existing and existing.prompt_file else "prompt.txt"
        file_edit = urwid.Edit(('label', "File: "), default_file)

        # Multiline prompt editor - use TabPassEdit so Tab passes through for navigation
        prompt_edit = TabPassEdit(
            edit_text=existing.prompt if existing else "{input}",
            multiline=True
        )

        output_edit = urwid.Edit(('label', "Output var: "), existing.output_var if existing else "response")

        vars_available = self._get_available_vars(idx)
        vars_text = urwid.Text(('label', f"Variables: {', '.join('{'+v+'}' for v in vars_available)}"))

        status_text = urwid.Text("")

        def do_load():
            """Actually load prompt from file."""
            filename = file_edit.edit_text.strip()
            tool_dir = get_tools_dir() / self._current_tool.name
            prompt_path = tool_dir / filename

            try:
                prompt_edit.set_edit_text(prompt_path.read_text())
                status_text.set_text(('success', f"Loaded from {filename}"))
            except Exception as e:
                status_text.set_text(('error', f"Load error: {e}"))

        def on_load(_):
            """Load prompt from file with confirmation."""
            filename = file_edit.edit_text.strip()
            if not filename:
                status_text.set_text(('error', "Enter a filename first"))
                return

            tool_dir = get_tools_dir() / self._current_tool.name
            prompt_path = tool_dir / filename

            if not prompt_path.exists():
                status_text.set_text(('error', f"File not found: {filename}"))
                return

            # Show confirmation dialog
            def on_yes(_):
                self.close_overlay()
                do_load()

            def on_no(_):
                self.close_overlay()

            confirm_body = urwid.Text(f"Load from '{filename}'?\nThis will replace the current prompt.")
            confirm_dialog = Dialog("Confirm Load", confirm_body, [("Yes", on_yes), ("No", on_no)])
            self.show_overlay(confirm_dialog, width=50, height=8)

        def on_ok(_):
            provider = selected_provider[0]
            prompt = prompt_edit.edit_text.strip()
            output_var = output_edit.edit_text.strip() or "response"
            prompt_file = file_edit.edit_text.strip() or None

            # Auto-save to file if filename is set
            if prompt_file:
                tool_dir = get_tools_dir() / self._current_tool.name
                tool_dir.mkdir(parents=True, exist_ok=True)
                prompt_path = tool_dir / prompt_file
                try:
                    prompt_path.write_text(prompt)
                except Exception as e:
                    status_text.set_text(('error', f"Save error: {e}"))
                    return

            step = PromptStep(prompt=prompt, provider=provider, output_var=output_var, prompt_file=prompt_file)

            if existing and idx >= 0:
                self._current_tool.steps[idx] = step
            else:
                self._current_tool.steps.append(step)

            self.close_overlay()
            self._show_tool_builder()

        def on_cancel(_):
            self.close_overlay()

        def on_external_edit(_):
            """Open prompt in external editor ($EDITOR)."""
            import os
            import subprocess
            import tempfile

            current_prompt = prompt_edit.edit_text

            # Stop the urwid loop temporarily
            if self.loop:
                self.loop.stop()

            try:
                # Create temp file with current prompt
                with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
                    f.write(current_prompt)
                    temp_path = f.name

                # Get editor from environment
                editor = os.environ.get('EDITOR', os.environ.get('VISUAL', 'nano'))

                # Run editor
                subprocess.run([editor, temp_path], check=True)

                # Read back the edited prompt
                with open(temp_path, 'r') as f:
                    new_prompt = f.read()

                # Update the prompt editor
                prompt_edit.set_edit_text(new_prompt)
                status_text.set_text(('success', f"Prompt updated from {editor}"))

                # Clean up temp file
                os.unlink(temp_path)

            except subprocess.CalledProcessError:
                status_text.set_text(('error', "Editor exited with error"))
            except FileNotFoundError:
                status_text.set_text(('error', f"Editor '{editor}' not found"))
            except Exception as e:
                status_text.set_text(('error', f"Edit error: {e}"))
            finally:
                # Restart the urwid loop
                if self.loop:
                    self.loop.start()

        load_btn = Button3DCompact("Load", on_load)
        edit_btn = Button3DCompact("$EDITOR", on_external_edit)

        # Prompt editor in a box - use ListBox for proper focus handling and scrolling
        # Wrap in DOSScrollBar for DOS-style scrollbar with arrow buttons
        prompt_edit_styled = urwid.AttrMap(prompt_edit, 'edit', 'edit_focus')
        prompt_walker = urwid.SimpleFocusListWalker([prompt_edit_styled])
        prompt_listbox = urwid.ListBox(prompt_walker)
        prompt_scrollbar = DOSScrollBar(prompt_listbox)
        prompt_box = urwid.LineBox(prompt_scrollbar, title="Prompt")

        # Use TabCyclePile so Tab cycles between sections
        # Note: All flow widgets must be explicitly wrapped in ('pack', ...) when
        # the Pile contains weighted items (urwid 3.x requirement)
        body = TabCyclePile([
            ('pack', vars_text),
            ('pack', urwid.Divider()),
            ('pack', urwid.Columns([
                ('pack', urwid.Text(('label', "Provider: "))),
                ('weight', 1, provider_btn),
                ('pack', urwid.Text(" ")),
                ('pack', provider_select_btn),
            ])),
            ('pack', urwid.Divider()),
            ('pack', urwid.Columns([
                ('weight', 1, urwid.AttrMap(file_edit, 'edit', 'edit_focus')),
                ('pack', urwid.Text(" ")),
                ('pack', load_btn),
                ('pack', urwid.Text(" ")),
                ('pack', edit_btn),
            ])),
            ('pack', status_text),
            ('weight', 1, prompt_box),
            ('pack', urwid.Divider()),
            ('pack', urwid.AttrMap(output_edit, 'edit', 'edit_focus')),
        ], tab_positions=[2, 4, 6, 8])

        title = "Edit Prompt Step" if existing else "Add Prompt Step"
        dialog = Dialog(title, body, [("OK", on_ok), ("Cancel", on_cancel)])
        self.show_overlay(dialog, width=75, height=22)

    def _add_code_dialog(self, existing=None, idx=-1):
        """Add/edit code step with multiline editor, file support, and AI auto-adjust."""
        from .tool import get_tools_dir
        from .providers import load_providers, call_provider

        # File name input (default based on output_var)
        default_output_var = existing.output_var if existing else "processed"
        default_file = existing.code_file if existing and existing.code_file else f"{default_output_var}.py"
        file_edit = urwid.Edit(('label', "File: "), default_file)

        # Multiline code editor with undo/redo (Alt+U / Alt+R)
        default_code = existing.code if existing else f"{default_output_var} = input.upper()"
        code_edit = UndoableEdit(
            edit_text=default_code,
            multiline=True
        )

        output_edit = urwid.Edit(('label', "Output var: "), existing.output_var if existing else "processed")

        vars_available = self._get_available_vars(idx)
        vars_text = urwid.Text(('label', f"Variables: {', '.join(vars_available)}"))

        status_text = urwid.Text("")

        # --- Auto-adjust AI feature ---
        providers = load_providers()
        provider_names = [p.name for p in providers]
        if not provider_names:
            provider_names = ["mock"]
        selected_ai_provider = [provider_names[0]]

        ai_provider_btn_text = urwid.Text(provider_names[0])
        ai_provider_btn = urwid.AttrMap(
            urwid.Padding(ai_provider_btn_text, left=1, right=1),
            'edit', 'edit_focus'
        )

        def show_ai_provider_dropdown(_):
            provider_lookup = {p.name: p.description for p in providers}
            desc_text = urwid.Text("")
            desc_box = urwid.AttrMap(urwid.Padding(desc_text, left=1, right=1), 'label')

            def update_description(name):
                desc = provider_lookup.get(name, "")
                desc_text.set_text(('label', desc if desc else "No description"))

            def select_provider(name):
                def callback(_):
                    selected_ai_provider[0] = name
                    ai_provider_btn_text.set_text(name)
                    self.close_overlay()
                return callback

            class DescriptiveButton(urwid.Button):
                def __init__(btn_self, name, desc_callback):
                    super().__init__(name, on_press=select_provider(name))
                    btn_self._name = name
                    btn_self._desc_callback = desc_callback

                def render(btn_self, size, focus=False):
                    if focus:
                        btn_self._desc_callback(btn_self._name)
                    return super().render(size, focus)

            items = []
            for name in provider_names:
                short_desc = provider_lookup.get(name, "")
                if "|" in short_desc:
                    short_desc = short_desc.split("|", 1)[1].strip()[:20]
                else:
                    short_desc = short_desc[:20]
                label = f"{name:<18} {short_desc}"
                btn = DescriptiveButton(name, update_description)
                btn.set_label(label)
                items.append(urwid.AttrMap(btn, 'button', 'button_focus'))

            update_description(provider_names[0])
            listbox = urwid.ListBox(urwid.SimpleFocusListWalker(items))
            popup_body = urwid.Pile([
                ('weight', 1, listbox),
                ('pack', urwid.Divider('─')),
                ('pack', desc_box),
            ])
            popup = Dialog("Select Provider", popup_body, [])
            self.show_overlay(popup, width=50, height=min(len(provider_names) + 6, 16))

        ai_provider_select_btn = Button3DCompact("▼", on_press=show_ai_provider_dropdown)

        # Default prompt template for AI code generation/adjustment
        # Show variables in triple-quote format so the AI follows the pattern
        vars_formatted = ', '.join(f'\"\"\"{{{v}}}\"\"\"' for v in vars_available)
        default_ai_prompt = f"""Write inline Python code (NOT a function definition) according to my instruction.

The code runs directly with variable substitution. Assign any "Available Variables" used to a new standard variable first, then use that variable in the code. Use triple quotes and curly braces since the substituted content may contain quotes/newlines.

Example:
my_var = \"\"\"{{variable}}\"\"\"

INSTRUCTION: [Describe what you want]

CURRENT CODE:
```python
{{code}}
```

AVAILABLE VARIABLES: {vars_formatted}

IMPORTANT: Return ONLY executable inline code. Do NOT wrap in a function.
No explanations, no markdown fencing, just the code."""

        # Multiline editable prompt for AI with DOS-style scrollbar
        ai_prompt_edit = TabPassEdit(edit_text=default_ai_prompt, multiline=True)
        ai_prompt_styled = urwid.AttrMap(ai_prompt_edit, 'edit', 'edit_focus')
        ai_prompt_walker = urwid.SimpleFocusListWalker([ai_prompt_styled])
        ai_prompt_listbox = urwid.ListBox(ai_prompt_walker)
        ai_prompt_scrollbar = DOSScrollBar(ai_prompt_listbox)
        ai_prompt_box = urwid.LineBox(ai_prompt_scrollbar, title="Prompt")

        # Output/feedback area for AI responses
        ai_output_text = urwid.Text("")
        ai_output_walker = urwid.SimpleFocusListWalker([ai_output_text])
        ai_output_listbox = urwid.ListBox(ai_output_walker)
        ai_output_box = urwid.LineBox(ai_output_listbox, title="Output & Feedback")

        def on_auto_adjust(_):
            prompt_template = ai_prompt_edit.edit_text.strip()
            if not prompt_template:
                ai_output_text.set_text(('error', "Enter a prompt for the AI"))
                return

            current_code = code_edit.edit_text.strip()

            # Replace {code} placeholder with actual code
            prompt = prompt_template.replace("{code}", current_code)

            provider_name = selected_ai_provider[0]
            ai_output_text.set_text(('label', f"Calling {provider_name}...\nPlease wait..."))
            self.refresh()

            result = call_provider(provider_name, prompt)

            if result.success:
                new_code = result.text.strip()
                # Strip markdown code fences if present
                if new_code.startswith("```python"):
                    new_code = new_code[9:]
                if new_code.startswith("```"):
                    new_code = new_code[3:]
                if new_code.endswith("```"):
                    new_code = new_code[:-3]
                new_code = new_code.strip()

                code_edit.set_edit_text(new_code)
                ai_output_text.set_text(('success', f"✓ Code updated successfully!\n\nProvider: {provider_name}\nResponse length: {len(result.text)} chars"))
            else:
                error_msg = result.error or "Unknown error"
                ai_output_text.set_text(('error', f"✗ Error from {provider_name}:\n\n{error_msg}"))

        auto_adjust_btn = Button3DCompact("Auto-adjust", on_auto_adjust)

        # Build the AI assist box with provider selector, prompt editor, output area, and button
        ai_provider_row = urwid.Columns([
            ('pack', urwid.Text(('label', "Provider: "))),
            ('pack', ai_provider_btn),
            ('pack', ai_provider_select_btn),
        ])

        ai_assist_content = urwid.Pile([
            ('pack', ai_provider_row),
            ('pack', urwid.Divider()),
            ('weight', 2, ai_prompt_box),
            ('weight', 1, ai_output_box),
            ('pack', urwid.Padding(auto_adjust_btn, align='center', width=16)),
        ])
        ai_assist_box = urwid.LineBox(ai_assist_content, title="AI Assisted Auto-adjust")
        # --- End Auto-adjust feature ---

        def do_load():
            """Actually load code from file."""
            filename = file_edit.edit_text.strip()
            tool_dir = get_tools_dir() / self._current_tool.name
            code_path = tool_dir / filename

            try:
                code_edit.set_edit_text(code_path.read_text())
                status_text.set_text(('success', f"Loaded from {filename}"))
            except Exception as e:
                status_text.set_text(('error', f"Load error: {e}"))

        def on_load(_):
            """Load code from file with confirmation."""
            filename = file_edit.edit_text.strip()
            if not filename:
                status_text.set_text(('error', "Enter a filename first"))
                return

            tool_dir = get_tools_dir() / self._current_tool.name
            code_path = tool_dir / filename

            if not code_path.exists():
                status_text.set_text(('error', f"File not found: {filename}"))
                return

            def on_yes(_):
                self.close_overlay()
                do_load()

            def on_no(_):
                self.close_overlay()

            confirm_body = urwid.Text(f"Load from '{filename}'?\nThis will replace the current code.")
            confirm_dialog = Dialog("Confirm Load", confirm_body, [("Yes", on_yes), ("No", on_no)])
            self.show_overlay(confirm_dialog, width=50, height=8)

        def on_ok(_):
            import ast

            code = code_edit.edit_text.strip()
            output_var = output_edit.edit_text.strip() or "processed"
            code_file = file_edit.edit_text.strip() or None

            if code:
                try:
                    ast.parse(code)
                except SyntaxError as e:
                    line_info = f" (line {e.lineno})" if e.lineno else ""
                    status_text.set_text(('error', f"Syntax error{line_info}: {e.msg}"))
                    return

            if code_file:
                tool_dir = get_tools_dir() / self._current_tool.name
                tool_dir.mkdir(parents=True, exist_ok=True)
                code_path = tool_dir / code_file
                try:
                    code_path.write_text(code)
                except Exception as e:
                    status_text.set_text(('error', f"Save error: {e}"))
                    return

            step = CodeStep(code=code, output_var=output_var, code_file=code_file)

            if existing and idx >= 0:
                self._current_tool.steps[idx] = step
            else:
                self._current_tool.steps.append(step)

            self.close_overlay()
            self._show_tool_builder()

        def on_cancel(_):
            self.close_overlay()

        def on_external_edit(_):
            """Open code in external editor ($EDITOR)."""
            import os
            import subprocess
            import tempfile

            current_code = code_edit.edit_text

            # Stop the urwid loop temporarily
            if self.loop:
                self.loop.stop()

            try:
                # Create temp file with current code
                with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
                    f.write(current_code)
                    temp_path = f.name

                # Get editor from environment
                editor = os.environ.get('EDITOR', os.environ.get('VISUAL', 'nano'))

                # Run editor
                subprocess.run([editor, temp_path], check=True)

                # Read back the edited code
                with open(temp_path, 'r') as f:
                    new_code = f.read()

                # Update the code editor
                code_edit.set_edit_text(new_code)
                status_text.set_text(('success', f"Code updated from {editor}"))

                # Clean up temp file
                os.unlink(temp_path)

            except subprocess.CalledProcessError:
                status_text.set_text(('error', "Editor exited with error"))
            except FileNotFoundError:
                status_text.set_text(('error', f"Editor '{editor}' not found"))
            except Exception as e:
                status_text.set_text(('error', f"Edit error: {e}"))
            finally:
                # Restart the urwid loop
                if self.loop:
                    self.loop.start()

        load_btn = Button3DCompact("Load", on_load)
        edit_btn = Button3DCompact("$EDITOR", on_external_edit)

        # Code editor in a box - use ListBox for proper focus handling and scrolling
        # Wrap in DOSScrollBar for DOS-style scrollbar with arrow buttons
        code_edit_styled = urwid.AttrMap(code_edit, 'edit', 'edit_focus')
        code_walker = urwid.SimpleFocusListWalker([code_edit_styled])
        code_listbox = urwid.ListBox(code_walker)
        code_scrollbar = DOSScrollBar(code_listbox)
        code_box = urwid.LineBox(code_scrollbar, title="Code")

        # Layout: Code editor on left, AI assist box on right
        main_columns = urwid.Columns([
            ('weight', 1, code_box),
            ('weight', 1, ai_assist_box),
        ], dividechars=1)

        # Use TabCyclePile so Tab cycles between sections
        # Note: All flow widgets must be explicitly wrapped in ('pack', ...) when
        # the Pile contains weighted items (urwid 3.x requirement)
        body = TabCyclePile([
            ('pack', vars_text),
            ('pack', urwid.Divider()),
            ('pack', urwid.Columns([
                ('weight', 1, urwid.AttrMap(file_edit, 'edit', 'edit_focus')),
                ('pack', urwid.Text(" ")),
                ('pack', load_btn),
                ('pack', urwid.Text(" ")),
                ('pack', edit_btn),
            ])),
            ('pack', status_text),
            ('weight', 1, main_columns),
            ('pack', urwid.Divider()),
            ('pack', urwid.AttrMap(output_edit, 'edit', 'edit_focus')),
        ], tab_positions=[2, 4, 6])

        title = "Edit Code Step" if existing else "Add Code Step"
        dialog = Dialog(title, body, [("OK", on_ok), ("Cancel", on_cancel)])
        self.show_overlay(dialog, width=90, height=30)

    def _on_save_tool(self, _):
        """Save the tool."""
        tool = self._current_tool

        # Update from edits - widgets are wrapped in AttrMap, access base_widget
        if not self._is_edit:
            # Name edit is an AttrMap wrapping an Edit
            name_edit = self._name_edit.base_widget if hasattr(self._name_edit, 'base_widget') else self._name_edit
            if hasattr(name_edit, 'edit_text'):
                tool.name = name_edit.edit_text.strip()
        tool.description = self._desc_edit.base_widget.edit_text.strip()
        tool.output = self._output_edit.base_widget.edit_text.strip()

        if not tool.name:
            self.message_box("Error", "Tool name is required.")
            return

        # Validate tool name
        is_valid, error_msg = validate_tool_name(tool.name)
        if not is_valid:
            self.message_box("Error", error_msg)
            return

        if not self._is_edit and tool_exists(tool.name):
            def on_yes():
                save_tool(tool)
                self.message_box("Success", f"Tool '{tool.name}' saved!", self.show_main_menu)
            self.yes_no("Overwrite?", f"Tool '{tool.name}' exists. Overwrite?", on_yes=on_yes)
        else:
            save_tool(tool)
            self.message_box("Success", f"Tool '{tool.name}' saved!", self.show_main_menu)

    def _on_cancel_tool(self, _):
        """Cancel tool editing."""
        self.show_main_menu()

    def _test_tool(self, tool):
        """Test a tool with mock input."""
        def on_input(text):
            from .runner import run_tool
            output, code = run_tool(
                tool=tool,
                input_text=text,
                custom_args={},
                provider_override="mock",
                dry_run=False,
                show_prompt=False,
                verbose=False
            )
            result = f"Exit code: {code}\n\nOutput:\n{output[:300]}"
            self.message_box("Test Result", result)

        self.input_dialog("Test Input", "Enter test input", "Hello world", on_input)


    # ==================== Provider Management ====================

    def manage_providers(self):
        """Manage providers."""
        self._show_providers_menu()

    def _show_providers_menu(self):
        """Show providers management menu."""
        providers = load_providers()
        self._selected_provider_name = None
        self._provider_walker = None

        def on_provider_focus(name):
            """Called when a provider is focused."""
            self._selected_provider_name = name
            # Update selection state on all items
            if self._provider_walker:
                for item in self._provider_walker:
                    if isinstance(item, SelectableToolItem):
                        item.set_selected(item.name == name)

        def on_provider_activate(name):
            """Called when Enter is pressed on a provider."""
            self.close_overlay()
            self._edit_provider_menu(name)

        def on_add(_):
            self.close_overlay()
            self._add_provider_dialog()

        def on_edit(_):
            if self._selected_provider_name:
                self.close_overlay()
                self._edit_provider_menu(self._selected_provider_name)
            else:
                self.message_box("Edit", "No provider selected.")

        def on_cancel(_):
            self.close_overlay()

        # Build provider list
        items = []
        for p in providers:
            item = SelectableToolItem(f"{p.name}: {p.command}", on_select=on_provider_activate)
            item.name = p.name  # Store the actual provider name
            items.append(item)

        if not items:
            items.append(urwid.Text(('label', "  (no providers)  ")))

        self._provider_walker = urwid.SimpleFocusListWalker(items)
        listbox = ToolListBox(self._provider_walker, on_focus_change=on_provider_focus)
        listbox_box = urwid.LineBox(listbox, title='Providers')

        # Buttons row
        add_btn = ClickableButton("Add", on_add)
        edit_btn = ClickableButton("Edit", on_edit)
        cancel_btn = ClickableButton("Cancel", on_cancel)
        buttons = urwid.Columns([
            ('pack', add_btn),
            ('pack', urwid.Text("  ")),
            ('pack', edit_btn),
            ('pack', urwid.Text("  ")),
            ('pack', cancel_btn),
        ])
        buttons_centered = urwid.Padding(buttons, align='center', width='pack')

        # Layout
        body = urwid.Pile([
            ('weight', 1, listbox_box),
            ('pack', urwid.Divider()),
            ('pack', buttons_centered),
        ])

        # Wrap in a frame with title
        header = urwid.Text(('header', ' Manage Providers '), align='center')
        frame = urwid.Frame(body, header=header)
        frame = urwid.LineBox(frame)
        frame = urwid.AttrMap(frame, 'dialog')

        height = min(len(providers) + 10, 18)
        self.show_overlay(frame, width=55, height=height)

        # Set initial selection
        if providers:
            self._selected_provider_name = providers[0].name
            on_provider_focus(providers[0].name)

    def _add_provider_dialog(self):
        """Add a new provider."""
        name_edit = urwid.Edit(('label', "Name: "), "")
        cmd_edit = urwid.Edit(('label', "Command: "), "")
        desc_edit = urwid.Edit(('label', "Description: "), "")

        def on_ok(_):
            name = name_edit.edit_text.strip()
            cmd = cmd_edit.edit_text.strip()
            desc = desc_edit.edit_text.strip()

            if name and cmd:
                add_provider(Provider(name=name, command=cmd, description=desc))
                self.close_overlay()
                self.message_box("Success", f"Provider '{name}' added.", self._show_providers_menu)
            else:
                self.message_box("Error", "Name and command are required.")

        def on_cancel(_):
            self.close_overlay()
            self._show_providers_menu()

        body = urwid.Pile([
            urwid.AttrMap(name_edit, 'edit', 'edit_focus'),
            urwid.Divider(),
            urwid.AttrMap(cmd_edit, 'edit', 'edit_focus'),
            urwid.Divider(),
            urwid.AttrMap(desc_edit, 'edit', 'edit_focus'),
        ])

        dialog = Dialog("Add Provider", body, [("OK", on_ok), ("Cancel", on_cancel)])
        self.show_overlay(dialog, width=55, height=14)

    def _edit_provider_menu(self, name):
        """Edit a provider."""
        provider = get_provider(name)
        if not provider:
            return

        name_edit = urwid.Edit(('label', "Name: "), provider.name)
        cmd_edit = urwid.Edit(('label', "Command: "), provider.command)
        desc_edit = urwid.Edit(('label', "Description: "), provider.description or "")

        def on_save(_):
            new_name = name_edit.edit_text.strip()
            cmd = cmd_edit.edit_text.strip()
            desc = desc_edit.edit_text.strip()

            if new_name and cmd:
                # Delete old provider if name changed
                if new_name != name:
                    delete_provider(name)
                # Save with new/same name
                add_provider(Provider(name=new_name, command=cmd, description=desc))
                self.close_overlay()
                self.message_box("Success", f"Provider '{new_name}' saved.", self._show_providers_menu)
            else:
                self.message_box("Error", "Name and command are required.")

        def on_delete(_):
            self.close_overlay()
            def do_delete():
                delete_provider(name)
                self.message_box("Deleted", f"Provider '{name}' deleted.", self._show_providers_menu)
            self.yes_no("Confirm", f"Delete provider '{name}'?", on_yes=do_delete, on_no=self._show_providers_menu)

        def on_cancel(_):
            self.close_overlay()
            self._show_providers_menu()

        body = urwid.Pile([
            urwid.AttrMap(name_edit, 'edit', 'edit_focus'),
            urwid.Divider(),
            urwid.AttrMap(cmd_edit, 'edit', 'edit_focus'),
            urwid.Divider(),
            urwid.AttrMap(desc_edit, 'edit', 'edit_focus'),
        ])

        dialog = Dialog("Edit Provider", body, [("Save", on_save), ("Delete", on_delete), ("Cancel", on_cancel)])
        self.show_overlay(dialog, width=55, height=16)


def run_ui():
    """Entry point for the urwid UI."""
    ui = SmartToolsUI()
    ui.run()


if __name__ == "__main__":
    run_ui()
