import tkinter as tk
import tkinter.font as tkFont # フォントオブジェクトを扱うためにインポート
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
from PIL import Image, ImageTk # Pillow for image handling

# --- Constants ---
CONFIG_FILE = "iniedit.ini" # Configuration file for editor settings
TEXT_INI_FILE = "text.ini" # File for insertion data
COLOR_INI_FILE = "color.ini" # File for syntax highlighting

# --- INI File Reading/Writing Functions ---
def read_ini(filepath):
    """
    Reads settings from a simple INI file (key=val format, no sections required).
    Attempts to auto-detect encoding, falls back to utf-8.
    """
    settings = {}
    if not os.path.exists(filepath):
        return settings

    detected_encoding = 'utf-8'
    try:
        with open(filepath, 'rb') as f_raw:
            raw_data = f_raw.read()
            result = chardet.detect(raw_data)
            if result['encoding'] and result['confidence'] > 0.8:
                detected_encoding = result['encoding']
    except Exception as e:
        print(f"Warning: Could not detect encoding for {filepath}: {e}")

    try:
        with open(filepath, 'r', encoding=detected_encoding, errors='ignore') as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith('#'):
                    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} with encoding {detected_encoding}: {e}")
        messagebox.showerror("INIファイル読み込みエラー", f"'{os.path.basename(filepath)}' の読み込み中にエラーが発生しました:\n{e}")
    return settings

def write_ini(filepath, settings):
    """Writes settings to a simple 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}")

def parse_multiline_ini(filepath):
    """
    Parses a key-value file that may contain multi-line values enclosed in \"\"\"
    """
    settings = {}
    if not os.path.exists(filepath):
        return settings
    
    detected_encoding = 'utf-8'
    try:
        with open(filepath, 'rb') as f_raw:
            raw_data = f_raw.read()
            result = chardet.detect(raw_data)
            if result['encoding'] and result['confidence'] > 0.8:
                detected_encoding = result['encoding']
    except Exception as e:
        print(f"Warning: Could not detect encoding for {filepath}: {e}")

    try:
        with open(filepath, 'r', encoding=detected_encoding, errors='ignore') as f:
            content = f.read()
        
        in_multiline = False
        current_key = None
        current_value = []

        for line in content.splitlines():
            stripped_line = line.strip()
            
            if in_multiline:
                if stripped_line.endswith('"""'):
                    current_value.append(stripped_line.rstrip('"""'))
                    settings[current_key] = '\n'.join(current_value)
                    in_multiline = False
                    current_key = None
                    current_value = []
                else:
                    current_value.append(line)
            elif '=' in line:
                if stripped_line.startswith('#'): continue
                key, value_part = line.split('=', 1)
                key = key.strip()
                value_part = value_part.strip()
                
                if value_part.startswith('"""'):
                    in_multiline = True
                    current_key = key
                    if value_part.endswith('"""') and len(value_part) > 5:
                        settings[key] = value_part[3:-3]
                        in_multiline = False
                        current_key = None
                    else:
                        current_value = [value_part[3:]]
                else:
                    settings[key] = value_part
    except Exception as e:
        print(f"Error reading multiline INI file {filepath}: {e}")
        messagebox.showerror("INIファイル読み込みエラー", f"'{os.path.basename(filepath)}' の読み込み中にエラーが発生しました:\n{e}")
        
    return settings

# --- Simple TOML/INI Parser Class (for main editor content) ---
class SimpleTomlParser:
    """
    A simplified TOML/INI parser.
    Handles sections ([section]) and key=value pairs.
    """
    def __init__(self):
        self.data = {}
        self.sections = []

    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))
                
                match_inner_section = re.match(r"^\[([^\]]+)\].*", stripped_line)
                parsed_section_key = match_inner_section.group(1).strip() if match_inner_section else ""
                
                if parsed_section_key not in self.data:
                    self.data[parsed_section_key] = {}
                current_section = parsed_section_key
            
            elif "=" in stripped_line:
                try:
                    key, value = stripped_line.split("=", 1)
                    key = key.strip()
                    value = value.strip()
                    
                    target_section_data = self.data.setdefault(current_section, {})
                    target_section_data[key] = value
                except ValueError:
                    pass
        return self.data, self.sections

