import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog, scrolledtext
import os
import argparse
import chardet # Character encoding detection library
from tkinterdnd2 import DND_FILES, TkinterDnD # TkinterDnD2 library for drag-and-drop
import re # Regular expression module
import subprocess # For running external commands

# --- INI File Reading/Writing Functions ---
def read_ini(filepath):
    """Reads settings from an INI file (key=val format)."""
    settings = {}
    if not os.path.exists(filepath):
        return settings

    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith('#'):  # Skip empty lines or comments
                    continue
                if '=' in line:
                    key, value = line.split('=', 1)
                    settings[key.strip()] = value.strip()
    except Exception as e:
        print(f"Error reading INI file {filepath}: {e}")
        messagebox.showerror("INIファイル読み込みエラー", f"'{os.path.basename(filepath)}' の読み込み中にエラーが発生しました:\n{e}")
    return settings

def write_ini(filepath, settings):
    """Writes settings to an INI file (key=val format)."""
    try:
        with open(filepath, 'w', encoding='utf-8') as f:
            for key, value in settings.items():
                f.write(f"{key}={value}\n")
    except Exception as e:
        print(f"Error writing INI file {filepath}: {e}")
        messagebox.showerror("INIファイル書き込みエラー", f"'{os.path.basename(filepath)}' の書き込み中にエラーが発生しました:\n{e}")

# --- Simple TOML/INI Parser Class ---
class SimpleTomlParser:
    """
    A simplified TOML/INI parser.
    Does not strictly adhere to TOML specifications.
    Handles sections ([section]) and key=value pairs.
    Treats [section].key-like lines as sections for extraction.
    """
    def __init__(self):
        self.data = {}
        self.sections = []  # Stores (extracted_section_name, line_number, original_line_content)

    def parse(self, content):
        self.data = {}
        self.sections = []
        current_section = ""
        lines = content.splitlines()
        section_start_pattern = re.compile(r"^\s*\[")

        for i, line in enumerate(lines):
            stripped_line = line.strip()
            if stripped_line.startswith("#") or not stripped_line:
                continue

            if section_start_pattern.match(stripped_line) and "=" not in stripped_line:
                extracted_section_name = stripped_line
                self.sections.append((extracted_section_name, i + 1, line))
                
                # For associating key-value pairs with a section, extract the inner part
                match_inner_section = re.match(r"^\[([^\]]+)\].*", stripped_line)
                current_section = match_inner_section.group(1).strip() if match_inner_section else ""
                
                if current_section and current_section not in self.data:
                     self.data[current_section] = {}
                elif not current_section and "" not in self.data: # For top-level keys
                    self.data[""] = {}
            elif "=" in stripped_line:
                try:
                    key, value = stripped_line.split("=", 1)
                    key = key.strip()
                    value = value.strip()
                    target_data = self.data[current_section] if current_section else self.data.setdefault("", {})
                    target_data[key] = value
                except ValueError:
                    pass
        return self.data, self.sections

