
from __future__ import annotations

import asyncio
import re
import uuid
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum, auto
from pathlib import Path
from typing import (
    Any, Callable, Dict, List, Optional, Type, Union,
    TYPE_CHECKING, Awaitable, Coroutine
)

if TYPE_CHECKING:
    from textual.app import App
    from textual.widget import Widget
    from textual.screen import Screen
    from textual.message import Message
    from textual.css.query import QueryType

class PluginSettingType(Enum):
    STRING = auto()
    INTEGER = auto()
    BOOLEAN = auto()
    SELECT = auto()
    COLOR = auto()
    KEYBIND = auto()
    TEXT = auto()
    PATH = auto()
    JSON = auto()
    LIST = auto()

@dataclass
class PluginSetting:
    key: str
    label: str
    type: PluginSettingType
    default: Any = None
    description: str = ""
    options: List[tuple] = field(default_factory=list)
    min_value: Optional[int] = None
    max_value: Optional[int] = None
    placeholder: str = ""
    required: bool = False
    hidden: bool = False

@dataclass
class PluginMeta:
    name: str
    id: str
    version: str = "1.0.0"
    description: str = ""
    author: str = "Unknown"
    website: str = ""
    requires_restart: bool = False
    tags: List[str] = field(default_factory=list)
    dependencies: List[str] = field(default_factory=list)
    min_app_version: str = ""
    license: str = "MIT"

class HookType(Enum):

    ON_APP_START = "on_app_start"
    ON_APP_STOP = "on_app_stop"
    ON_APP_READY = "on_app_ready"
    ON_CONNECT = "on_connect"
    ON_DISCONNECT = "on_disconnect"

    ON_MESSAGE_RECEIVE = "on_message_receive"
    ON_MESSAGE_SEND = "on_message_send"
    ON_MESSAGE_RENDER = "on_message_render"
    ON_MESSAGE_CONTEXT_MENU = "on_message_context_menu"
    ON_MESSAGE_DELETE = "on_message_delete"
    ON_MESSAGE_EDIT = "on_message_edit"

    ON_SIDEBAR_COMPOSE = "on_sidebar_compose"
    ON_COMPOSER_COMPOSE = "on_composer_compose"
    ON_CHAT_HEADER_COMPOSE = "on_chat_header_compose"
    ON_CHAT_FOOTER_COMPOSE = "on_chat_footer_compose"
    ON_TOOLBAR_COMPOSE = "on_toolbar_compose"

    ON_COMPOSE_SUBMIT = "on_compose_submit"
    ON_KEY_PRESS = "on_key_press"
    ON_KEY_RELEASE = "on_key_release"

    ON_CHAT_SWITCH = "on_chat_switch"
    ON_SCREEN_CHANGE = "on_screen_change"
    ON_TAB_CHANGE = "on_tab_change"

    ON_USER_CLICK = "on_user_click"
    ON_USER_JOIN = "on_user_join"
    ON_USER_LEAVE = "on_user_leave"
    ON_USER_STATUS_CHANGE = "on_user_status_change"

    ON_CALL_START = "on_call_start"
    ON_CALL_END = "on_call_end"
    ON_CALL_MUTE = "on_call_mute"

    ON_REQUEST_RECEIVE = "on_request_receive"
    ON_REQUEST_CLICK = "on_request_click"

    ON_COMMAND = "on_command"
    ON_SLASH_COMMAND = "on_slash_command"

    ON_SETTINGS_OPEN = "on_settings_open"
    ON_SETTINGS_SAVE = "on_settings_save"
    ON_SETTINGS_TAB_COMPOSE = "on_settings_tab_compose"

    ON_FILE_UPLOAD = "on_file_upload"
    ON_FILE_DOWNLOAD = "on_file_download"

    ON_THEME_CHANGE = "on_theme_change"

@dataclass
class RegisteredButton:
    id: str
    label: str
    callback: Callable
    icon: str = ""
    tooltip: str = ""
    location: str = "toolbar"
    variant: str = "default"
    classes: str = ""
    order: int = 100

@dataclass
class RegisteredPanel:
    id: str
    title: str
    widget_class: Type["Widget"]
    icon: str = ""
    location: str = "sidebar"
    width: Optional[int] = None
    height: Optional[int] = None
    collapsible: bool = True
    default_collapsed: bool = False