# --- ToolTip Class ---
class ToolTip:
    """Provides a tooltip for a given widget."""
    def __init__(self, widget, text):
        self.widget = widget
        self.text = text
        self.tip_window = None
        self.id = None
        self.widget.bind("<Enter>", self.enter)
        self.widget.bind("<Leave>", self.leave)
        self.widget.bind("<ButtonPress>", self.leave)

    def enter(self, event=None): self.schedule()
    def leave(self, event=None): self.unschedule(); self.hide()
    def schedule(self): self.unschedule(); self.id = self.widget.after(500, self.show)
    def unschedule(self):
        if self.id: self.widget.after_cancel(self.id)
        self.id = None

    def show(self):
        if self.tip_window or not self.text: return
        x, y, _, _ = self.widget.bbox("insert")
        x = x + self.widget.winfo_rootx() + 25
        y = y + self.widget.winfo_rooty() + 20
        self.tip_window = tw = tk.Toplevel(self.widget)
        tw.wm_overrideredirect(True)
        tw.wm_geometry(f"+{x}+{y}")
        label = tk.Label(tw, text=self.text, background="#ffffe0", relief=tk.SOLID, borderwidth=1, font=("tahoma", "8", "normal"))
        label.pack(ipadx=1)

    def hide(self):
        if self.tip_window: self.tip_window.destroy()
        self.tip_window = None

