import sys
import re
import json
import os
from PySide6.QtWidgets import (QApplication, QMainWindow, QTextEdit,
                             QFileDialog, QMessageBox, QComboBox, QToolBar, QStatusBar,
                             QDockWidget, QListWidget, QListWidgetItem,
                             QLineEdit, QPushButton, QLabel, QVBoxLayout, QWidget, QInputDialog)
from PySide6.QtGui import (QAction, QFont, QTextCursor, QTextBlockFormat,
                           QSyntaxHighlighter, QTextCharFormat, QTextDocument, QColor)
from PySide6.QtCore import Qt

class RubyHighlighter(QSyntaxHighlighter):
    """「｜」や「｛るび｝」の記号部分だけを自動で薄いグレーにするハイライター"""
    def __init__(self, parent=None):
        super().__init__(parent)

        self.symbol_format = QTextCharFormat()
        self.symbol_format.setForeground(QColor("#aaaaaa"))

        self.ruby_text_format = QTextCharFormat()
        self.ruby_text_format.setForeground(QColor("#888888"))

        self.pattern = re.compile(r"(｜)([^｜｛｝]+)(｛)([^｝]+)(｝)")

    def highlightBlock(self, text):
        for match in self.pattern.finditer(text):
            s1, e1 = match.span(1)
            self.setFormat(s1, e1 - s1, self.symbol_format)

            s3, e3 = match.span(3)
            self.setFormat(s3, e3 - s3, self.symbol_format)

            s4, e4 = match.span(4)
            self.setFormat(s4, e4 - s4, self.ruby_text_format)

            s5, e5 = match.span(5)
            self.setFormat(s5, e5 - s5, self.symbol_format)

