# Urwid __init__.py - all the stuff you're likely to care about
#
#    Copyright (C) 2004-2012  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

import importlib.util
import sys
import types
import typing
import warnings

from urwid.canvas import (
    BlankCanvas,
    Canvas,
    CanvasCache,
    CanvasCombine,
    CanvasError,
    CanvasJoin,
    CanvasOverlay,
    CompositeCanvas,
    SolidCanvas,
    TextCanvas,
)
from urwid.command_map import (
    ACTIVATE,
    CURSOR_DOWN,
    CURSOR_LEFT,
    CURSOR_MAX_LEFT,
    CURSOR_MAX_RIGHT,
    CURSOR_PAGE_DOWN,
    CURSOR_PAGE_UP,
    CURSOR_RIGHT,
    CURSOR_UP,
    REDRAW_SCREEN,
    CommandMap,
    command_map,
)
from urwid.font import (
    Font,
    FontRegistry,
    HalfBlock5x4Font,
    HalfBlock6x5Font,
    HalfBlock7x7Font,
    HalfBlockHeavy6x5Font,
    Sextant2x2Font,
    Sextant3x3Font,
    Thin3x3Font,
    Thin4x3Font,
    Thin6x6Font,
    get_all_fonts,
)
from urwid.signals import (
    MetaSignals,
    Signals,
    connect_signal,
    disconnect_signal,
    disconnect_signal_by_key,
    emit_signal,
    register_signal,
)
from urwid.str_util import calc_text_pos, calc_width, is_wide_char, move_next_char, move_prev_char, within_double_byte
from urwid.text_layout import LayoutSegment, StandardTextLayout, TextLayout, default_layout
from urwid.util import (
    MetaSuper,
    TagMarkupException,
    apply_target_encoding,
    calc_trim_text,
    decompose_tagmarkup,
    detected_encoding,
    get_encoding_mode,
    int_scale,
    is_mouse_event,
    set_encoding,
    supports_unicode,
)
from urwid.version import version as __version__
from urwid.version import version_tuple as __version_tuple__

from . import display, event_loop, widget
from .display import (
    BLACK,
    BROWN,
    DARK_BLUE,
    DARK_CYAN,
    DARK_GRAY,
    DARK_GREEN,
    DARK_MAGENTA,
    DARK_RED,
    DEFAULT,
    LIGHT_BLUE,
    LIGHT_CYAN,
    LIGHT_GRAY,
    LIGHT_GREEN,
    LIGHT_MAGENTA,
    LIGHT_RED,
    UPDATE_PALETTE_ENTRY,
    WHITE,
    YELLOW,
    AttrSpec,
    AttrSpecError,
    BaseScreen,
    RealTerminal,
    ScreenError,
)
from .event_loop import AsyncioEventLoop, EventLoop, ExitMainLoop, MainLoop, SelectEventLoop
from .widget import (
    ANY,
    BOTTOM,
    BOX,
    CENTER,
    CLIP,
    ELLIPSIS,
    FIXED,
    FLOW,
    GIVEN,
    LEFT,
    MIDDLE,
    PACK,
    RELATIVE,
    RELATIVE_100,
    RIGHT,
    SPACE,
    TOP,
    WEIGHT,
    Align,
    AttrMap,
    AttrMapError,
    AttrWrap,
    BarGraph,
    BarGraphError,
    BarGraphMeta,
    BigText,
    BoxAdapter,
    BoxAdapterError,
    Button,
    CheckBox,
    CheckBoxError,
    Columns,
    ColumnsError,
    Divider,
    Edit,
    EditError,
    Filler,
    FillerError,
    Frame,
    FrameError,
    GraphVScale,
    GridFlow,
    GridFlowError,
    IntEdit,
    LineBox,
    ListBox,
    ListBoxError,
    ListWalker,
    ListWalkerError,
    MonitoredFocusList,
    MonitoredList,
    Overlay,
    OverlayError,
    Padding,
    PaddingError,
    ParentNode,
    Pile,
    PileError,
    PopUpLauncher,
    PopUpTarget,
    ProgressBar,
    RadioButton,
    Scrollable,
    ScrollBar,
    SelectableIcon,
    SimpleFocusListWalker,
    SimpleListWalker,
    Sizing,
    SolidFill,
    Text,
    TextError,
    TreeListBox,
    TreeNode,
    TreeWalker,
    TreeWidget,
    TreeWidgetError,
    VAlign,
    WHSettings,
    Widget,
    WidgetContainerMixin,
    WidgetDecoration,
    WidgetDisable,
    WidgetError,
    WidgetMeta,
    WidgetPlaceholder,
    WidgetWrap,
    WidgetWrapError,
    WrapMode,
    delegate_to_widget_mixin,
    fixed_size,
    scale_bar_values,
)

