import logging
from typing import Callable, Any

from fasthtml.components import *

from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.helpers import Ids, mk
from myfasthtml.core.commands import Command
from myfasthtml.core.instances import MultipleInstance, BaseInstance
from myfasthtml.core.matching_utils import subsequence_matching, fuzzy_matching

logger = logging.getLogger("Search")


class Commands(BaseCommands):
  def search(self):
    return (Command("Search", f"Search {self._owner.items_names}", self._owner.on_search).
            htmx(target=f"#{self._owner.get_id()}-results",
                 trigger="keyup changed delay:300ms",
                 swap="innerHTML"))


class Search(MultipleInstance):
  def __init__(self,
               parent: BaseInstance,
               _id=None,
               items_names=None,  # what is the name of the items to filter
               items=None,  # first set of items to filter
               get_attr: Callable[[Any], str] = None,  # items is  a list of objects: how to  get the str to filter
               template: Callable[[Any], Any] = None):  # once filtered, what to render ?
    """
    Represents a component for managing and filtering a list of items based on specific criteria.

    This class initializes with a session, an optional identifier, a list of item names,
    a callable for extracting a string value from items, and a template callable for rendering
    the filtered items. It provides functionality to handle and organize item-based operations.

    :param _id: Optional identifier for the component.
    :param items: An optional list of names for the items to be filtered.
    :param get_attr: Callable function to extract a string value from an item for filtering. Defaults to a
                     function that returns the item as is.
    :param template: Callable function to render the filtered items. Defaults to a Div rendering function.
    """
    super().__init__(parent, _id=_id)
    self.items_names = items_names or ''
    self.items = items or []
    self.filtered = self.items.copy()
    self.get_attr = get_attr or (lambda x: x)
    self.template = template or Div
    self.commands = Commands(self)
  
  def set_items(self, items):
    self.items = items
    self.filtered = self.items.copy()
    return self
  
  def on_search(self, query):
    logger.debug(f"on_search {query=}")
    self.search(query)
    return tuple(self._mk_search_results())
  
  def search(self, query):
    logger.debug(f"search {query=}")
    if query is None or query.strip() == "":
      self.filtered = self.items.copy()
    
    else:
      res_seq = subsequence_matching(query, self.items, get_attr=self.get_attr)
      res_fuzzy = fuzzy_matching(query, self.items, get_attr=self.get_attr)
      self.filtered = res_seq + res_fuzzy
    
    return self.filtered
  
  def _mk_search_results(self):
    return [self.template(item) for item in self.filtered]
  
  def render(self):
    return Div(
      mk.mk(Input(name="query", id=f"{self._id}-search", type="text", placeholder="Search...", cls="input input-xs"),
            command=self.commands.search()),
      Div(
        *self._mk_search_results(),
        id=f"{self._id}-results",
        cls="mf-search-results",
      ),
      id=f"{self._id}",
    )
  
  def __ft__(self):
    return self.render()
