# markdownconverter/gui/gui.py

import os
import sys
import logging
import re
import subprocess
import tkinter as tk
from datetime import date
from tkinter import simpledialog
from pathlib import Path
import ttkbootstrap as tb
from tkinter.scrolledtext import ScrolledText
from ttkbootstrap.scrolled import ScrolledFrame
from ttkbootstrap.constants import *
from tkinter import filedialog, messagebox, StringVar, BooleanVar

from ..core.core import (
    convert_markdown, convert_docx_to_pdf, scan_template_for_placeholders,
    ConverterNotFoundError, TemplatePlaceholderError
)
from ..utils.config import (
    save_configuration, load_configuration,
    KEY_LAST_MARKDOWN, KEY_LAST_PROFILE, KEY_PROFILES, KEY_TEMPLATE_PATH, KEY_VALUES
)
from ..utils.logger import (
    setup_basic_logging, add_tkinter_handler,
    shutdown_logging_system, get_logger
)
from .batch_converter import BatchConverterTab
# EditorWindow non viene usato in questo file, ma lo lasciamo per coerenza
# from .editor import EditorWindow 

log = get_logger(__name__)

# ... (open_with_default_app, open_output_folder, ProfileManagerWindow sono INVARIATE)
# ... (ti fornisco comunque il codice completo per sicurezza)

def open_with_default_app(filepath: str):
    log.info(f"Request to open file at path: '{filepath}'")
    p = Path(filepath)
    if not p.is_file():
        log.warning("Open file requested, but path is invalid or file does not exist.")
        messagebox.showwarning("Warning", "No output file or folder to open.")
        return
    try:
        log.info(f"Opening '{p}' with default application.")
        if sys.platform == "win32": os.startfile(p)
        elif sys.platform == "darwin": subprocess.run(["open", p], check=True)
        else: subprocess.run(["xdg-open", p], check=True)
    except Exception as e:
        log.error(f"Failed to open '{p}': {e}", exc_info=True)
        messagebox.showerror("Error", f"Could not open the file/folder:\n{str(e)}")

def open_output_folder(filepath: str):
    if not filepath:
        log.warning("Open folder requested, but no output file has been generated yet.")
        messagebox.showwarning("Warning", "No output file has been generated yet.")
        return
    folder = Path(filepath).parent
    open_with_default_app(str(folder))

