"""
SnC: Mob Spawner Editor
A GUI tool for managing hostile mob spawning in Minecraft biome files.

Reads biome templates from the 'biome/' directory, presents checkboxes
for each hostile mob found in those biomes, and generates
modified biome JSON files with unchecked mobs removed from spawning.
"""

import json
import os
import sys
import tkinter as tk
from tkinter import ttk, messagebox
from pathlib import Path
import copy

# ─── Path config ────────────────────────────────────────────────────────
SCRIPT_DIR = Path(os.path.dirname(os.path.abspath(__file__)))
BIOMES_DIR = SCRIPT_DIR / "biome"

# Output goes to data/<namespace>/worldgen/biome relative to the datapack root
DATAPACK_ROOT = SCRIPT_DIR.parents[4]
OUTPUT_BASE = DATAPACK_ROOT / "data"

# ─── Color Palette ──────────────────────────────────────────────────────────────
BG_DARK = "#2d1e18"
BG_PANEL = "#0f0b08"
ACCENT = "#757575"
ACCENT_HOVER = "#979797"
TEXT_ACCENT = "#fcfc00"
TEXT_PRIMARY = "#ffffff"
TEXT_SECONDARY = "#7e7e7e"
SUCCESS = "#55FF55"

def discover_biome_sources():
    """
    Discover all namespace directories inside biome/.
    Returns a dict: { namespace: [list of .json file paths] }
    """
    sources = {}
    if not BIOMES_DIR.exists():
        return sources

    for namespace_dir in sorted(BIOMES_DIR.iterdir()):
        if namespace_dir.is_dir():
            namespace = namespace_dir.name
            json_files = sorted(namespace_dir.glob("*.json"))
            if json_files:
                sources[namespace] = json_files

    return sources

def extract_monster_types(biome_sources):
    """
    Scan all source biome files and return the set of mob types
    found in the 'monster' spawner category.
    """
    found = set()
    for namespace, files in biome_sources.items():
        for filepath in files:
            try:
                with open(filepath, "r", encoding="utf-8") as f:
                    data = json.load(f)
                monsters = data.get("spawners", {}).get("monster", [])
                for entry in monsters:
                    mob_type = entry.get("type", "")
                    if mob_type:
                        found.add(mob_type)
            except (json.JSONDecodeError, KeyError):
                pass
    return found

def generate_biomes(biome_sources, enabled_mobs, log_callback):
    """
    For each source biome, remove unchecked mobs from the 'monster'
    spawner list and write the result to the output directory.
    """
    total_files = 0
    total_removed = 0

    for namespace, files in biome_sources.items():
        output_dir = OUTPUT_BASE / namespace / "worldgen" / "biome"
        output_dir.mkdir(parents=True, exist_ok=True)

        for filepath in files:
            biome_name = filepath.stem
            try:
                with open(filepath, "r", encoding="utf-8") as f:
                    data = json.load(f)
            except (json.JSONDecodeError, IOError) as e:
                log_callback(f"  ⚠ Error reading {namespace}:{biome_name}: {e}")
                continue

            # Deep copy to avoid mutating cached data
            output_data = copy.deepcopy(data)

            # Filter the monster spawner list
            spawners = output_data.get("spawners", {})
            original_monsters = spawners.get("monster", [])
            filtered_monsters = [
                entry for entry in original_monsters
                if entry.get("type", "") in enabled_mobs
            ]

            removed_count = len(original_monsters) - len(filtered_monsters)
            total_removed += removed_count

            spawners["monster"] = filtered_monsters
            output_data["spawners"] = spawners

            # Write as compact JSON (Minecraft-style, single line)
            output_path = output_dir / f"{biome_name}.json"
            with open(output_path, "w", encoding="utf-8") as f:
                json.dump(output_data, f, separators=(",", ":"))

            total_files += 1

            if removed_count > 0:
                log_callback(f"  {namespace}:{biome_name} — removed {removed_count} mob(s)")
            else:
                log_callback(f"  {namespace}:{biome_name} — no changes")

    return total_files, total_removed

