import os
import sys
import re
from html import escape # HTMLエスケープ処理用
try:
    from pptx import Presentation
except:
    print("\npptx2pdf_recursive.py: Import error: pptx")
    input("Install: pip install python-pptx\n")


root_dir = "."  # 現在のディレクトリ
output_path = "extracted_pptx_text.html"
max_level = -1  # 無制限
extract_mode = "paragraph" # デフォルトの抽出モード

# python script.py [root_dir] [output_path] [max_level] [extract_mode]
if len(sys.argv) > 1: root_dir = sys.argv[1]
if len(sys.argv) > 2: output_path = sys.argv[2]
if len(sys.argv) > 3: max_level = sys.argv[3]
if len(sys.argv) > 4: extract_mode = sys.argv[4]
try:
    max_level = int(max_level)
except ValueError:
    print(f"Error: Invalid max_level '{max_level}'.")
    exit()


def extract_text_from_pptx_slides(pptx_path, mode="paragraph"):
    """
    指定されたPowerPointファイルから、各スライドのテキストをリストで抽出する。
    引数:
        pptx_path (str): PowerPointファイルのフルパス。
        mode (str): テキストの抽出単位。"paragraph" (デフォルト) または "run"。
    戻り値:
        list: 各スライドのデータを含む辞書のリスト。
              各辞書は "slide_number", "first_line", "full_text" を含む。
    """
    slides_data = []
    try:
        prs = Presentation(pptx_path)
        for i, slide in enumerate(prs.slides):
            current_slide_texts = [] # 現在のスライドの全てのテキストを収集するリスト

            # 1. スライドタイトルを最初に抽出
            slide_title_text = ""
            if slide.shapes.title: # 標準のタイトルプレースホルダーがあるか確認
                slide_title_text = slide.shapes.title.text_frame.text.strip()
                if slide_title_text:
                    current_slide_texts.append(escape(slide_title_text))

            # 2. その他のシェイプからテキストを抽出 (modeに応じて)
            for shape in slide.shapes:
                # タイトルシェイプが既に処理されている場合はスキップ
                if shape == slide.shapes.title:
                    continue

                if hasattr(shape, "text_frame") and shape.text_frame:
                    if mode == "paragraph":
                        # 段落単位でテキストを収集
                        for paragraph in shape.text_frame.paragraphs:
                            para_runs_text = []
                            for run in paragraph.runs:
                                if run.text:
                                    para_runs_text.append(run.text.strip())
                            if para_runs_text:
                                # 段落内のランをスペースで結合し、HTMLエスケープして追加
                                current_slide_texts.append(escape(" ".join(para_runs_text).strip()))
                    elif mode == "run":
                        # ラン単位でテキストを収集
                        for paragraph in shape.text_frame.paragraphs:
                            for run in paragraph.runs:
                                if run.text:
                                    # 各ランのテキストをHTMLエスケープして追加
                                    current_slide_texts.append(escape(run.text.strip()))
                    else:
                        # 未知のモードの場合、警告を表示し、デフォルトの段落モードを使用
                        print(f"Warning: Unknown extract_mode '{mode}'. Defaulting to 'paragraph' mode for {pptx_path}.")
                        for paragraph in shape.text_frame.paragraphs:
                            para_runs_text = []
                            for run in paragraph.runs:
                                if run.text:
                                    para_runs_text.append(run.text.strip())
                            if para_runs_text:
                                current_slide_texts.append(escape(" ".join(para_runs_text).strip()))

            # スライド全体のテキストを結合
            full_text = "\n".join(current_slide_texts).strip()
            # 最初の行を決定 (タイトルがあればそれが最初、なければ最初の抽出テキスト)
            first_line = current_slide_texts[0] if current_slide_texts else "(No text found)"
            
            slides_data.append({
                "slide_number": i + 1,
                "first_line": first_line,
                "full_text": full_text
            })

    except Exception as e:
        print(f"Error processing {pptx_path}: {e}")
        slides_data.append({
            "slide_number": "Error",
            "first_line": f"Error extracting text: {escape(str(e))}",
            "full_text": f"Error extracting text from this slide: {escape(str(e))}"
        })
    return slides_data


