import tkinter as tk
from collections.abc import Callable
from datetime import date
from tkinter import messagebox, ttk

from ccma.domain.dates import age_label, date_input_hint
from ccma.domain.models import Asset, Member
from ccma.storage.repository import MemberRepository, RepositoryError


def _activate_modal_window(window: tk.Toplevel, focus_widget: tk.Widget | None = None) -> None:
    try:
        window.update_idletasks()
        window.deiconify()
        window.lift()
        window.focus_force()
        if focus_widget is not None and focus_widget.winfo_exists():
            focus_widget.focus_set()
        window.grab_set()
        window.attributes("-topmost", True)
        window.after_idle(lambda: window.winfo_exists() and window.attributes("-topmost", False))
    except tk.TclError:
        return


class NewMemberDialog(tk.Toplevel):
    def __init__(self, master: tk.Misc, repository: MemberRepository, on_created: Callable[[Member], None]):
        super().__init__(master)
        self.repository = repository
        self.on_created = on_created
        self.title("Neue Mitgliederakte")
        self.transient(master.winfo_toplevel())
        self.resizable(False, False)
        self.number_policy = repository.get_member_number_policy()
        self.variables = {
            name: tk.StringVar()
            for name in (
                "first_name",
                "last_name",
                "nickname",
                "email",
                "phone",
                "birth_date",
                "application_date",
                "member_number",
            )
        }
        self._build_ui()
        self.bind("<Escape>", lambda _event: self.destroy())
        self.bind("<Return>", lambda _event: self._create())
        self.after_idle(self._activate_modal)
        self.after_idle(self._focus_first)

    def _activate_modal(self) -> None:
        _activate_modal_window(self, self.entries.get("first_name"))

    def _build_ui(self) -> None:
        frame = ttk.Frame(self, padding=18)
        frame.pack(fill="both", expand=True)
        fields = [
            ("Vorname *", "first_name"),
            ("Nachname *", "last_name"),
            ("Nickname", "nickname"),
            ("E-Mail-Adresse", "email"),
            ("Telefonnummer", "phone"),
            (f"Geburtsdatum ({date_input_hint()})", "birth_date"),
            (f"Antragsdatum ({date_input_hint()})", "application_date"),
        ]
        if self.number_policy["mode"] == "manual":
            fields.append(("Mitgliedsnummer *", "member_number"))
        self.entries: dict[str, ttk.Entry] = {}
        for row, (label, key) in enumerate(fields):
            ttk.Label(frame, text=label).grid(row=row, column=0, sticky="w", pady=5, padx=(0, 12))
            if key == "birth_date":
                birth_row = ttk.Frame(frame)
                birth_row.grid(row=row, column=1, sticky="ew", pady=5)
                birth_row.columnconfigure(0, weight=1)
                entry = ttk.Entry(birth_row, textvariable=self.variables[key], width=24)
                entry.grid(row=0, column=0, sticky="ew")
                self.birth_age_var = tk.StringVar(value="Alter: —")
                ttk.Label(birth_row, textvariable=self.birth_age_var, style="Mono.TLabel").grid(
                    row=0, column=1, sticky="w", padx=(10, 0)
                )
                self.variables[key].trace_add(
                    "write",
                    lambda *_args: self.birth_age_var.set(age_label(self.variables["birth_date"].get())),
                )
            else:
                entry = ttk.Entry(frame, textvariable=self.variables[key], width=38)
                entry.grid(row=row, column=1, sticky="ew", pady=5)
            self.entries[key] = entry
        button_row = len(fields)
        if self.number_policy["mode"] == "automatic":
            preview = self.repository.preview_member_number(self.number_policy["pattern"])
            ttk.Label(frame, text="Mitgliedsnummer").grid(
                row=button_row, column=0, sticky="w", pady=5, padx=(0, 12)
            )
            ttk.Label(frame, text=f"Automatisch: {preview}", style="TimelineHeader.TLabel").grid(
                row=button_row, column=1, sticky="w", pady=5
            )
            button_row += 1
        buttons = ttk.Frame(frame)
        buttons.grid(row=button_row, column=0, columnspan=2, sticky="e", pady=(16, 0))
        ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left", padx=(0, 8))
        ttk.Button(buttons, text="Akte anlegen", style="Accent.TButton", command=self._create).pack(
            side="left"
        )

    def _focus_first(self) -> None:
        self.entries["first_name"].focus_set()

    def _create(self) -> None:
        try:
            member = self.repository.create_member(
                **{key: variable.get() for key, variable in self.variables.items()}
            )
        except RepositoryError as exc:
            messagebox.showerror("Akte konnte nicht angelegt werden", str(exc), parent=self)
            return
        self.destroy()
        self.on_created(member)