# ─── GUI Application ────────────────────────────────────────────────────────────
class MobSpawnerEditorApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Shingeki no Craft: Mob Spawner Editor")
        self.root.configure(bg=BG_DARK)
        self.root.resizable(True, True)
        self.root.minsize(520, 500)

        # Center the window
        self.root.update_idletasks()
        w, h = 560, 700
        x = (self.root.winfo_screenwidth() // 2) - (w // 2)
        y = (self.root.winfo_screenheight() // 2) - (h // 2)
        self.root.geometry(f"{w}x{h}+{x}+{y}")

        # Load data
        self.biome_sources = discover_biome_sources()
        self.mob_list = sorted(list(extract_monster_types(self.biome_sources)))

        # Track checkbox variables
        self.mob_vars = {}
        self.mob_rows = {}

        # Count biome and namespaces
        self.total_biomes = sum(len(f) for f in self.biome_sources.values())
        self.namespace_count = len(self.biome_sources)

        self._setup_styles()
        self._build_ui()

    def _setup_styles(self):
        """Configure ttk styles for the dark theme."""
        self.style = ttk.Style()
        self.style.theme_use("clam")

        self.style.configure("Dark.TFrame", background=BG_DARK)
        self.style.configure("Panel.TFrame", background=BG_PANEL)
        self.style.configure("Card.TFrame", background=ACCENT, relief="flat")

        self.style.configure(
            "Title.TLabel",
            background=BG_DARK,
            foreground=TEXT_ACCENT,
            font=("Consolas", 18, "bold")
        )
        self.style.configure(
            "Subtitle.TLabel",
            background=BG_DARK,
            foreground=TEXT_SECONDARY,
            font=("Consolas", 10)
        )
        self.style.configure(
            "Info.TLabel",
            background=BG_PANEL,
            foreground=TEXT_PRIMARY,
            font=("Consolas", 9)
        )
        self.style.configure(
            "Warning.TLabel",
            background=BG_DARK,
            foreground=TEXT_ACCENT,
            font=("Consolas", 9, "italic")
        )
        self.style.configure(
            "Status.TLabel",
            background=BG_DARK,
            foreground=TEXT_SECONDARY,
            font=("Consolas", 9)
        )

        # Checkbutton style
        self.style.configure(
            "Mob.TCheckbutton",
            background=BG_PANEL,
            foreground=TEXT_PRIMARY,
            font=("Consolas", 11),
            indicatorsize=16,
        )
        self.style.map(
            "Mob.TCheckbutton",
            background=[("active", ACCENT)],
            foreground=[("active", TEXT_ACCENT)],
        )

    def _build_ui(self):
        """Build the main user interface."""
        main_frame = ttk.Frame(self.root, style="Dark.TFrame")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=16, pady=12)

        # ── Header ──────────────────────────────────────────────────────────
        ttk.Label(
            main_frame, text="Shingeki no Craft: Mob Spawner Editor",
            style="Title.TLabel"
        ).pack(anchor=tk.W, pady=(0, 2))

        ttk.Label(
            main_frame,
            text=f"Managing {self.total_biomes} biome across {self.namespace_count} namespace(s)",
            style="Subtitle.TLabel"
        ).pack(anchor=tk.W, pady=(0, 8))

        # ── Instruction ─────────────────────────────────────────────────────
        instruction_frame = ttk.Frame(main_frame, style="Panel.TFrame")
        instruction_frame.pack(fill=tk.X, pady=(0, 8))

        ttk.Label(
            instruction_frame,
            text="Check the mobs you want to KEEP spawning naturally.",
            style="Info.TLabel"
        ).pack(anchor=tk.W, padx=8, pady=(6, 6))

        ttk.Label(
            instruction_frame,
            text="Unchecked mobs will be REMOVED from all biome files.",
            style="Info.TLabel"
        ).pack(anchor=tk.W, padx=8, pady=(0, 6))

        # ── Select All / Deselect All / Search ───────────────────────────────
        btn_row = ttk.Frame(main_frame, style="Dark.TFrame")
        btn_row.pack(fill=tk.X, pady=(0, 6))

        self.select_all_btn = tk.Button(
            btn_row, text="Select All", command=self._select_all,
            bg=ACCENT, fg=TEXT_PRIMARY, activebackground=ACCENT,
            activeforeground="white", relief="flat", cursor="hand2",
            font=("Consolas", 9, "bold"), padx=12, pady=3
        )
        self.select_all_btn.pack(side=tk.LEFT, padx=(0, 6))

        self.deselect_all_btn = tk.Button(
            btn_row, text="Deselect All", command=self._deselect_all,
            bg=ACCENT, fg=TEXT_PRIMARY, activebackground=ACCENT,
            activeforeground="white", relief="flat", cursor="hand2",
            font=("Consolas", 9, "bold"), padx=12, pady=3
        )
        self.deselect_all_btn.pack(side=tk.LEFT)

        # Search Bar
        self.search_var = tk.StringVar()
        self.search_var.trace_add("write", self._on_search)
        
        search_frame = ttk.Frame(btn_row, style="Panel.TFrame")
        search_frame.pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=(12, 0))
        
        ttk.Label(
            search_frame, text="🔍 Search:", style="Info.TLabel"
        ).pack(side=tk.LEFT, padx=(8, 4), pady=4)
        
        self.search_entry = tk.Entry(
            search_frame, textvariable=self.search_var,
            bg=BG_DARK, fg=TEXT_PRIMARY, insertbackground=TEXT_PRIMARY,
            relief="flat", font=("Consolas", 10)
        )
        self.search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 8), pady=4)

        # ── Mob Checklist (scrollable) ──────────────────────────────────────
        list_container = ttk.Frame(main_frame, style="Panel.TFrame")
        list_container.pack(fill=tk.BOTH, expand=True, pady=(0, 8))

        canvas = tk.Canvas(
            list_container, bg=BG_PANEL, highlightthickness=0,
            borderwidth=0
        )
        scrollbar = ttk.Scrollbar(
            list_container, orient=tk.VERTICAL, command=canvas.yview
        )
        self.checklist_frame = ttk.Frame(canvas, style="Panel.TFrame")

        self.checklist_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )

        canvas.create_window((0, 0), window=self.checklist_frame, anchor=tk.NW)
        canvas.configure(yscrollcommand=scrollbar.set)

        canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=4, pady=4)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # Enable mousewheel scrolling
        def on_mousewheel(event):
            canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")

        canvas.bind_all("<MouseWheel>", on_mousewheel)

        # Populate checkboxes
        self._populate_mob_checkboxes()

        # ── Warning label ───────────────────────────────────────────────────
        ttk.Label(
            main_frame,
            text="⚠  Non-selected mobs will be removed from natural spawning.",
            style="Warning.TLabel"
        ).pack(anchor=tk.W, pady=(0, 8))

        # ── Generate Button ─────────────────────────────────────────────────
        self.generate_btn = tk.Button(
            main_frame,
            text="Generate!",
            command=self._on_generate,
            bg=ACCENT,
            fg="white",
            activebackground=ACCENT_HOVER,
            activeforeground="white",
            relief="flat",
            cursor="hand2",
            font=("Consolas", 13, "bold"),
            padx=20,
            pady=10,
        )
        self.generate_btn.pack(fill=tk.X, pady=(0, 8))

        # ── Log Output ──────────────────────────────────────────────────────
        log_frame = ttk.Frame(main_frame, style="Panel.TFrame")
        log_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 4))

        self.log_text = tk.Text(
            log_frame,
            bg="#0d1117",
            fg="#8b949e",
            insertbackground=TEXT_PRIMARY,
            font=("Consolas", 9),
            relief="flat",
            height=6,
            state=tk.DISABLED,
            wrap=tk.WORD,
            borderwidth=0,
            padx=8,
            pady=6,
        )
        log_scrollbar = ttk.Scrollbar(
            log_frame, orient=tk.VERTICAL, command=self.log_text.yview
        )
        self.log_text.configure(yscrollcommand=log_scrollbar.set)

        self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # ── Status Bar ──────────────────────────────────────────────────────
        self.status_label = ttk.Label(
            main_frame,
            text='You can close this window after the "Done!" message.',
            style="Status.TLabel"
        )
        self.status_label.pack(anchor=tk.W, pady=(4, 0))

    def _populate_mob_checkboxes(self):
        """Create a checkbox for each mob in the mob list."""
        for mob_id in self.mob_list:
            var = tk.BooleanVar(value=True)
            self.mob_vars[mob_id] = var

            row = ttk.Frame(self.checklist_frame, style="Panel.TFrame")
            row.pack(fill=tk.X, padx=4, pady=1)
            self.mob_rows[mob_id] = row

            cb = ttk.Checkbutton(
                row,
                text=f"  {mob_id}",
                variable=var,
                style="Mob.TCheckbutton",
            )
            cb.pack(side=tk.LEFT, padx=(4, 0), pady=2)

    def _select_all(self):
        for var in self.mob_vars.values():
            var.set(True)

    def _deselect_all(self):
        for var in self.mob_vars.values():
            var.set(False)

    def _on_search(self, *args):
        """Filter the mob list based on search input."""
        query = self.search_var.get().lower().strip()
        
        # Unpack all to maintain order when repacking
        for row in self.mob_rows.values():
            row.pack_forget()
            
        # Repack only matching in original order
        for mob_id in self.mob_list:
            if query in mob_id.lower():
                self.mob_rows[mob_id].pack(fill=tk.X, padx=4, pady=1)

    def _log(self, message):
        """Append a message to the log text widget."""
        self.log_text.configure(state=tk.NORMAL)
        self.log_text.insert(tk.END, message + "\n")
        self.log_text.see(tk.END)
        self.log_text.configure(state=tk.DISABLED)
        self.root.update_idletasks()

    def _clear_log(self):
        """Clear the log text widget."""
        self.log_text.configure(state=tk.NORMAL)
        self.log_text.delete("1.0", tk.END)
        self.log_text.configure(state=tk.DISABLED)

    def _on_generate(self):
        """Handle the Generate button click."""
        self.generate_btn.configure(state=tk.DISABLED, text="Generating...")
        self._clear_log()

        # Build the set of enabled mob IDs
        enabled_mobs = {
            mob_id for mob_id, var in self.mob_vars.items() if var.get()
        }

        disabled_mobs = {
            mob_id for mob_id, var in self.mob_vars.items() if not var.get()
        }

        self._log("═══════════════════════════════════════════")
        self._log("  SnC: Hostile Mob Spawner Generator")
        self._log("═══════════════════════════════════════════")
        self._log("")

        if disabled_mobs:
            self._log(f"Removing {len(disabled_mobs)} mob type(s):")
            for mob in sorted(disabled_mobs):
                self._log(f"  ✗ {mob}")
            self._log("")
        else:
            self._log("All mobs are enabled — no mobs will be removed.")
            self._log("")

        self._log(f"Processing {self.total_biomes} biome file(s)...")
        self._log("")

        try:
            total_files, total_removed = generate_biomes(
                self.biome_sources, enabled_mobs, self._log
            )

            self._log("")
            self._log("───────────────────────────────────────────")
            self._log(f"  ✔ Done! Generated {total_files} file(s).")
            self._log(f"  ✔ Removed {total_removed} mob spawner entries total.")
            self._log("───────────────────────────────────────────")

            self.status_label.configure(
                text="✔ Done! You can close this window.",
                foreground=SUCCESS,
            )

        except Exception as e:
            self._log(f"\n  ✗ ERROR: {e}")
            self.status_label.configure(
                text="✗ An error occurred. Check the log above.",
                foreground=ACCENT,
            )

        self.generate_btn.configure(state=tk.NORMAL, text="Generate!")

# ─── Entry Point ────────────────────────────────────────────────────────────────
def main():
    root = tk.Tk()

    # Set the window icon if available
    try:
        root.iconbitmap(default="")
    except tk.TclError:
        pass

    app = MobSpawnerEditorApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()