class UltimateOutlineEditor(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("構造化エディタ")
        self.resize(1100, 700)

        self.is_modified = False
        # ↓【追加】現在開いているファイルのパスを保持する変数を追加します
        self.current_file_path = None

        self.editor = QTextEdit(self)
        self.setCentralWidget(self.editor)
        self.editor_font = QFont("Noto Serif Japanese", 14)
        self.editor.setFont(self.editor_font)

        block_format = QTextBlockFormat()
        block_format.setLineHeight(150.0, QTextBlockFormat.LineHeightTypes.ProportionalHeight.value)

        cursor = self.editor.textCursor()
        cursor.select(QTextCursor.SelectionType.Document)
        cursor.setBlockFormat(block_format)

        self.highlighter = RubyHighlighter(self.editor.document())

        self.editor.textChanged.connect(self.update_char_count)
        self.editor.cursorPositionChanged.connect(self.update_char_count)

        self.update_char_count()

        # 2. メニューバーと「ファイル」「表示」メニュー
        menubar = self.menuBar()
        file_menu = menubar.addMenu("ファイル(&F)")
        view_menu = menubar.addMenu("表示(&V)")

        new_action = QAction("新規作成(&N)", self)
        new_action.setShortcut("Ctrl+N")
        new_action.triggered.connect(self.new_file)
        file_menu.addAction(new_action)

        file_menu.addSeparator()
        open_action = QAction("開く(&O)...", self)
        open_action.setShortcut("Ctrl+O")
        open_action.triggered.connect(self.open_file)
        file_menu.addAction(open_action)

        save_action = QAction("保存(&S)...", self)
        save_action.setShortcut("Ctrl+S")
        save_action.triggered.connect(self.save_file)
        file_menu.addAction(save_action)

        file_menu.addSeparator()
        exit_action = QAction("終了(&X)", self)
        exit_action.setShortcut("Alt+F4")
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)

        # 3. 左側のアウトラインペイン（ドックウィジェット）
        self.dock = QDockWidget("アウトライン（目次）", self)
        self.dock.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea)

        dock_content = QWidget()
        dock_layout = QVBoxLayout(dock_content)
        dock_layout.setContentsMargins(0, 0, 0, 0)
        dock_layout.setSpacing(0)

        self.toggle_btn_close = QPushButton("◀ 目次を隠す", self)
        self.toggle_btn_close.setStyleSheet("text-align: left; padding: 5px; background: #e0e0e0; border: none;")
        self.toggle_btn_close.clicked.connect(self.hide_outline_pane)
        dock_layout.addWidget(self.toggle_btn_close)

        self.outline_list = QListWidget(self)
        self.outline_list.setFont(QFont("Noto Serif Japanese", 11))
        dock_layout.addWidget(self.outline_list)

        dock_content.setLayout(dock_layout)
        self.dock.setWidget(dock_content)
        self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.dock)

        view_menu.addAction(self.dock.toggleViewAction())
        self.outline_list.itemClicked.connect(self.jump_to_heading)

        # 4. エディタの左端に常駐する「▶」開くボタン用のツールバー
        self.left_open_toolbar = QToolBar("目次を開く")
        self.addToolBar(Qt.ToolBarArea.LeftToolBarArea, self.left_open_toolbar)
        self.toggle_btn_open = QPushButton("▶\n目\n次", self)
        self.toggle_btn_open.setFixedWidth(24)
        self.toggle_btn_open.setStyleSheet("background: #f0f0f0; border: 1px solid #ccc; font-size: 10px;")
        self.toggle_btn_open.clicked.connect(self.show_outline_pane)
        self.left_open_toolbar.addWidget(self.toggle_btn_open)
        self.left_open_toolbar.setVisible(False)

        self.dock.visibilityChanged.connect(self.on_dock_visibility_changed)

        # 5. メインツールバーの作成（1段目：スタイル ＆ 検索・置換 ＆ ルビ）
        toolbar = QToolBar("メインツールバー")
        self.addToolBar(toolbar)

        self.style_combo = QComboBox(self)
        self.style_combo.addItems(["標準（本文）", "タイトル", "大見出し", "中見出し", "引用"])
        self.style_combo.currentIndexChanged.connect(self.apply_style)
        toolbar.addWidget(self.style_combo)

        toolbar.addSeparator()

        toolbar.addWidget(QLabel("  検索: "))
        self.search_input = QLineEdit(self)
        self.search_input.setFixedWidth(120)
        self.search_input.textChanged.connect(self.highlight_search_text)
        self.search_input.returnPressed.connect(self.jump_to_next_match)
        toolbar.addWidget(self.search_input)

        search_btn = QPushButton("次へ", self)
        search_btn.clicked.connect(self.jump_to_next_match)
        toolbar.addWidget(search_btn)

        toolbar.addWidget(QLabel("  置換: "))
        self.replace_input = QLineEdit(self)
        self.replace_input.setFixedWidth(120)
        toolbar.addWidget(self.replace_input)

        replace_btn = QPushButton("すべて置換", self)
        replace_btn.clicked.connect(self.replace_all_text)
        toolbar.addWidget(replace_btn)

        toolbar.addSeparator()

        ruby_btn = QPushButton("ルビを振る", self)
        ruby_btn.setStyleSheet("background: #e8f4fd; border: 1px solid #b3d7ff; font-weight: bold;")
        ruby_btn.clicked.connect(self.add_ruby)
        toolbar.addWidget(ruby_btn)

        # ──────────────────────────────────────────────────
        # ★【外観調整版】ツールバーを改行し、2段目を新しく作成する
        # ──────────────────────────────────────────────────
        self.addToolBarBreak()  # ツールバーを次の行へ改行させる命令

        dict_toolbar = QToolBar("辞書・補完ツールバー")
        self.addToolBar(dict_toolbar)

        # 【辞書登録エリア】単語入力、読み入力（幅2倍）、登録ボタン（短め＋薄色）
        dict_toolbar.addWidget(QLabel(" ［辞書登録］単語: "))
        self.dict_word_input = QLineEdit(self)
        self.dict_word_input.setFixedWidth(150)  # 単語入力欄
        self.dict_word_input.setPlaceholderText("固有名詞など")
        dict_toolbar.addWidget(self.dict_word_input)

        dict_toolbar.addWidget(QLabel(" 読み: "))
        self.dict_yomi_input = QLineEdit(self)
        self.dict_yomi_input.setFixedWidth(120)  # ★読みの幅を従来の60から2倍（120）に拡張
        self.dict_yomi_input.setPlaceholderText("よみ")
        dict_toolbar.addWidget(self.dict_yomi_input)

        dict_reg_btn = QPushButton("登録", self)
        dict_reg_btn.setFixedWidth(50)  # ★ボタンの横幅を少し短めに制限
        # ★「ルビを振る」ボタンに合わせた優しい薄青色の背景と境界線、文字に太字の装飾を適用
        dict_reg_btn.setStyleSheet("background: #f0f8ff; border: 1px solid #b3d7ff; font-weight: bold;")
        dict_reg_btn.clicked.connect(self.register_word)
        dict_toolbar.addWidget(dict_reg_btn)

        dict_toolbar.addSeparator()

        # 【インクリメンタル補完エリア】
        dict_toolbar.addWidget(QLabel(" ［補完］検索: "))
        self.autocomplete_search = QLineEdit(self)
        self.autocomplete_search.setFixedWidth(120)
        self.autocomplete_search.setPlaceholderText("頭文字")
        self.autocomplete_search.textChanged.connect(self.search_word_incremental)
        dict_toolbar.addWidget(self.autocomplete_search)

        dict_toolbar.addWidget(QLabel(" 候補: "))
        self.autocomplete_combo = QComboBox(self)
        self.autocomplete_combo.setFixedWidth(180)
        self.autocomplete_combo.activated.connect(self.insert_word)
        dict_toolbar.addWidget(self.autocomplete_combo)

        # 6. 各種イベントの連動
        self.editor.cursorPositionChanged.connect(self.update_combo_from_cursor)
        self.editor.textChanged.connect(self.on_text_changed)

        # 7. ステータスバー
        self.statusbar = QStatusBar(self)
        self.setStatusBar(self.statusbar)

        self.update_char_count()
        self.editor.setFocus()
        self.activate_ime()

        self.is_modified = False
        self.current_file_path = None
        self.setWindowTitle("新規ファイル - 構造化アウトラインエディタ")

        # ★【新規追加】辞書ファイルの保存パス設定と自動読み込み
        current_dir = os.path.dirname(os.path.abspath(__file__))
        self.dict_file_path = os.path.join(current_dir, "dict.json")
        self.dictionary = self.load_dictionary()

    def on_text_changed(self):
        print("文字が変更されました！")
        self.is_modified = True
        self.update_char_count()
        self.refresh_outline()
        self.highlight_search_text()

    def new_file(self):
        if self.is_modified:
            msg_box = QMessageBox(self)
            msg_box.setIcon(QMessageBox.Icon.Warning)
            msg_box.setWindowTitle("保存確認")
            msg_box.setText("変更が保存されていません。新しい文書を作成する前に変更を保存しますか？")

            save_button = msg_box.addButton("保存する", QMessageBox.ButtonRole.AcceptRole)
            discard_button = msg_box.addButton("保存しない", QMessageBox.ButtonRole.DestructiveRole)
            cancel_button = msg_box.addButton("キャンセル", QMessageBox.ButtonRole.RejectRole)
            msg_box.setDefaultButton(save_button)

            msg_box.exec()
            clicked_button = msg_box.clickedButton()

            if clicked_button == save_button:
                if not self.save_file():
                    return
            elif clicked_button == cancel_button:
                return

        self.editor.clear()
        self.current_file_path = None
        self.setWindowTitle("新規ファイル - 構造化アウトラインエディタ")
        self.is_modified = False
        self.statusBar().showMessage("新規文書を作成しました", 3000)

    def closeEvent(self, event):
        if not self.is_modified:
            event.accept()
            return

        msg_box = QMessageBox(self)
        msg_box.setIcon(QMessageBox.Icon.Warning)
        msg_box.setWindowTitle("保存確認")
        msg_box.setText("変更が保存されていません。変更を保存しますか？")

        save_button = msg_box.addButton("保存する", QMessageBox.ButtonRole.AcceptRole)
        discard_button = msg_box.addButton("保存しない", QMessageBox.ButtonRole.DestructiveRole)
        cancel_button = msg_box.addButton("キャンセル", QMessageBox.ButtonRole.RejectRole)
        msg_box.setDefaultButton(save_button)

        msg_box.exec()
        clicked_button = msg_box.clickedButton()

        if clicked_button == save_button:
            success = self.save_file()
            if success:
                event.accept()
            else:
                event.ignore()
        elif clicked_button == discard_button:
            event.accept()
        else:
            event.ignore()

    def activate_ime(self):
        self.editor.setAttribute(Qt.WidgetAttribute.WA_InputMethodEnabled, True)
        input_method = QApplication.inputMethod()
        if input_method:
            input_method.update(Qt.InputMethodQuery.ImQueryAll)

    def hide_outline_pane(self):
        self.dock.setVisible(False)
        self.left_open_toolbar.setVisible(True)

    def show_outline_pane(self):
        self.dock.setVisible(True)
        self.left_open_toolbar.setVisible(False)

    def on_dock_visibility_changed(self, visible):
        if not visible and not self.dock.isFloating():
            self.left_open_toolbar.setVisible(True)
        else:
            self.left_open_toolbar.setVisible(False)

    def update_char_count(self):
        # ↓【修正】文字数に関係なく、何かしらの入力・削除があれば「変更あり」にします
        self.is_modified = True

        total_count = len(self.editor.toPlainText())
        cursor = self.editor.textCursor()
        if cursor.hasSelection():
            selected_text = cursor.selectedText().replace('\u2029', '\n')
            selected_count = len(selected_text)
            self.statusBar().showMessage(f"文字数: {total_count}文字 （選択: {selected_count}文字）")
        else:
            self.statusBar().showMessage(f"文字数: {total_count}文字")

    def refresh_outline(self):
        self.outline_list.blockSignals(True)
        current_item = self.outline_list.currentItem()
        selected_text = current_item.text() if current_item else None
        self.outline_list.clear()

        doc = self.editor.document()
        block = doc.begin()

        while block.isValid():
            block_format = block.blockFormat()
            level = block_format.headingLevel()
            text = block.text().strip()

            if level in [1, 2, 3] and text:
                indent = ""
                if level == 2: indent = "  "
                if level == 3: indent = "    "

                prefix = "■ " if level == 1 else "◆ " if level == 2 else "▲ "
                item_text = f"{indent}{prefix}{text}"

                item = QListWidgetItem(item_text)
                item.setData(Qt.ItemDataRole.UserRole, block.fragmentIndex())
                self.outline_list.addItem(item)

                if selected_text and item_text == selected_text:
                    self.outline_list.setCurrentItem(item)

            block = block.next()
        self.outline_list.blockSignals(False)

    def jump_to_heading(self, item):
        target_fragment_id = item.data(Qt.ItemDataRole.UserRole)
        doc = self.editor.document()
        block = doc.begin()

        while block.isValid():
            if block.fragmentIndex() == target_fragment_id:
                cursor = self.editor.textCursor()
                cursor.setPosition(block.position())
                self.editor.setTextCursor(cursor)

                block_top = self.editor.document().documentLayout().blockBoundingRect(block).top()
                scrollbar = self.editor.verticalScrollBar()
                if scrollbar:
                    scrollbar.setValue(int(block_top))

                self.editor.setFocus()
                break
            block = block.next()

    def add_ruby(self):
        cursor = self.editor.textCursor()
        if not cursor.hasSelection():
            self.statusbar.showMessage("ルビを振りたい文字を選択してください", 3000)
            return

        selected_text = cursor.selectedText()
        ruby_text, ok = QInputDialog.getText(
            self, "ルビ（ふりがな）の追加", f"「{selected_text}」のルビを入力してください:"
        )

        if ok and ruby_text:
            ruby_plain_text = f"｜{selected_text}｛{ruby_text}｝"
            cursor.beginEditBlock()
            cursor.insertText(ruby_plain_text)
            cursor.endEditBlock()
            self.editor.setFocus()

    def highlight_search_text(self):
        search_str = self.search_input.text()
        extra_selections = []

        if search_str:
            doc = self.editor.document()
            cursor = QTextCursor(doc)
            light_green_format = QTextCharFormat()
            light_green_format.setBackground(QColor(200, 240, 200))

            while True:
                cursor = doc.find(search_str, cursor)
                if cursor.isNull():
                    break
                selection = QTextEdit.ExtraSelection()
                selection.format = light_green_format
                selection.cursor = cursor
                extra_selections.append(selection)
        self.editor.setExtraSelections(extra_selections)

    def jump_to_next_match(self):
        search_str = self.search_input.text()
        if not search_str:
            return

        found = self.editor.find(search_str)
        if not found:
            cursor = self.editor.textCursor()
            cursor.movePosition(QTextCursor.MoveOperation.Start)
            self.editor.setTextCursor(cursor)
            if not self.editor.find(search_str):
                self.statusbar.showMessage(f"「{search_str}」は見つかりませんでした", 3000)

    def replace_all_text(self):
        search_str = self.search_input.text()
        replace_str = self.replace_input.text()
        if not search_str:
            return

        cursor = self.editor.textCursor()
        cursor.beginEditBlock()
        self.editor.moveCursor(QTextCursor.MoveOperation.Start)
        count = 0
        while self.editor.find(search_str):
            self.editor.textCursor().insertText(replace_str)
            count += 1

        cursor.endEditBlock()
        self.statusbar.showMessage(f"{count}箇所の文字を置換しました", 3000)
        self.highlight_search_text()

    def update_combo_from_cursor(self):
        cursor = self.editor.textCursor()
        block_format = cursor.blockFormat()

        self.style_combo.blockSignals(True)
        level = block_format.headingLevel()
        margin = block_format.leftMargin()

        if level == 1:
            self.style_combo.setCurrentText("タイトル")
        elif level == 2:
            self.style_combo.setCurrentText("大見出し")
        elif level == 3:
            self.style_combo.setCurrentText("中見出し")
        elif margin == 40:
            self.style_combo.setCurrentText("引用")
        else:
            self.style_combo.setCurrentText("標準（本文）")
        self.style_combo.blockSignals(False)

    def apply_style(self):
        selected_style = self.style_combo.currentText()
        cursor = self.editor.textCursor()

        cursor.beginEditBlock()
        cursor.select(QTextCursor.SelectionType.BlockUnderCursor)

        block_format = QTextBlockFormat()
        char_format = QTextCharFormat()
        char_format.setFontFamily("Noto Serif Japanese")

        if selected_style == "タイトル":
            block_format.setHeadingLevel(1)
            char_format.setFontPointSize(28)
            char_format.setFontWeight(QFont.Weight.Bold)
            block_format.setLeftMargin(0)
        elif selected_style == "大見出し":
            block_format.setHeadingLevel(2)
            char_format.setFontPointSize(20)
            char_format.setFontWeight(QFont.Weight.Bold)
            block_format.setLeftMargin(0)
        elif selected_style == "中見出し":
            block_format.setHeadingLevel(3)
            char_format.setFontPointSize(16)
            char_format.setFontWeight(QFont.Weight.Bold)
            block_format.setLeftMargin(0)
        elif selected_style == "引用":
            block_format.setHeadingLevel(0)
            char_format.setFontPointSize(12)
            char_format.setFontItalic(True)
            block_format.setLeftMargin(40)
        else:
            block_format.setHeadingLevel(0)
            char_format.setFontPointSize(14)
            char_format.setFontWeight(QFont.Weight.Normal)
            char_format.setFontItalic(False)
            block_format.setLeftMargin(0)

        cursor.setBlockFormat(block_format)
        cursor.setCharFormat(char_format)
        cursor.endEditBlock()
        self.editor.setTextCursor(cursor)
        self.editor.setFocus()

    def open_file(self):
        # 【修正】1つのメニューに *.html と *.txt を共存させます
        file_filter = "対応フォーマット (*.html *.txt);;すべてのファイル (*)"

        file_path, _ = QFileDialog.getOpenFileName(
            self, "ファイルを開く", "", file_filter
        )
        if not file_path:
            return

        try:
            with open(file_path, "r", encoding="utf-8") as f:
                content = f.read()

            if file_path.endswith(".html"):
                body_match = re.search(r"<body>(.*?)</body>", content, re.DOTALL)
                html_body = body_match.group(1).strip() if body_match else content

                self.editor.clear()
                cursor = self.editor.textCursor()
                blocks = re.split(r"(<(?:p|h1|h2|h3|blockquote|br)[^>]*>.*?</(?:p|h1|h2|h3|blockquote)>|<br>)", html_body, flags=re.DOTALL)

                for block in blocks:
                    block = block.strip()
                    if not block: continue
                    if block in ["<br>", "<br/>", "<br />"]:
                        cursor.insertBlock()
                        continue

                    tag_match = re.match(r"<([a-zA-Z1-3]+)[^>]*>(.*?)</\1>", block, re.DOTALL)
                    if tag_match:
                        tag_name = tag_match.group(1).lower()
                        inner_text = tag_match.group(2).strip()
                        inner_text = re.sub(r"<ruby>\s*<rb>([^<]+)</rb>\s*<rt>([^<]+)</rt>\s*</ruby>", r"｜\1｛\2｝", inner_text)

                        if cursor.position() != 0 or self.editor.toPlainText():
                            cursor.insertBlock()

                        char_format = cursor.charFormat()
                        font = char_format.font()

                        if tag_name == "h1":
                            font.setPointSize(28)
                            font.setBold(True)
                        elif tag_name == "h2":
                            font.setPointSize(22)
                            font.setBold(True)
                        elif tag_name == "h3":
                            font.setPointSize(16)
                            font.setBold(True)
                        elif tag_name == "blockquote":
                            font.setPointSize(14)
                            font.setBold(False)
                            font.setItalic(True)
                        else:
                            font.setPointSize(14)
                            font.setBold(False)
                            font.setItalic(False)

                        char_format.setFont(font)
                        cursor.setCharFormat(char_format)
                        cursor.insertText(inner_text)

                cursor.movePosition(QTextCursor.MoveOperation.Start)
                self.editor.setTextCursor(cursor)
            else:
                self.editor.setPlainText(content)
                self.editor.setFont(self.editor_font)
                self.editor.setFocus()
                if hasattr(self, 'update_char_count'):
                    self.update_char_count()

            self.current_file_path = file_path
            file_name = os.path.basename(file_path)
            self.setWindowTitle(f"{file_name} - 構造化アウトラインエディタ")
            self.is_modified = False
            self.statusBar().showMessage("ファイルを読み込みました", 3000)
        except Exception as e:
            QMessageBox.critical(self, "エラー", f"ファイルの読み込みに失敗しました:\n{e}")

    # ==========================================
    # 【新機能】上書き保存 (Ctrl+Sで呼ばれるメイン処理)
    # ==========================================
    def save_file(self):
        # 既にファイルパスがある場合は、ダイアログを出さずにそのまま保存
        if self.current_file_path:
            return self._execute_save(self.current_file_path)
        else:
            # パスがない（新規作成）の場合は「名前を付けて保存」を実行
            return self.save_file_as()

    # ==========================================
    # 【修正】名前を付けて保存（1つのメニューに2フォーマットを共存）
    # ==========================================
    def save_file_as(self):
        # 現在開いているファイルのパスがテキストなら、初期ファイル名も .txt に合わせる
        if self.current_file_path:
            default_path = self.current_file_path
        else:
            default_path = os.path.join(os.getcwd(), "新規ファイル.html")

        # 【ポイント】丸括弧の中にスペース区切りで *.html と *.txt を共存させます
        file_filter = "対応フォーマット (*.html *.txt);;すべてのファイル (*)"

        file_path, selected_filter = QFileDialog.getSaveFileName(
            self, "ファイルを保存", default_path, file_filter
        )

        if file_path:
            # 保存するファイルの拡張子が「.txt」かどうかでHTMLかテキストかを自動判別します
            if file_path.endswith(".txt"):
                is_html = False
            else:
                # 拡張子が未入力の場合や、.html の場合はHTMLとして保存
                if not file_path.endswith(".html") and not file_path.endswith(".txt"):
                    # 拡張子が打ち込まれなかった場合の安全策として .html を付与
                    file_path += ".html"
                is_html = True

            return self._execute_save(file_path, is_html=is_html)
        return False

    # ==========================================
    # 【内部処理】実際の書き込みロジック (上書き・新規共通)
    # ==========================================
    def _execute_save(self, file_path, is_html=None):
        try:
            pattern = r"｜([^｜｛｝]+)｛([^｝]+)｝"

            # 形式が指定されていない（上書き保存などの）場合は拡張子から自動判別
            if is_html is None:
                is_html = file_path.endswith(".html")

            if is_html:
                html_blocks = []
                document = self.editor.document()

                for i in range(document.blockCount()):
                    block = document.findBlockByNumber(i)
                    block_text = block.text()
                    if not block_text.strip():
                        html_blocks.append("<br>")
                        continue

                    char_format = block.charFormat()
                    font = char_format.font()
                    font_size = font.pointSize()

                    if font_size >= 30: tag = "h1"
                    elif font_size >= 22: tag = "h2"
                    elif font_size >= 16: tag = "h3"
                    elif font.italic(): tag = "blockquote"
                    else: tag = "p"

                    converted_text = re.sub(pattern, r"<ruby><rb>\1</rb><rt>\2</rt></ruby>", block_text)
                    html_blocks.append(f"<{tag}>{converted_text}</{tag}>")

                html_body = "\n".join(html_blocks)
                final_html = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>保存された文書</title>