class NewAssetDialog(tk.Toplevel):
    def __init__(self, master: tk.Misc, repository: MemberRepository, on_created: Callable[[Asset], None]):
        super().__init__(master)
        self.repository = repository
        self.on_created = on_created
        self.members = repository.list_members()
        self.member_labels = {
            f"{member.member_number or member.member_id} · {member.display_name}": member.member_id
            for member in self.members
        }
        self.title("Neues Asset")
        self.transient(master.winfo_toplevel())
        self.resizable(False, False)
        self.variables = {
            name: tk.StringVar()
            for name in (
                "label",
                "category",
                "inventory_number",
                "serial_number",
                "deposit_amount_default",
                "location",
                "handed_over_at",
                "condition",
                "estimated_value",
                "owner_name",
            )
        }
        self.owner_type_var = tk.StringVar(value="VEREIN")
        self.owner_member_var = tk.StringVar()
        self._build_ui()
        self.bind("<Escape>", lambda _event: self.destroy())
        self.bind("<Return>", lambda _event: self._create())
        self.after_idle(self._activate_modal)

    def _build_ui(self) -> None:
        frame = ttk.Frame(self, padding=18)
        frame.pack(fill="both", expand=True)
        fields = [
            ("Bezeichnung *", "label"),
            ("Kategorie", "category"),
            ("Inventarnummer", "inventory_number"),
            ("Seriennummer", "serial_number"),
            ("Kaution (EUR)", "deposit_amount_default"),
            ("Standort", "location"),
            ("Übergabedatum", "handed_over_at"),
            ("Zustand bei Übergabe", "condition"),
            ("Wert / Kaufpreis", "estimated_value"),
        ]
        self.entries: dict[str, ttk.Entry] = {}
        for row, (label, key) in enumerate(fields):
            ttk.Label(frame, text=label).grid(row=row, column=0, sticky="w", pady=5, padx=(0, 12))
            entry = ttk.Entry(frame, textvariable=self.variables[key], width=38)
            entry.grid(row=row, column=1, sticky="ew", pady=5)
            self.entries[key] = entry
        owner_row = len(fields)
        ttk.Label(frame, text="Eigentümer").grid(row=owner_row, column=0, sticky="w", pady=5, padx=(0, 12))
        ttk.Combobox(
            frame,
            textvariable=self.owner_type_var,
            values=("VEREIN", "MITGLIED", "EXTERN"),
            state="readonly",
            width=35,
        ).grid(row=owner_row, column=1, sticky="ew", pady=5)
        ttk.Label(frame, text="Eigentümer-Mitglied").grid(
            row=owner_row + 1, column=0, sticky="w", pady=5, padx=(0, 12)
        )
        ttk.Combobox(
            frame,
            textvariable=self.owner_member_var,
            values=list(self.member_labels),
            state="readonly",
            width=35,
        ).grid(row=owner_row + 1, column=1, sticky="ew", pady=5)
        ttk.Label(frame, text="Externer Eigentümer").grid(
            row=owner_row + 2, column=0, sticky="w", pady=5, padx=(0, 12)
        )
        ttk.Entry(frame, textvariable=self.variables["owner_name"], width=38).grid(
            row=owner_row + 2, column=1, sticky="ew", pady=5
        )
        ttk.Label(frame, text="Interne Notiz").grid(
            row=owner_row + 3,
            column=0,
            sticky="nw",
            pady=5,
            padx=(0, 12),
        )
        self.notes_text = tk.Text(frame, width=38, height=5, wrap="word")
        self.notes_text.grid(row=owner_row + 3, column=1, sticky="ew", pady=5)
        buttons = ttk.Frame(frame)
        buttons.grid(row=owner_row + 4, column=0, columnspan=2, sticky="e", pady=(16, 0))
        ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left", padx=(0, 8))
        ttk.Button(
            buttons,
            text="Asset anlegen",
            style="Accent.TButton",
            command=self._create,
        ).pack(side="left")
        self.after_idle(lambda: self.entries["label"].focus_set())

    def _activate_modal(self) -> None:
        _activate_modal_window(self, self.entries.get("label"))

    def _create(self) -> None:
        owner_type = {"VEREIN": "club", "MITGLIED": "member", "EXTERN": "external"}[self.owner_type_var.get()]
        try:
            asset = self.repository.create_asset(
                **{key: variable.get() for key, variable in self.variables.items() if key != "owner_name"},
                owner_type=owner_type,
                owner_member_id=(
                    self.member_labels.get(self.owner_member_var.get(), "") if owner_type == "member" else ""
                ),
                owner_name=self.variables["owner_name"].get() if owner_type == "external" else "",
                notes=self.notes_text.get("1.0", "end-1c"),
            )
        except RepositoryError as exc:
            messagebox.showerror("Asset konnte nicht angelegt werden", str(exc), parent=self)
            return
        self.destroy()
        self.on_created(asset)


