import logging
import uuid
from dataclasses import dataclass
from typing import Any

from fasthtml.common import Div, Span
from fasthtml.xtend import Script

from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.Search import Search
from myfasthtml.controls.VisNetwork import VisNetwork
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.instances import MultipleInstance, BaseInstance, InstancesManager
from myfasthtml.icons.fluent_p1 import tabs24_regular
from myfasthtml.icons.fluent_p3 import dismiss_circle16_regular, tab_add24_regular

logger = logging.getLogger("TabsManager")

vis_nodes = [
    {"id": 1, "label": "Node 1"},
    {"id": 2, "label": "Node 2"},
    {"id": 3, "label": "Node 3"},
    {"id": 4, "label": "Node 4"},
    {"id": 5, "label": "Node 5"}
]

vis_edges = [
    {"from": 1, "to": 3},
    {"from": 1, "to": 2},
    {"from": 2, "to": 4},
    {"from": 2, "to": 5},
    {"from": 3, "to": 3}
]


@dataclass
class Boundaries:
  """Store component boundaries"""
  width: int = 1020
  height: int = 782


class TabsManagerState(DbObject):
  def __init__(self, owner):
    super().__init__(owner)
    with self.initializing():
      # persisted in DB
      self.tabs: dict[str, Any] = {}
      self.tabs_order: list[str] = []
      self.active_tab: str | None = None
      
      # must not be persisted in DB
      self._tabs_content: dict[str, Any] = {}


class Commands(BaseCommands):
  def show_tab(self, tab_id):
    return Command(f"{self._prefix}ShowTab",
                   "Activate or show a specific tab",
                   self._owner.show_tab, tab_id).htmx(target=f"#{self._id}-controller", swap="outerHTML")
  
  def close_tab(self, tab_id):
    return Command(f"{self._prefix}CloseTab",
                   "Close a specific tab",
                   self._owner.close_tab, tab_id).htmx(target=f"#{self._id}", swap="outerHTML")
  
  def add_tab(self, label: str, component: Any, auto_increment=False):
    return (Command(f"{self._prefix}AddTab",
                    "Add a new tab",
                    self._owner.on_new_tab, label, component, auto_increment).
            htmx(target=f"#{self._id}-controller"))