# --- Main Editor Class ---
class TomlEditor:
    def __init__(self, master, initial_file=None, initial_line=None, initial_col=None):
        self.master = master
        master.title("Inifile Editor")
        master.geometry("800x600")

        self.current_file = initial_file
        self.file_encoding = "auto"  # Default encoding mode

        self.script_dir = os.path.dirname(os.path.abspath(__file__))
        self.config_filepath = os.path.join(self.script_dir, "iniedit.ini")
        self.text_ini_filepath = os.path.join(self.script_dir, "text.ini") # Corrected from test.ini

        self.settings = read_ini(self.config_filepath)
        self._apply_saved_settings()

        self._load_text_ini_data() # Load text.ini data for context menu

        # --- Text Area ---
        self.text_area = scrolledtext.ScrolledText(master, wrap=tk.WORD, undo=True)
        self.text_area.pack(expand=True, fill="both")
        
        # Bindings (placed here after methods are defined for clarity, though Python's class evaluation handles this)
        self.text_area.bind("<<Modified>>", self.on_text_modified)
        self.text_area.bind("<KeyRelease>", self.update_status_bar)
        self.text_area.bind("<ButtonRelease-1>", self.update_status_bar)

        # Drag & Drop Setup
        self.text_area.drop_target_register(DND_FILES)
        self.text_area.dnd_bind("<<Drop>>", self.handle_drop)
        
        # Context Menu Setup
        self.context_menu = tk.Menu(self.text_area, tearoff=0)
        self.context_menu.add_command(label="切り取り", command=lambda: self.text_area.event_generate("<<Cut>>"))
        self.context_menu.add_command(label="コピー", command=lambda: self.text_area.event_generate("<<Copy>>"))
        self.context_menu.add_command(label="貼り付け", command=lambda: self.text_area.event_generate("<<Paste>>"))
        self.context_menu.add_separator()
        
        # text.ini insertion submenu
        self.insert_ini_menu = tk.Menu(self.context_menu, tearoff=0)
        self.context_menu.add_cascade(label="text.iniから挿入", menu=self.insert_ini_menu)
        self.context_menu.add_command(label="text.ini再読み込み", command=self._load_text_ini_data) # Add reload command
        
        self.text_area.bind("<Button-3>", self._show_context_menu) # Right-click to show context menu

        # --- Menu Bar ---
        self.menubar = tk.Menu(master)
        master.config(menu=self.menubar)

        # File Menu
        file_menu = tk.Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="ファイル", menu=file_menu)
        file_menu.add_command(label="新規", command=self.new_file)
        file_menu.add_command(label="開く", command=self.open_file)
        file_menu.add_command(label="保存", command=self.save_file)
        file_menu.add_command(label="名前を付けて保存...", command=self.save_file_as)
        file_menu.add_separator()
        file_menu.add_command(label="ファイル場所を開く", command=self._open_file_location)
        file_menu.add_command(label="ターミナルを開く", command=self._open_terminal)
        file_menu.add_separator()

        # Encoding Submenu
        self.encoding_menu = tk.Menu(file_menu, tearoff=0)
        file_menu.add_cascade(label="文字コード", menu=self.encoding_menu)
        self.selected_encoding = tk.StringVar(value="auto") # Default to auto-detect
        self._add_encoding_options()

        file_menu.add_separator()
        file_menu.add_command(label="終了", command=master.quit)

        # Edit Menu
        edit_menu = tk.Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="編集", menu=edit_menu)
        edit_menu.add_command(label="検索", command=self.find_text) # Corrected binding
        edit_menu.add_command(label="置換", command=self.replace_text)
        edit_menu.add_separator()
        edit_menu.add_command(label="元に戻す", command=self.text_area.edit_undo)
        edit_menu.add_command(label="やり直し", command=self.text_area.edit_redo)
        edit_menu.add_separator()
        edit_menu.add_command(label="文書先頭へジャンプ", command=self._jump_to_start)
        edit_menu.add_command(label="文書末へジャンプ", command=self._jump_to_end)
        edit_menu.add_command(label="行番号を指定してジャンプ", command=self._jump_to_line)
        edit_menu.add_separator()
        edit_menu.add_command(label="切り取り", command=lambda: self.text_area.event_generate("<<Cut>>"))
        edit_menu.add_command(label="コピー", command=lambda: self.text_area.event_generate("<<Copy>>"))
        edit_menu.add_command(label="貼り付け", command=lambda: self.text_area.event_generate("<<Paste>>"))

        # View Menu
        view_menu = tk.Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="表示", menu=view_menu)
        self.word_wrap_var = tk.BooleanVar(value=True)
        view_menu.add_checkbutton(label="折り返し", onvalue=True, offvalue=False, variable=self.word_wrap_var, command=self.toggle_word_wrap)

        # Tools Menu
        tools_menu = tk.Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="ツール", menu=tools_menu)
        tools_menu.add_command(label="セクションダイアログ", command=self.show_section_dialog)
        
        self._add_external_commands_to_menu(tools_menu) # Add external commands

        # --- Status Bar ---
        self.status_bar = tk.Label(master, text="行: 1, 桁: 1 | 文字コード: 自動判別", bd=1, relief=tk.SUNKEN, anchor=tk.W)
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)

        self.update_status_bar() # Update status bar initially

        # Initial file load and cursor position setup
        if self.current_file:
            self._load_initial_file(self.current_file)
            if initial_line:
                # Use after() to ensure text is loaded before jumping
                self.master.after(100, lambda: self._jump_to_line_col(initial_line, initial_col))
        else:
            self.master.title("TOML Editor - 無題")
            self.status_bar.config(text=f"行: 1, 桁: 1 | 文字コード: 自動判別")
            
        # Register handler for window closing
        self.master.protocol("WM_DELETE_WINDOW", self._on_closing)

    # --- Utility Methods ---
    def _add_external_commands_to_menu(self, menu):
        """Loads external commands from iniedit.ini and adds them to the Tools menu."""
        added_any = False
        for i in range(1, 10): # Check for external_cmd1 to external_cmd9
            cmd_name_key = f'external_cmd{i}_name'
            cmd_path_key = f'external_cmd{i}_path'
            
            cmd_name = self.settings.get(cmd_name_key)
            cmd_path = self.settings.get(cmd_path_key)

            if cmd_name and cmd_path:
                if not added_any:
                    menu.add_separator() # Add a separator before the first external command
                    added_any = True
                menu.add_command(label=cmd_name, command=lambda cmd=cmd_path: self._run_external_command(cmd))
        if added_any:
            menu.add_separator() # Add a separator after the last external command

    def _run_external_command(self, command_template):
        """Executes an external command, replacing {{file_path}} with the current file."""
        if not self.current_file or not os.path.exists(self.current_file):
            messagebox.showwarning("コマンド実行エラー", "ファイルが保存されていないか、存在しません。")
            return
        
        # Replace the placeholder with the current file path, enclosed in quotes
        command = command_template.replace("{{file_path}}", f'"{self.current_file}"')
        
        try:
            # Use shell=True for complex commands with placeholders. Be cautious with untrusted commands.
            # cwd sets the working directory for the subprocess.
            subprocess.Popen(command, shell=True, cwd=os.path.dirname(self.current_file))
        except Exception as e:
            messagebox.showerror("コマンド実行エラー", f"コマンドの実行に失敗しました:\n{command}\nエラー: {e}")

    def _open_file_location(self):
        """Opens the directory of the current file in the system file manager."""
        if not self.current_file or not os.path.exists(self.current_file):
            messagebox.showwarning("ファイル場所を開く", "ファイルが保存されていないか、存在しません。")
            return

        directory = os.path.dirname(self.current_file)
        file_manager_path = self.settings.get('file_manager_path')

        if not file_manager_path:
            messagebox.showwarning("設定不足", "iniedit.iniに 'file_manager_path' が設定されていません。")
            return
        
        try:
            if os.name == 'nt':  # Windows
                subprocess.Popen([file_manager_path, directory])
            elif os.uname().sysname == 'Darwin':  # macOS
                subprocess.Popen([file_manager_path, directory])
            else:  # Linux/Unix-like
                subprocess.Popen([file_manager_path, directory])
        except Exception as e:
            messagebox.showerror("エラー", f"ファイラーの起動に失敗しました:\n{file_manager_path} {directory}\nエラー: {e}")

    def _open_terminal(self):
        """Opens a terminal in the directory of the current file."""
        if not self.current_file or not os.path.exists(self.current_file):
            messagebox.showwarning("ターミナルを開く", "ファイルが保存されていないか、存在しません。")
            return

        directory = os.path.dirname(self.current_file)
        terminal_path = self.settings.get('terminal_path')

        if not terminal_path:
            messagebox.showwarning("設定不足", "iniedit.iniに 'terminal_path' が設定されていません。")
            return

        try:
            # Using shell=True for flexible command interpretation, and cwd for setting directory.
            # E.g., on Windows: cmd.exe /K "cd /d C:\path\to\dir"
            # On Linux: gnome-terminal --working-directory=/path/to/dir
            subprocess.Popen(terminal_path, shell=True, cwd=directory)
        except Exception as e:
            messagebox.showerror("エラー", f"ターミナルの起動に失敗しました:\n{terminal_path} (cwd: {directory})\nエラー: {e}")

    def _load_text_ini_data(self):
        """Loads data from text.ini into self.test_ini_data for context menu insertion."""
        self.test_ini_data = read_ini(self.text_ini_filepath)
        messagebox.showinfo("情報", f"text.ini を再読み込みしました。", parent=self.master)

    def _apply_saved_settings(self):
        """Applies saved window settings (position and size) from iniedit.ini."""
        try:
            width = int(self.settings.get('main_width', 800))
            height = int(self.settings.get('main_height', 600))
            pos_x = int(self.settings.get('main_pos_x', 100))
            pos_y = int(self.settings.get('main_pos_y', 100))
            self.master.geometry(f"{width}x{height}+{pos_x}+{pos_y}")
        except ValueError:
            self.master.geometry("800x600+100+100")

    def _on_closing(self):
        """Handles application closing, saving settings and prompting to save modified files."""
        if self.text_area.edit_modified():
            if messagebox.askyesno("未保存の変更", "ファイルを保存しますか？"):
                self.save_file()
        
        # Record main window's current position and size
        self.settings['main_width'] = self.master.winfo_width()
        self.settings['main_height'] = self.master.winfo_height()
        self.settings['main_pos_x'] = self.master.winfo_x()
        self.settings['main_pos_y'] = self.master.winfo_y()

        write_ini(self.config_filepath, self.settings)
        self.master.destroy()

    def on_text_modified(self, event=None):
        """Updates the window title to indicate unsaved changes (*)."""
        if self.text_area.edit_modified():
            if self.current_file:
                self.master.title(f"TOML Editor - {os.path.basename(self.current_file)}*")
            else:
                self.master.title("TOML Editor - 無題*")
        else:
            if self.current_file:
                self.master.title(f"TOML Editor - {os.path.basename(self.current_file)}")
            else:
                self.master.title("TOML Editor - 無題")
        self.text_area.edit_modified(False) # Reset the modified flag after checking

    def update_status_bar(self, event=None):
        """Updates the status bar with current cursor position and file encoding."""
        line, char = self.text_area.index(tk.INSERT).split('.')
        encoding_display = self.file_encoding.upper() if self.file_encoding != "auto" else "自動判別"
        self.status_bar.config(text=f"行: {line}, 桁: {int(char) + 1} | 文字コード: {encoding_display}")

    def _show_context_menu(self, event):
        """Displays the right-click context menu."""
        self.insert_ini_menu.delete(0, tk.END) # Clear existing items
        if self.test_ini_data:
            for key, value in self.test_ini_data.items():
                self.insert_ini_menu.add_command(label=key, command=lambda v=value: self._insert_value_from_ini(v))
        else:
            self.insert_ini_menu.add_command(label="text.iniが空か見つかりません", state=tk.DISABLED)

        self.context_menu.post(event.x_root, event.y_root)

    def _insert_value_from_ini(self, value):
        """Inserts a value from text.ini at the current cursor position."""
        self.text_area.insert(tk.INSERT, value)
        self.text_area.edit_modified(True) # Mark document as modified
        self.on_text_modified() # Update title bar

    # --- Jump Functions ---
    def _jump_to_start(self):
        """Jumps the cursor to the beginning of the document."""
        self.text_area.mark_set(tk.INSERT, "1.0")
        self.text_area.see(tk.INSERT)
        self.update_status_bar()

    def _jump_to_end(self):
        """Jumps the cursor to the end of the document."""
        self.text_area.mark_set(tk.INSERT, tk.END)
        self.text_area.see(tk.INSERT)
        self.update_status_bar()

    def _jump_to_line(self):
        """Prompts for a line number and jumps to it."""
        line_str = simpledialog.askstring("行へジャンプ", "ジャンプする行番号を入力してください:", parent=self.master)
        if line_str:
            try:
                line_number = int(line_str)
                self._jump_to_line_col(line_number, 0) # Jump to column 0 (start of line)
            except ValueError:
                messagebox.showerror("入力エラー", "有効な行番号を入力してください。")

    def _jump_to_line_col(self, line, col):
        """Jumps to a specific line and column number (internal use)."""
        if line is None: # If --line argument was not provided
            return

        # Ensure line number is valid
        if line < 1:
            line = 1
        
        # Get the total number of lines in the document
        last_line = int(self.text_area.index("end-1c").split('.')[0])
        if line > last_line:
            line = last_line

        self.text_area.mark_set(tk.INSERT, f"{line}.{col}")
        self.text_area.see(tk.INSERT) # Scroll to the cursor position
        self.update_status_bar() # Update status bar

    # --- Encoding Options ---
    def _add_encoding_options(self):
        """Populates the encoding submenu with common options."""
        encodings = ["utf-8", "shift_jis", "cp932", "euc_jp", "iso2022_jp"]
        # 'Auto-detect' option first
        self.encoding_menu.add_radiobutton(label="自動判別", value="auto",
                                            variable=self.selected_encoding,
                                            command=lambda: self._on_encoding_selected(is_auto=True))
        self.encoding_menu.add_separator()
        # Other explicit encoding options
        for enc in encodings:
            self.encoding_menu.add_radiobutton(label=enc.upper(), value=enc,
                                                variable=self.selected_encoding,
                                                command=self._on_encoding_selected)
    
    def _on_encoding_selected(self, is_auto=False):
        """Handles encoding selection from the menu."""
        if not is_auto:
            self.file_encoding = self.selected_encoding.get()
        else:
            self.file_encoding = "auto" # Set to 'auto' for detection on next file open
        self.update_status_bar()


    def _load_initial_file(self, file_path):
        """Loads the specified file initially, setting up title and status."""
        self.current_file = file_path
        # Force selected_encoding to 'auto' for initial load to ensure detection
        self.selected_encoding.set("auto") 
        self._read_file_with_encoding_detection(file_path)
        self.master.title(f"TOML Editor - {os.path.basename(file_path)}")
        self.text_area.edit_reset() # Reset modified flag after initial load
        self.update_status_bar()

    def _read_file_with_encoding_detection(self, file_path):
        """Reads a file, attempting to detect encoding if 'auto' is selected."""
        try:
            with open(file_path, "rb") as f:
                raw_data = f.read()

            if self.selected_encoding.get() == "auto":
                result = chardet.detect(raw_data)
                detected_encoding = result['encoding']
                confidence = result['confidence']

                if detected_encoding and confidence > 0.8:
                    self.file_encoding = detected_encoding
                else:
                    self.file_encoding = "utf-8" # Fallback to UTF-8 if detection is unreliable
                    messagebox.showwarning("文字コード判別",
                                           f"文字コードを自動判別できませんでした。UTF-8で開きます。\n"
                                           f"検出結果: {detected_encoding} (確信度: {confidence:.2f})")
            else:
                self.file_encoding = self.selected_encoding.get() # Use explicitly selected encoding

            content = raw_data.decode(self.file_encoding)
            self.text_area.delete(1.0, tk.END)
            self.text_area.insert(tk.END, content)
            self.selected_encoding.set(self.file_encoding) # Update menu selection to actual encoding

        except UnicodeDecodeError:
            messagebox.showerror("文字コードエラー",
                                   f"選択された文字コード '{self.file_encoding}' でファイルをデコードできませんでした。\n"
                                   "別の文字コードを試してください。")
            self.text_area.delete(1.0, tk.END)
            self.file_encoding = "utf-8" # Reset on error
            self.selected_encoding.set(self.file_encoding)
        except Exception as e:
            messagebox.showerror("エラー", f"ファイルの読み込み中にエラーが発生しました:\n{e}")
            self.text_area.delete(1.0, tk.END)
            self.file_encoding = "utf-8"
            self.selected_encoding.set(self.file_encoding)


    def handle_drop(self, event):
        """Handles file drag-and-drop events."""
        file_path = event.data.strip('{}')
        # Handle multiple file paths in case of D&D on Windows
        if ' ' in file_path and os.name == 'nt':
             file_paths = file_path.split('} {')
             file_path = file_paths[0].strip('{}')
        
        if os.path.isfile(file_path):
            if self.text_area.edit_modified():
                if messagebox.askyesno("未保存の変更", "現在のファイルを保存しますか？"):
                    self.save_file()
            self._load_initial_file(file_path) # Load via the common method
        else:
            messagebox.showwarning("D&Dエラー", "ドロップされたファイルが無効です。")


    def new_file(self):
        """Creates a new empty document."""
        if self.text_area.edit_modified():
            if not messagebox.askyesno("未保存の変更", "現在のファイルを保存しますか？"):
                self.text_area.delete(1.0, tk.END)
                self.current_file = None
                self.master.title("TOML Editor - 無題")
                self.text_area.edit_reset()
                self.file_encoding = "auto" # Reset to auto-detect for new file
                self.selected_encoding.set(self.file_encoding)
                self.update_status_bar()
                return

        self.text_area.delete(1.0, tk.END)
        self.current_file = None
        self.master.title("TOML Editor - 無題")
        self.text_area.edit_reset()
        self.file_encoding = "auto"
        self.selected_encoding.set(self.file_encoding)
        self.update_status_bar()


    def open_file(self):
        """Opens an existing file from a dialog."""
        if self.text_area.edit_modified():
            if messagebox.askyesno("未保存の変更", "現在のファイルを保存しますか？"):
                self.save_file()

        file_types = [
            ("INI files", "*.ini"), # Default to INI files in dialog
            ("TOML files", "*.toml"),
            ("Text files", "*.txt"),
            ("All files", "*.*")
        ]
        file_path = filedialog.askopenfilename(filetypes=file_types)
        if file_path:
            self._load_initial_file(file_path) # Load via the common method
            self.current_file = file_path
            self.master.title(f"TOML Editor - {os.path.basename(file_path)}")
            self.text_area.edit_reset()
        self.update_status_bar()

    def save_file(self):
        """Saves the current file, or calls save_file_as if no file is open."""
        if self.current_file:
            try:
                # If encoding is 'auto', save as UTF-8. Otherwise, use detected/selected encoding.
                encoding_to_save = self.file_encoding if self.file_encoding != "auto" else "utf-8"
                with open(self.current_file, "w", encoding=encoding_to_save) as file:
                    file.write(self.text_area.get(1.0, tk.END))
                self.master.title(f"TOML Editor - {os.path.basename(self.current_file)}")
                self.text_area.edit_reset()
            except Exception as e:
                messagebox.showerror("エラー", f"ファイルの保存中にエラーが発生しました:\n{e}")
        else:
            self.save_file_as()
        self.update_status_bar()

    def save_file_as(self):
        """Saves the current file with a new name/location."""
        file_types = [
            ("TOML files", "*.toml"), # Default extension for save dialog
            ("INI files", "*.ini"),
            ("Text files", "*.txt"),
            ("All files", "*.*")
        ]
        file_path = filedialog.asksaveasfilename(defaultextension=".toml", filetypes=file_types)
        if file_path:
            try:
                encoding_to_save = self.file_encoding if self.file_encoding != "auto" else "utf-8"
                with open(file_path, "w", encoding=encoding_to_save) as file:
                    file.write(self.text_area.get(1.0, tk.END))
                self.current_file = file_path
                self.master.title(f"TOML Editor - {os.path.basename(file_path)}")
                self.text_area.edit_reset()
            except Exception as e:
                messagebox.showerror("エラー", f"ファイルの保存中にエラーが発生しました:\n{e}")
        self.update_status_bar()

    # --- Find/Replace Functions ---
    def find_text(self):
        """Displays the Find dialog."""
        self.find_dialog = tk.Toplevel(self.master)
        self.find_dialog.title("検索")
        self.find_dialog.transient(self.master) # Make it a child of the main window
        self.find_dialog.grab_set() # Make it modal to the main window
        self.find_dialog.resizable(False, False)

        tk.Label(self.find_dialog, text="検索:").grid(row=0, column=0, padx=5, pady=5)
        self.find_entry = tk.Entry(self.find_dialog)
        self.find_entry.grid(row=0, column=1, padx=5, pady=5)
        self.find_entry.focus_set()

        # Add regex checkbox
        self.regex_var_find = tk.BooleanVar(value=False)
        tk.Checkbutton(self.find_dialog, text="正規表現", variable=self.regex_var_find).grid(row=1, column=1, sticky=tk.W, padx=5)

        tk.Button(self.find_dialog, text="次を検索", command=self._find_next).grid(row=0, column=2, padx=5, pady=5)
        tk.Button(self.find_dialog, text="閉じる", command=self.find_dialog.destroy).grid(row=1, column=2, padx=5, pady=5)

        self.find_entry.bind("<Return>", lambda event: self._find_next())

    def _find_next(self):
        """Finds and highlights the next occurrence of the search text."""
        search_text = self.find_entry.get()
        if not search_text:
            return

        is_regex = self.regex_var_find.get()
        
        start_index = self.text_area.index(tk.INSERT)
        self.text_area.tag_remove("found", "1.0", tk.END) # Clear previous highlight

        try:
            # Perform search based on regex flag
            if is_regex:
                pos = self.text_area.search(search_text, start_index, stopindex=tk.END, regexp=True, nocase=True)
            else:
                pos = self.text_area.search(search_text, start_index, stopindex=tk.END, nocase=True)
            
            if pos:
                # Determine the length of the matched text for highlighting
                if is_regex:
                    # Match only the part of the current line after 'pos'
                    line_content_after_pos = self.text_area.get(pos, f"{pos} lineend")
                    match_obj = re.match(search_text, line_content_after_pos, re.IGNORECASE)
                    if match_obj:
                        match_length = match_obj.end() - match_obj.start()
                    else:
                        match_length = len(search_text) # Fallback if re.match fails unexpectedly
                else:
                    match_length = len(search_text)

                end_pos = f"{pos}+{match_length}c"
                self.text_area.tag_add("found", pos, end_pos)
                self.text_area.tag_config("found", background="yellow")
                self.text_area.mark_set(tk.INSERT, end_pos) # Move cursor to end of found text
                self.text_area.see(tk.INSERT) # Scroll to cursor
            else:
                # If not found from current position, search from beginning of document
                if is_regex:
                    pos = self.text_area.search(search_text, "1.0", stopindex=tk.END, regexp=True, nocase=True)
                else:
                    pos = self.text_area.search(search_text, "1.0", stopindex=tk.END, nocase=True)
                    
                if pos:
                    if is_regex:
                        line_content_after_pos = self.text_area.get(pos, f"{pos} lineend")
                        match_obj = re.match(search_text, line_content_after_pos, re.IGNORECASE)
                        if match_obj:
                            match_length = match_obj.end() - match_obj.start()
                        else:
                            match_length = len(search_text)
                    else:
                        match_length = len(search_text)

                    end_pos = f"{pos}+{match_length}c"
                    self.text_area.tag_add("found", pos, end_pos)
                    self.text_area.tag_config("found", background="yellow")
                    self.text_area.mark_set(tk.INSERT, end_pos)
                    self.text_area.see(tk.INSERT)
                else:
                    messagebox.showinfo("検索", "テキストが見つかりませんでした。", parent=self.find_dialog)
        except re.error as e:
            messagebox.showerror("正規表現エラー", f"無効な正規表現です:\n{e}", parent=self.find_dialog)

    def replace_text(self):
        """Displays the Replace dialog."""
        self.replace_dialog = tk.Toplevel(self.master)
        self.replace_dialog.title("置換")
        self.replace_dialog.transient(self.master)
        self.replace_dialog.grab_set()
        self.replace_dialog.resizable(False, False)

        tk.Label(self.replace_dialog, text="検索:").grid(row=0, column=0, padx=5, pady=5)
        self.replace_find_entry = tk.Entry(self.replace_dialog)
        self.replace_find_entry.grid(row=0, column=1, padx=5, pady=5)
        self.replace_find_entry.focus_set()

        tk.Label(self.replace_dialog, text="置換:").grid(row=1, column=0, padx=5, pady=5)
        self.replace_entry = tk.Entry(self.replace_dialog)
        self.replace_entry.grid(row=1, column=1, padx=5, pady=5)

        # Add regex checkbox to replace dialog
        self.regex_var_replace = tk.BooleanVar(value=False)
        tk.Checkbutton(self.replace_dialog, text="正規表現", variable=self.regex_var_replace).grid(row=2, column=1, sticky=tk.W, padx=5)

        tk.Button(self.replace_dialog, text="次を検索", command=self._find_next_for_replace).grid(row=0, column=2, padx=5, pady=5)
        tk.Button(self.replace_dialog, text="置換", command=self._do_replace).grid(row=1, column=2, padx=5, pady=5)
        tk.Button(self.replace_dialog, text="すべて置換", command=self._do_replace_all).grid(row=2, column=2, padx=5, pady=5)
        tk.Button(self.replace_dialog, text="閉じる", command=self.replace_dialog.destroy).grid(row=3, column=2, padx=5, pady=5)

    def _find_next_for_replace(self):
        """Finds and highlights the next occurrence for replacement."""
        search_text = self.replace_find_entry.get()
        if not search_text:
            return

        is_regex = self.regex_var_replace.get()

        start_index = self.text_area.index(tk.INSERT)
        self.text_area.tag_remove("found", "1.0", tk.END)
        
        try:
            if is_regex:
                pos = self.text_area.search(search_text, start_index, stopindex=tk.END, regexp=True, nocase=True)
            else:
                pos = self.text_area.search(search_text, start_index, stopindex=tk.END, nocase=True)

            if pos:
                if is_regex:
                    line_content_after_pos = self.text_area.get(pos, f"{pos} lineend")
                    match_obj = re.match(search_text, line_content_after_pos, re.IGNORECASE)
                    if match_obj:
                        match_length = match_obj.end() - match_obj.start()
                    else:
                        match_length = len(search_text)
                else:
                    match_length = len(search_text)

                end_pos = f"{pos}+{match_length}c"
                self.text_area.tag_add("found", pos, end_pos)
                self.text_area.tag_config("found", background="yellow")
                self.text_area.mark_set(tk.INSERT, end_pos)
                self.text_area.see(tk.INSERT)
            else:
                # If not found from current position, search from beginning of document
                if is_regex:
                    pos = self.text_area.search(search_text, "1.0", stopindex=tk.END, regexp=True, nocase=True)
                else:
                    pos = self.text_area.search(search_text, "1.0", stopindex=tk.END, nocase=True)

                if pos:
                    if is_regex:
                        line_content_after_pos = self.text_area.get(pos, f"{pos} lineend")
                        match_obj = re.match(search_text, line_content_after_pos, re.IGNORECASE)
                        if match_obj:
                            match_length = match_obj.end() - match_obj.start()
                        else:
                            match_length = len(search_text)
                    else:
                        match_length = len(search_text)

                    end_pos = f"{pos}+{match_length}c"
                    self.text_area.tag_add("found", pos, end_pos)
                    self.text_area.tag_config("found", background="yellow")
                    self.text_area.mark_set(tk.INSERT, end_pos)
                    self.text_area.see(tk.INSERT)
                else:
                    messagebox.showinfo("検索", "テキストが見つかりませんでした。", parent=self.replace_dialog)
        except re.error as e:
            messagebox.showerror("正規表現エラー", f"無効な正規表現です:\n{e}", parent=self.replace_dialog)

    def _do_replace(self):
        """Replaces the currently selected and matched text."""
        search_text = self.replace_find_entry.get()
        replace_with = self.replace_entry.get()
        if not search_text:
            return

        is_regex = self.regex_var_replace.get()

        current_selection_start = self.text_area.index(tk.SEL_FIRST) if self.text_area.tag_ranges(tk.SEL) else None
        current_selection_end = self.text_area.index(tk.SEL_LAST) if self.text_area.tag_ranges(tk.SEL) else None

        try:
            if current_selection_start:
                selected_text = self.text_area.get(current_selection_start, current_selection_end)
                
                match = None
                if is_regex:
                    # For regex, check if the selected_text exactly matches the regex pattern
                    match = re.fullmatch(search_text, selected_text, re.IGNORECASE)
                else:
                    # For non-regex, check for simple case-insensitive equality
                    if selected_text.lower() == search_text.lower():
                        match = True

                if match:
                    self.text_area.replace(current_selection_start, current_selection_end, replace_with)
                    self.text_area.tag_remove("found", "1.0", tk.END)
                    # Move cursor past the replaced text and update
                    self.text_area.mark_set(tk.INSERT, f"{current_selection_start}+{len(replace_with)}c")
                    self.text_area.see(tk.INSERT)
                    self.text_area.edit_modified(True) # Mark as modified
                    self.on_text_modified() # Update title
                    self._find_next_for_replace() # Find next for potential subsequent replacements
                    return
            
            # If no selection, or selection doesn't match, just find the next one
            self._find_next_for_replace()

        except re.error as e:
            messagebox.showerror("正規表現エラー", f"無効な正規表現です:\n{e}", parent=self.replace_dialog)

    def _do_replace_all(self):
        """Replaces all occurrences of the search text."""
        search_text = self.replace_find_entry.get()
        replace_with = self.replace_entry.get()
        if not search_text:
            return

        is_regex = self.regex_var_replace.get()
        
        self.text_area.edit_separator() # Group all replacements into a single undo step

        count = 0
        start_pos = "1.0" # Start search from the beginning of the document
        
        try:
            while True:
                if is_regex:
                    pos = self.text_area.search(search_text, start_pos, stopindex=tk.END, regexp=True, nocase=True)
                else:
                    pos = self.text_area.search(search_text, start_pos, stopindex=tk.END, nocase=True)
                    
                if not pos: # No more occurrences found
                    break
                
                # Determine the actual length of the matched text for replacement
                if is_regex:
                    line_content_after_pos = self.text_area.get(pos, f"{pos} lineend")
                    match_obj = re.match(search_text, line_content_after_pos, re.IGNORECASE)
                    if match_obj:
                        match_length = match_obj.end() - match_obj.start()
                    else:
                        match_length = len(search_text) # Fallback, though ideally shouldn't happen
                else:
                    match_length = len(search_text)
                    
                end_pos = f"{pos}+{match_length}c"
                
                self.text_area.replace(pos, end_pos, replace_with)
                start_pos = f"{pos}+{len(replace_with)}c" # Continue search after the replaced text
                count += 1
            
            self.text_area.tag_remove("found", "1.0", tk.END) # Clear all highlights
            if count > 0:
                self.text_area.edit_modified(True) # Mark as modified if replacements occurred
                self.on_text_modified() # Update title bar
            messagebox.showinfo("置換", f"{count} 件を置換しました。", parent=self.replace_dialog)
        except re.error as e:
            messagebox.showerror("正規表現エラー", f"無効な正規表現です:\n{e}", parent=self.replace_dialog)

    def toggle_word_wrap(self):
        """Toggles word wrapping on/off in the text area."""
        if self.word_wrap_var.get():
            self.text_area.config(wrap=tk.WORD)
        else:
            self.text_area.config(wrap=tk.NONE)

    # --- Section Dialog Functions ---
    def show_section_dialog(self):
        """Displays the Section dialog, extracting sections from the current document."""
        self.section_dialog = tk.Toplevel(self.master)
        self.section_dialog.title("セクション")

        # Apply saved dimensions for the section dialog
        try:
            width = int(self.settings.get('section_dialog_width', 300))
            height = int(self.settings.get('section_dialog_height', 400))
            self.section_dialog.geometry(f"{width}x{height}")
        except ValueError:
            self.section_dialog.geometry("300x400")

        self.section_listbox = tk.Listbox(self.section_dialog)
        self.section_listbox.pack(expand=True, fill="both", padx=10, pady=10)
        self.section_listbox.bind("<<ListboxSelect>>", self._on_section_select)

        self._populate_sections() # Populate the listbox with sections

        close_button = tk.Button(self.section_dialog, text="閉じる", command=self._on_section_dialog_closing)
        close_button.pack(pady=5)

        # Register handler for closing the dialog
        self.section_dialog.protocol("WM_DELETE_WINDOW", self._on_section_dialog_closing)

    def _on_section_dialog_closing(self):
        """Handles closing the section dialog, saving its size."""
        self.settings['section_dialog_width'] = self.section_dialog.winfo_width()
        self.settings['section_dialog_height'] = self.section_dialog.winfo_height()
        write_ini(self.config_filepath, self.settings) # Save updated settings
        self.section_dialog.destroy()

    def _populate_sections(self):
        """Fills the section listbox with detected sections from the document."""
        self.section_listbox.delete(0, tk.END) # Clear previous items
        content = self.text_area.get(1.0, tk.END)
        parser = SimpleTomlParser()
        _, sections = parser.parse(content)
        self.parsed_sections = sections # Store parsed sections

        for extracted_section_name, _, _ in self.parsed_sections:
            self.section_listbox.insert(tk.END, extracted_section_name.strip())

    def _on_section_select(self, event):
        """Handles selection in the section listbox, jumping to the corresponding line."""
        selected_indices = self.section_listbox.curselection()
        if selected_indices:
            index = selected_indices[0]
            # Retrieve the original line content for accurate search
            extracted_section_name, line_number, original_line_content = self.parsed_sections[index]

            search_text = original_line_content.strip()

            self.text_area.tag_remove("found_section", "1.0", tk.END) # Clear previous section highlight

            # Define search range as the specific line
            start_index = f"{line_number}.0"
            end_index = f"{line_number}.end"

            # Search within that line for the exact text (case-insensitive)
            pos = self.text_area.search(search_text, start_index, stopindex=end_index, nocase=True)

            if pos:
                end_pos = f"{pos}+{len(search_text)}c"
                self.text_area.tag_add("found_section", pos, end_pos)
                self.text_area.tag_config("found_section", background="lightblue") # Highlight section
                self.text_area.mark_set(tk.INSERT, pos) # Move cursor to start of section
                self.text_area.see(tk.INSERT) # Scroll to cursor
            else:
                # Fallback: if search fails (e.g., text changed), just jump by line number
                self.text_area.mark_set(tk.INSERT, f"{line_number}.0")
                self.text_area.see(tk.INSERT)
                messagebox.showwarning("検索失敗", "セクションの元の行内容が見つかりませんでした。", parent=self.section_dialog)

            self.update_status_bar() # Update status bar after jumping

# --- Main Application Entry Point ---
def main():
    parser = argparse.ArgumentParser(description="簡易TOMLエディタ")
    parser.add_argument("input_file", nargs="?", default=None,
                        help="編集するTOMLファイルのパス (オプション)")
    parser.add_argument("--line", type=int, default=None,
                        help="開始行番号 (オプション)")
    parser.add_argument("--col", type=int, default=0,
                        help="開始桁番号 (オプション, デフォルトは0)")
    args = parser.parse_args()

    # Use TkinterDnD.Tk() for drag-and-drop functionality
    root = TkinterDnD.Tk()
    editor = TomlEditor(root, initial_file=args.input_file, initial_line=args.line, initial_col=args.col)
    root.mainloop()

if __name__ == "__main__":
    main()