class EditAssetDialog(tk.Toplevel):
    def __init__(
        self,
        master: tk.Misc,
        repository: MemberRepository,
        asset_id: str,
        on_saved: Callable[[Asset], None],
    ):
        super().__init__(master)
        self.repository = repository
        self.asset_id = asset_id
        self.on_saved = on_saved
        self.asset = repository.get_asset(asset_id)
        self.members = repository.list_members()
        self.member_labels = {
            f"{member.member_number or member.member_id} · {member.display_name}": member.member_id
            for member in self.members
        }
        owner_member_label = next(
            (
                label
                for label, member_id in self.member_labels.items()
                if member_id == self.asset.owner_member_id
            ),
            "",
        )
        self.title("Asset bearbeiten")
        self.transient(master.winfo_toplevel())
        self.resizable(False, False)
        self.variables = {
            "label": tk.StringVar(value=self.asset.label),
            "category": tk.StringVar(value=self.asset.category),
            "inventory_number": tk.StringVar(value=self.asset.inventory_number),
            "serial_number": tk.StringVar(value=self.asset.serial_number),
            "deposit_amount_default": tk.StringVar(value=self.asset.deposit_amount_default),
            "status": tk.StringVar(value=self.asset.status),
            "location": tk.StringVar(value=self.asset.location),
            "handed_over_at": tk.StringVar(value=self.asset.handed_over_at),
            "condition": tk.StringVar(value=self.asset.condition),
            "estimated_value": tk.StringVar(value=self.asset.estimated_value),
            "owner_name": tk.StringVar(value=self.asset.owner_name),
        }
        self.owner_type_var = tk.StringVar(
            value={"club": "VEREIN", "member": "MITGLIED", "external": "EXTERN"}.get(
                self.asset.owner_type, "VEREIN"
            )
        )
        self.owner_member_var = tk.StringVar(value=owner_member_label)
        self._build_ui()
        self.bind("<Escape>", lambda _event: self.destroy())
        self.bind("<Return>", lambda _event: self._save())
        self.after_idle(self._activate_modal)

    def _build_ui(self) -> None:
        frame = ttk.Frame(self, padding=18)
        frame.pack(fill="both", expand=True)
        issued = bool(self.asset.current_holder_member_id)
        fields = [
            ("Bezeichnung *", "label"),
            ("Kategorie", "category"),
            ("Inventarnummer", "inventory_number"),
            ("Seriennummer", "serial_number"),
            ("Kaution (EUR)", "deposit_amount_default"),
            ("Standort", "location"),
            ("Übergabedatum", "handed_over_at"),
            ("Zustand bei Übergabe", "condition"),
            ("Wert / Kaufpreis", "estimated_value"),
        ]
        self.entries: dict[str, ttk.Entry] = {}
        for row, (label, key) in enumerate(fields):
            ttk.Label(frame, text=label).grid(row=row, column=0, sticky="w", pady=5, padx=(0, 12))
            state = "readonly" if key == "deposit_amount_default" and issued else "normal"
            entry = ttk.Entry(frame, textvariable=self.variables[key], width=38, state=state)
            entry.grid(row=row, column=1, sticky="ew", pady=5)
            self.entries[key] = entry
        owner_row = len(fields)
        ttk.Label(frame, text="Eigentümer").grid(row=owner_row, column=0, sticky="w", pady=5, padx=(0, 12))
        ttk.Combobox(
            frame,
            textvariable=self.owner_type_var,
            values=("VEREIN", "MITGLIED", "EXTERN"),
            state="readonly",
            width=35,
        ).grid(row=owner_row, column=1, sticky="ew", pady=5)
        ttk.Label(frame, text="Eigentümer-Mitglied").grid(
            row=owner_row + 1, column=0, sticky="w", pady=5, padx=(0, 12)
        )
        ttk.Combobox(
            frame,
            textvariable=self.owner_member_var,
            values=list(self.member_labels),
            state="readonly",
            width=35,
        ).grid(row=owner_row + 1, column=1, sticky="ew", pady=5)
        ttk.Label(frame, text="Externer Eigentümer").grid(
            row=owner_row + 2, column=0, sticky="w", pady=5, padx=(0, 12)
        )
        ttk.Entry(frame, textvariable=self.variables["owner_name"], width=38).grid(
            row=owner_row + 2, column=1, sticky="ew", pady=5
        )
        ttk.Label(frame, text="Status").grid(row=owner_row + 3, column=0, sticky="w", pady=5, padx=(0, 12))
        status_values = [
            value
            for key, value in (
                ("available", "VERFUEGBAR"),
                ("lost", "VERLOREN"),
                ("retired", "AUSGEMUSTERT"),
            )
        ]
        self.status_map = {
            "VERFUEGBAR": "available",
            "VERLOREN": "lost",
            "AUSGEMUSTERT": "retired",
        }
        self.status_var = tk.StringVar(
            value={
                "available": "VERFUEGBAR",
                "lost": "VERLOREN",
                "retired": "AUSGEMUSTERT",
            }.get(self.asset.status, "VERFUEGBAR")
        )
        self.status_box = ttk.Combobox(
            frame,
            textvariable=self.status_var,
            values=status_values,
            state="readonly" if not issued else "disabled",
            width=35,
        )
        self.status_box.grid(row=owner_row + 3, column=1, sticky="ew", pady=5)
        note_row = owner_row + 4
        ttk.Label(frame, text="Interne Notiz").grid(row=note_row, column=0, sticky="nw", pady=5, padx=(0, 12))
        self.notes_text = tk.Text(frame, width=38, height=5, wrap="word")
        self.notes_text.grid(row=note_row, column=1, sticky="ew", pady=5)
        self.notes_text.insert("1.0", self.asset.notes)
        info_row = note_row + 1
        info_text = (
            "Kaution kann nur geändert werden, wenn das Asset nicht ausgegeben ist."
            if issued
            else "Status kann hier auf verfuegbar, verloren oder ausgemustert gesetzt werden."
        )
        ttk.Label(frame, text=info_text, style="Mono.TLabel").grid(
            row=info_row, column=0, columnspan=2, sticky="w", pady=(4, 0)
        )
        buttons = ttk.Frame(frame)
        buttons.grid(row=info_row + 1, column=0, columnspan=2, sticky="e", pady=(16, 0))
        ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left", padx=(0, 8))
        ttk.Button(buttons, text="Speichern", style="Accent.TButton", command=self._save).pack(side="left")
        self.after_idle(lambda: self.entries["label"].focus_set())

    def _activate_modal(self) -> None:
        _activate_modal_window(self, self.entries.get("label"))

    def _save(self) -> None:
        self.asset.label = self.variables["label"].get()
        self.asset.category = self.variables["category"].get()
        self.asset.inventory_number = self.variables["inventory_number"].get()
        self.asset.serial_number = self.variables["serial_number"].get()
        self.asset.notes = self.notes_text.get("1.0", "end-1c")
        self.asset.location = self.variables["location"].get()
        self.asset.handed_over_at = self.variables["handed_over_at"].get()
        self.asset.condition = self.variables["condition"].get()
        self.asset.estimated_value = self.variables["estimated_value"].get()
        self.asset.owner_type = {"VEREIN": "club", "MITGLIED": "member", "EXTERN": "external"}[
            self.owner_type_var.get()
        ]
        self.asset.owner_member_id = (
            self.member_labels.get(self.owner_member_var.get(), "")
            if self.asset.owner_type == "member"
            else ""
        )
        self.asset.owner_name = (
            self.variables["owner_name"].get() if self.asset.owner_type == "external" else ""
        )
        if not self.asset.current_holder_member_id:
            self.asset.deposit_amount_default = self.variables["deposit_amount_default"].get()
            self.asset.status = self.status_map.get(self.status_var.get(), self.asset.status)
        try:
            self.repository.save_asset(self.asset)
        except RepositoryError as exc:
            messagebox.showerror("Asset konnte nicht gespeichert werden", str(exc), parent=self)
            return
        self.destroy()
        self.on_saved(self.asset)


