"""Minimal stub widgets for debugging widget-freeze issues.

FORMIKO_STUB_EDITOR values:
  1 or "tv"   – plain GtkTextView (no GtkSource at all)         [freeze gone]
  "gsv"       – GtkSource.View + GtkSource.Buffer, minimal config
                (no language/syntax, no gutter, no spell, no CSS)
  "gsv_lang"  – GtkSource.View + GtkSource.Buffer WITH syntax language
                (adds syntax highlighting, tests GtkSource.Buffer validation)
  "gsv_gutter"– like "gsv_lang" but also enables line-number gutter
                (adds GtkSourceGutter child widgets)
  "gsv_full"  – GtkSource.View with all real SourceView features EXCEPT
                spell checker and CSS font (closest to real minus those)

FORMIKO_STUB_RENDERER values:
  1 – Gtk.DrawingArea (label) instead of WebKitWebView

Both can be set at the same time.

Binary-search intent:
  gsv        → no freeze? → GtkSource.Buffer validation is the cause
  gsv_lang   → freeze?    → syntax/language highlighting is the cause
  gsv_gutter → freeze?    → gutter child widgets are the cause
"""

from os.path import basename

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("GtkSource", "5")
from gi.repository import GLib, GObject, Gtk, GtkSource


# ---------------------------------------------------------------------------
# Shared mixin: common interface that DocumentPage expects from any editor.
# ---------------------------------------------------------------------------

class _StubEditorMixin(Gtk.ScrolledWindow):
    """Base for all stub editors: shared interface for DocumentPage."""

    __gsignals__ = {
        "file-type": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
        "scroll-changed": (GObject.SignalFlags.RUN_LAST, None, (float,)),
    }

    def _init_common(self, inner_view, text_buffer):
        self.__file_path = ""
        self.__changes = 0
        self.source_view = inner_view
        self.text_buffer = text_buffer
        self.text_buffer.connect("changed", self._on_buf_changed)
        adj = self.get_vadjustment()
        adj.connect("value-changed", self._on_scroll)

    @property
    def changes(self):
        return self.__changes

    @property
    def text(self):
        start, end = self.text_buffer.get_bounds()
        return self.text_buffer.get_text(start, end, False)

    @property
    def file_path(self):
        return self.__file_path

    @property
    def file_name(self):
        return basename(self.__file_path) if self.__file_path else ""

    @property
    def is_modified(self):
        return self.text_buffer.get_modified()

    @property
    def position(self):
        adj = self.get_vadjustment()
        upper = adj.get_upper() - adj.get_page_size()
        return adj.get_value() / upper if upper > 0 else 0.0

    def read_from_file(self, file_name):
        self.__file_path = file_name
        try:
            with open(file_name, encoding="utf-8", errors="replace") as fh:
                content = fh.read()
        except OSError:
            content = ""
        self.text_buffer.set_text(content)
        self.text_buffer.set_modified(False)
        self.__changes += 1
        ext = file_name.rsplit(".", 1)[-1] if "." in file_name else ""
        self.emit("file-type", ext)

    # no-op stubs for methods called by EditorActionGroup/DocumentPage
    def inc_changes(self, *_): self.__changes += 1  # noqa: E704
    def set_list_features_enabled(self, _e): pass  # noqa: E704
    def change_mime_type(self, _p): pass  # noqa: E704
    def set_period_save(self, _v): pass  # noqa: E704
    def set_check_spelling(self, *_): pass  # noqa: E704
    def set_auto_bullet(self, _v): pass  # noqa: E704
    def set_tab_indent_bullet(self, _v): pass  # noqa: E704
    def set_spaces_instead_of_tabs(self, _v): pass  # noqa: E704
    def set_text_wrapping(self, _v): pass  # noqa: E704
    def set_white_chars(self, _v): pass  # noqa: E704

    def _on_buf_changed(self, _buf):
        self.__changes += 1

    def _on_scroll(self, adj):
        upper = adj.get_upper() - adj.get_page_size()
        self.emit("scroll-changed", adj.get_value() / upper if upper > 0 else 0.0)