<style>
    body {{
        font-family: "Noto Serif Japanese", "Hiragino Mincho ProN", "MS Mincho", serif;
        font-size: 14pt;
        line-height: 1.8;
        color: #222222;
        padding: 40px;
        max-width: 800px;
        margin: 0 auto;
    }}
    h1 {{ font-size: 32pt; font-weight: bold; margin-top: 30px; margin-bottom: 20px; text-align: center; }}
    h2 {{ font-size: 24pt; font-weight: bold; margin-top: 35px; margin-bottom: 15px; border-bottom: 1px solid #ddd; padding-bottom: 5px; }}
    h3 {{ font-size: 18pt; font-weight: bold; margin-top: 25px; margin-bottom: 10px; }}
    p {{ margin: 0 0 18px 0; text-indent: 1em; }}
    blockquote {{ margin: 20px 40px; padding: 10px 20px; border-left: 4px solid #ccc; color: #555; background-color: #f9f9f9; font-style: italic; }}
    ruby {{ ruby-position: over; }}
    rt {{ font-size: 9pt; color: #555555; }}
</style>
</head>
<body>
{html_body}
</body>
</html>"""
                with open(file_path, "w", encoding="utf-8") as f:
                    f.write(final_html)
            else:
                raw_text = self.editor.toPlainText()
                final_text = re.sub(pattern, r"\1（\2）", raw_text)
                with open(file_path, "w", encoding="utf-8") as f:
                    f.write(final_text)

            self.statusBar().showMessage("保存が完了しました", 3000)
            self.is_modified = False
            self.current_file_path = file_path
            file_name = os.path.basename(file_path)
            self.setWindowTitle(f"{file_name} - 構造化アウトラインエディタ")
            return True
        except Exception as e:
            QMessageBox.critical(self, "エラー", f"ファイルの保存に失敗しました:\n{e}")
            return False

    # ★【新規追加】辞書機能・インクリメンタル補完のための各種メソッド
    def load_dictionary(self):
        """jsonファイルから辞書を読み込む処理"""
        if os.path.exists(self.dict_file_path):
            try:
                with open(self.dict_file_path, "r", encoding="utf-8") as f:
                    return json.load(f)
            except Exception as e:
                print(f"辞書の読み込みに失敗しました: {e}")
                return {}
        return {}

    def save_dictionary(self):
        """辞書データをjsonファイルに自動保存する処理"""
        try:
            with open(self.dict_file_path, "w", encoding="utf-8") as f:
                json.dump(self.dictionary, f, ensure_ascii=False, indent=4)
        except Exception as e:
            print(f"辞書の保存に失敗しました: {e}")

    def register_word(self):
        """単語を新しく登録する処理"""
        yomi = self.dict_yomi_input.text().strip()
        word = self.dict_word_input.text().strip()

        if yomi and word:
            if yomi not in self.dictionary:
                self.dictionary[yomi] = []
            if word not in self.dictionary[yomi]:
                self.dictionary[yomi].append(word)

            self.save_dictionary()
            self.dict_yomi_input.clear()
            self.dict_word_input.clear()
            self.statusBar().showMessage(f"辞書に登録しました: {yomi} -> {word}", 3000)
            self.search_word_incremental()

    def search_word_incremental(self):
        """入力された文字をもとにインクリメンタルに候補を絞り込む処理"""
        search_key = self.autocomplete_search.text().strip()

        self.autocomplete_combo.blockSignals(True)
        self.autocomplete_combo.clear()

        if search_key:
            matching_candidates = []
            for yomi, words in self.dictionary.items():
                if yomi.startswith(search_key):
                    matching_candidates.extend(words)

            if matching_candidates:
                matching_candidates = list(dict.fromkeys(matching_candidates))
                self.autocomplete_combo.addItems(matching_candidates)

        self.autocomplete_combo.blockSignals(False)

    def insert_word(self):
        """選択された候補の単語をエディタのカーソル位置にパッと挿入する処理"""
        selected_word = self.autocomplete_combo.currentText()
        if selected_word:
            cursor = self.editor.textCursor()
            cursor.beginEditBlock()
            cursor.insertText(selected_word)
            cursor.endEditBlock()

            self.autocomplete_search.clear()
            self.autocomplete_combo.clear()
            self.editor.setFocus()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = UltimateOutlineEditor()
    window.show()
    sys.exit(app.exec())