# Optional event loops with external dependencies

try:
    from .event_loop import TornadoEventLoop
except ImportError:
    pass

try:
    from .event_loop import GLibEventLoop
except ImportError:
    pass

try:
    from .event_loop import TwistedEventLoop
except ImportError:
    pass

try:
    from .event_loop import TrioEventLoop
except ImportError:
    pass

# OS Specific
if sys.platform != "win32":
    from .vterm import TermCanvas, TermCharset, Terminal, TermModes

    # ZMQEventLoop cause interpreter crash on windows
    try:
        from .event_loop import ZMQEventLoop
    except ImportError:
        pass

# Backward compatibility
VERSION = __version_tuple__


# Moved modules handling
__locals: dict[str, typing.Any] = locals()  # use mutable access for pure lazy loading

# Backward compatible lazy load with deprecation warnings
_moved_warn: dict[str, str] = {
    "lcd_display": "urwid.display.lcd",
    "html_fragment": "urwid.display.html_fragment",
    "web_display": "urwid.display.web",
    "monitored_list": "urwid.widget.monitored_list",
    "listbox": "urwid.widget.listbox",
    "treetools": "urwid.widget.treetools",
}
# Backward compatible lazy load without any warnings
# Before DeprecationWarning need to start PendingDeprecationWarning process.


def lazy_import(name: str, package: str | None = None) -> types.ModuleType:
    """Lazy import implementation from Python documentation.

    Useful for cases where no warnings expected for moved modules.
    """
    spec = importlib.util.find_spec(name, package)
    if not spec:
        raise ImportError(f"No module named {name!r}")
    if not spec.loader:
        raise ImportError(f"Module named {name!r} is invalid")

    loader = importlib.util.LazyLoader(spec.loader)
    spec.loader = loader
    module = importlib.util.module_from_spec(spec)
    if not package:
        sys.modules[name] = module
    else:
        sys.modules[f"{package.rstrip('.')}.{name.lstrip('.')}"] = module
    loader.exec_module(module)
    return module


class _MovedModule(types.ModuleType):
    """Special class to handle moved modules.

    PEP-0562 handles moved modules attributes, but unfortunately not handle nested modules access
    like "from xxx.yyy import zzz"
    """

    __slots__ = ("_moved_from", "_moved_to")

    def __init__(self, moved_from: str, moved_to: str) -> None:
        super().__init__(moved_from.join(".")[-1])
        self._moved_from = moved_from
        self._moved_to = moved_to

    def __getattr__(self, name: str) -> typing.Any:
        real_module = importlib.import_module(self._moved_to)
        sys.modules[self._moved_from] = real_module
        return getattr(real_module, name)


display_common = lazy_import("urwid.display.common")
raw_display = lazy_import("urwid.display.raw")
curses_display = lazy_import("urwid.display.curses")
escape = lazy_import("urwid.display.escape")


class _MovedModuleWarn(_MovedModule):
    """Special class to handle moved modules.

    Produce DeprecationWarning messages for imports.
    """

    __slots__ = ()

    def __getattr__(self, name: str) -> typing.Any:
        warnings.warn(
            f"{self._moved_from} is moved to {self._moved_to}",
            DeprecationWarning,
            stacklevel=2,
        )
        return super().__getattr__(name)


for _name, _module in _moved_warn.items():
    _module_path = f"{__name__}.{_name}"
    sys.modules[_module_path] = _MovedModuleWarn(_module_path, _module)


def __getattr__(name: str) -> typing.Any:
    """Get attributes lazy.

    :return: attribute by name
    :raises AttributeError: attribute is not defined for lazy load
    """
    if name in _moved_warn:
        warnings.warn(
            f"{name} is moved to {_moved_warn[name]}",
            DeprecationWarning,
            stacklevel=2,
        )

        mod = importlib.import_module(_moved_warn[name])
        __locals[name] = mod
        return mod
    raise AttributeError(f"{name} not found in {__package__}")