class ProfileManagerWindow(tb.Toplevel):
    def __init__(self, parent, config_data, app_instance):
        super().__init__(master=parent, title="Manage Profiles")
        self.geometry("500x400")
        self.transient(parent)
        self.config_data = config_data
        self.app = app_instance
        self.selected_profile_name = None
        self._setup_widgets()
        self._load_profiles()

    def _setup_widgets(self):
        main_frame = tb.Frame(self, padding=10); main_frame.pack(fill=tk.BOTH, expand=True)
        main_frame.rowconfigure(0, weight=1); main_frame.columnconfigure(0, weight=1)
        list_frame = tb.Labelframe(main_frame, text="Existing Profiles", padding=5)
        list_frame.grid(row=0, column=0, sticky="nsew", padx=(0, 10))
        list_frame.rowconfigure(0, weight=1); list_frame.columnconfigure(0, weight=1)
        self.profile_listbox = tk.Listbox(list_frame, exportselection=False)
        self.profile_listbox.grid(row=0, column=0, sticky="nsew")
        scrollbar = tb.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.profile_listbox.yview)
        scrollbar.grid(row=0, column=1, sticky="ns")
        self.profile_listbox.config(yscrollcommand=scrollbar.set)
        self.profile_listbox.bind("<<ListboxSelect>>", self._on_profile_select)
        button_frame = tb.Frame(main_frame); button_frame.grid(row=0, column=1, sticky="ns")
        tb.Button(button_frame, text="New...", command=self._new_profile, bootstyle=SUCCESS).pack(fill=tk.X, pady=5)
        self.rename_btn = tb.Button(button_frame, text="Rename...", command=self._rename_profile, state=tk.DISABLED)
        self.rename_btn.pack(fill=tk.X, pady=5)
        self.delete_btn = tb.Button(button_frame, text="Delete", command=self._delete_profile, state=tk.DISABLED, bootstyle=DANGER)
        self.delete_btn.pack(fill=tk.X, pady=5)
        tb.Button(button_frame, text="Close", command=self.destroy).pack(fill=tk.X, pady=(20, 5))

    def _load_profiles(self):
        self.profile_listbox.delete(0, tk.END)
        for profile_name in sorted(self.config_data[KEY_PROFILES].keys()):
            self.profile_listbox.insert(tk.END, profile_name)
        self._on_profile_select()

    def _on_profile_select(self, event=None):
        selection = self.profile_listbox.curselection()
        if not selection:
            self.selected_profile_name = None
            self.rename_btn.config(state=tk.DISABLED); self.delete_btn.config(state=tk.DISABLED)
        else:
            self.selected_profile_name = self.profile_listbox.get(selection[0])
            self.rename_btn.config(state=tk.NORMAL); self.delete_btn.config(state=tk.NORMAL)

    def _new_profile(self):
        profile_name = simpledialog.askstring("New Profile", "Enter a name for the new profile:", parent=self)
        if not profile_name: return
        if profile_name in self.config_data[KEY_PROFILES]:
            messagebox.showerror("Error", f"A profile named '{profile_name}' already exists.", parent=self); return
        template_path = filedialog.askopenfilename(title=f"Select Template for '{profile_name}'", filetypes=[("Word Documents", "*.docx")], parent=self)
        if not template_path: return
        self.config_data[KEY_PROFILES][profile_name] = {KEY_TEMPLATE_PATH: template_path, KEY_VALUES: {}}
        self.app.update_config(self.config_data)
        self.app.update_profile_combobox(new_selection=profile_name)
        self._load_profiles()

    def _rename_profile(self):
        if not self.selected_profile_name: return
        new_name = simpledialog.askstring("Rename Profile", f"Enter a new name for '{self.selected_profile_name}':", parent=self)
        if not new_name or new_name == self.selected_profile_name: return
        if new_name in self.config_data[KEY_PROFILES]:
            messagebox.showerror("Error", f"A profile named '{new_name}' already exists.", parent=self); return
        self.config_data[KEY_PROFILES][new_name] = self.config_data[KEY_PROFILES].pop(self.selected_profile_name)
        if self.config_data[KEY_LAST_PROFILE] == self.selected_profile_name:
            self.config_data[KEY_LAST_PROFILE] = new_name
        self.app.update_config(self.config_data)
        self.app.update_profile_combobox(new_selection=new_name)
        self._load_profiles()

    def _delete_profile(self):
        if not self.selected_profile_name: return
        if not messagebox.askyesno("Confirm Delete", f"Are you sure you want to delete profile '{self.selected_profile_name}'?", parent=self): return
        del self.config_data[KEY_PROFILES][self.selected_profile_name]
        if self.config_data[KEY_LAST_PROFILE] == self.selected_profile_name: self.config_data[KEY_LAST_PROFILE] = ""
        self.app.update_config(self.config_data)
        self.app.update_profile_combobox()
        self._load_profiles()

class MarkdownConverterApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Markdown Converter")
        self.root.geometry("900x850")
        self._setup_logging()
        self.config = load_configuration()
        self.dynamic_entry_vars = {}
        self.loaded_profile_name = ""
        self.selected_file = StringVar(value=self.config.get(KEY_LAST_MARKDOWN, ""))
        
        # --- NEW CODE ---
        # Add a trace to the StringVar. Whenever its value is written,
        # the _on_selected_file_change callback will be executed.
        self.selected_file.trace_add("write", self._on_selected_file_change)
        
        self.active_profile_name = StringVar() # Initialized empty
        self.add_toc_var = BooleanVar(value=True)
        self.docx_output_path = StringVar()
        self.pdf_direct_output_path = StringVar()
        self.pdf_from_docx_output_path = StringVar()
        self._build_ui()
        self.update_profile_combobox() # This will set active_profile_name
        self.root.protocol("WM_DELETE_WINDOW", self._on_closing)

    def _setup_logging(self):
        self.log_config = {"default_root_level": logging.DEBUG, "format": "%(asctime)s [%(levelname)-8s] %(name)-20s: %(message)s", "date_format": "%H:%M:%S", "enable_console": True, "enable_file": True, "file_path": "markdown_converter.log", "colors": {logging.DEBUG: "gray", logging.INFO: "black", logging.WARNING: "orange", logging.ERROR: "red", logging.CRITICAL: "purple"}}
        setup_basic_logging(root_tk_instance_for_processor=self.root, logging_config_dict=self.log_config)

    def _build_ui(self):
        # Crea il Notebook (sistema a tab)
        self.notebook = tb.Notebook(self.root)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # Tab 1: Conversione Singola (funzionalità originale)
        self.single_converter_tab = tb.Frame(self.notebook, padding=10)
        self.notebook.add(self.single_converter_tab, text="Conversione Singola")
        
        # Tab 2: Conversione Batch
        self.batch_converter_tab = BatchConverterTab(self.notebook)
        self.notebook.add(self.batch_converter_tab, text="Conversione Batch")
        
        # Costruisce la UI del tab singolo nel container
        self._build_single_converter_ui()
    
    def _build_single_converter_ui(self):
        """Costruisce l'interfaccia per la conversione singola."""
        container = self.single_converter_tab
        profile_frame = tb.Labelframe(container, text="Profile & Source File", padding=10)
        profile_frame.pack(fill=tk.X, pady=(0, 10)); profile_frame.columnconfigure(1, weight=1)
        tb.Label(profile_frame, text="Active Profile:").grid(row=0, column=0, padx=5, pady=5, sticky="w")
        self.profile_combobox = tb.Combobox(profile_frame, textvariable=self.active_profile_name, state="readonly")
        self.profile_combobox.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        self.profile_combobox.bind("<<ComboboxSelected>>", self._on_profile_change)
        tb.Button(profile_frame, text="Manage...", command=self.open_profile_manager).grid(row=0, column=2, padx=5, pady=5)
        tb.Label(profile_frame, text="Markdown File:").grid(row=1, column=0, padx=5, pady=5, sticky="w")
        tb.Entry(profile_frame, textvariable=self.selected_file).grid(row=1, column=1, padx=5, pady=5, sticky="ew")
        tb.Button(profile_frame, text="Browse...", command=self.browse_markdown).grid(row=1, column=2, padx=5, pady=5)
        self.placeholder_frame = ScrolledFrame(container, autohide=True, bootstyle="round")
        self.placeholder_frame.pack(fill=tk.BOTH, expand=True, pady=10)
        options_frame = tb.Frame(container); options_frame.pack(fill=tk.X, pady=(0, 10))
        tb.Checkbutton(options_frame, text="Add TOC", variable=self.add_toc_var, bootstyle="primary-round-toggle").pack(side=tk.LEFT, padx=(0, 20))
        tb.Button(options_frame, text="Open Source Folder", command=lambda: open_output_folder(self.selected_file.get())).pack(side=tk.LEFT, padx=5)
        action_frame = tb.Labelframe(container, text="Conversion Actions", padding=10)
        action_frame.pack(fill=tk.X, pady=10); action_frame.columnconfigure(1, weight=1)
        tb.Button(action_frame, text="MD -> DOCX", command=lambda: self.convert("DOCX"), width=12).grid(row=0, column=0, padx=5, pady=5)
        tb.Entry(action_frame, textvariable=self.docx_output_path).grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        tb.Button(action_frame, text="Open", command=self.open_docx_output).grid(row=0, column=2, padx=5, pady=5)
        tb.Button(action_frame, text="MD -> PDF", command=lambda: self.convert("PDF"), width=12).grid(row=1, column=0, padx=5, pady=5)
        tb.Entry(action_frame, textvariable=self.pdf_direct_output_path).grid(row=1, column=1, padx=5, pady=5, sticky="ew")
        tb.Button(action_frame, text="Open", command=self.open_pdf_direct_output).grid(row=1, column=2, padx=5, pady=5)
        self.docx_to_pdf_btn = tb.Button(action_frame, text="DOCX -> PDF", command=self.convert_from_docx_to_pdf, bootstyle="info", width=12, state=tk.DISABLED)
        self.docx_to_pdf_btn.grid(row=2, column=0, padx=5, pady=5)
        tb.Entry(action_frame, textvariable=self.pdf_from_docx_output_path).grid(row=2, column=1, padx=5, pady=5, sticky="ew")
        tb.Button(action_frame, text="Open", command=self.open_pdf_from_docx_output).grid(row=2, column=2, padx=5, pady=5)
        log_frame = tb.Labelframe(container, text="Log Viewer", padding=10)
        log_frame.pack(fill=tk.BOTH, expand=True, pady=(10,0))
        log_text_widget = ScrolledText(log_frame, wrap=tk.WORD, state=tk.DISABLED, height=10)
        log_text_widget.pack(fill=tk.BOTH, expand=True)
        add_tkinter_handler(gui_log_widget=log_text_widget, root_tk_instance_for_gui_handler=self.root, logging_config_dict=self.log_config)

    def open_docx_output(self): open_with_default_app(self.docx_output_path.get())
    def open_pdf_direct_output(self): open_with_default_app(self.pdf_direct_output_path.get())
    def open_pdf_from_docx_output(self): open_with_default_app(self.pdf_from_docx_output_path.get())

    def update_profile_combobox(self, new_selection=None):
        profiles = sorted(self.config[KEY_PROFILES].keys())
        self.profile_combobox['values'] = profiles
        last_profile = self.config.get(KEY_LAST_PROFILE)
        if new_selection and new_selection in profiles:
            self.active_profile_name.set(new_selection)
        elif last_profile in profiles:
            self.active_profile_name.set(last_profile)
        else:
            self.active_profile_name.set("")
        self._load_profile_data()

    def open_profile_manager(self):
        win = ProfileManagerWindow(self.root, self.config, self)
        self.root.wait_window(win)

    def _on_profile_change(self, event=None):
        self._save_current_values()
        self.config[KEY_LAST_PROFILE] = self.active_profile_name.get()
        self._load_profile_data()

    def _load_profile_data(self):
        for widget in self.placeholder_frame.winfo_children(): widget.destroy()
        self.dynamic_entry_vars.clear()
        
        profile_name = self.active_profile_name.get()
        self.loaded_profile_name = profile_name # Memorizza il profilo caricato
        
        if not profile_name:
            tb.Label(self.placeholder_frame, text="No profile selected. Create or select a profile.", bootstyle=INFO).pack(pady=10)
            self._update_output_paths(); return
        
        profile_data = self.config[KEY_PROFILES].get(profile_name, {})
        template_path = profile_data.get(KEY_TEMPLATE_PATH)
        if not template_path or not os.path.exists(template_path):
            tb.Label(self.placeholder_frame, text=f"Template file not found:\n{template_path}", bootstyle=WARNING, wraplength=700).pack(pady=10)
            return
        try:
            dynamic, structural = scan_template_for_placeholders(template_path)
            if not (dynamic or structural):
                tb.Label(self.placeholder_frame, text="No placeholders found in template.", bootstyle=INFO).pack(pady=10); return
            
            placeholder_grid = tb.Frame(self.placeholder_frame); placeholder_grid.pack(fill=tk.X, padx=5, pady=5); placeholder_grid.columnconfigure(1, weight=1)
            saved_values = profile_data.get(KEY_VALUES, {})
            row = 0
            for ph in dynamic:
                label_text = ph.replace("%%", "") + ":"
                tb.Label(placeholder_grid, text=label_text).grid(row=row, column=0, padx=5, pady=3, sticky="e")
                entry_var = StringVar(value=saved_values.get(ph, ""))
                entry = tb.Entry(placeholder_grid, textvariable=entry_var); entry.grid(row=row, column=1, columnspan=2, padx=5, pady=3, sticky="ew")
                entry_var.trace_add("write", lambda *_, ph_key=ph: self._on_placeholder_change(ph_key))
                self.dynamic_entry_vars[ph] = entry_var
                row += 1
            if structural:
                tb.Separator(placeholder_grid).grid(row=row, column=0, columnspan=3, pady=10, sticky="ew"); row += 1
                for ph in structural:
                    label_text = ph.replace("%%", "") + ":"
                    tb.Label(placeholder_grid, text=label_text).grid(row=row, column=0, padx=5, pady=3, sticky="e")
                    tb.Entry(placeholder_grid, state="readonly", textvariable=StringVar(value="<Automatic>")).grid(row=row, column=1, padx=5, pady=3, sticky="ew")
                    tb.Label(placeholder_grid, text="Managed by application", bootstyle=SECONDARY).grid(row=row, column=2, padx=5, pady=3, sticky="w")
                    row += 1
        except (FileNotFoundError, TemplatePlaceholderError) as e:
            tb.Label(self.placeholder_frame, text=f"Error: {e}", bootstyle=DANGER, wraplength=700).pack(pady=10)
        self._update_output_paths()

    def _on_placeholder_change(self, placeholder_key):
        if placeholder_key in ["%%DOC_PROJECT%%", "%%DOC_NUMBER%%", "%%DOC_REV%%"]: self._update_output_paths()

    def _gather_current_values(self): return {ph: var.get() for ph, var in self.dynamic_entry_vars.items()}
    def _save_current_values(self):
        profile_to_save = self.loaded_profile_name
        if not profile_to_save or profile_to_save not in self.config[KEY_PROFILES]: return
        self.config[KEY_PROFILES][profile_to_save][KEY_VALUES] = self._gather_current_values()
        log.info(f"Updated values in memory for profile '{profile_to_save}'.")

    def _confirm_overwrite(self, filepath: str) -> bool:
        if os.path.exists(filepath): return messagebox.askyesno("Confirm Overwrite", f"File exists:\n{filepath}\n\nOverwrite?")
        return True

    def browse_markdown(self):
        path = filedialog.askopenfilename(filetypes=[("Markdown files", "*.md")])
        if path: self.selected_file.set(path)

    def _update_output_paths(self):
        source_path_str = self.selected_file.get()
        if not source_path_str: return
        output_dir = Path(source_path_str).parent
        values = self._gather_current_values()
        project = values.get("%%DOC_PROJECT%%", "NoProject"); doc_num = values.get("%%DOC_NUMBER%%", "NoNum"); rev = values.get("%%DOC_REV%%", "NoRev")
        today = date.today().strftime("%Y%m%d")
        def sanitize(text: str) -> str: return re.sub(r'[\s/\\:*?"<>|]+', '_', text)
        docx_fn = f"{sanitize(project)}_SUM_{sanitize(doc_num)}_{sanitize(rev)}_{today}.docx"
        self.docx_output_path.set(str(output_dir / docx_fn))
        pdf_from_docx_fn = f"{sanitize(project)}_SUM_{sanitize(doc_num)}_{sanitize(rev)}_{today}.pdf"
        self.pdf_from_docx_output_path.set(str(output_dir / pdf_from_docx_fn))
        pdf_direct_fn = f"{sanitize(project)}_SUM_{today}.pdf"
        self.pdf_direct_output_path.set(str(output_dir / pdf_direct_fn))

    def convert(self, fmt):
        self.docx_to_pdf_btn.config(state=tk.DISABLED)
        input_path = self.selected_file.get()
        if not input_path: messagebox.showerror("Error", "Please select a Markdown file."); return
        profile_name = self.active_profile_name.get()
        if not profile_name: messagebox.showerror("Error", "Please select a profile."); return
        template_path = self.config[KEY_PROFILES][profile_name].get(KEY_TEMPLATE_PATH)
        if fmt == "DOCX" and not template_path: messagebox.showwarning("Warning", "The selected profile has no template associated."); return
        output_path = self.docx_output_path.get() if fmt == "DOCX" else self.pdf_direct_output_path.get()
        if not output_path: messagebox.showerror("Error", "Please specify a valid output path."); return
        if not self._confirm_overwrite(output_path): return
        self._save_current_values()
        save_configuration(self.config)
        metadata = self._gather_current_values()
        try:
            result_path = convert_markdown(input_path, output_path, fmt, self.add_toc_var.get(), template_path, metadata)
            messagebox.showinfo("Success", f"File converted successfully:\n{result_path}")
            if fmt == "DOCX": self.docx_to_pdf_btn.config(state=tk.NORMAL)
        except Exception as e:
            log.critical(f"An error occurred during conversion: {e}", exc_info=True)
            messagebox.showerror("Error", f"An error occurred during conversion:\n{str(e)}")

    def convert_from_docx_to_pdf(self):
        input_path = self.docx_output_path.get()
        output_path = self.pdf_from_docx_output_path.get()
        if not input_path or not os.path.exists(input_path): messagebox.showerror("Error", "Source DOCX file does not exist. Generate it first."); return
        if not output_path: messagebox.showerror("Error", "Please specify a valid output path for the PDF."); return
        if not self._confirm_overwrite(output_path): return
        try:
            pdf_output = convert_docx_to_pdf(input_path, output_path)
            messagebox.showinfo("Success", f"DOCX successfully converted to PDF:\n{pdf_output}")
        except ConverterNotFoundError as e: messagebox.showerror("Converter Not Found", str(e))
        except Exception as e: messagebox.showerror("DOCX to PDF Conversion Error", f"An unexpected error occurred.\nError: {str(e)}")

    def update_config(self, new_config):
        self.config = new_config
        save_configuration(self.config)

    def _on_closing(self):
        self._save_current_values()
        self.config[KEY_LAST_MARKDOWN] = self.selected_file.get()
        self.config[KEY_LAST_PROFILE] = self.active_profile_name.get()
        save_configuration(self.config)
        shutdown_logging_system()
        self.root.destroy()

    def _on_selected_file_change(self, *args):
        """
        Callback function that is triggered whenever the self.selected_file
        StringVar is written to. It ensures the output paths are updated.
        """
        self._update_output_paths()
    
def run_app():
    root = tb.Window(themename="sandstone")
    app = MarkdownConverterApp(root)
    root.mainloop()