# ---------------------------------------------------------------------------
# Stub variant: plain GtkTextView  (FORMIKO_STUB_EDITOR=1 or "tv")
# ---------------------------------------------------------------------------

class StubEditor(_StubEditorMixin):
    """GtkScrolledWindow + plain GtkTextView. No GtkSource at all."""

    def __init__(self, _win, _preferences, _action_name=None):
        super().__init__(hexpand=True, vexpand=True)
        buf = Gtk.TextBuffer()
        view = Gtk.TextView(buffer=buf, hexpand=True, vexpand=True)
        self.set_child(view)
        self._init_common(view, buf)


# ---------------------------------------------------------------------------
# Stub variant: GtkSource.View + bare GtkSource.Buffer (no lang, no gutter)
#   FORMIKO_STUB_EDITOR=gsv
# Tests: does GtkSource.View itself (with empty Source buffer) freeze?
# ---------------------------------------------------------------------------

class StubEditorGSV(_StubEditorMixin):
    """GtkScrolledWindow + GtkSource.View, minimal config, NO language/gutter."""

    def __init__(self, _win, _preferences, _action_name=None):
        super().__init__(hexpand=True, vexpand=True)
        GtkSource.init()
        buf = GtkSource.Buffer()
        view = GtkSource.View.new_with_buffer(buf)
        view.set_hexpand(True)
        view.set_vexpand(True)
        self.set_child(view)
        self._init_common(view, buf)


# ---------------------------------------------------------------------------
# Stub variant: GtkSource.View + GtkSource.Buffer WITH syntax language
#   FORMIKO_STUB_EDITOR=gsv_lang
# Tests: does syntax highlighting (language / GtkSourceEngine) freeze?
# ---------------------------------------------------------------------------

class StubEditorGSVLang(_StubEditorMixin):
    """GtkSource.View + GtkSource.Buffer with RST syntax language set."""

    def __init__(self, _win, preferences, _action_name=None):
        super().__init__(hexpand=True, vexpand=True)
        GtkSource.init()
        lm = GtkSource.LanguageManager.get_default()
        lang = lm.get_language("restructuredtext") or lm.get_language("markdown")
        buf = GtkSource.Buffer.new_with_language(lang) if lang else GtkSource.Buffer()
        view = GtkSource.View.new_with_buffer(buf)
        view.set_hexpand(True)
        view.set_vexpand(True)
        self.set_child(view)
        self._init_common(view, buf)


# ---------------------------------------------------------------------------
# Stub variant: gsv_lang + line-number gutter enabled
#   FORMIKO_STUB_EDITOR=gsv_gutter
# Tests: do the GtkSourceGutter child widgets cause the freeze?
# ---------------------------------------------------------------------------

class StubEditorGSVGutter(_StubEditorMixin):
    """GtkSource.View + language + show_line_numbers gutter enabled."""

    def __init__(self, _win, preferences, _action_name=None):
        super().__init__(hexpand=True, vexpand=True)
        GtkSource.init()
        lm = GtkSource.LanguageManager.get_default()
        lang = lm.get_language("restructuredtext") or lm.get_language("markdown")
        buf = GtkSource.Buffer.new_with_language(lang) if lang else GtkSource.Buffer()
        view = GtkSource.View.new_with_buffer(buf)
        view.set_hexpand(True)
        view.set_vexpand(True)
        view.set_show_line_numbers(True)
        self.set_child(view)
        self._init_common(view, buf)


# ---------------------------------------------------------------------------
# Stub variant: near-real SourceView but WITHOUT spell/CSS font setup
#   FORMIKO_STUB_EDITOR=gsv_full
# Tests: does spell checker / CssProvider setup matter?
# ---------------------------------------------------------------------------

class StubEditorGSVFull(_StubEditorMixin):
    """GtkSource.View with language, gutter, highlight — without spell/CSS."""

    def __init__(self, _win, preferences, _action_name=None):
        super().__init__(hexpand=True, vexpand=True)
        GtkSource.init()
        lm = GtkSource.LanguageManager.get_default()
        lang = lm.get_language("restructuredtext") or lm.get_language("markdown")
        buf = GtkSource.Buffer.new_with_language(lang) if lang else GtkSource.Buffer()
        view = GtkSource.View.new_with_buffer(buf)
        view.set_hexpand(True)
        view.set_vexpand(True)
        view.set_show_line_numbers(True)
        view.set_highlight_current_line(True)
        view.set_auto_indent(True)
        view.set_tab_width(4)
        self.set_child(view)
        self._init_common(view, buf)