def generate_html_output(root_dir, output_path, max_level=-1, extract_mode="paragraph"): # extract_mode引数を追加
    """
    PowerPointファイルからテキストを抽出し、検索可能なHTMLファイルを生成する。
    """
    if not os.path.isdir(root_dir):
        print(f"Error: Root directory '{root_dir}' does not exist.")
        return

    root_dir_abs = os.path.abspath(root_dir)
    print(f"Searching for .pptx files in '{root_dir_abs}' (max_level: {max_level})...")

    # --- HTMLのDOCTYPE, head, そして body の開始タグと検索UIまで ---
    html_doc_start = f"""
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PowerPoint Text Extractor</title>
    <style>
        body {{ font-family: sans-serif; margin: 20px; }}
        h1 {{ color: #333; }}
        h3 {{ margin-top: 30px; border-bottom: 1px solid #ccc; padding-bottom: 5px; }}
        a {{ text-decoration: none; color: #0066cc; }}
        a:hover {{ text-decoration: underline; }}
        ol {{ list-style-type: decimal; padding-left: 25px; }}
        details {{ border: 1px solid #eee; margin-bottom: 5px; border-radius: 4px; background-color: #f9f9f9; }}
        summary {{ padding: 10px; cursor: pointer; background-color: #e9e9e9; border-bottom: 1px solid #eee; font-weight: bold; }}
        details[open] summary {{ background-color: #ddd; border-bottom: 1px solid #ccc; }}
        details p {{ padding: 10px; white-space: pre-wrap; word-break: break-word; }}
        #search-container {{
            position: fixed;
            top: 20px;
            right: 20px;
            background-color: #fff;
            border: 1px solid #ccc;
            padding: 15px;
            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
            z-index: 1000;
            border-radius: 8px;
            width: 300px;
        }}
        #search-container h4 {{ margin-top: 0; color: #555; }}
        #search-container label {{ display: block; margin-bottom: 5px; font-weight: bold; }}
        #search-container input[type="text"] {{ width: calc(100% - 22px); padding: 8px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 4px; }}
        #search-container button {{ padding: 8px 12px; margin-right: 5px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }}
        #search-container button:hover {{ background-color: #0056b3; }}
        #search-container #match-count {{ margin-top: 10px; font-weight: bold; color: #333; }}
        .highlight {{ background-color: yellow; font-weight: bold; }}
    </style>
</head>
<body>
    <h1>PowerPoint Text Extraction Results</h1>

    <button onclick="toggleSearchPanel()">検索</button>

    <div id="search-container" style="display: none;">
        <h4>テキスト検索</h4>
        <label for="search-input">正規表現:</label>
        <input type="text" id="search-input" placeholder="検索キーワードを入力...">
        <button onclick="performSearch(true)">前へ</button>
        <button onclick="performSearch(false)">次へ</button>
        <div id="match-count"></div>
    </div>

    <div id="results">
    """

    # --- JavaScriptコードブロック ---
    # PythonのRaw文字列 (r""") を使用して、バックスラッシュのエスケープ警告を回避
    javascript_code_block = r"""
    <script>
        let searchResults = [];
        let currentMatchIndex = -1;
        let lastSearchTerm = "";
        let isSearchPanelVisible = false;

        function toggleSearchPanel() {
            const panel = document.getElementById('search-container');
            isSearchPanelVisible = !isSearchPanelVisible;
            panel.style.display = isSearchPanelVisible ? 'block' : 'none';
            if (isSearchPanelVisible) {
                document.getElementById('search-input').focus();
            }
        }

        // 正規表現の特殊文字をエスケープする関数
        function escapeRegExp(string) {
            // stringがnullまたはundefinedの場合を考慮
            if (string === null || typeof string === 'undefined') {
                return '';
            }
            return String(string).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        }

        function highlightText(text, term) {
            if (!term) return text;
            if (!text) return ''; // textがnull/undefinedの場合をハンドリング
            try {
                // escapeRegExpを適用してからRegExpオブジェクトを作成
                const escapedTerm = escapeRegExp(term);
                // 空の正規表現を避ける
                if (escapedTerm === '') {
                    return text;
                }
                const regex = new RegExp(escapedTerm, 'gi'); // 大文字小文字を区別せず、グローバル検索
                return text.replace(regex, '<span class="highlight">$&</span>');
            } catch (e) {
                console.error("Invalid regex in highlightText:", e);
                return text; // 無効な正規表現の場合はハイライトしない
            }
        }

        function clearHighlights() {
            document.querySelectorAll('.highlight').forEach(span => {
                // spanをそのテキストコンテンツで置き換える
                const textNode = document.createTextNode(span.textContent);
                span.parentNode.replaceChild(textNode, span);
            });
            // details タグの summary も含めて全てリセット
            document.querySelectorAll('details').forEach(detail => {
                const summary = detail.querySelector('summary');
                const contentP = detail.querySelector('p');
                if (detail.dataset.originalSummary) {
                    summary.innerHTML = detail.dataset.originalSummary;
                    delete detail.dataset.originalSummary; // クリアしたら削除
                }
                if (detail.dataset.originalContent) {
                    contentP.innerHTML = detail.dataset.originalContent;
                    delete detail.dataset.originalContent; // クリアしたら削除
                }
            });
        }


        function performSearch(isForward) {
            const searchTerm = document.getElementById('search-input').value;
            const matchCountElement = document.getElementById('match-count');

            if (!searchTerm) {
                clearHighlights();
                matchCountElement.textContent = "";
                return;
            }
            
            // 空の正規表現で検索しないようにする
            const escapedSearchTermForRegex = escapeRegExp(searchTerm);
            if (escapedSearchTermForRegex === '') {
                clearHighlights();
                matchCountElement.textContent = "";
                return;
            }


            if (searchTerm !== lastSearchTerm) {
                clearHighlights(); // 新しい検索の場合はハイライトをクリア
                searchResults = [];
                currentMatchIndex = -1;
                lastSearchTerm = searchTerm;

                // すべてのテキストコンテンツから検索
                document.querySelectorAll('details').forEach((detail) => {
                    // datasetに保存されたオリジナルテキストを使用
                    const originalSummaryText = detail.dataset.originalSummary ? detail.dataset.originalSummary : detail.querySelector('summary').textContent;
                    const originalContentText = detail.dataset.originalContent ? detail.dataset.originalContent : detail.querySelector('p').textContent;

                    try {
                        const regex = new RegExp(escapedSearchTermForRegex, 'gi'); // 検索語を正規表現としてエスケープ
                        if (originalSummaryText.match(regex) || originalContentText.match(regex)) {
                            searchResults.push(detail); // マッチしたdetails要素を保存
                        }
                    } catch (e) {
                        console.error("Invalid regex in performSearch (loop):", e);
                        matchCountElement.textContent = "無効な正規表現";
                        return; // 無効な正規表現の場合は処理を中断
                    }
                });

                matchCountElement.textContent = `一致: ${searchResults.length}件`;
                if (searchResults.length === 0) return;
            }

            if (searchResults.length === 0) {
                matchCountElement.textContent = "一致なし";
                return;
            }

            // 次または前のマッチに移動
            if (isForward) {
                currentMatchIndex = (currentMatchIndex + 1) % searchResults.length;
            } else {
                currentMatchIndex = (currentMatchIndex - 1 + searchResults.length) % searchResults.length;
            }

            // 現在のハイライトをクリアしてから新しいハイライトを適用
            clearHighlights();

            const targetDetail = searchResults[currentMatchIndex];
            if (targetDetail) {
                // スクロールして表示
                targetDetail.scrollIntoView({ behavior: 'smooth', block: 'center' });
                targetDetail.open = true; // detailsを開く

                const summary = targetDetail.querySelector('summary');
                const contentP = targetDetail.querySelector('p');

                // オリジナルの内容を保存 (複数回の検索に備えるため)
                // highlightTextは引数としてtextContentを受け取るので、innerHTMLを直接渡さない
                if (!targetDetail.dataset.originalSummary) {
                    targetDetail.dataset.originalSummary = summary.innerHTML; // InnerHTMLを保存
                }
                if (!targetDetail.dataset.originalContent) {
                    targetDetail.dataset.originalContent = contentP.innerHTML; // InnerHTMLを保存
                }
                
                // ハイライトを適用: highlightTextには元のtextContentを渡す
                summary.innerHTML = highlightText(summary.textContent, searchTerm);
                contentP.innerHTML = highlightText(contentP.textContent, searchTerm);
                
                matchCountElement.textContent = `一致: ${searchResults.length}件 (${currentMatchIndex + 1}/${searchResults.length})`;
            }
        }

        // 初期状態でパネルを隠す
        document.addEventListener('DOMContentLoaded', () => {
            document.getElementById('search-container').style.display = 'none';
        });

    </script>
"""

    # --- HTMLドキュメントの終了部分 (</body>と</html>) ---
    html_doc_end = """
    </div> <!-- /#results -->
</body>
</html>
"""
    # HTMLの各部分を格納するリスト
    final_html_parts = [html_doc_start] # まずはドキュメントの開始部分を追加

    file_count = 0
    # os.walkを絶対パスで開始
    for dirpath, dirnames, filenames in os.walk(root_dir_abs):
        current_level = dirpath.count(os.sep) - root_dir_abs.count(os.sep)
        
        if max_level != -1 and current_level >= max_level:
            del dirnames[:]
            continue

        for filename in filenames:
            if filename.lower().endswith(".pptx"):
                pptx_full_path = os.path.join(dirpath, filename)
                file_count += 1
                
                # HTMLエスケープされたフルパス
                escaped_pptx_full_path = escape(pptx_full_path)

                # 各PPTXファイルの内容を追加
                final_html_parts.append(f'<h3><a href="file:///{escaped_pptx_full_path.replace("\\", "/")}" target="_blank">{escaped_pptx_full_path}</a></h3>\n')
                final_html_parts.append('<ol>\n')

                # extract_modeを渡す
                slides_data = extract_text_from_pptx_slides(pptx_full_path, mode=extract_mode)
                for slide_data in slides_data:
                    # summaryのテキストをHTMLエスケープ (既に行われているが念のため)
                    summary_text = f"Slide {slide_data['slide_number']}: {slide_data['first_line']}"
                    # full_textもHTMLエスケープ (既に行われているが念のため)
                    full_text = slide_data['full_text']
                    
                    final_html_parts.append(f"""
                    <li>
                        <details>
                            <summary>{summary_text}</summary>
                            <p>{full_text}</p>
                        </details>
                    </li>
                    """)
                final_html_parts.append('</ol>\n')
    
    if file_count == 0:
        final_html_parts.append("<p>指定されたディレクトリに.pptxファイルが見つかりませんでした。</p>\n")

    # JavaScriptコードブロックをメインコンテンツの後に、</body>タグの直前に追加
    final_html_parts.append(javascript_code_block)
    
    # HTMLドキュメントの終了部分を追加
    final_html_parts.append(html_doc_end)

    # 全てのHTMLパーツを結合して最終的なHTMLコンテンツを生成
    final_html_content = "".join(final_html_parts)

    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(final_html_content)

    print(f"\nHTML output complete. Results saved to '{output_path}'")
    print(f"Total {file_count} .pptx files processed.")


if __name__ == "__main__":
    # 抽出モードのバリデーション
    if extract_mode not in ["run", "paragraph"]:
        print(f"Warning: Invalid extract_mode '{extract_mode}'. Using default: {default_extract_mode}")
        extract_mode = default_extract_mode

    generate_html_output(root_dir, output_path, max_level, extract_mode) # extract_modeを渡す
    print("\nProgram execution completed.")
    input("\nPress ENTER to terminate>>")
    
    