from __future__ import annotations

import numpy as np

from .tkannotation import tkAnnotationController
from .tkbuttons import tkButtonController
from .tkcoords import convert_axes_coordinates
from .tkcrosshair import tkCrosshairController
from .tkdataset import tkDataSetRegistry
from .tkeventmanager import tkEventManager
from .tkmovable import tkMovablePointController, tkMovableTextController
from .tknearest import tkNearestFinder
from .tkpopup import tkPopupController


class tkPlotEvent:
    """Compatibility-oriented facade for interactive Matplotlib helpers."""

    def __init__(self, plt, distance="r", **kwargs):
        self.plt = plt
        self.distance = distance
        self.stop_flag = False
        self.hit_itarget = None
        self.hit_axisinf = None
        self._registry = tkDataSetRegistry()
        self.axis_inf = self._registry.items
        self._events = tkEventManager()
        self._nearest = tkNearestFinder(distance)

        self._annotation_controller = tkAnnotationController(self._events)
        self.annotation = self._annotation_controller.state
        self._crosshair_controller = tkCrosshairController(self._events)
        self.follow_mouse = self._crosshair_controller.state
        self._move_points_controller = tkMovablePointController(self._events)
        self.move_points = self._move_points_controller.state
        self._move_text_controller = tkMovableTextController(self._events)
        self.move_text = self._move_text_controller.state
        self._popup_controller = tkPopupController(self._events)
        self._button_controller = tkButtonController(plt)
        self.stop_button = None

    def add_data(self, axis_inf=None): return self._registry.add(axis_inf)
    def remove(self, index): return self._registry.remove(index)
    def convert_coord(self, x0, y0, axis_s, axis_t): return convert_axes_coordinates(x0, y0, axis_s, axis_t)

    def find_target_axes(self, event, axes=None):
        if axes is not None:
            for i, axis in enumerate(axes):
                if event.inaxes is axis: return i, axis
            return None, None
        indices, items = [], []
        for i, item in enumerate(self.axis_inf):
            if event.inaxes is item["axis"]:
                indices.append(i); items.append(item)
        return (indices, items) if indices else (None, None)

    def find_nearest_data(self, x, y, itarget_list, hit_axisinf_list):
        class Event: pass
        event = Event(); event.xdata = x; event.ydata = y
        event.inaxes = hit_axisinf_list[0]["axis"] if hit_axisinf_list else None
        result = self._nearest.find(event, itarget_list, hit_axisinf_list)
        if result is None: return None, None, None, np.inf
        return result.dataset_index, result.artist_index, result.data_index, result.distance2

    def display_data(self, iinf, idata, idx, axis_inf):
        if iinf is None: return
        artist = axis_inf["data"][idata]
        x = float(np.asarray(artist.xdata)[idx]); y = float(np.asarray(artist.ydata)[idx])
        print(f"Data [{axis_inf.get('label', iinf)}], index={idx}: x={x:g}, y={y:g}")
        xlist, xlabels = axis_inf.get("xlist"), axis_inf.get("xlabels")
        if xlist is not None:
            labels = xlabels or [f"data[{i}]" for i in range(len(xlist))]
            for label, values in zip(labels, xlist):
                try: value = values[idx]
                except Exception: continue
                print(f"  {label}: {value}")

    def onclick(self, event):
        indices, datasets = self.find_target_axes(event)
        if indices is None: return
        result = self._nearest.find(event, indices, datasets)
        if result is None: return
        self.hit_itarget = result.dataset_index; self.hit_axisinf = result.dataset
        self.display_data(result.dataset_index, result.artist_index, result.data_index, result.dataset)

    def register_event(self, fig, event="button_press_event", callback=None):
        return self._events.connect(fig, event, callback or self.onclick, key=("generic", event, id(fig)))
    def register_click(self, fig, event="button_press_event", callback=None): return self.register_event(fig, event, callback)
    def register_pick(self, fig, event="pick_event", callback=None): return self.register_event(fig, event, callback or self.onpick)
    def register_redraw(self, fig, event="draw_event", callback=None): return self.register_event(fig, event, callback or self.ondraw)
    def register_key(self, fig, event="key_press_event", callback=None): return self.register_event(fig, event, callback or self.onkey)
    def onpick(self, event):
        label = event.artist.get_text() if hasattr(event.artist, "get_text") else event.artist.get_label()
        print(f"Data [{label}] is clicked")
    def ondraw(self, event): pass
    def onkey(self, event): pass

    def prepare_annotation(self): self._annotation_controller.prepare(); self.annotation = self._annotation_controller.state
    def register_annotation_event(self, fig, **kwargs): return self._annotation_controller.register(fig, **kwargs)
    def prepare_move_points(self): self.move_points = self._move_points_controller.state
    def register_move_points_event(self, fig): return self._move_points_controller.register(fig)
    def prepare_move_text(self, fig=None):
        self._move_text_controller.state.fig = fig; self.move_text = self._move_text_controller.state
    def register_move_text_event(self, fig=None, activate=False): return self._move_text_controller.register(fig, activate)
    def register_follow_mouse_event(self, fig, activate=True, on_mouse_move=None):
        return self._crosshair_controller.register(fig, activate, on_mouse_move)
    def prepare_popup_menu(self, fig, parent=None):
        self.popup_menu = self._popup_controller.prepare(fig, parent); return self.popup_menu
    def register_popup_menu_event(self, on_click=None): return self._popup_controller.register(on_click)

    def button_click(self, event): self.stop_flag = True
    def add_button(self, button_region=(0.15, 0.95, 0.10, 0.03), plot_region=(0.92, 0.15),
                   text="stop", color="#f8e58c", hovercolor="#38b48b", callback=None):
        self.plot_region = plot_region
        self.button = self._button_controller.add_button(button_region, text, color, hovercolor, callback or self.button_click)
        return self.button
    def finalize_button(self, text="finished"):
        if hasattr(self, "button"): self.button.label.set_text(text)
    def add_stop_button(self, **kwargs):
        self.stop_button = self._button_controller.add_stop_button(**kwargs); return self.stop_button
    def layout(self, show=False):
        region = getattr(self, "plot_region", (0.92, 0.15))
        self.plt.subplots_adjust(top=region[0], bottom=region[1])
        if show: self.plt.show()
    def disconnect_all(self): self._events.disconnect_all()