class IssueAssetDialog(tk.Toplevel):
    def __init__(
        self,
        master: tk.Misc,
        repository: MemberRepository,
        asset_id: str,
        on_assigned: Callable[[Asset], None],
        *,
        preselected_member_id: str = "",
    ):
        super().__init__(master)
        self.repository = repository
        self.asset_id = asset_id
        self.on_assigned = on_assigned
        self.preselected_member_id = preselected_member_id
        self.title("Asset ausgeben")
        self.transient(master.winfo_toplevel())
        self.resizable(True, True)
        self.search_var = tk.StringVar()
        self.members = self.repository.list_members()
        self._build_ui()
        self.bind("<Escape>", lambda _event: self.destroy())
        self.bind("<Return>", lambda _event: self._assign())
        self.after_idle(self._activate_modal)

    def _build_ui(self) -> None:
        frame = ttk.Frame(self, padding=18)
        frame.pack(fill="both", expand=True)
        frame.columnconfigure(1, weight=1)
        frame.rowconfigure(3, weight=1)
        asset = self.repository.get_asset(self.asset_id)
        ttk.Label(frame, text="Asset").grid(row=0, column=0, sticky="w", pady=5, padx=(0, 12))
        ttk.Label(frame, text=asset.label, style="TimelineHeader.TLabel").grid(
            row=0,
            column=1,
            sticky="w",
            pady=5,
        )
        ttk.Label(frame, text="Suche").grid(row=1, column=0, sticky="w", pady=5, padx=(0, 12))
        self.search_entry = ttk.Entry(frame, textvariable=self.search_var, width=42)
        self.search_entry.grid(row=1, column=1, sticky="ew", pady=5)
        self.search_var.trace_add("write", lambda *_args: self._render_members())
        ttk.Label(frame, text="Mitglied").grid(row=2, column=0, sticky="nw", pady=5, padx=(0, 12))
        self.member_tree = ttk.Treeview(
            frame,
            columns=("number", "name", "email"),
            show="headings",
            height=10,
        )
        for key, title, width in (
            ("number", "Nummer", 120),
            ("name", "Mitglied", 220),
            ("email", "E-Mail", 220),
        ):
            self.member_tree.heading(key, text=title)
            self.member_tree.column(key, width=width, anchor="w")
        self.member_tree.grid(row=3, column=0, columnspan=2, sticky="nsew", pady=5)
        self.member_tree.bind("<Double-1>", lambda _event: self._assign())
        self.member_tree.bind("<Return>", lambda _event: self._assign())
        self.member_tree.bind("<<TreeviewSelect>>", lambda _event: self._sync_selected_member_label())
        self.selected_member_var = tk.StringVar(value="Kein Mitglied ausgewählt.")
        ttk.Label(frame, textvariable=self.selected_member_var, style="Mono.TLabel").grid(
            row=4, column=0, columnspan=2, sticky="w", pady=(4, 0)
        )
        buttons = ttk.Frame(frame)
        buttons.grid(row=5, column=0, columnspan=2, sticky="e", pady=(16, 0))
        ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left", padx=(0, 8))
        ttk.Button(buttons, text="Ausgeben", style="Accent.TButton", command=self._assign).pack(side="left")
        self._render_members()
        self.after_idle(self.search_entry.focus_set)

    def _activate_modal(self) -> None:
        _activate_modal_window(self, self.search_entry)

    def _assign(self) -> None:
        selected = self.member_tree.selection()
        member_id = selected[0] if selected else ""
        if not member_id:
            messagebox.showerror("Ausgabe fehlgeschlagen", "Bitte ein Mitglied auswählen.", parent=self)
            return
        try:
            asset = self.repository.assign_asset(self.asset_id, member_id)
        except RepositoryError as exc:
            messagebox.showerror("Ausgabe fehlgeschlagen", str(exc), parent=self)
            return
        self.destroy()
        self.on_assigned(asset)

    def _render_members(self) -> None:
        self.member_tree.delete(*self.member_tree.get_children())
        query = self.search_var.get().strip().casefold()
        filtered = [
            member for member in self.members if not query or query in self._member_search_text(member)
        ]
        for member in filtered:
            self.member_tree.insert(
                "",
                "end",
                iid=member.member_id,
                values=(member.member_number, member.display_name, member.email),
            )
        selected_id = self.preselected_member_id if self.preselected_member_id else ""
        if filtered:
            target_id = (
                selected_id
                if selected_id and any(member.member_id == selected_id for member in filtered)
                else filtered[0].member_id
            )
            self.member_tree.selection_set(target_id)
            self.member_tree.focus(target_id)
            self.member_tree.see(target_id)
        self._sync_selected_member_label()

    def _sync_selected_member_label(self) -> None:
        selected = self.member_tree.selection()
        if not selected:
            self.selected_member_var.set("Kein Mitglied ausgewählt.")
            return
        member = next((item for item in self.members if item.member_id == selected[0]), None)
        if member is None:
            self.selected_member_var.set("Kein Mitglied ausgewählt.")
            return
        self.selected_member_var.set(f"Ausgewählt: {self._member_label(member)}")

    @staticmethod
    def _member_search_text(member: Member) -> str:
        return " ".join(
            value.casefold()
            for value in (
                member.member_number,
                member.first_name,
                member.last_name,
                member.nickname,
                member.display_name,
                member.email,
            )
            if value
        )

    @staticmethod
    def _member_label(member: Member) -> str:
        prefix = member.member_number or member.member_id
        name = member.display_name or member.member_id
        return f"{prefix} · {name}"