@dataclass
class RegisteredContextMenuItem:
    id: str
    label: str
    callback: Callable
    icon: str = ""
    target: str = "message"
    condition: Optional[Callable] = None
    order: int = 100

@dataclass
class RegisteredKeybinding:
    id: str
    key: str
    callback: Callable
    description: str = ""
    global_: bool = False
    context: str = ""

@dataclass
class RegisteredCommand:
    name: str
    callback: Callable
    description: str = ""
    usage: str = ""
    aliases: List[str] = field(default_factory=list)
    hidden: bool = False

@dataclass
class RegisteredWidget:
    id: str
    widget_class: Type["Widget"]
    location: str
    order: int = 100
    props: Dict[str, Any] = field(default_factory=dict)

@dataclass
class RegisteredSettingsTab:
    id: str
    title: str
    icon: str = ""
    widget_class: Optional[Type["Widget"]] = None
    compose_callback: Optional[Callable] = None
    order: int = 100

class Plugin(ABC):

    def __init__(self):
        self._app: Optional["App"] = None
        self._enabled: bool = False
        self._settings_values: Dict[str, Any] = {}
        self._settings_loaded: bool = False
        self._hooks: Dict[HookType, List[Callable]] = {}

        self._buttons: Dict[str, RegisteredButton] = {}
        self._panels: Dict[str, RegisteredPanel] = {}
        self._context_menu_items: Dict[str, RegisteredContextMenuItem] = {}
        self._keybindings: Dict[str, RegisteredKeybinding] = {}
        self._commands: Dict[str, RegisteredCommand] = {}
        self._widgets: Dict[str, RegisteredWidget] = {}
        self._settings_tabs: Dict[str, RegisteredSettingsTab] = {}
        self._mounted_widgets: List["Widget"] = []

        self._virtual_table_schemas: Dict[str, Dict[str, str]] = {}

    @property
    @abstractmethod
    def meta(self) -> PluginMeta:
        pass

    @property
    def settings(self) -> List[PluginSetting]:
        return []

    @property
    def css(self) -> str:
        return ""

    @property
    def app(self) -> Optional["App"]:
        return self._app

    @app.setter
    def app(self, value: "App") -> None:
        self._app = value

    @property
    def enabled(self) -> bool:
        return self._enabled

    @enabled.setter
    def enabled(self, value: bool) -> None:
        self._enabled = value

    @property
    def db(self) -> Any:
        if self._app and hasattr(self._app, 'db'):
            return self._app.db
        return None

    @property
    def network(self) -> Any:
        if self._app and hasattr(self._app, 'net'):
            return self._app.net
        return None

    @property
    def current_chat(self) -> Optional[str]:
        if self._app and hasattr(self._app, 'active_chat'):
            return self._app.active_chat
        return None

    @property
    def current_user(self) -> Optional[Dict[str, Any]]:
        if self._app and hasattr(self._app, 'me'):
            return self._app.me
        return None

    def get_lang(self) -> str:
        if self._app and hasattr(self._app, 'lang'):
            return self._app.lang or "en"
        return "en"

    @property
    def translations(self) -> dict[str, dict[str, str]]:
        return {}

    def t(self, key: str, **kwargs) -> str:
        lang = self.get_lang()

        plugin_trans = getattr(self, '_plugin_translations', None)
        if not plugin_trans:
            try:
                plugin_trans = self.translations or {}
            except Exception:
                plugin_trans = {}

        if plugin_trans:

            if lang in plugin_trans:
                lang_trans = plugin_trans[lang]
                if key in lang_trans:
                    try:
                        return lang_trans[key].format(**kwargs) if kwargs else lang_trans[key]
                    except (KeyError, ValueError):
                        return lang_trans[key]

            if "en" in plugin_trans:
                en_trans = plugin_trans["en"]
                if key in en_trans:
                    try:
                        return en_trans[key].format(**kwargs) if kwargs else en_trans[key]
                    except (KeyError, ValueError):
                        return en_trans[key]

        try:
            from ui.i18n import t as global_translate
            return global_translate(lang, key, **kwargs)
        except Exception:
            return key

    def translate(self, key: str, **kwargs) -> str:
        return self.t(key, **kwargs)

    def _ensure_settings_loaded(self) -> None:
        if self._settings_loaded:
            return
        try:
            from plugins.loader import get_plugin_loader
            loader = get_plugin_loader()
            if loader:
                saved = loader.get_plugin_settings(self.meta.id)
                if saved:
                    self._settings_values = saved.copy()
        except Exception:
            pass
        self._settings_loaded = True

    def get_setting(self, key: str, default: Any = None) -> Any:

        self._ensure_settings_loaded()

        if key in self._settings_values:
            return self._settings_values[key]

        for setting in self.settings:
            if setting.key == key:
                return setting.default if default is None else default
        return default

    def set_setting(self, key: str, value: Any) -> None:
        self._ensure_settings_loaded()
        self._settings_values[key] = value
        self._save_settings()

    def load_settings(self, values: Dict[str, Any]) -> None:
        self._settings_values = values.copy()
        self._settings_loaded = True

    def reload_settings(self) -> None:
        self._settings_loaded = False
        self._settings_values = {}
        self._ensure_settings_loaded()

    def save_settings(self) -> Dict[str, Any]:
        return self._settings_values.copy()

    def _save_settings(self) -> None:
        from plugins.loader import get_plugin_loader
        loader = get_plugin_loader()
        loader.save_plugin_settings(self.meta.id, self._settings_values)

    def on_load(self) -> None:
        pass

    def on_unload(self) -> None:
        self._cleanup()

    def on_enable(self) -> None:
        pass

    def on_disable(self) -> None:
        self._cleanup()

    def _cleanup(self) -> None:
        for widget in self._mounted_widgets:
            try:
                widget.remove()
            except Exception:
                pass
        self._mounted_widgets.clear()

        self._close_plugin_db()

    def register_hook(self, hook_type: HookType, callback: Callable) -> None:
        if hook_type not in self._hooks:
            self._hooks[hook_type] = []
        self._hooks[hook_type].append(callback)

    def unregister_hook(self, hook_type: HookType, callback: Callable) -> None:
        if hook_type in self._hooks:
            try:
                self._hooks[hook_type].remove(callback)
            except ValueError:
                pass

    def get_hooks(self, hook_type: HookType) -> List[Callable]:
        return self._hooks.get(hook_type, [])

    def register_button(
        self,
        id: str,
        label: str,
        callback: Callable,
        icon: str = "",
        tooltip: str = "",
        location: str = "toolbar",
        variant: str = "default",
        classes: str = "",
        order: int = 100
    ) -> RegisteredButton:
        btn = RegisteredButton(
            id=f"{self.meta.id}_{id}",
            label=label,
            callback=callback,
            icon=icon,
            tooltip=tooltip,
            location=location,
            variant=variant,
            classes=classes,
            order=order
        )
        self._buttons[id] = btn
        return btn

    def unregister_button(self, id: str) -> None:
        self._buttons.pop(id, None)

    def get_buttons(self, location: Optional[str] = None) -> List[RegisteredButton]:
        buttons = list(self._buttons.values())
        if location:
            buttons = [b for b in buttons if b.location == location]
        return sorted(buttons, key=lambda b: b.order)

    def register_panel(
        self,
        id: str,
        title: str,
        widget_class: Type["Widget"],
        icon: str = "",
        location: str = "sidebar",
        width: Optional[int] = None,
        height: Optional[int] = None,
        collapsible: bool = True,
        default_collapsed: bool = False
    ) -> RegisteredPanel:
        panel = RegisteredPanel(
            id=f"{self.meta.id}_{id}",
            title=title,
            widget_class=widget_class,
            icon=icon,
            location=location,
            width=width,
            height=height,
            collapsible=collapsible,
            default_collapsed=default_collapsed
        )
        self._panels[id] = panel
        return panel

    def unregister_panel(self, id: str) -> None:
        self._panels.pop(id, None)

    def get_panels(self, location: Optional[str] = None) -> List[RegisteredPanel]:
        panels = list(self._panels.values())
        if location:
            panels = [p for p in panels if p.location == location]
        return panels

    def register_context_menu_item(
        self,
        id: str,
        label: str,
        callback: Callable,
        icon: str = "",
        target: str = "message",
        condition: Optional[Callable] = None,
        order: int = 100
    ) -> RegisteredContextMenuItem:
        item = RegisteredContextMenuItem(
            id=f"{self.meta.id}_{id}",
            label=label,
            callback=callback,
            icon=icon,
            target=target,
            condition=condition,
            order=order
        )
        self._context_menu_items[id] = item
        return item

    def get_context_menu_items(self, target: Optional[str] = None) -> List[RegisteredContextMenuItem]:
        items = list(self._context_menu_items.values())
        if target:
            items = [i for i in items if i.target == target]
        return sorted(items, key=lambda i: i.order)

    def register_keybinding(
        self,
        id: str,
        key: str,
        callback: Callable,
        description: str = "",
        global_: bool = False,
        context: str = ""
    ) -> RegisteredKeybinding:
        kb = RegisteredKeybinding(
            id=f"{self.meta.id}_{id}",
            key=key,
            callback=callback,
            description=description,
            global_=global_,
            context=context
        )
        self._keybindings[id] = kb
        return kb

    def get_keybindings(self) -> List[RegisteredKeybinding]:
        return list(self._keybindings.values())

    def register_command(
        self,
        name: str,
        callback: Callable,
        description: str = "",
        usage: str = "",
        aliases: Optional[List[str]] = None,
        hidden: bool = False
    ) -> RegisteredCommand:
        cmd = RegisteredCommand(
            name=name,
            callback=callback,
            description=description,
            usage=usage or f"/{name}",
            aliases=aliases or [],
            hidden=hidden
        )
        self._commands[name] = cmd
        return cmd

    def get_commands(self) -> List[RegisteredCommand]:
        return [c for c in self._commands.values() if not c.hidden]

    def get_all_commands(self) -> Dict[str, RegisteredCommand]:
        return self._commands.copy()

    def register_widget(
        self,
        id: str,
        widget_class: Type["Widget"],
        location: str,
        order: int = 100,
        **props
    ) -> RegisteredWidget:
        widget = RegisteredWidget(
            id=f"{self.meta.id}_{id}",
            widget_class=widget_class,
            location=location,
            order=order,
            props=props
        )
        self._widgets[id] = widget
        return widget

    def get_widgets(self, location: Optional[str] = None) -> List[RegisteredWidget]:
        widgets = list(self._widgets.values())
        if location:
            widgets = [w for w in widgets if w.location == location]
        return sorted(widgets, key=lambda w: w.order)

    def register_settings_tab(
        self,
        id: str,
        title: str,
        icon: str = "",
        widget_class: Optional[Type["Widget"]] = None,
        compose_callback: Optional[Callable] = None,
        order: int = 100
    ) -> RegisteredSettingsTab:
        tab = RegisteredSettingsTab(
            id=f"{self.meta.id}_{id}",
            title=title,
            icon=icon,
            widget_class=widget_class,
            compose_callback=compose_callback,
            order=order
        )
        self._settings_tabs[id] = tab
        return tab

    def get_settings_tabs(self) -> List[RegisteredSettingsTab]:
        return sorted(self._settings_tabs.values(), key=lambda t: t.order)

    def log(self, message: str) -> None:
        print(f"[{self.meta.name}] {message}")

    def notify(self, message: str, severity: str = "information", timeout: Optional[float] = None) -> None:
        if self._app:
            if timeout is None:

                base_timeout = 5.0
                extra_time = len(message) / 50.0
                timeout = min(base_timeout + extra_time, 30.0)

                if severity == "error":
                    timeout = max(timeout, 10.0)
                elif severity == "warning":
                    timeout = max(timeout, 7.0)

            self._app.notify(message, severity=severity, timeout=timeout)

    def notify_persistent(self, message: str, severity: str = "information") -> None:
        if self._app:
            self._app.notify(message, severity=severity, timeout=0)

    def show_toast(self, message: str, timeout: float = 2.0) -> None:
        self.notify(message, timeout=timeout)

    def show_dialog(
        self,
        title: str,
        content: Union[str, "Widget"],
        buttons: Optional[List[tuple]] = None,
        callback: Optional[Callable] = None
    ) -> None:
        if not self._app:
            return
        from ui.dialogs import PluginDialog
        dialog = PluginDialog(
            title=title,
            content=content,
            buttons=buttons or [("OK", "ok")],
            plugin_id=self.meta.id
        )
        self._app.push_screen(dialog, callback)

    def show_confirm(
        self,
        title: str,
        message: str,
        callback: Callable[[bool], None]
    ) -> None:
        def on_result(result):
            callback(result == "yes")
        self.show_dialog(
            title=title,
            content=message,
            buttons=[("Yes", "yes"), ("No", "no")],
            callback=on_result
        )

    def show_input(
        self,
        title: str,
        prompt: str,
        default: str = "",
        callback: Optional[Callable[[Optional[str]], None]] = None
    ) -> None:
        if not self._app:
            return
        from ui.dialogs import PluginInputDialog
        dialog = PluginInputDialog(
            title=title,
            prompt=prompt,
            default=default,
            plugin_id=self.meta.id
        )
        self._app.push_screen(dialog, callback)

    def mount_widget(
        self,
        widget: "Widget",
        target: Optional[Union[str, "Widget"]] = None,
        before: Optional["Widget"] = None,
        after: Optional["Widget"] = None
    ) -> None:
        if not self._app:
            return
        try:
            if target is None:
                self._app.mount(widget, before=before, after=after)
            elif isinstance(target, str):
                container = self._app.query_one(target)
                container.mount(widget, before=before, after=after)
            else:
                target.mount(widget, before=before, after=after)
            self._mounted_widgets.append(widget)
        except Exception as e:
            self.log(f"Error mounting widget: {e}")

    async def mount_widget_async(
        self,
        widget: "Widget",
        target: Optional[Union[str, "Widget"]] = None
    ) -> None:
        if not self._app:
            return
        try:
            if target is None:
                await self._app.mount(widget)
            elif isinstance(target, str):
                container = self._app.query_one(target)
                await container.mount(widget)
            else:
                await target.mount(widget)
            self._mounted_widgets.append(widget)
        except Exception as e:
            self.log(f"Error mounting widget: {e}")

    def unmount_widget(self, widget: "Widget") -> None:
        try:
            widget.remove()
            if widget in self._mounted_widgets:
                self._mounted_widgets.remove(widget)
        except Exception as e:
            self.log(f"Error unmounting widget: {e}")

    def query(self, selector: str):
        if not self._app:
            return []
        return self._app.query(selector)

    def query_one(self, selector: str, expect_type: Optional[Type] = None) -> Optional["Widget"]:
        if not self._app:
            return None
        try:
            if expect_type:
                return self._app.query_one(selector, expect_type)
            return self._app.query_one(selector)
        except Exception:
            return None

    def push_screen(self, screen: "Screen", callback: Optional[Callable] = None) -> None:
        if self._app:
            self._app.push_screen(screen, callback)

    def pop_screen(self) -> None:
        if self._app:
            self._app.pop_screen()

    def switch_screen(self, screen: Union[str, "Screen"]) -> None:
        if self._app:
            self._app.switch_screen(screen)

    def send_message(self, content: str, chat_id: Optional[str] = None) -> None:
        if not self._app:
            return
        target_chat = chat_id or self.current_chat
        if not target_chat:
            self.log("No chat selected")
            return
        if hasattr(self._app, 'send_message'):
            self._app.send_message(content, target_chat)
        elif hasattr(self._app, '_do_send'):
            asyncio.create_task(self._app._do_send(content))

    def get_messages(self, chat_id: Optional[str] = None, limit: int = 50) -> List[Dict]:
        if not self.db:
            return []
        target_chat = chat_id or self.current_chat
        if not target_chat:
            return []
        try:
            return list(self.db.list_messages(target_chat, limit=limit))
        except Exception:
            return []

    def jump_to_message(self, message_id: str) -> bool:
        if not self._app or not message_id:
            return False

        msg_widgets = getattr(self._app, '_msg_widgets', {})
        if message_id not in msg_widgets:
            return False

        try:
            from ui.widgets.messaging import MessageWidget
            w = msg_widgets[message_id]
            if isinstance(w, MessageWidget):
                w.post_message(MessageWidget.JumpRequested(w, message_id))
                return True
        except Exception:
            pass

        return False

    def _get_db(self):
        if self._app and hasattr(self._app, 'db') and self._app.db:
            return self._app.db
        return None

    def _generate_row_id(self, data: Dict[str, Any]) -> str:

        for pk_field in ('id', 'msg_id', 'message_id', 'key', 'row_id'):
            if pk_field in data and data[pk_field] is not None:
                return str(data[pk_field])
        return str(uuid.uuid4())

    def plugin_db_create_table(self, table_name: str, columns: Dict[str, str]) -> None:

        if not hasattr(self, '_virtual_table_schemas'):
            self._virtual_table_schemas = {}
        self._virtual_table_schemas[table_name] = columns

    def plugin_db_insert(self, table_name: str, data: Dict[str, Any]) -> None:
        db = self._get_db()
        if not db:
            self.log("Warning: No database available for plugin_db_insert")
            return

        row_id = self._generate_row_id(data)
        db.plugin_data_insert(self.meta.id, table_name, row_id, data)

    def plugin_db_select(
        self, table_name: str, where: str = "", params: tuple = (), limit: int = 0
    ) -> List[Dict[str, Any]]:
        db = self._get_db()
        if not db:
            return []

        if not where:

            return db.plugin_data_select(self.meta.id, table_name, limit=limit)

        import re

        simple_match = re.match(r'^(\w+)\s*=\s*\?$', where.strip())
        if simple_match and len(params) == 1:
            field = simple_match.group(1)
            return db.plugin_data_select(
                self.meta.id, table_name,
                where_field=field, where_value=params[0],
                limit=limit
            )

        try:

            json_where_parts = []
            pattern = re.compile(r'(\w+)\s*([=<>!]+)\s*\?')

            for match in pattern.finditer(where):
                field = match.group(1)
                op = match.group(2)
                json_where_parts.append(f"JSON_EXTRACT(data_json, '$.{field}') {op} ?")

            if json_where_parts:

                json_where = where
                for match in pattern.finditer(where):
                    field = match.group(1)
                    op = match.group(2)
                    old_expr = f"{field} {op} ?"
                    new_expr = f"JSON_EXTRACT(data_json, '$.{field}') {op} ?"
                    json_where = json_where.replace(old_expr, new_expr, 1)

                result = db.plugin_data_execute_json_query(
                    self.meta.id, table_name, json_where, params
                )
                if limit > 0:
                    return result[:limit]
                return result
        except Exception as e:
            self.log(f"Warning: Complex WHERE parsing failed: {e}")

        all_rows = db.plugin_data_select(self.meta.id, table_name)

        if where and params:
            import re
            matches = re.findall(r'(\w+)\s*=\s*\?', where)
            if matches and len(matches) == len(params):
                filtered = []
                for row in all_rows:
                    match = True
                    for field, value in zip(matches, params):
                        if str(row.get(field, "")) != str(value):
                            match = False
                            break
                    if match:
                        filtered.append(row)
                return filtered[:limit] if limit > 0 else filtered

        return all_rows[:limit] if limit > 0 else all_rows

    def plugin_db_delete(self, table_name: str, where: str, params: tuple = ()) -> int:
        db = self._get_db()
        if not db:
            return 0

        import re

        simple_match = re.match(r'^(\w+)\s*=\s*\?$', where.strip())
        if simple_match and len(params) == 1:
            field = simple_match.group(1)

            if field in ('id', 'msg_id', 'message_id', 'key', 'row_id'):
                return db.plugin_data_delete(self.meta.id, table_name, str(params[0]))

            return db.plugin_data_delete_where(self.meta.id, table_name, field, params[0])

        rows = self.plugin_db_select(table_name, where, params)
        count = 0
        for row in rows:
            row_id = self._generate_row_id(row)
            count += db.plugin_data_delete(self.meta.id, table_name, row_id)
        return count

    def plugin_db_update(
        self, table_name: str, data: Dict[str, Any], where: str, params: tuple = ()
    ) -> int:
        db = self._get_db()
        if not db:
            return 0

        rows = self.plugin_db_select(table_name, where, params)
        count = 0

        for row in rows:

            updated_row = {**row, **data}
            row_id = self._generate_row_id(updated_row)
            db.plugin_data_insert(self.meta.id, table_name, row_id, updated_row)
            count += 1

        return count

    def plugin_db_count(self, table_name: str, where: str = "", params: tuple = ()) -> int:
        db = self._get_db()
        if not db:
            return 0

        import re

        if not where:
            return db.plugin_data_count(self.meta.id, table_name)

        simple_match = re.match(r'^(\w+)\s*=\s*\?$', where.strip())
        if simple_match and len(params) == 1:
            field = simple_match.group(1)
            return db.plugin_data_count(self.meta.id, table_name, field, params[0])

        return len(self.plugin_db_select(table_name, where, params))

    def plugin_db_clear_table(self, table_name: str) -> int:
        db = self._get_db()
        if not db:
            return 0
        return db.plugin_data_clear_table(self.meta.id, table_name)

    def plugin_db_clear_all(self) -> int:
        db = self._get_db()
        if not db:
            return 0
        return db.plugin_data_clear_all(self.meta.id)

    def db_get(self, key: str, default: Any = None) -> Any:
        db = self._get_db()
        if not db:
            return default

        try:
            data = db.plugin_data_get(self.meta.id, "kv_store", key)
            if data and "value" in data:
                return data["value"]
            return default
        except Exception:
            return default

    def db_set(self, key: str, value: Any) -> None:
        db = self._get_db()
        if not db:
            self.log("Warning: No database available for db_set")
            return

        try:
            db.plugin_data_insert(
                self.meta.id, "kv_store", key,
                {"key": key, "value": str(value) if value is not None else None}
            )
        except Exception as e:
            self.log(f"Error saving to db: {e}")

    def db_delete(self, key: str) -> None:
        db = self._get_db()
        if not db:
            return

        try:
            db.plugin_data_delete(self.meta.id, "kv_store", key)
        except Exception:
            pass

    def db_list_keys(self, prefix: str = "") -> List[str]:
        db = self._get_db()
        if not db:
            return []

        try:
            rows = db.plugin_data_select(self.meta.id, "kv_store")
            keys = [row.get("key", "") for row in rows if row.get("key")]
            if prefix:
                keys = [k for k in keys if k.startswith(prefix)]
            return keys
        except Exception:
            return []

    def _close_plugin_db(self) -> None:

        pass

    def plugin_db_execute(self, sql: str, params: tuple = ()) -> None:
        self.log(f"Warning: plugin_db_execute() is deprecated and has no effect. SQL: {sql[:50]}...")

    def plugin_db_commit(self) -> None:
        pass

    def call_later(self, callback: Callable, delay: float = 0) -> None:
        if self._app:
            if delay > 0:
                self._app.call_later(lambda: asyncio.create_task(self._delayed_call(callback, delay)))
            else:
                self._app.call_later(callback)

    async def _delayed_call(self, callback: Callable, delay: float) -> None:
        await asyncio.sleep(delay)
        callback()

    def run_async(self, coro: Coroutine) -> None:
        asyncio.create_task(coro)

    def t(self, key: str, **kwargs) -> str:
        if self._app and hasattr(self._app, 't'):
            return self._app.t(key, **kwargs)
        return key

    def inject_css(self, css: str) -> None:
        if self._app:
            if not hasattr(self._app, '_plugin_css'):
                self._app._plugin_css = {}
            self._app._plugin_css[self.meta.id] = css
            try:
                self._app.refresh_css()
            except Exception:
                pass

    def remove_css(self) -> None:
        if self._app and hasattr(self._app, '_plugin_css'):
            self._app._plugin_css.pop(self.meta.id, None)
            try:
                self._app.refresh_css()
            except Exception:
                pass

    def on_message_receive(self, sender: str, message: str, meta: dict) -> Optional[str]:
        return None

    def on_message_send(self, message: str) -> Optional[str]:
        return None

    def on_message_render(self, sender: str, message: str, meta: dict) -> Optional[str]:
        return None

    def on_command(self, command: str, args: List[str]) -> bool:
        if command in self._commands:
            try:
                self._commands[command].callback(args)
                return True
            except Exception as e:
                self.log(f"Error in command {command}: {e}")
        return False

    def on_key_press(self, key: str, modifiers: List[str]) -> bool:
        key_combo = "+".join(modifiers + [key]) if modifiers else key
        for kb in self._keybindings.values():
            if kb.key.lower() == key_combo.lower():
                try:
                    kb.callback()
                    return True
                except Exception as e:
                    self.log(f"Error in keybinding {kb.id}: {e}")
        return False

    def on_app_ready(self) -> None:
        if self.css:
            self.inject_css(self.css)

class PluginError(Exception):
    pass

class PluginLoadError(PluginError):
    pass

class PluginSettingsError(PluginError):
    pass

class PluginDependencyError(PluginError):
    pass
