"""
Test conftest to provide headless-safe tkinter/ttk stubs so GUI tests can run in CI
without a real Tcl/Tk installation. This file is intentionally minimal: it implements
only the methods and attributes used by the project test-suite.
"""
from types import SimpleNamespace
import builtins

import tkinter as tk
from tkinter import ttk


class DummyVar:
    def __init__(self, value=None):
        self._value = value

    def get(self):
        return self._value

    def set(self, v):
        self._value = v


class DummyWidget:
    def __init__(self, master=None, **kwargs):
        self.master = master
        self.children = {}
        self._config = dict(kwargs)
    # Option database placeholder used by some GUI code (kept as method _options)
        # expose a tk interpreter object for tkinter internals
        try:
            self.tk = getattr(master, "tk")
        except Exception:
            # Provide a tiny interpreter facade used by some widgets/PIL backends
            class DummyInterp:
                def __init__(self):
                    self._commands = {}

                def createcommand(self, name, func):
                    self._commands[name] = func
                    return name

                def deletecommand(self, name):
                    self._commands.pop(name, None)

                def call(self, *args, **kwargs):
                    return None

            self.tk = DummyInterp()
        # default identifiers used by tkinter internals
        self._last_child_ids = {}
        parent_w = getattr(master, "_w", ".")
        self._w = parent_w + f".{id(self)}"

    # geometry managers
    def pack(self, *a, **k):
        return None

    def grid(self, *a, **k):
        return None

    def place(self, *a, **k):
        return None

    def pack_forget(self):
        return None

    def pack_propagate(self, flag=None):
        # emulate tkinter.Frame.pack_propagate; tests only set False to disable
        # geometry propagation. We store it for introspection if needed.
        if not hasattr(self, '_pack_propagate'):
            self._pack_propagate = True
        if flag is not None:
            self._pack_propagate = bool(flag)
        return None

    def config(self, **kwargs):
        self._config.update(kwargs)

    # tkinter widgets often expose 'configure' as an alias
    def configure(self, **kwargs):
        return self.config(**kwargs)

    def bind(self, *a, **k):
        return None

    def destroy(self):
        return None

    def grab_set(self):
        return None

    def grab_release(self):
        return None

    def after(self, ms, func=None, *args):
        # return a fake id
        return f"after-{id(self)}-{ms}"

    def after_idle(self, func, *args):
        # matplotlib backend expects after_idle to schedule an idle call.
        # For headless tests call immediately and return an id-like token.
        try:
            func(*args)
        except Exception:
            # ignore exceptions raised by idle work in tests
            pass
        return f"after-idle-{id(self)}"

    def after_cancel(self, id_):
        return None

    def see(self, *a, **k):
        return None

    def focus(self, *a, **k):
        return None

    def focus_set(self):
        return None

    def selection_set(self, *a, **k):
        return None

    def __setitem__(self, key, value):
        # allow widget[key] = child assignment used by some windows
        self.children[key] = value

    def __getitem__(self, key):
        return self.children.get(key)

    def selection(self):
        return []

    # window-like helpers expected on Toplevel/Tk
    def title(self, *a, **k):
        return None

    def geometry(self, *a, **k):
        return None

    def transient(self, master):
        return None

    def resizable(self, a, b):
        return None

    def protocol(self, *a, **k):
        return None

    def minsize(self, *a, **k):
        return None

    def rowconfigure(self, index, **kwargs):
        if not hasattr(self, "_row_config"):
            self._row_config = {}
        self._row_config[index] = kwargs
        return None

    def columnconfigure(self, index, **kwargs):
        if not hasattr(self, "_col_config"):
            self._col_config = {}
        self._col_config[index] = kwargs
        return None

    # aliases some code expects
    def grid_rowconfigure(self, index, **kwargs):
        return self.rowconfigure(index, **kwargs)

    def grid_columnconfigure(self, index, **kwargs):
        return self.columnconfigure(index, **kwargs)

    def set(self, *a, **k):
        # scrollbar.set compatibility
        return None

    def create_image(self, *a, **k):
        return f"img-{id(self)}"

    def create_rectangle(self, *a, **k):
        return f"rect-{id(self)}"

    def create_line(self, *a, **k):
        return f"line-{id(self)}"

    def create_oval(self, *a, **k):
        return f"oval-{id(self)}"

    def delete(self, *a, **k):
        # generic delete for canvas or widgets
        return None

    def itemconfig(self, *a, **k):
        return None

    def move(self, *a, **k):
        return None

    def create_text(self, *a, **k):
        return f"text-{id(self)}"

    def create_window(self, *a, **k):
        return f"window-{id(self)}"

    def cget(self, key):
        return self._config.get(key)

    def winfo_exists(self):
        return True

    def winfo_width(self):
        return getattr(self, '_width', 100)

    def winfo_height(self):
        return getattr(self, '_height', 100)

    def winfo_children(self):
        return list(self.children.values())

    def pack_info(self):
        return {}

    def grid_info(self):
        return {}

    def winfo_toplevel(self):
        # walk up to the top-level
        node = self
        while getattr(node, "master", None) is not None:
            node = node.master
        return node

    def winfo_rootx(self):
        return 0

    def winfo_rooty(self):
        return 0

    def winfo_x(self):
        return 0

    def winfo_y(self):
        return 0

    def wait_window(self, widget=None):
        # non-blocking stub for tests
        return None

    def _options(self, options: dict):
        """Convert an options dict into the flattened list expected by
        tk interpreter calls (e.g. ['-title', 'MyTitle', '-message', '...']).
        Tests only need that this is callable and returns a sequence.
        """
        res = []
        for k, v in (options or {}).items():
            # tkinter option names are prefixed with '-' in the interpreter
            res.append(f"-{k}")
            res.append(v)
        return res

    # Minimal text buffer API used by scrolledtext.ScrolledText in the GUI
    def insert(self, index, text, tag=None):
        if not hasattr(self, '_text'):
            self._text = ''
        # simple append semantics for tests
        self._text = (self._text or '') + str(text)

    def delete(self, start, end=None):
        # clear buffer entirely for simplicity
        self._text = ''

    def get(self, start, end=None):
        return getattr(self, '_text', '')

    def tag_config(self, *a, **k):
        return None

    def index(self, idx):
        # return a dummy index
        return '1.0'