# ---------------------------------------------------------------------------
# Granular variants — binary search which gsv_full setting prevents freeze
# ---------------------------------------------------------------------------

def _make_gsv_lang_buf():
    """Helper: GtkSource.Buffer with RST/markdown language."""
    GtkSource.init()
    lm = GtkSource.LanguageManager.get_default()
    lang = lm.get_language("restructuredtext") or lm.get_language("markdown")
    return GtkSource.Buffer.new_with_language(lang) if lang else GtkSource.Buffer()


class StubEditorGSVLangHCL(_StubEditorMixin):
    """gsv_lang + highlight_current_line=True.  FORMIKO_STUB_EDITOR=gsv_lang_hcl"""

    def __init__(self, _win, _preferences, _action_name=None):
        super().__init__(hexpand=True, vexpand=True)
        buf = _make_gsv_lang_buf()
        view = GtkSource.View.new_with_buffer(buf)
        view.set_hexpand(True)
        view.set_vexpand(True)
        view.set_highlight_current_line(True)
        self.set_child(view)
        self._init_common(view, buf)


class StubEditorGSVLangAI(_StubEditorMixin):
    """gsv_lang + auto_indent=True.  FORMIKO_STUB_EDITOR=gsv_lang_ai"""

    def __init__(self, _win, _preferences, _action_name=None):
        super().__init__(hexpand=True, vexpand=True)
        buf = _make_gsv_lang_buf()
        view = GtkSource.View.new_with_buffer(buf)
        view.set_hexpand(True)
        view.set_vexpand(True)
        view.set_auto_indent(True)
        self.set_child(view)
        self._init_common(view, buf)


class StubEditorGSVLangTW(_StubEditorMixin):
    """gsv_lang + tab_width=4.  FORMIKO_STUB_EDITOR=gsv_lang_tw"""

    def __init__(self, _win, _preferences, _action_name=None):
        super().__init__(hexpand=True, vexpand=True)
        buf = _make_gsv_lang_buf()
        view = GtkSource.View.new_with_buffer(buf)
        view.set_hexpand(True)
        view.set_vexpand(True)
        view.set_tab_width(4)
        self.set_child(view)
        self._init_common(view, buf)


class StubEditorGSVLangNoHL(_StubEditorMixin):
    """gsv_lang but highlight_syntax=False.  FORMIKO_STUB_EDITOR=gsv_lang_nohl"""

    def __init__(self, _win, _preferences, _action_name=None):
        super().__init__(hexpand=True, vexpand=True)
        buf = _make_gsv_lang_buf()
        buf.set_highlight_syntax(False)
        view = GtkSource.View.new_with_buffer(buf)
        view.set_hexpand(True)
        view.set_vexpand(True)
        self.set_child(view)
        self._init_common(view, buf)


# ---------------------------------------------------------------------------
# Order-of-operations tests: BEFORE vs AFTER set_child()
#
# Real SourceView does:  create view → set_child() → set properties
# Working stubs do:      create view → set properties → set_child()
#
# Test if the ORDER is the cause.
# ---------------------------------------------------------------------------

class StubEditorGSVLangAIAfter(_StubEditorMixin):
    """gsv_lang + auto_indent=True set AFTER set_child (like real SourceView).
    FORMIKO_STUB_EDITOR=gsv_lang_ai_after

    If this freezes by typing (unlike gsv_lang_ai which does not), ORDER matters.
    """

    def __init__(self, _win, _preferences, _action_name=None):
        super().__init__(hexpand=True, vexpand=True)
        buf = _make_gsv_lang_buf()
        view = GtkSource.View.new_with_buffer(buf)
        view.set_hexpand(True)
        view.set_vexpand(True)
        self.set_child(view)          # ← set_child FIRST (like real SourceView)
        view.set_auto_indent(True)    # ← property AFTER (like real SourceView)
        self._init_common(view, buf)