# --- Main Editor Class ---
class TomlEditor:
    def __init__(self, master, initial_file=None, initial_line=None, initial_col=None):
        self.master = master
        self.loading = True
        master.title("Inifile Editor")
        self.last_search_term = ""
        self.last_search_is_regex = False
        self.last_search_case_sensitive = False
        self.script_dir = os.path.dirname(os.path.abspath(__file__))
        self.config_filepath = os.path.join(self.script_dir, CONFIG_FILE)
        self.text_ini_filepath = os.path.join(self.script_dir, TEXT_INI_FILE)
        self.color_ini_filepath = os.path.join(self.script_dir, COLOR_INI_FILE)
        self.settings = read_ini(self.config_filepath)
        self._apply_saved_settings()
        
        # Font setup
        font_family = self.settings.get('font_family', 'Consolas')
        try: font_size = int(self.settings.get('font_size', 11))
        except ValueError: font_size = 11
        self.editor_font = tkFont.Font(family=font_family, size=font_size)
        
        self.current_file = initial_file
        self.file_encoding = "auto"
        self.parser = SimpleTomlParser()
        self.text_ini_data = {}
        
        # Highlighting setup
        self.highlight_rules = []
        self.highlight_fonts = {}

        # --- UI Setup ---
        self.toolbar = tk.Frame(master, bd=1, relief=tk.RAISED)
        self.text_area = scrolledtext.ScrolledText(master, wrap=tk.WORD, undo=True, font=self.editor_font)
        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)
        self.text_area.drop_target_register(DND_FILES)
        self.text_area.dnd_bind("<<Drop>>", self.handle_drop)
        self.context_menu = tk.Menu(self.text_area, tearoff=0)
        self.text_area.bind("<Button-3>", self._show_context_menu)
        self.status_bar = tk.Label(master, text="行: 1, 桁: 1 | 文字コード: 自動判別", bd=1, relief=tk.SUNKEN, anchor=tk.W)
        
        self._add_toolbar_buttons()
        self._create_menus()
        
        self.toolbar.pack(side=tk.TOP, fill=tk.X)
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        self.text_area.pack(expand=True, fill="both")
        
        self._bind_shortcuts()
        self.update_status_bar()
        
        if self.current_file:
            self._load_initial_file(self.current_file)
            if initial_line:
                self.master.after(100, lambda: self._jump_to_line_col(initial_line, initial_col))
        else:
            self.master.title("Inifile Editor - 無題")
            self.status_bar.config(text="行: 1, 桁: 1 | 文字コード: 自動判別")
        
        self._load_text_ini_data()
        self.master.protocol("WM_DELETE_WINDOW", self._on_closing)
        self.master.after(100, self._finish_loading)

    def _finish_loading(self):
        """Finalize the loading process after the main loop has started."""
        self.loading = False
        self.text_area.edit_reset()
        self.on_text_modified()

    def _add_toolbar_buttons(self):
        self.icons = {}
        icon_size = (18, 18)
        icon_paths = { "new": "icons/new.png", "open": "icons/open.png", "save": "icons/save.png", "find": "icons/find.png", "replace": "icons/replace.png", "undo": "icons/undo.png", "redo": "icons/redo.png", "cut": "icons/cut.png", "copy": "icons/copy.png", "paste": "icons/paste.png", "sections": "icons/sections.png", }
        for name, path in icon_paths.items():
            path = os.path.join(self.script_dir, path)
            try:
                img = Image.open(path).resize(icon_size, Image.Resampling.LANCZOS)
                self.icons[name] = ImageTk.PhotoImage(img)
            except Exception as e:
                print(f"Error loading icon {path}: {e}")
                self.icons[name] = None
        
        buttons_info = [
            ("new", self.new_file, "新規ファイル (Ctrl+N)"),
            ("open", self.open_file, "ファイルを開く (Ctrl+O)"),
            ("save", self.save_file, "ファイルを保存 (Ctrl+S)"),
            ("separator",),
            ("find", self.find_text, "テキストを検索 (Ctrl+F)"),
            ("replace", self.replace_text, "テキストを置換 (Ctrl+H)"),
            ("separator",),
            ("undo", self.text_area.edit_undo, "元に戻す (Ctrl+Z)"),
            ("redo", self.text_area.edit_redo, "やり直し (Ctrl+Y)"),
            ("separator",),
            ("cut", lambda: self.text_area.event_generate("<<Cut>>"), "切り取り (Ctrl+X)"),
            ("copy", lambda: self.text_area.event_generate("<<Copy>>"), "コピー (Ctrl+C)"),
            ("paste", lambda: self.text_area.event_generate("<<Paste>>"), "貼り付け (Ctrl+V)"),
            ("separator",),
            ("sections", self.show_section_dialog, "セクションダイアログを表示"),
        ]
        for info in buttons_info:
            if info[0] == "separator":
                tk.Frame(self.toolbar, width=1, bg="gray").pack(side=tk.LEFT, fill=tk.Y, padx=5)
                continue
            name, command, tooltip = info
            btn = tk.Button(self.toolbar, command=command)
            if self.icons.get(name): btn.config(image=self.icons[name])
            else: btn.config(text=name.capitalize())
            btn.pack(side=tk.LEFT, padx=2, pady=2)
            ToolTip(btn, tooltip)

    def _create_menus(self):
        self.menubar = tk.Menu(self.master)
        self.master.config(menu=self.menubar)
        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, accelerator="Ctrl+N")
        file_menu.add_command(label="開く", command=self.open_file, accelerator="Ctrl+O")
        file_menu.add_command(label="保存", command=self.save_file, accelerator="Ctrl+S")
        file_menu.add_command(label="名前を付けて保存...", command=self.save_file_as, accelerator="Ctrl+Shift+S")
        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()
        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")
        self._add_encoding_options()
        file_menu.add_separator()
        file_menu.add_command(label="終了", command=self._on_closing, accelerator="Ctrl+Q")
        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, accelerator="Ctrl+F")
        edit_menu.add_command(label="次を検索", command=self.find_next_menu, accelerator="F3")
        edit_menu.add_command(label="前を検索", command=self.find_previous_menu, accelerator="Shift+F3")
        edit_menu.add_command(label="置換...", command=self.replace_text, accelerator="Ctrl+H")
        edit_menu.add_separator()
        edit_menu.add_command(label="元に戻す", command=self.text_area.edit_undo, accelerator="Ctrl+Z")
        edit_menu.add_command(label="やり直し", command=self.text_area.edit_redo, accelerator="Ctrl+Y")
        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>>"), accelerator="Ctrl+X")
        edit_menu.add_command(label="コピー", command=lambda: self.text_area.event_generate("<<Copy>>"), accelerator="Ctrl+C")
        edit_menu.add_command(label="貼り付け", command=lambda: self.text_area.event_generate("<<Paste>>"), accelerator="Ctrl+V")
        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 = tk.Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="ツール", menu=tools_menu)
        tools_menu.add_command(label="セクションダイアログ", command=self.show_section_dialog)
        tools_menu.add_command(label="ハイライト適用", command=self.manual_apply_highlighting)
        self._add_external_commands_to_menu(tools_menu)

    def _bind_shortcuts(self):
        self.master.bind("<Control-n>", lambda event: self.new_file())
        self.master.bind("<Control-o>", lambda event: self.open_file())
        self.master.bind("<Control-s>", lambda event: self.save_file())
        self.master.bind("<Control-Shift-S>", lambda event: self.save_file_as())
        self.master.bind("<Control-q>", lambda event: self._on_closing())
        self.master.bind("<Control-f>", lambda event: self.find_text())
        self.master.bind("<F3>", lambda event: self.find_next_menu())
        self.master.bind("<Shift-F3>", lambda event: self.find_previous_menu())
        self.master.bind("<Control-h>", lambda event: self.replace_text())
        self.master.bind("<Control-z>", lambda event: self.text_area.edit_undo())
        self.master.bind("<Control-y>", lambda event: self.text_area.edit_redo())

    # --- Highlighting Methods ---
    def manual_apply_highlighting(self):
        """Loads rules, prints them, and applies highlighting."""
        print("--- Applying Syntax Highlighting ---")
        self._setup_highlighting()
        self._apply_highlighting()
        print("--- Highlighting complete. ---")

    def _setup_highlighting(self):
        """Loads highlighting rules and configures tags."""
        self.highlight_rules = []
        self.highlight_fonts = {} 
        highlight_data = read_ini(self.color_ini_filepath)

        print("Reading color.ini for highlighting rules...")
        for i, (pattern_str, format_str) in enumerate(highlight_data.items()):
            try:
                style, color = format_str.split(':')
                style = style.lower().strip()
                color = color.strip()
                tag_name = f"highlight_{i}"

                family = self.editor_font.cget("family")
                size = self.editor_font.cget("size")
                weight = "normal"
                slant = "roman"

                if style == 'bold':
                    weight = 'bold'
                elif style == 'italic':
                    slant = 'italic'
                
                new_font = tkFont.Font(family=family, size=size, weight=weight, slant=slant)
                
                self.highlight_fonts[tag_name] = new_font
                self.text_area.tag_configure(tag_name, foreground=color, font=new_font)
                
                self.highlight_rules.append((re.compile(pattern_str), tag_name))
                
                print(f"  Rule {i}: Pattern='{pattern_str}', Style='{style}', Color='{color}', Tag='{tag_name}'")

            except Exception as e:
                print(f"  Invalid highlight rule for '{pattern_str}': {e}")

    def _apply_highlighting(self):
        """Applies syntax highlighting to the text area based on pre-configured rules."""
        for _, tag_name in self.highlight_rules:
            self.text_area.tag_remove(tag_name, "1.0", tk.END)

        content = self.text_area.get("1.0", tk.END)
        for pattern, tag_name in self.highlight_rules:
            for match in pattern.finditer(content):
                start, end = match.span()
                start_index = self.text_area.index(f"1.0 + {start} chars")
                end_index = self.text_area.index(f"1.0 + {end} chars")
                self.text_area.tag_add(tag_name, start_index, end_index)

    # --- Other Methods ---
    def find_next_menu(self):
        if self.last_search_term: self._perform_find(self.last_search_term, self.last_search_is_regex, self.last_search_case_sensitive, backwards=False)
        else: self.find_text()

    def find_previous_menu(self):
        if self.last_search_term: self._perform_find(self.last_search_term, self.last_search_is_regex, self.last_search_case_sensitive, backwards=True)
        else: self.find_text()

    def _substitute_variables(self, command_template):
        """Substitutes {{var}} placeholders in a string."""
        if self.current_file:
            command_template = command_template.replace("{{file_path}}", f'"{self.current_file}"')

        def replacer(match):
            var_name = match.group(1)
            if var_name in self.settings:
                return self.settings[var_name]
            return os.environ.get(var_name, "")

        return re.sub(r'\{\{(.*?)\}\}', replacer, command_template)

    def _add_external_commands_to_menu(self, menu):
        added_any = False
        for i in range(1, 10):
            cmd_name = self.settings.get(f'external_cmd{i}_name')
            cmd_path = self.settings.get(f'external_cmd{i}_path')
            if cmd_name and cmd_path:
                if not added_any: menu.add_separator(); 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()

    def _run_external_command(self, command_template):
        command = self._substitute_variables(command_template)
        try:
