import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from pathlib import Path
import json
from PIL import Image
from PIL.PngImagePlugin import PngInfo

# Application metadata
APP_VERSION = "1.0.0"
APP_NAME = "PNG Metadata Editor"

class PNGMetadataEditor:
    def __init__(self, root):
        self.root = root
        self.root.title(f"{APP_NAME} - No file loaded")
        self.root.geometry("900x700")

        self.current_file = None
        self.metadata_dict = {}
        self.has_unsaved_changes = False
        self.status_timer = None

        self.setup_ui()

        # Bring window to foreground on startup
        self.root.lift()
        self.root.attributes('-topmost', True)
        self.root.after_idle(self.root.attributes, '-topmost', False)
        self.root.focus_force()

    def setup_ui(self):
        # Top frame for file operations
        top_frame = ttk.Frame(self.root, padding="10")
        top_frame.pack(fill=tk.X)

        ttk.Button(top_frame, text="Open PNG File", command=self.open_file).pack(side=tk.LEFT, padx=5)
        ttk.Button(top_frame, text="Save Changes", command=self.save_changes).pack(side=tk.LEFT, padx=5)
        ttk.Button(top_frame, text="About", command=self.show_about).pack(side=tk.RIGHT, padx=5)  # New About button

        self.file_label = ttk.Label(top_frame, text="No file loaded", foreground="gray")
        self.file_label.pack(side=tk.LEFT, padx=20)

        # Unsaved changes indicator
        self.changes_label = ttk.Label(top_frame, text="", foreground="orange")
        self.changes_label.pack(side=tk.LEFT, padx=10)

        # PanedWindow for splitting tree and detail view
        paned = ttk.PanedWindow(self.root, orient=tk.VERTICAL)
        paned.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        # Top pane - Treeview
        tree_frame = ttk.Frame(paned)
        paned.add(tree_frame, weight=2)

        # Treeview with scrollbar
        tree_scroll = ttk.Scrollbar(tree_frame)
        tree_scroll.pack(side=tk.RIGHT, fill=tk.Y)

        self.tree = ttk.Treeview(tree_frame, columns=("Key", "Value"), show="headings",
                                  yscrollcommand=tree_scroll.set)
        self.tree.heading("Key", text="Key")
        self.tree.heading("Value", text="Value Preview")
        self.tree.column("Key", width=200)
        self.tree.column("Value", width=600)
        self.tree.pack(fill=tk.BOTH, expand=True)

        tree_scroll.config(command=self.tree.yview)

        # Bind selection to update detail view
        self.tree.bind("<<TreeviewSelect>>", self.on_selection_change)
        # Bind double-click to edit
        self.tree.bind("<Double-1>", self.edit_entry)

        # Bottom pane - Detail view
        detail_frame = ttk.Frame(paned)
        paned.add(detail_frame, weight=1)

        ttk.Label(detail_frame, text="Value Detail:", font=("TkDefaultFont", 10, "bold")).pack(anchor=tk.W, padx=5, pady=5)

        # Text widget with scrollbar for value display
        detail_scroll = ttk.Scrollbar(detail_frame)
        detail_scroll.pack(side=tk.RIGHT, fill=tk.Y)

        self.detail_text = tk.Text(detail_frame, wrap=tk.WORD, yscrollcommand=detail_scroll.set,
                                    font=("Menlo", 11), height=8)
        self.detail_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        detail_scroll.config(command=self.detail_text.yview)

        # Button frame for operations
        button_frame = ttk.Frame(self.root, padding="10")
        button_frame.pack(fill=tk.X)

        ttk.Button(button_frame, text="Add Field", command=self.add_field).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="Edit Field", command=self.edit_entry).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="Delete Field", command=self.delete_field).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="Copy Value", command=self.copy_value).pack(side=tk.LEFT, padx=5)

        # Status bar at the bottom
        status_frame = ttk.Frame(self.root, relief=tk.SUNKEN)
        status_frame.pack(fill=tk.X, side=tk.BOTTOM)

        self.status_label = ttk.Label(status_frame, text="Ready", padding=(10, 5))
        self.status_label.pack(side=tk.LEFT, fill=tk.X, expand=True)

    def show_about(self):
        """Show application about dialog"""
        about_text = f"{APP_NAME} v{APP_VERSION}\n\n" \
                     "A graphical tool for viewing and editing metadata in PNG files.\n\n" \
                     "Author: Robert Tusa\n" \
                     "License: MIT"

        messagebox.showinfo(
            f"About {APP_NAME}",
            about_text,
            parent=self.root
        )

    def set_status(self, message, duration=3000, color=""):
        """Set status bar message that auto-clears after duration (ms)"""
        # Cancel any existing timer
        if self.status_timer:
            self.root.after_cancel(self.status_timer)

        # Set the message
        self.status_label.config(text=message, foreground=color)

        # Schedule clearing the message
        if duration > 0:
            self.status_timer = self.root.after(duration, lambda: self.status_label.config(text="Ready", foreground=""))

    def mark_as_modified(self):
        """Mark the document as having unsaved changes"""
        if not self.has_unsaved_changes:
            self.has_unsaved_changes = True
            self.update_title()
            self.changes_label.config(text="Unsaved changes")

    def mark_as_saved(self):
        """Mark the document as saved"""
        self.has_unsaved_changes = False
        self.update_title()
        self.changes_label.config(text="")

    def update_title(self):
        """Update window title with unsaved indicator"""
        base_title = f"{APP_NAME} v{APP_VERSION}"
        if self.current_file:
            filename = Path(self.current_file).name
            if self.has_unsaved_changes:
                self.root.title(f"{base_title} - {filename} *")
            else:
                self.root.title(f"{base_title} - {filename}")
        else:
            self.root.title(base_title)

    def format_value_for_display(self, value):
        """Try to format value as JSON if possible, otherwise return as-is"""
        try:
            # Try to parse as JSON
            parsed = json.loads(value)
            # If successful, return pretty-printed JSON
            return json.dumps(parsed, indent=2, ensure_ascii=False)
        except (json.JSONDecodeError, TypeError):
            # Not JSON, return original value
            return value

    def truncate_value(self, value, max_length=80):
        """Truncate long values for tree display"""
        value_str = str(value)
        if len(value_str) > max_length:
            return value_str[:max_length] + "..."
        return value_str

    def on_selection_change(self, event):
        """Update detail view when selection changes"""
        selected = self.tree.selection()
        if selected:
            item = selected[0]
            key, _ = self.tree.item(item, "values")

            if key in self.metadata_dict:
                value = self.metadata_dict[key]
                formatted_value = self.format_value_for_display(value)

                # Update detail text
                self.detail_text.config(state=tk.NORMAL)
                self.detail_text.delete(1.0, tk.END)
                self.detail_text.insert(1.0, formatted_value)
                self.detail_text.config(state=tk.DISABLED)

    def open_file(self):
        filepath = filedialog.askopenfilename(
            title="Select PNG file",
            filetypes=[("PNG files", "*.png"), ("All files", "*.*")]
        )

        if filepath:
            try:
                self.current_file = filepath

                # Read metadata using PIL
                img = Image.open(filepath)
                self.metadata_dict = {}

                # Extract text chunks (tEXt, zTXt, iTXt)
                if hasattr(img, 'text'):
                    self.metadata_dict = dict(img.text)

                self.file_label.config(text=Path(filepath).name, foreground="")
                self.mark_as_saved()
                self.refresh_tree()

                field_count = len(self.metadata_dict)
                self.set_status(f"Loaded {field_count} metadata field(s) from {Path(filepath).name}", color="green")
            except Exception as e:
                messagebox.showerror("Error", f"Failed to open file:\n{str(e)}")
                self.set_status("Failed to open file", color="red")

    def refresh_tree(self):
        # Clear existing items
        for item in self.tree.get_children():
            self.tree.delete(item)

        # Clear detail view
        self.detail_text.config(state=tk.NORMAL)
        self.detail_text.delete(1.0, tk.END)
        self.detail_text.config(state=tk.DISABLED)

        # Add metadata entries from dictionary
        for key, value in self.metadata_dict.items():
            truncated = self.truncate_value(value)
            self.tree.insert("", tk.END, values=(key, truncated))

    def add_field(self):
        if self.current_file is None:
            messagebox.showwarning("No File", "Please open a PNG file first")
            return

        dialog = tk.Toplevel(self.root)
        dialog.title("Add Field")
        dialog.geometry("600x400")
        dialog.minsize(400, 300)

        # Make dialog resizable
        dialog.rowconfigure(1, weight=1)
        dialog.columnconfigure(1, weight=1)

        # Key entry
        ttk.Label(dialog, text="Key:").grid(row=0, column=0, padx=10, pady=10, sticky=tk.W)
        key_entry = ttk.Entry(dialog, width=50)
        key_entry.grid(row=0, column=1, padx=10, pady=10, sticky=tk.EW)

        # Value text with label
        ttk.Label(dialog, text="Value:").grid(row=1, column=0, padx=10, pady=10, sticky=tk.NW)

        # Frame for text widget and scrollbar
        text_frame = ttk.Frame(dialog)
        text_frame.grid(row=1, column=1, padx=10, pady=10, sticky=tk.NSEW)

        value_scroll = ttk.Scrollbar(text_frame)
        value_scroll.pack(side=tk.RIGHT, fill=tk.Y)

        value_text = tk.Text(text_frame, wrap=tk.WORD, yscrollcommand=value_scroll.set,
                            font=("Menlo", 11))
        value_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        value_scroll.config(command=value_text.yview)

        # Button frame
        button_frame = ttk.Frame(dialog)
        button_frame.grid(row=2, column=0, columnspan=2, pady=10, sticky=tk.E)

        def save_new_field():
            key = key_entry.get().strip()
            value = value_text.get(1.0, tk.END).strip()

            if not key:
                messagebox.showwarning("Invalid Input", "Key cannot be empty")
                return

            self.metadata_dict[key] = value
            self.mark_as_modified()
            self.refresh_tree()
            self.set_status(f"Added field '{key}'", color="green")
            dialog.destroy()

        ttk.Button(button_frame, text="Cancel", command=dialog.destroy).pack(side=tk.RIGHT, padx=5)
        ttk.Button(button_frame, text="Add", command=save_new_field).pack(side=tk.RIGHT, padx=5)

        key_entry.focus()

    def edit_entry(self, event=None):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("No Selection", "Please select a field to edit")
            return

        item = selected[0]
        key, _ = self.tree.item(item, "values")
        current_value = self.metadata_dict[key]

        # Format value as JSON if possible
        formatted_value = self.format_value_for_display(current_value)

        dialog = tk.Toplevel(self.root)
        dialog.title("Edit Field")
        dialog.geometry("600x400")
        dialog.minsize(400, 300)

        # Make dialog resizable
        dialog.rowconfigure(1, weight=1)
        dialog.columnconfigure(1, weight=1)

        # Key display
        ttk.Label(dialog, text="Key:").grid(row=0, column=0, padx=10, pady=10, sticky=tk.W)
        key_label = ttk.Label(dialog, text=key, font=("TkDefaultFont", 10, "bold"))
        key_label.grid(row=0, column=1, padx=10, pady=10, sticky=tk.W)

        # Value text with label
        ttk.Label(dialog, text="Value:").grid(row=1, column=0, padx=10, pady=10, sticky=tk.NW)

        # Frame for text widget and scrollbar
        text_frame = ttk.Frame(dialog)
        text_frame.grid(row=1, column=1, padx=10, pady=10, sticky=tk.NSEW)

        value_scroll = ttk.Scrollbar(text_frame)
        value_scroll.pack(side=tk.RIGHT, fill=tk.Y)

        value_text = tk.Text(text_frame, wrap=tk.WORD, yscrollcommand=value_scroll.set,
                            font=("Menlo", 11))
        value_text.insert(1.0, formatted_value)
        value_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        value_scroll.config(command=value_text.yview)

        # Button frame
        button_frame = ttk.Frame(dialog)
        button_frame.grid(row=2, column=0, columnspan=2, pady=10, sticky=tk.E)

        def save_edit():
            new_value = value_text.get(1.0, tk.END).strip()
            self.metadata_dict[key] = new_value
            self.mark_as_modified()
            self.refresh_tree()
            self.set_status(f"Updated field '{key}'", color="green")
            dialog.destroy()

        ttk.Button(button_frame, text="Cancel", command=dialog.destroy).pack(side=tk.RIGHT, padx=5)
        ttk.Button(button_frame, text="Save", command=save_edit).pack(side=tk.RIGHT, padx=5)

        value_text.focus()

    def delete_field(self):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("No Selection", "Please select a field to delete")
            return

        item = selected[0]
        key, _ = self.tree.item(item, "values")

        if messagebox.askyesno("Confirm Delete", f"Delete field '{key}'?"):
            if key in self.metadata_dict:
                del self.metadata_dict[key]
                self.mark_as_modified()
                self.refresh_tree()
                self.set_status(f"Deleted field '{key}'", color="green")

    def copy_value(self):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("No Selection", "Please select a field to copy")
            return

        item = selected[0]
        key, _ = self.tree.item(item, "values")
        value = self.metadata_dict[key]

        self.root.clipboard_clear()
        self.root.clipboard_append(value)
        self.set_status(f"Copied value of '{key}' to clipboard", color="green")

    def save_changes(self):
        if self.current_file is None:
            messagebox.showwarning("No File", "Please open a PNG file first")
            return

        try:
            # Open the image
            img = Image.open(self.current_file)

            # Create new PngInfo object with only our metadata
            metadata = PngInfo()
            for key, value in self.metadata_dict.items():
                metadata.add_text(key, value)

            # Save the image with new metadata (this replaces all text chunks)
            img.save(self.current_file, pnginfo=metadata)

            self.mark_as_saved()
            self.set_status("Metadata saved successfully", color="green")
        except Exception as e:
            messagebox.showerror("Error", f"Failed to save metadata:\n{str(e)}")
            self.set_status("Failed to save metadata", color="red")

if __name__ == "__main__":
    root = tk.Tk()
    app = PNGMetadataEditor(root)
    root.mainloop()