class StubEditorGSVLangCSS(_StubEditorMixin):
    """gsv_lang + CSS monospace font added BEFORE set_child (like real SourceView).
    FORMIKO_STUB_EDITOR=gsv_lang_css

    Tests whether the CssProvider with monospace font alone causes the freeze.
    Real SourceView adds CSS before set_child, then sets auto_indent after.
    """

    def __init__(self, _win, _preferences, _action_name=None):
        super().__init__(hexpand=True, vexpand=True)
        buf = _make_gsv_lang_buf()
        view = GtkSource.View.new_with_buffer(buf)
        view.set_hexpand(True)
        view.set_vexpand(True)
        # Add monospace font CSS (same as real SourceView does before set_child)
        css = Gtk.CssProvider()
        css.load_from_string("textview { font-family: monospace; font-size: 11pt; }")
        view.get_style_context().add_provider(
            css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
        )
        self.set_child(view)
        self._init_common(view, buf)


class StubEditorGSVRealOrder(_StubEditorMixin):
    """Mimics real SourceView init order: CSS before set_child, props after.
    FORMIKO_STUB_EDITOR=gsv_real_order

    If this freezes like the real app, init order is confirmed as the root cause.
    """

    def __init__(self, _win, _preferences, _action_name=None):
        super().__init__(hexpand=True, vexpand=True)
        buf = _make_gsv_lang_buf()
        view = GtkSource.View.new_with_buffer(buf)
        view.set_hexpand(True)
        view.set_vexpand(True)
        # CSS before set_child (real SourceView order)
        css = Gtk.CssProvider()
        css.load_from_string("textview { font-family: monospace; font-size: 11pt; }")
        view.get_style_context().add_provider(
            css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
        )
        self.set_child(view)          # ← set_child first
        # Properties after set_child (real SourceView order)
        view.set_tab_width(8)
        view.set_auto_indent(True)
        view.set_show_line_numbers(False)
        view.set_highlight_current_line(False)
        self._init_common(view, buf)


class StubEditorGSVRealOrderColor(_StubEditorMixin):
    """gsv_real_order + GtkSource.StyleScheme applied after set_child.
    FORMIKO_STUB_EDITOR=gsv_real_color

    Tests whether _apply_color_scheme() (text_buffer.set_style_scheme) after
    set_child re-introduces the typing freeze.  If this freezes, move the
    color scheme setup to before set_child in the real SourceView.
    """

    def __init__(self, _win, _preferences, _action_name=None):
        super().__init__(hexpand=True, vexpand=True)
        buf = _make_gsv_lang_buf()
        view = GtkSource.View.new_with_buffer(buf)
        view.set_hexpand(True)
        view.set_vexpand(True)
        css = Gtk.CssProvider()
        css.load_from_string("textview { font-family: monospace; font-size: 11pt; }")
        view.get_style_context().add_provider(
            css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
        )
        self.set_child(view)
        view.set_tab_width(8)
        view.set_auto_indent(True)
        view.set_show_line_numbers(False)
        view.set_highlight_current_line(False)
        # Apply color scheme AFTER set_child (same as real SourceView)
        manager = GtkSource.StyleSchemeManager.get_default()
        scheme = manager.get_scheme("Adwaita")
        if scheme:
            buf.set_style_scheme(scheme)
        self._init_common(view, buf)


class StubRenderer(Gtk.Overlay):
    """Minimal renderer using Gtk.DrawingArea instead of WebKitWebView."""

    def __init__(self, _win, parser="rst", writer="html4", style=""):
        super().__init__(hexpand=True, vexpand=True)

        self.src = None
        self.file_name = None
        self.pos = 0
        self.position = 0.0

        label = Gtk.Label(
            label="[stub renderer – WebKit disabled]",
            hexpand=True,
            vexpand=True,
        )
        self.set_child(label)

    def set_parser(self, _parser):
        pass

    def set_writer(self, _writer):
        pass

    def set_style(self, _style):
        pass

    def set_tab_width(self, _width):
        pass

    def render(self, _src, _file_name, _pos=0):
        pass

    def render_output(self):
        pass

    def scroll_to_position(self, _position):
        pass