#            cwd = os.path.dirname(self.current_file) if self.current_file else self.script_dir
            if not self.current_file or not os.path.exists(self.current_file):
                cwd = self.script_dir
            else:
                cwd = os.path.dirname(self.current_file)
            subprocess.Popen(command, shell=True, cwd=cwd)
        except Exception as e:
            messagebox.showerror("コマンド実行エラー", f"コマンドの実行に失敗しました:\n{command}\nエラー: {e}")

    def _open_file_location(self):
        if not self.current_file or not os.path.exists(self.current_file):
            directory = self.script_dir
        else:
            directory = os.path.dirname(self.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)
        if os.name == 'nt': subprocess.Popen(f'explorer "{directory}"')
        elif os.uname().sysname == 'Darwin': subprocess.Popen(['open', directory])
        else: subprocess.Popen(['xdg-open', directory])

    def _open_terminal(self):
        if not self.current_file or not os.path.exists(self.current_file):
            directory = self.script_dir
        else:
            directory = os.path.dirname(self.current_file)
        
        if os.name == 'nt':
            os.startfile('cmd.exe', cwd=directory)
        else:
            terminal_cmd = os.environ.get('TERMINAL', 'x-terminal-emulator')
            try:
                subprocess.Popen([terminal_cmd], cwd=directory)
            except FileNotFoundError:
                messagebox.showerror("エラー", f"ターミナル '{terminal_cmd}' が見つかりません。")

    def _apply_saved_settings(self):
        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):
        if self.text_area.edit_modified():
            response = messagebox.askyesnocancel("終了", "変更が保存されていません。保存しますか？")
            if response is True: # Yes
                self.save_file()
            elif response is None: # Cancel
                return
        
        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):
        if self.loading:
            return
        title = "Inifile Editor - "
        filename = os.path.basename(self.current_file) if self.current_file else "無題"
        if self.text_area.edit_modified():
            title += f"{filename}*"
        else:
            title += filename
        self.master.title(title)
        
    def update_status_bar(self, event=None):
        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 _load_text_ini_data(self):
        self.text_ini_data = parse_multiline_ini(self.text_ini_filepath)

    def _show_context_menu(self, event):
        self.context_menu.delete(0, tk.END)
        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()
        
        insert_ini_menu = tk.Menu(self.context_menu, tearoff=0)
        self.context_menu.add_cascade(label=f"{TEXT_INI_FILE}から挿入", menu=insert_ini_menu)

        if self.text_ini_data:
            nested_data = {}
            for key, value in self.text_ini_data.items():
                parts = key.split('\\')
                d = nested_data
                for part in parts[:-1]:
                    d = d.setdefault(part, {})
                d[parts[-1]] = value
            
            self._build_dynamic_menu(insert_ini_menu, nested_data)
        else:
            insert_ini_menu.add_command(label=f"({TEXT_INI_FILE}が空か見つかりません)", state=tk.DISABLED)
        
        self.context_menu.add_separator()
        self.context_menu.add_command(label=f"{TEXT_INI_FILE}再読み込み", command=self._load_text_ini_data)
        
        try: self.context_menu.tk_popup(event.x_root, event.y_root)
        finally: self.context_menu.grab_release()

    def _build_dynamic_menu(self, parent_menu, data_dict):
        for key, value in sorted(data_dict.items()):
            if isinstance(value, dict):
                submenu = tk.Menu(parent_menu, tearoff=0)
                parent_menu.add_cascade(label=key, menu=submenu)
                self._build_dynamic_menu(submenu, value)
            else:
                parent_menu.add_command(label=key, command=lambda v=value: self._insert_value_from_ini(v))

    def _insert_value_from_ini(self, value):
        insert_text = str(value) + '\n'
        self.text_area.insert(tk.INSERT, insert_text)
        self.text_area.edit_modified(True)

    def _jump_to_start(self): self.text_area.mark_set(tk.INSERT, "1.0"); self.text_area.see(tk.INSERT); self.update_status_bar()
    def _jump_to_end(self): self.text_area.mark_set(tk.INSERT, tk.END); self.text_area.see(tk.INSERT); self.update_status_bar()
    def _jump_to_line(self):
        line_str = simpledialog.askstring("行へジャンプ", "ジャンプする行番号を入力してください:", parent=self.master)
        if line_str:
            try: self._jump_to_line_col(int(line_str), 0)
            except ValueError: messagebox.showerror("入力エラー", "有効な行番号を入力してください。")

    def _jump_to_line_col(self, line, col):
        if line is None: return
        if line < 1: line = 1
        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)
        self.update_status_bar()

    def _add_encoding_options(self):
        encodings = ["utf-8", "shift_jis", "cp932", "euc_jp", "iso2022_jp"]
        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()
        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):
        self.file_encoding = "auto" if is_auto else self.selected_encoding.get()
        self.update_status_bar()

    def _load_initial_file(self, file_path):
        self.loading = True
        self.current_file = file_path
        self.selected_encoding.set("auto")
        self._read_file_with_encoding_detection(file_path)
        self.master.title(f"Inifile Editor - {os.path.basename(file_path)}")
        self.update_status_bar()
        self.master.after(50, self._finish_loading)

    def _read_file_with_encoding_detection(self, file_path):
        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']
                if detected_encoding and result['confidence'] > 0.8: self.file_encoding = detected_encoding
                else: self.file_encoding = "utf-8"
            else: self.file_encoding = self.selected_encoding.get()
            content = raw_data.decode(self.file_encoding, errors='ignore')
            self.text_area.delete(1.0, tk.END)
            self.text_area.insert(1.0, content)
            self.selected_encoding.set(self.file_encoding)
        except Exception as e:
            messagebox.showerror("エラー", f"ファイルの読み込み中にエラーが発生しました:\n{e}")
            self.text_area.delete(1.0, tk.END)

    def handle_drop(self, event):
        file_path = event.data.strip('{}')
        if ' ' in file_path and os.name == 'nt': file_path = file_path.split('} {')[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)
        else: messagebox.showwarning("D&Dエラー", "ドロップされたファイルが無効です。")

    def new_file(self):
        if self.text_area.edit_modified():
            if not messagebox.askyesno("未保存の変更", "現在のファイルを保存しますか？"): self.save_file()
        self.loading = True
        self.text_area.delete(1.0, tk.END)
        self.current_file = None
        self.master.title("Inifile Editor - 無題")
        self.file_encoding = "auto"
        self.selected_encoding.set(self.file_encoding)
        self.update_status_bar()
        self.master.after(50, self._finish_loading)

    def open_file(self):
        if self.text_area.edit_modified():
            if messagebox.askyesno("未保存の変更", "現在のファイルを保存しますか？"): self.save_file()
        file_types = [("INI files", "*.ini"), ("TOML files", "*.toml"), ("Text files", "*.txt"), ("All files", "*.*")]
        file_path = filedialog.askopenfilename(filetypes=file_types)
        if file_path: self._load_initial_file(file_path)

    def _save_content(self, 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, newline='') as file:
                content = self.text_area.get(1.0, tk.END).rstrip('\n')
                file.write(content)
            self.current_file = file_path
            self.master.title(f"Inifile Editor - {os.path.basename(file_path)}")
            self.text_area.edit_reset()
        except Exception as e:
            messagebox.showerror("エラー", f"ファイルの保存中にエラーが発生しました:\n{e}")

    def save_file(self):
        if self.current_file: self._save_content(self.current_file)
        else: self.save_file_as()

    def save_file_as(self):
        file_types = [("INI files", "*.ini"), ("TOML files", "*.toml"), ("Text files", "*.txt"), ("All files", "*.*")]
        file_path = filedialog.asksaveasfilename(defaultextension=".toml", filetypes=file_types)
        if file_path: self._save_content(file_path)

    def _on_find_dialog_closing(self):
        self.settings['find_dialog_width'] = self.find_dialog.winfo_width()
        self.settings['find_dialog_height'] = self.find_dialog.winfo_height()
        write_ini(self.config_filepath, self.settings)
        self.find_dialog.destroy()

    def find_text(self):
        if hasattr(self, 'find_dialog') and self.find_dialog.winfo_exists():
            self.find_dialog.lift(); self.find_dialog.focus_set(); return
        self.find_dialog = tk.Toplevel(self.master)
        self.find_dialog.title("検索")
        try:
            width = int(self.settings.get('find_dialog_width', 350))
            height = int(self.settings.get('find_dialog_height', 150))
            self.find_dialog.geometry(f"{width}x{height}")
        except ValueError: self.find_dialog.geometry("350x150")
        self.find_dialog.protocol("WM_DELETE_WINDOW", self._on_find_dialog_closing)
        self.find_dialog.grid_columnconfigure(1, weight=1)
        tk.Label(self.find_dialog, text="検索:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.find_entry = tk.Entry(self.find_dialog)
        self.find_entry.grid(row=0, column=1, columnspan=2, padx=5, pady=5, sticky="we")
        self.find_entry.focus_set()
        
        check_frame = tk.Frame(self.find_dialog)
        check_frame.grid(row=1, column=0, columnspan=2, sticky=tk.W, padx=5)
        self.find_regex_var = tk.BooleanVar(value=False)
        tk.Checkbutton(check_frame, text="正規表現", variable=self.find_regex_var).pack(side=tk.LEFT)
        self.find_case_var = tk.BooleanVar(value=False)
        tk.Checkbutton(check_frame, text="大文字小文字を区別する", variable=self.find_case_var).pack(side=tk.LEFT)

        button_frame = tk.Frame(self.find_dialog)
        button_frame.grid(row=2, column=0, columnspan=3, pady=5)
        tk.Button(button_frame, text="前を検索", command=self._find_previous).pack(side=tk.LEFT, padx=5)
        tk.Button(button_frame, text="次を検索", command=self._find_next).pack(side=tk.LEFT, padx=5)
        tk.Button(button_frame, text="閉じる", command=self._on_find_dialog_closing).pack(side=tk.LEFT, padx=5)
        self.find_dialog.bind("<Return>", lambda e: self._find_next())
        self.find_dialog.bind("<Shift-Return>", lambda e: self._find_previous())
        self.find_dialog.bind("<Escape>", lambda e: self._on_find_dialog_closing())

    def _find_next(self):
        self.last_search_term = self.find_entry.get()
        self.last_search_is_regex = self.find_regex_var.get()
        self.last_search_case_sensitive = self.find_case_var.get()
        self._perform_find(self.last_search_term, self.last_search_is_regex, self.last_search_case_sensitive, backwards=False)

    def _find_previous(self):
        self.last_search_term = self.find_entry.get()
        self.last_search_is_regex = self.find_regex_var.get()
        self.last_search_case_sensitive = self.find_case_var.get()
        self._perform_find(self.last_search_term, self.last_search_is_regex, self.last_search_case_sensitive, backwards=True)

    def _perform_find(self, search_text, is_regex, case_sensitive, backwards=False):
        if not search_text: return
        start_index = self.text_area.index(tk.INSERT)
        self.text_area.tag_remove("found", "1.0", tk.END)
        try:
            nocase = not case_sensitive
            pos = self.text_area.search(search_text, start_index, stopindex="1.0" if backwards else tk.END, backwards=backwards, regexp=is_regex, nocase=nocase)
            if not pos:
                pos = self.text_area.search(search_text, tk.END if backwards else "1.0", stopindex="1.0" if backwards else tk.END, backwards=backwards, regexp=is_regex, nocase=nocase)
                if not pos: messagebox.showinfo("検索", "テキストが見つかりませんでした。"); return
            
            if is_regex:
                flags = 0 if case_sensitive else re.IGNORECASE
                content_after = self.text_area.get(pos, f"{pos} lineend")
                match = re.match(search_text, content_after, flags)
                match_len = len(match.group(0)) if match else 0
            else: match_len = len(search_text)
            
            end_pos = f"{pos}+{match_len}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, pos if backwards else end_pos)
            self.text_area.see(tk.INSERT)
        except re.error as e: messagebox.showerror("正規表現エラー", f"無効な正規表現です:\n{e}")
        
    def _on_replace_dialog_closing(self):
        self.settings['replace_dialog_width'] = self.replace_dialog.winfo_width()
        self.settings['replace_dialog_height'] = self.replace_dialog.winfo_height()
        write_ini(self.config_filepath, self.settings)
        self.replace_dialog.destroy()

    def replace_text(self):
        if hasattr(self, 'replace_dialog') and self.replace_dialog.winfo_exists():
            self.replace_dialog.lift(); self.replace_dialog.focus_set(); return
        self.replace_dialog = tk.Toplevel(self.master)
        self.replace_dialog.title("置換")
        try:
            width = int(self.settings.get('replace_dialog_width', 400))
            height = int(self.settings.get('replace_dialog_height', 180))
            self.replace_dialog.geometry(f"{width}x{height}")
        except ValueError: self.replace_dialog.geometry("400x180")
        self.replace_dialog.protocol("WM_DELETE_WINDOW", self._on_replace_dialog_closing)
        self.replace_dialog.grid_columnconfigure(1, weight=1)
        
        tk.Label(self.replace_dialog, text="検索:").grid(row=0, column=0, padx=5, pady=2, sticky=tk.W)
        self.replace_find_entry = tk.Entry(self.replace_dialog)
        self.replace_find_entry.grid(row=0, column=1, padx=5, pady=2, sticky="we")
        self.replace_find_entry.focus_set()
        
        tk.Label(self.replace_dialog, text="置換:").grid(row=1, column=0, padx=5, pady=2, sticky=tk.W)
        self.replace_entry = tk.Entry(self.replace_dialog)
        self.replace_entry.grid(row=1, column=1, padx=5, pady=2, sticky="we")
        
        check_frame = tk.Frame(self.replace_dialog)
        check_frame.grid(row=2, column=1, sticky=tk.W, padx=5)
        self.replace_regex_var = tk.BooleanVar(value=False)
        tk.Checkbutton(check_frame, text="正規表現", variable=self.replace_regex_var).pack(side=tk.LEFT)
        self.replace_case_var = tk.BooleanVar(value=False)
        tk.Checkbutton(check_frame, text="大文字小文字を区別する", variable=self.replace_case_var).pack(side=tk.LEFT)

        button_frame = tk.Frame(self.replace_dialog)
        button_frame.grid(row=0, column=2, rowspan=4, padx=5)
        tk.Button(button_frame, text="次を検索", command=self._find_next_for_replace).pack(fill=tk.X, pady=2)
        tk.Button(button_frame, text="置換", command=self._do_replace).pack(fill=tk.X, pady=2)
        tk.Button(button_frame, text="すべて置換", command=self._do_replace_all).pack(fill=tk.X, pady=2)
        tk.Button(button_frame, text="閉じる", command=self._on_replace_dialog_closing).pack(fill=tk.X, pady=2)
        self.replace_dialog.bind("<Escape>", lambda e: self._on_replace_dialog_closing())

    def _find_next_for_replace(self):
        search_text = self.replace_find_entry.get()
        is_regex = self.replace_regex_var.get()
        case_sensitive = self.replace_case_var.get()
        self._perform_find(search_text, is_regex, case_sensitive, backwards=False)

    def _do_replace(self):
        if not self.text_area.tag_ranges(tk.SEL):
            self._find_next_for_replace()
            return
        search_text = self.replace_find_entry.get()
        replace_with = self.replace_entry.get()
        is_regex = self.replace_regex_var.get()
        case_sensitive = self.replace_case_var.get()
        try:
            selected_text = self.text_area.get(tk.SEL_FIRST, tk.SEL_LAST)
            match = False
            if is_regex:
                flags = 0 if case_sensitive else re.IGNORECASE
                if re.fullmatch(search_text, selected_text, flags): match = True
            elif not case_sensitive and selected_text.lower() == search_text.lower(): match = True
            elif case_sensitive and selected_text == search_text: match = True
            
            if match:
                self.text_area.delete(tk.SEL_FIRST, tk.SEL_LAST)
                self.text_area.insert(tk.SEL_FIRST, replace_with)
                self.text_area.edit_modified(True)
            self._find_next_for_replace()
        except re.error as e: messagebox.showerror("正規表現エラー", f"無効な正規表現です:\n{e}", parent=self.replace_dialog)

    def _do_replace_all(self):
        search_text = self.replace_find_entry.get()
        replace_with = self.replace_entry.get()
        if not search_text: return
        is_regex = self.replace_regex_var.get()
        case_sensitive = self.replace_case_var.get()
        content = self.text_area.get(1.0, tk.END)
        try:
            flags = 0 if case_sensitive else re.IGNORECASE
            if is_regex:
                new_content, count = re.subn(search_text, replace_with, content, flags=flags)
            else:
                new_content, count = re.subn(re.escape(search_text), replace_with, content, flags=flags)

            if count > 0:
                self.text_area.delete(1.0, tk.END)
                self.text_area.insert(1.0, new_content)
                self.text_area.edit_modified(True)
            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):
        self.text_area.config(wrap=tk.WORD if self.word_wrap_var.get() else tk.NONE)

    def show_section_dialog(self):
        if hasattr(self, 'section_dialog') and self.section_dialog.winfo_exists():
            self.section_dialog.lift(); return
        self.section_dialog = tk.Toplevel(self.master)
        self.section_dialog.title("セクション")
        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")
        
        top_frame = tk.Frame(self.section_dialog)
        top_frame.pack(fill=tk.X, padx=5, pady=5)
        
        self.section_regex_var = tk.StringVar(value=r'^\s*\[.*\]')
        
        regex_entry = tk.Entry(top_frame, textvariable=self.section_regex_var)
        regex_entry.pack(side=tk.LEFT, expand=True, fill=tk.X)
        
        search_button = tk.Button(top_frame, text="検索", command=self._search_and_populate_sections)
        search_button.pack(side=tk.LEFT, padx=(5, 0))
        
        self.section_case_var = tk.BooleanVar(value=False)
        case_check = tk.Checkbutton(self.section_dialog, text="大文字小文字を区別する", variable=self.section_case_var, command=self._search_and_populate_sections)
        case_check.pack(anchor=tk.W, padx=5)

        self.section_listbox = tk.Listbox(self.section_dialog)
        self.section_listbox.pack(expand=True, fill="both", padx=5, pady=(0, 5))
        self.section_listbox.bind("<<ListboxSelect>>", self._on_section_select)
        
        self.section_dialog.protocol("WM_DELETE_WINDOW", self._on_section_dialog_closing)
        
        self._search_and_populate_sections()

    def _on_section_dialog_closing(self):
        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)
        self.section_dialog.destroy()

    def _search_and_populate_sections(self):
        self.section_listbox.delete(0, tk.END)
        self.found_sections_list = []
        regex_str = self.section_regex_var.get()
        if not regex_str: return
        
        try:
            flags = 0 if self.section_case_var.get() else re.IGNORECASE
            pattern = re.compile(regex_str, flags)
        except re.error as e:
            messagebox.showerror("正規表現エラー", f"無効な正規表現です:\n{e}", parent=self.section_dialog)
            return
            
        content = self.text_area.get(1.0, tk.END)
        lines = content.splitlines()
        nlines = len(lines)
        for i, line in enumerate(lines):
            if pattern.search(line):
                caption = None
                if nlines > i+1:
                    _aa = lines[i+1].split('=', 1)
                    if len(_aa) > 1 and _aa[0].lower() == 'caption':
                        caption = _aa[1].strip()

                self.found_sections_list.append((line.strip(), i + 1))
                if caption is not None:
                    self.section_listbox.insert(tk.END, f"{line.strip()} ({caption})")
                else:
                    self.section_listbox.insert(tk.END, line.strip())

    def _on_section_select(self, event):
        selected_indices = self.section_listbox.curselection()
        if selected_indices:
            index = selected_indices[0]
            _, line_number = self.found_sections_list[index]
            self.text_area.mark_set(tk.INSERT, f"{line_number}.0")
            self.text_area.see(tk.INSERT)
            self.text_area.tag_remove("found_section", "1.0", tk.END)
            self.text_area.tag_add("found_section", f"{line_number}.0", f"{line_number}.end")
            self.text_area.tag_config("found_section", background="lightblue")
            self.update_status_bar()

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