class DummyTreeview(DummyWidget):
    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)
        self._items = {}

    def delete(self, *items):
        for it in items:
            if it in self._items:
                del self._items[it]

    def insert(self, parent, pos, iid=None, values=None):
        if iid is None:
            # generate a deterministic id
            iid = f"item-{len(self._items)+1}"
        self._items[iid] = values
        return iid

    def get_children(self):
        return list(self._items.keys())

    def heading(self, *a, **k):
        return None

    def column(self, *a, **k):
        return None

    def configure(self, **kwargs):
        # accept yscroll or other widget bindings
        self._config.update(kwargs)
        return None

    def yview(self, *a, **k):
        return None

    def yview(self, *a, **k):
        return None

    def item(self, iid, **kwargs):
        # emulate getting/setting an item
        if kwargs:
            # set values if provided
            if 'values' in kwargs:
                self._items[iid] = kwargs['values']
            return None
        return {'values': self._items.get(iid)}

    def set(self, item, column=None, value=None):
        # behave like setting a cell value
        if item in self._items:
            vals = list(self._items[item]) if self._items[item] is not None else []
            # try to set by index
            try:
                idx = int(column)
                # expand if needed
                while len(vals) <= idx:
                    vals.append(None)
                vals[idx] = value
                self._items[item] = tuple(vals)
            except Exception:
                # fallback: ignore
                pass

    def selection(self):
        # return empty selection by default
        return []