class AssetClaimDialog(tk.Toplevel):
    def __init__(
        self,
        master: tk.Misc,
        repository: MemberRepository,
        asset_id: str,
        member_id: str,
        *,
        preset_title: str,
        preset_amount: str,
        preset_description: str,
        claim_type: str,
        on_created: Callable[[str], None],
    ):
        super().__init__(master)
        self.repository = repository
        self.asset_id = asset_id
        self.member_id = member_id
        self.claim_type = claim_type
        self.on_created = on_created
        self.title("Forderung aus Asset anlegen")
        self.transient(master.winfo_toplevel())
        self.resizable(False, False)
        self.variables = {
            "title": tk.StringVar(value=preset_title),
            "amount": tk.StringVar(value=preset_amount),
            "due_date": tk.StringVar(value=date.today().isoformat()),
        }
        self.preset_description = preset_description
        self._build_ui()
        self.bind("<Escape>", lambda _event: self.destroy())
        self.bind("<Return>", lambda _event: self._create())
        self.after_idle(self._activate_modal)

    def _build_ui(self) -> None:
        frame = ttk.Frame(self, padding=18)
        frame.pack(fill="both", expand=True)
        asset = self.repository.get_asset(self.asset_id)
        member = self.repository.get_member(self.member_id)
        ttk.Label(frame, text="Asset").grid(row=0, column=0, sticky="w", pady=5, padx=(0, 12))
        ttk.Label(frame, text=asset.label, style="TimelineHeader.TLabel").grid(
            row=0,
            column=1,
            sticky="w",
            pady=5,
        )
        ttk.Label(frame, text="Mitglied").grid(row=1, column=0, sticky="w", pady=5, padx=(0, 12))
        ttk.Label(frame, text=f"{member.member_number or member.member_id} · {member.display_name}").grid(
            row=1, column=1, sticky="w", pady=5
        )
        row_offset = 2
        for index, (label, key) in enumerate(
            (
                ("Titel *", "title"),
                ("Betrag (EUR) *", "amount"),
                (f"Fällig am ({date_input_hint()}) *", "due_date"),
            )
        ):
            ttk.Label(frame, text=label).grid(
                row=row_offset + index,
                column=0,
                sticky="w",
                pady=5,
                padx=(0, 12),
            )
            ttk.Entry(frame, textvariable=self.variables[key], width=42).grid(
                row=row_offset + index, column=1, sticky="ew", pady=5
            )
        ttk.Label(frame, text="Beschreibung").grid(
            row=row_offset + 3,
            column=0,
            sticky="nw",
            pady=5,
            padx=(0, 12),
        )
        self.description_text = tk.Text(frame, width=42, height=5, wrap="word")
        self.description_text.grid(row=row_offset + 3, column=1, sticky="ew", pady=5)
        self.description_text.insert("1.0", self.preset_description)
        info = (
            "Negative Betraege werden als Gutschrift dokumentiert. "
            "Die Auszahlung selbst wird im aktuellen CCMA-Stand nicht als eigener Workflow modelliert."
        )
        ttk.Label(frame, text=info, style="Mono.TLabel").grid(
            row=row_offset + 4, column=0, columnspan=2, sticky="w", pady=(4, 0)
        )
        buttons = ttk.Frame(frame)
        buttons.grid(row=row_offset + 5, column=0, columnspan=2, sticky="e", pady=(16, 0))
        ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left", padx=(0, 8))
        ttk.Button(
            buttons,
            text="Forderung anlegen",
            style="Accent.TButton",
            command=self._create,
        ).pack(side="left")
        self.after_idle(lambda: frame.focus_set())

    def _activate_modal(self) -> None:
        _activate_modal_window(self)

    def _create(self) -> None:
        try:
            result = self.repository.create_manual_claim(
                self.member_id,
                title=self.variables["title"].get(),
                amount=self.variables["amount"].get(),
                due_date=self.variables["due_date"].get(),
                description=self.description_text.get("1.0", "end-1c"),
                claim_type=self.claim_type,
                references={"asset_id": self.asset_id},
            )
        except RepositoryError as exc:
            messagebox.showerror("Forderung konnte nicht angelegt werden", str(exc), parent=self)
            return
        self.destroy()
        self.on_created(str(result["claim"]["claim_id"]))


