from __future__ import annotations

import tkinter as tk
from tkinter import ttk
from typing import Callable


def filter_matches_row(
    app: object,
    row: dict,
    filter_item: dict,
    *,
    date_fields: set[str],
    all_fields: list[str],
    field_value_getter: Callable[[dict, str], object] | None = None,
) -> bool:
    field = str(filter_item.get("field", "__all__") or "__all__")
    query = str(filter_item.get("query", "") or "").strip().lower()
    from_text = str(filter_item.get("from", "") or "").strip()
    to_text = str(filter_item.get("to", "") or "").strip()

    if field in date_fields and (from_text or to_text):
        getter = field_value_getter or (lambda item, key: item.get(key))
        return bool(app.is_within_date_range(getter(row, field), from_text, to_text))
    if not query:
        return True

    getter = field_value_getter or (lambda item, key: item.get(key))
    if field == "__all__":
        return query in " ".join(str(app.display_value(getter(row, key))) for key in all_fields).lower()
    return query in str(app.display_value(getter(row, field))).strip().lower()


def create_extra_filter_row(
    app: object,
    *,
    module_key: str,
    parent: ttk.Frame,
    options: list[tuple[str, str]],
    date_fields: set[str],
    row_index: int,
    remove_command: Callable[[dict], None],
    submit_command: Callable[[object | None], object],
    translate_filter_field: Callable[[str], str],
    option_label_getter: Callable[[str, str], str] | None = None,
) -> dict:
    label_for_option = option_label_getter or (lambda _key, text_key: app.t(text_key))
    field_var = tk.StringVar(value=translate_filter_field("__all__"))
    value_var = tk.StringVar()
    from_var = tk.StringVar()
    to_var = tk.StringVar()
    row_data: dict[str, object] = {
        "field_var": field_var,
        "value_var": value_var,
        "from_var": from_var,
        "to_var": to_var,
        "field_internal": "__all__",
        "committed_field_internal": "__all__",
        "committed_query": "",
        "committed_from": "",
        "committed_to": "",
    }

    field_label = ttk.Label(parent, text=app.t("samples_filter_field_label"))
    field_label.grid(row=row_index, column=0, sticky="w", padx=(0, 6), pady=(8, 0))
    field_combo = ttk.Combobox(
        parent,
        textvariable=field_var,
        values=[label_for_option(key, text_key) for key, text_key in options],
        state="readonly",
        width=22,
    )
    field_combo.grid(row=row_index, column=1, sticky="w", pady=(8, 0))
    value_label = ttk.Label(parent, text=app.t("samples_filter_value_label"))
    value_label.grid(row=row_index, column=2, sticky="w", padx=(12, 6), pady=(8, 0))
    value_entry = ttk.Entry(parent, textvariable=value_var, width=36)
    value_entry.grid(row=row_index, column=3, sticky="w", padx=(0, 12), pady=(8, 0))
    from_label = ttk.Label(parent, text=app.t("date_from_label"))
    from_label.grid(row=row_index, column=4, sticky="w", padx=(0, 6), pady=(8, 0))
    from_entry = ttk.Entry(parent, textvariable=from_var, width=12)
    from_entry.grid(row=row_index, column=5, sticky="w", padx=(0, 12), pady=(8, 0))
    app.bind_date_entry_picker(from_entry, from_var)
    to_label = ttk.Label(parent, text=app.t("date_to_label"))
    to_label.grid(row=row_index, column=6, sticky="w", padx=(0, 6), pady=(8, 0))
    to_entry = ttk.Entry(parent, textvariable=to_var, width=12)
    to_entry.grid(row=row_index, column=7, sticky="w", pady=(8, 0))
    app.bind_date_entry_picker(to_entry, to_var)
    remove_button = ttk.Button(parent, text=app.t("po_remove_filter"), command=lambda: remove_command(row_data))
    remove_button.grid(row=row_index, column=13, sticky="w", padx=(8, 0), pady=(8, 0))

    row_data.update(
        {
            "module_key": module_key,
            "field_label": field_label,
            "field_combo": field_combo,
            "value_label": value_label,
            "value_entry": value_entry,
            "from_label": from_label,
            "from_entry": from_entry,
            "to_label": to_label,
            "to_entry": to_entry,
            "remove_button": remove_button,
        }
    )

    def update_state() -> None:
        is_date_field = str(row_data.get("field_internal", "__all__") or "__all__") in date_fields
        value_entry.configure(state="disabled" if is_date_field else "normal")
        from_entry.configure(state="normal" if is_date_field else "disabled")
        to_entry.configure(state="normal" if is_date_field else "disabled")

    def on_field_changed(_event: object | None = None) -> None:
        selected = field_var.get()
        for internal, text_key in options:
            if selected == label_for_option(internal, text_key):
                row_data["field_internal"] = internal
                break
        update_state()

    field_combo.bind("<<ComboboxSelected>>", on_field_changed)
    for entry in (value_entry, from_entry, to_entry):
        entry.bind("<Return>", submit_command)
        entry.bind("<KP_Enter>", submit_command)
    update_state()
    value_entry.focus_set()
    return row_data