class DummyTk(DummyWidget):
    def __init__(self, *a, **k):
        super().__init__(None)
        # tk attribute used by tkinter internals: provide a fake interpreter
        class _Interp:
            def __init__(self):
                self._commands = {}

            def createcommand(self, name, func):
                self._commands[name] = func
                return name

            def deletecommand(self, name):
                self._commands.pop(name, None)

            def call(self, *args, **kwargs):
                return None

        self.tk = _Interp()
        self._last_child_ids = {}
        self._w = "."

    def after(self, ms, func=None, *args):
        # For logger tests which schedule via after
        return f"after-{ms}"

    def title(self, *a, **k):
        return None

    def geometry(self, *a, **k):
        return None

    def protocol(self, *a, **k):
        return None

    def withdraw(self):
        return None

    def wait_window(self, widget=None):
        return None

    def focus_set(self):
        return None

    def winfo_x(self):
        return 0
        self._w = "."
        # emulate option database expected by some GUI code
        self._options = {}
    def winfo_y(self):
        return 0


# Apply the lightweight stubs to tkinter and ttk used names
tk.Tk = DummyTk
tk.Toplevel = DummyWidget
tk.StringVar = DummyVar
tk.BooleanVar = DummyVar
tk.DoubleVar = DummyVar
tk.IntVar = DummyVar

ttk.Frame = DummyWidget
ttk.LabelFrame = DummyWidget
ttk.Scrollbar = DummyWidget
ttk.Button = DummyWidget
ttk.Label = DummyWidget
ttk.Scale = DummyWidget
ttk.Combobox = DummyWidget
ttk.Checkbutton = DummyWidget
ttk.Treeview = DummyTreeview

# Replace tk.Canvas as matplotlib backend sometimes creates it
tk.Canvas = DummyWidget

# Ensure scrolledtext.ScrolledText uses our DummyWidget so get/insert/delete work
try:
    import tkinter.scrolledtext as scrolledtext
    scrolledtext.ScrolledText = DummyWidget
except Exception:
    pass


class DummyMenu(DummyWidget):
    def add_cascade(self, label=None, menu=None, **kwargs):
        return None

    def add_command(self, label=None, command=None, **kwargs):
        return None

    def add_separator(self):
        return None


tk.Menu = DummyMenu

# Add set to scrollbar widget
def _scrollbar_set(*a, **k):
    return None

ttk.Scrollbar.set = staticmethod(lambda *a, **k: None)

# Some modules import names at top-level; also set them on builtins to be safe
builtins._DUMMY_TK_INSTALLED_FOR_TESTS = True


# PhotoImage stub used by some GUI code
class DummyPhotoImage:
    def __init__(self, *a, **k):
        self._size = k.get('size', (1, 1))

        # Provide a .tk attribute similar to real PhotoImage which holds the tk interpreter
        try:
            self.tk = getattr(tk, '_default_root').tk
        except Exception:
            self.tk = SimpleNamespace(call=lambda *a, **k: None)

    def subsample(self, *a, **k):
        return self

    def zoom(self, *a, **k):
        return self


tk.PhotoImage = DummyPhotoImage


# Minimal ttk.Style stub
class DummyStyle:
    def __init__(self):
        self._styles = {}

    def configure(self, style_name, **kwargs):
        self._styles[style_name] = kwargs

    def lookup(self, style_name, option, default=None):
        return self._styles.get(style_name, {}).get(option, default)


ttk.Style = DummyStyle

# If PIL.ImageTk is available, ensure its PhotoImage is replaced by our dummy to avoid
# AttributeError when Pillow's PhotoImage expects a real tk interpreter in headless tests.
try:
    from PIL import ImageTk

    ImageTk.PhotoImage = DummyPhotoImage
except Exception:
    pass


# provide a default root used by some libraries
_DEFAULT_ROOT = DummyTk()
tk._default_root = _DEFAULT_ROOT

# ensure Scrollbar is available on tk and ttk
tk.Scrollbar = DummyWidget
ttk.Scrollbar = DummyWidget

# add update stubs used by some widgets/backends
def _noop_update(self=None):
    return None

DummyWidget.update = _noop_update
DummyWidget.update_idletasks = _noop_update
DummyTk.update = _noop_update
DummyTk.update_idletasks = _noop_update