class TabsManager(MultipleInstance):
  _tab_count = 0
  
  def __init__(self, parent, _id=None):
    super().__init__(parent, _id=_id)
    self._state = TabsManagerState(self)
    self.commands = Commands(self)
    self._boundaries = Boundaries()
    self._search = Search(self,
                          items=self._get_tab_list(),
                          get_attr=lambda x: x["label"],
                          template=self._mk_tab_button)
    logger.debug(f"TabsManager created with id: {self._id}")
    logger.debug(f"  tabs       : {self._get_ordered_tabs()}")
    logger.debug(f"  active tab : {self._state.active_tab}")
  
  def get_state(self):
    return self._state
  
  def _get_ordered_tabs(self):
    return {tab_id: self._state.tabs.get(tab_id, None) for tab_id in self._state.tabs_order}
  
  def _get_tab_content(self, tab_id):
    if tab_id not in self._state.tabs:
      return None
    tab_config = self._state.tabs[tab_id]
    if tab_config["component_type"] is None:
      return None
    return InstancesManager.dynamic_get(self, tab_config["component_type"], tab_config["component_id"])
  
  @staticmethod
  def _get_tab_count():
    res = TabsManager._tab_count
    TabsManager._tab_count += 1
    return res
  
  def on_new_tab(self, label: str, component: Any, auto_increment=False):
    logger.debug(f"on_new_tab {label=}, {component=}, {auto_increment=}")
    if auto_increment:
      label = f"{label}_{self._get_tab_count()}"
    component = component or VisNetwork(self, nodes=vis_nodes, edges=vis_edges)
    
    tab_id = self._tab_already_exists(label, component)
    if tab_id:
      return self.show_tab(tab_id)
    
    tab_id = self.add_tab(label, component)
    return (
        self._mk_tabs_controller(),
        self._wrap_tab_content(self._mk_tab_content(tab_id, component)),
        self._mk_tabs_header_wrapper(True),
    )
  
  def add_tab(self, label: str, component: Any, activate: bool = True) -> str:
    """
    Add a new tab or update an existing one with the same component type, ID and label.

    Args:
        label: Display label for the tab
        component: Component instance to display in the tab
        activate: Whether to activate the new/updated tab immediately (default: True)

    Returns:
        tab_id: The UUID of the tab (new or existing)
    """
    logger.debug(f"add_tab {label=}, component={component}, activate={activate}")
    # copy the state to avoid multiple database call
    state = self._state.copy()
    
    # Extract component ID if the component has a get_id() method
    component_type, component_id = None, None
    if isinstance(component, BaseInstance):
      component_type = component.get_prefix() if isinstance(component, BaseInstance) else type(component).__name__
      component_id = component.get_id()
    
    # Check if a tab with the same component_type, component_id AND label already exists
    existing_tab_id = self._tab_already_exists(label, component)
    
    if existing_tab_id:
      # Update existing tab (only the component instance in memory)
      tab_id = existing_tab_id
      state._tabs_content[tab_id] = component
    else:
      # Create new tab
      tab_id = str(uuid.uuid4())
      
      # Add tab metadata to state
      state.tabs[tab_id] = {
          'id': tab_id,
          'label': label,
          'component_type': component_type,
          'component_id': component_id
      }
      
      # Add tab to order
      state.tabs_order.append(tab_id)
      
      # Store component in memory
      state._tabs_content[tab_id] = component
    
    # Activate tab if requested
    if activate:
      state.active_tab = tab_id
    
    # finally, update the state
    self._state.update(state)
    self._search.set_items(self._get_tab_list())
    
    return tab_id
  
  def show_tab(self, tab_id):
    logger.debug(f"show_tab {tab_id=}")
    if tab_id not in self._state.tabs:
      logger.debug(f"  Tab not found.")
      return None
    
    logger.debug(f"  Tab label is: {self._state.tabs[tab_id]['label']}")
    self._state.active_tab = tab_id
    
    if tab_id not in self._state._tabs_content:
      logger.debug(f"  Content does not exist. Creating it.")
      content = self._get_tab_content(tab_id)
      tab_content = self._mk_tab_content(tab_id, content)
      self._state._tabs_content[tab_id] = tab_content
      return self._mk_tabs_controller(), self._wrap_tab_content(tab_content)
    else:
      logger.debug(f"  Content already exists. Just switch.")
      return self._mk_tabs_controller()
  
  def close_tab(self, tab_id: str):
    """
    Close a tab and remove it from the tabs manager.

    Args:
        tab_id: ID of the tab to close

    Returns:
        Self for chaining
    """
    logger.debug(f"close_tab {tab_id=}")
    if tab_id not in self._state.tabs:
      return self
    
    # Copy state
    state = self._state.copy()
    
    # Remove from tabs and order
    del state.tabs[tab_id]
    state.tabs_order.remove(tab_id)
    
    # Remove from content
    if tab_id in state._tabs_content:
      del state._tabs_content[tab_id]
    
    # If closing active tab, activate another one
    if state.active_tab == tab_id:
      if state.tabs_order:
        # Activate the first remaining tab
        state.active_tab = state.tabs_order[0]
      else:
        state.active_tab = None
    
    # Update state
    self._state.update(state)
    self._search.set_items(self._get_tab_list())
    
    return self
  
  def add_tab_btn(self):
    return mk.icon(tab_add24_regular,
                   id=f"{self._id}-add-tab-btn",
                   cls="mf-tab-btn",
                   command=self.commands.add_tab(f"Untitled",
                                                 None,
                                                 True))
  
  def _mk_tabs_controller(self):
    return Div(
      Div(id=f"{self._id}-controller", data_active_tab=f"{self._state.active_tab}"),
      Script(f'updateTabs("{self._id}-controller");'),
    )
  
  def _mk_tabs_header_wrapper(self, oob=False):
    # Create visible tab buttons
    visible_tab_buttons = [
        self._mk_tab_button(self._state.tabs[tab_id])
        for tab_id in self._state.tabs_order
        if tab_id in self._state.tabs
    ]
    
    header_content = [*visible_tab_buttons]
    
    return Div(
      Div(*header_content, cls="mf-tabs-header", id=f"{self._id}-header"),
      self._mk_show_tabs_menu(),
      id=f"{self._id}-header-wrapper",
      cls="mf-tabs-header-wrapper",
      hx_swap_oob="true" if oob else None
    )
  
  def _mk_tab_button(self, tab_data: dict, in_dropdown: bool = False):
    """
    Create a single tab button with its label and close button.

    Args:
        tab_id: Unique identifier for the tab
        tab_data: Dictionary containing tab information (label, component_type, etc.)
        in_dropdown: Whether this tab is rendered in the dropdown menu

    Returns:
        Button element representing the tab
    """
    tab_id = tab_data["id"]
    is_active = tab_id == self._state.active_tab
    
    close_btn = mk.mk(
      Span(dismiss_circle16_regular, cls="mf-tab-close-btn"),
      command=self.commands.close_tab(tab_id)
    )
    
    tab_label = mk.mk(
      Span(tab_data.get("label", "Untitled"), cls="mf-tab-label"),
      command=self.commands.show_tab(tab_id)
    )
    
    extra_cls = "mf-tab-in-dropdown" if in_dropdown else ""
    
    return Div(
      tab_label,
      close_btn,
      cls=f"mf-tab-button {extra_cls} {'mf-tab-active' if is_active else ''}",
      data_tab_id=tab_id,
      data_manager_id=self._id
    )
  
  def _mk_tab_content_wrapper(self):
    """
    Create the active tab content area.

    Returns:
        Div element containing the active tab content or empty container
    """
    
    if self._state.active_tab:
      active_tab = self._state.active_tab
      if active_tab in self._state._tabs_content:
        tab_content = self._state._tabs_content[active_tab]
      else:
        content = self._get_tab_content(active_tab)
        tab_content = self._mk_tab_content(active_tab, content)
        self._state._tabs_content[active_tab] = tab_content
    else:
      tab_content = self._mk_tab_content(None, None)
    
    return Div(
      tab_content,
      cls="mf-tab-content-wrapper",
      id=f"{self._id}-content-wrapper",
    )
  
  def _mk_tab_content(self, tab_id: str, content):
    is_active = tab_id == self._state.active_tab
    return Div(
      content if content else Div("No Content", cls="mf-empty-content"),
      cls=f"mf-tab-content {'hidden' if not is_active else ''}",  # ← ici
      id=f"{self._id}-{tab_id}-content",
    )
  
  def _mk_show_tabs_menu(self):
    return Div(
      mk.icon(tabs24_regular,
              size="32",
              tabindex="0",
              role="button",
              cls="btn btn-xs"),
      Div(
        self._search,
        tabindex="-1",
        cls="dropdown-content menu w-52 rounded-box bg-base-300 shadow-xl"
      ),
      cls="dropdown dropdown-end"
    )
  
  def _wrap_tab_content(self, tab_content):
    return Div(
      tab_content,
      hx_swap_oob=f"beforeend:#{self._id}-content-wrapper",
    )
  
  def _tab_already_exists(self, label, component):
    if not isinstance(component, BaseInstance):
      return None
    
    component_type = component.get_prefix() if isinstance(component, BaseInstance) else type(component).__name__
    component_id = component.get_id()
    
    if component_id is not None:
      for tab_id, tab_data in self._state.tabs.items():
        if (tab_data.get('component_type') == component_type and
            tab_data.get('component_id') == component_id and
            tab_data.get('label') == label):
          return tab_id
    
    return None
  
  def _get_tab_list(self):
    return [self._state.tabs[tab_id] for tab_id in self._state.tabs_order if tab_id in self._state.tabs]
  
  def update_boundaries(self):
    return Script(f"updateBoundaries('{self._id}');")
  
  def render(self):
    """
    Render the complete TabsManager component.

    Returns:
        Div element containing tabs header, content area, and resize script
    """
    return Div(
      self._mk_tabs_controller(),
      self._mk_tabs_header_wrapper(),
      self._mk_tab_content_wrapper(),
      cls="mf-tabs-manager",
      id=self._id,
    )
  
  def __ft__(self):
    return self.render()