class IntegrityWarningDialog(tk.Toplevel):
    def __init__(
        self,
        master: tk.Misc,
        *,
        title: str,
        warnings: list[str],
        on_confirm: Callable[[], None],
    ):
        super().__init__(master)
        self.on_confirm = on_confirm
        self.title(title)
        self.transient(master.winfo_toplevel())
        self.resizable(False, False)
        self.warnings = warnings
        self._build_ui()
        self.bind("<Escape>", lambda _event: self.destroy())
        self.after_idle(self._activate_modal)

    def _build_ui(self) -> None:
        frame = ttk.Frame(self, padding=18)
        frame.pack(fill="both", expand=True)
        message = (
            "ACHTUNG: Die zugehörigen JSON-Dateien wurden vermutlich extern geändert.\n\n"
            "Haben Sie alle Daten geprüft und soll der Hash jetzt aktualisiert werden?"
        )
        ttk.Label(frame, text=message, justify="left").grid(row=0, column=0, sticky="w")
        ttk.Label(
            frame,
            text="\n".join(f"• {item}" for item in self.warnings),
            style="Warning.TLabel",
            justify="left",
        ).grid(row=1, column=0, sticky="w", pady=(12, 0))
        buttons = ttk.Frame(frame)
        buttons.grid(row=2, column=0, sticky="e", pady=(18, 0))
        ttk.Button(buttons, text="Nein", command=self.destroy).pack(side="left", padx=(0, 8))
        ttk.Button(
            buttons,
            text="Ja, bestätigen",
            style="Accent.TButton",
            command=self._confirm,
        ).pack(side="left")

    def _activate_modal(self) -> None:
        _activate_modal_window(self)

    def _confirm(self) -> None:
        self.destroy()
        self.on_confirm()