def destroy_filter_row(row_data: dict) -> None:
    for widget_key in (
        "field_label",
        "field_combo",
        "value_label",
        "value_entry",
        "from_label",
        "from_entry",
        "to_label",
        "to_entry",
        "remove_button",
    ):
        widget = row_data.get(widget_key)
        if widget is None:
            continue
        try:
            widget.destroy()
        except tk.TclError:
            pass


def regrid_filter_rows(rows: list[dict], *, start_row: int = 1) -> None:
    placements = {
        "field_label": {"column": 0, "padx": (0, 6)},
        "field_combo": {"column": 1, "padx": 0},
        "value_label": {"column": 2, "padx": (12, 6)},
        "value_entry": {"column": 3, "padx": (0, 12)},
        "from_label": {"column": 4, "padx": (0, 6)},
        "from_entry": {"column": 5, "padx": (0, 12)},
        "to_label": {"column": 6, "padx": (0, 6)},
        "to_entry": {"column": 7, "padx": 0},
        "remove_button": {"column": 13, "padx": (8, 0)},
    }
    for index, row_data in enumerate(rows):
        row_index = start_row + index
        for widget_key, options in placements.items():
            widget = row_data.get(widget_key)
            if widget is None:
                continue
            try:
                widget.grid_configure(row=row_index, column=options["column"], sticky="w", padx=options["padx"], pady=(8, 0))
            except tk.TclError:
                pass


def refresh_filter_row_translations(
    app: object,
    rows: list[dict],
    *,
    options: list[tuple[str, str]],
    translate_filter_field: Callable[[str], str],
    option_label_getter: Callable[[str, str], str] | None = None,
) -> None:
    label_for_option = option_label_getter or (lambda _key, text_key: app.t(text_key))
    for row_data in rows:
        field_label = row_data.get("field_label")
        if field_label is not None:
            field_label.configure(text=app.t("samples_filter_field_label"))
        field_combo = row_data.get("field_combo")
        if field_combo is not None:
            field_combo.configure(values=[label_for_option(key, text_key) for key, text_key in options])
        field_var = row_data.get("field_var")
        if field_var is not None:
            field_var.set(translate_filter_field(str(row_data.get("field_internal", "__all__") or "__all__")))
        value_label = row_data.get("value_label")
        if value_label is not None:
            value_label.configure(text=app.t("samples_filter_value_label"))
        from_label = row_data.get("from_label")
        if from_label is not None:
            from_label.configure(text=app.t("date_from_label"))
        to_label = row_data.get("to_label")
        if to_label is not None:
            to_label.configure(text=app.t("date_to_label"))
        remove_button = row_data.get("remove_button")
        if remove_button is not None:
            remove_button.configure(text=app.t("po_remove_filter"))
