"""
textfsmgen.ui.common
====================

Utility functions for constructing and managing Tkinter UI components
in the TextFSMGen GUI application.
"""


from typing import Any, Optional, Tuple, Dict
from typing import Union

import webbrowser

from tkinter import ttk
import tkinter as tk

import textfsmgen.ui as ui
import textfsmgen.config as config


class Position:
    """Simple counter that tracks a numeric position."""

    def __init__(self, value: int = 0):
        self.value = value

    def increment(self) -> int:
        """Increase the position by one and return the new value."""
        self.value += 1
        return self.value


class RewriteSync:
    """Track and validate whether user text and rewritten text are in sync."""

    def __init__(self, app=None):
        self.initial_input = ""
        self.initial_output = ""
        self.is_application_app = is_application_app(app)
        if self.is_application_app:
            self.initial_input = extract_text(app.self.textarea.input)
            self.initial_output = extract_text(app.self.textarea.output)

    def __bool__(self):
        return self.is_application_app

    def __len__(self):
        return 1 if self.is_application_app else 0

    def is_synced(self, app) -> bool:
        """Return True if both input and output match the stored originals."""

        if not is_application_app(app) or not self:
            return False

        current_in = extract_text(app.self.textarea.input)
        current_out = extract_text(app.self.textarea.output)

        return (
            current_in.strip()
            and current_out.strip()
            and current_in == self.initial_input
            and current_out == self.initial_output
        )

    def is_outdated(self, app) -> bool:
        """Return True if input changed but output still matches the old rewrite."""
        if not is_application_app(app) or not self:
            return False

        current_in = extract_text(app.self.textarea.input)
        current_out = extract_text(app.self.textarea.output)

        return (
            current_in.strip()
            and current_in != self.initial_input
            and current_out == self.initial_output
        )

    def is_unrewritten(self, app) -> bool:
        """Return True if user input exists but no rewritten text is present."""
        if not is_application_app(app) or not self:
            return False

        current_in = extract_text(app.self.textarea.input).strip()
        current_out = extract_text(app.self.textarea.output).strip()
        return bool(current_in) and not current_out

    def is_input_empty(self, app) -> bool:
        """Return True if user input is empty."""
        if not is_application_app(app) or not self:
            return False
        return len(extract_text(app.self.textarea.input).strip()) == 0


def is_application_app(app):
    return type(app).__name__ == "Application"


def get_center_coordinates(
        parent: tk.Tk, child_width: int, child_height: int
) -> Tuple[int, int]:
    """Calculate coordinates to center a child window within its parent."""
    geometry = parent.winfo_geometry()  # format: "WxH+X+Y"
    size, x_str, y_str = geometry.split("+")
    parent_x, parent_y = int(x_str), int(y_str)
    parent_w, parent_h = map(int, size.split("x"))

    x = parent_x + (parent_w - child_width) // 2
    y = parent_y + (parent_h - child_height) // 2
    return x, y


def center_window(
    parent: Union[tk.Tk, tk.Toplevel],
    window: Union[tk.Tk, tk.Toplevel],
    width: int,
    height: int,
    x_resizable: bool = False,
    y_resizable: bool = False
) -> None:
    """Center a Tkinter window relative to its parent."""
    x, y = get_center_coordinates(parent, width, height)
    window.geometry(f"{width}x{height}+{x}+{y}")
    window.resizable(x_resizable, y_resizable)


def make_modal(dialog: Union[tk.Toplevel, tk.Tk]) -> None:
    """Configure a Tkinter window to behave as a modal dialog."""
    parent = dialog.master if dialog.master else None
    if dialog.master:
        dialog.transient(parent)

    dialog.wait_visibility()
    dialog.grab_set()
    dialog.wait_window()


def show_message_dialog(
    title: Optional[str] = None,
    error: Optional[str] = None,
    warning: Optional[str] = None,
    info: Optional[str] = None,
    question: Optional[str] = None,
    okcancel: Optional[str] = None,
    retrycancel: Optional[str] = None,
    yesno: Optional[str] = None,
    yesnocancel: Optional[str] = None,
    **options
) -> Any:
    """Display a tkinter message dialog based on the provided message type."""
    mapping = {
        error: ui.messagebox.showerror,
        warning: ui.messagebox.showwarning,
        info: ui.messagebox.showinfo,
        question: ui.messagebox.askquestion,
        okcancel: ui.messagebox.askokcancel,
        retrycancel: ui.messagebox.askretrycancel,
        yesno: ui.messagebox.askyesno,
        yesnocancel: ui.messagebox.askyesnocancel,
    }

    for message, func in mapping.items():
        if message:
            return func(title=title, message=message, **options)

    # Default fallback: show info dialog
    return ui.messagebox.showinfo(title=title, message=info or "", **options)

@ui.apply_layout
def create_styled_label(
    parent: tk.Widget,
    text: str = "",
    link: str = "",
    options: dict = None,
    increased_size: int = 0,
    bold: bool = False,
    underline: bool = False,
    italic: bool = False,
    layout: Optional[Tuple[str, Dict[str, Any]]] = None     # noqa
) -> ttk.Label:
    """Create a styled Tkinter label with optional hyperlink behavior."""

    def mouse_over(event):
        if "underline" not in event.widget.font:
            event.widget.configure(
                font=event.widget.font + ["underline"],
                cursor="hand2"
            )

    def mouse_out(event):
        event.widget.config(font=event.widget.font, cursor="arrow")

    def mouse_press(event):
        webbrowser.open_new_tab(event.widget.link)

    options = options if isinstance(options, dict) else {}

    if link:
        style = ttk.Style()
        style.configure("Blue.TLabel", foreground="blue")
        label = ui.Label(parent, text=text, style="Blue.TLabel", **options)
        label.bind("<Enter>", mouse_over)
        label.bind("<Leave>", mouse_out)
        label.bind("<Button-1>", mouse_press)
    else:
        label = ui.Label(parent, text=text, **options)

    font = ui.Font(name="TkDefaultFont", exists=True, root=label)
    font_spec = [font.cget("family"), font.cget("size") + increased_size]
    if bold:
        font_spec.append("bold")
    if underline:
        font_spec.append("underline")
    if italic:
        font_spec.append("italic")

    label.configure(font=font_spec)
    label.font = font_spec
    label.link = link

    return label


def open_app_resource(resource: str) -> None:
    """Open a specified application resource in a web browser."""
    resources = {
        "license_text": config.license_url,
        "documentation": config.documentation_url,
    }
    url = resources.get(resource)
    if url:
        webbrowser.open_new_tab(url)


def extract_text(widget) -> str:
    """Return textarea content without the trailing newline added by Tkinter."""
    if not isinstance(widget, ui.TextArea):
        return ""
    text = widget.get("1.0", "end")
    last_two = text[-2:]
    return text[:-2] if last_two == "\r\n" else text[:-1]


def clear_text(widget) -> None:
    """Clear all text from a Tkinter Text widget while preserving its state."""
    if not isinstance(widget, ui.TextArea):
        return
    original_state = widget["state"]
    widget.configure(state=tk.NORMAL)
    widget.delete("1.0", "end")
    widget.configure(state=original_state)


def set_text(widget, text: str) -> None:
    """Replace all text in a Tkinter Text widget while preserving its state."""
    if not isinstance(widget, ui.TextArea):
        return

    original_state = widget["state"]
    widget.configure(state=tk.NORMAL)

    widget.delete("1.0", "end")
    widget.insert("1.0", text)

    widget.configure(state=original_state)
