import os
import sys
import re
import shutil
import argparse
import time
from pathlib import Path
import traceback

try:
    import tktts 
    from tktts import tkTTS
    import pythoncom
    import win32com.client
except ImportError as e:
    print(f"Error: tktts/pythoncom/win32com.client のインポートエラー: {e}")
    print("tktts.py が同一ディレクトリに存在し、win32com.client がインストールされていることを確認してください。")
    sys.exit(1)


DEFAULT_ENGINE = "pyttsx3"
DEFAULT_VOICEVOX_ENDPOINT = "http://127.0.0.1:50021"
DEFAULT_AQUESTALK_PATH = "AquesTalkPlayer.exe"
DEFAULT_TEMP_DIR = "tts_temp_wavs_pptx" # 一時ファイルを格納するディレクトリ
DEFAULT_SPEAK_RATE = 150 # pyttsx3の読み上げ速度 (WPM)

VOICE_MAPS = {
    "pyttsx3": {"Zira": "Zira", "David": "David", "四国めたん": "Zira", "ずんだもん": "David", "れいむ": "Zira", "まりさ": "David"}, 
    "aquestalkplayer": {"四国めたん": "れいむ", "ずんだもん": "まりさ", "れいむ": "れいむ", "まりさ": "まりさ"},
    "openai": {"四国めたん": "nova", "ずんだもん": "shimmer", "れいむ": "alloy", "まりさ": "fable"},
}


def terminate():
    """プログラムを終了する"""
    input("\nPress ENTER to terminate>>\n")
    exit()

def initialize():
    # speak.py のオプションを可能な限り移植
    parser = argparse.ArgumentParser(
        description="PPTXノートから音声ファイルを生成し、自動再生リンクを設定するプログラム (Windows/PowerPoint, tktts使用)",
        formatter_class=argparse.RawTextHelpFormatter
    )

    parser.add_argument("--mode", choices=['list', 'map', 'conv'], help="実行モード:\n list: 利用可能な音声名を表示\n conv: 音声ファイルを生成しPPTXにリンク", default='conv')
    parser.add_argument("--monologue", "-m", type=int, default=0, help="独話形式 (カンマのない行も読み込む)")

    parser.add_argument("--tts", choices=["pyttsx3", "voicevox", "aquestalkplayer", "atp", "openai"], default=DEFAULT_ENGINE, help="TTSエンジンを選択")
    
    parser.add_argument("--input_path",    "-i", type=str, help="[convモード] ノートを追加する元のPPTXファイルパス")
    parser.add_argument("--narration_txt", "-n", type=str, help="[convモード] ノート内容を含む発言テキストファイルパス (オプション: 既存のノートがない場合)")
    parser.add_argument("--audio_dir",     "-a", type=str, default="audio_output", help="[convモード] 生成された音声ファイルを保存するディレクトリ")
    parser.add_argument("--output_path",   "-o", type=str, help="[convモード] 音声リンクが追加された出力PPTXファイルパス")
    
    parser.add_argument("--voices", "-v", type=str, default="", help="[tktts] voice_map の上書き (話者名=ボイス;話者名=ボイス)")
    parser.add_argument("--replace", "-r", type=str, default="", help="[tktts] 文字列置換ルール (key=val;key=val)")
    parser.add_argument("--temp_dir", type=str, default=DEFAULT_TEMP_DIR, help="一時ファイルを作成するディレクトリ名")
    
    # pyttsx3/AQT/OpenAI 共通 (speak.py準拠)
    parser.add_argument("--speak_rate", type=int, default=DEFAULT_SPEAK_RATE, help="[pyttsx3] 読み上げ速度 (WPM) / [AQT] 速度比")
    parser.add_argument("--tinterval", type=float, default=0.5, help="[AQT/OpenAI/VOICEVOX] 音声ファイル間に挿入する無音区間の長さ（秒）")
    
    # VOICEVOX 固有オプション (speak.py準拠)
    parser.add_argument("--endpoint", type=str, default=DEFAULT_VOICEVOX_ENDPOINT, help="[VOICEVOX] Engineのendpoint")
    parser.add_argument("--fspeak_rate", type=float, default=1.0, help="[VOICEVOX] 読み上げ速度比 (標準: 1.0)")
    parser.add_argument("--fspeak_pitch", type=float, default=0.0, help="[VOICEVOX] 声の高さ比 (標準: 0.0)")

    # AquesTalkPlayer 固有オプション (speak.py準拠)
    parser.add_argument("--aquestalk_path", type=str, default="AquesTalkPlayer.exe", help="[AQT] AquesTalkPlayer.exe の実行パス")
    
    # OpenAI 固有オプション (speak.py準拠)
    parser.add_argument("--instruction", type=str, default="", help="[OpenAI] TTS APIへの追加指示")


    args = parser.parse_args()
    return args

def parse_narration_file(narration_path, monologue = True):
    """発言テキストファイルからスライド番号とノート内容をパースする。"""
    slide_texts = {}
    current_slide = None
    current_lines = []
    try:
        with open(narration_path, "r", encoding="utf-8") as f:
             for line in f:
                 line = line.rstrip("\n")
                 if line.strip() == "" or line.strip() == '---': continue

                 m = re.match(r"#\s*Slide\s*(\d+)", line, re.IGNORECASE)
                 if m:
                     if current_slide is not None:
                         slide_texts[current_slide] = "\n".join(current_lines).strip()
                     current_slide = int(m.group(1))
                     current_lines = []
                 else:
                     if line.startswith('#'): continue
                     if line.startswith('(') and line.endswith(')'): continue
#                     if monologue:
                     if True:
                         current_lines.append(line)
                     else:
                        _aa = line.split(',', 1)
                        if len(_aa) == 2:
                            current_lines.append(_aa[1])
                        else:
                            current_lines.append(line)
             if current_slide is not None:
                 slide_texts[current_slide] = "\n".join(current_lines).strip()
    except Exception as e:
        print(f"❌ テキストファイルの読み込み/パースエラー: {e}")
        return None

    print(f"✅ テキストファイルを解析しました: {len(slide_texts)} スライド分のノートを検出。")
    return slide_texts

def add_notes_to_pptx_com(pptx_path, slide_texts, temp_pptx_path):
    # (既存の win32com.client を使用したノート書き込みロジック)
    ppt = win32com.client.Dispatch("PowerPoint.Application")
    try:
        shutil.copyfile(pptx_path, temp_pptx_path)
    except Exception as e:
        print(f"エラー: コピー中にエラー: {e}")
        ppt.Quit()
        return False
    
    try:
        full_path = os.path.abspath(temp_pptx_path)
        pres = ppt.Presentations.Open(full_path)
    except Exception as e:
        print(f"エラー: PowerPointで一時ファイルを開けませんでした: {e}")
        ppt.Quit()
        return False
        
    print("  ノートを一時ファイルに書き込み中...")
    success_count = 0
    total_slides = pres.Slides.Count
    for idx in range(1, total_slides + 1):
        if idx in slide_texts:
            notes_text = slide_texts[idx]
            slide = pres.Slides(idx)
            notes_page = slide.NotesPage
            for shp in notes_page.Shapes:
                if shp.Type == 14 and shp.PlaceholderFormat.Type == 2: # 14: msoTextBox, 2: ppNotesPlaceholder
                    shp.TextFrame.TextRange.Text = notes_text
                    success_count += 1
                    break

        # ループ内で解放 
        shp = None 
        notes_page = None 
        slide = None

    #リソースを全て解放
    slide = None
    shp = None
    notes_page = None

    try:
        pres.Save()
        pythoncom.PumpWaitingMessages()
        pres.Close()
        ppt.Quit()
        print(f"✅ ノートを一時PPTXファイルに書き込みました: {temp_pptx_path} ({success_count}件)")
        return True
    except Exception as e:
        print(f"エラー: 一時ファイルへの保存中にエラー: {e}")
        print(f"　　無視して続行します")
        ppt.Quit()
        return True
#        return False

def link_audio_autoplay(source_pptx_path, audio_tasks, output_pptx):
    # (既存の win32com.client を使用した音声リンク設定ロジック)
    print("\n--- 🔗 PPTXに音声ファイルをリンクし、自動再生を設定中 ---")
    try:
        shutil.copyfile(source_pptx_path, output_pptx)
    except Exception as e:
        print(f"エラー: 出力ファイル {output_pptx} へのコピー中にエラー: {e}")
        return

    ppt = win32com.client.Dispatch("PowerPoint.Application")
    try:
        pres = ppt.Presentations.Open(os.path.abspath(output_pptx))
    except Exception as e:
        print(f"エラー: PowerPointで出力ファイルを開けませんでした: {e}")
        ppt.Quit()
        return

    for idx, wav_path in audio_tasks:
        slide = pres.Slides(idx)
        slide_width = pres.PageSetup.SlideWidth
        slide_height = pres.PageSetup.SlideHeight
        icon_size = 40
        left = slide_width - icon_size - 10
        top = slide_height - icon_size - 10

        wav_path = os.path.abspath(wav_path)
        print(f"  🔗 リンク追加: スライド {idx} ({wav_path[-40:]})")
        if not os.path.exists(wav_path):
            break
        
        shape = slide.Shapes.AddMediaObject2(
            wav_path,
            LinkToFile=True,
            SaveWithDocument=False,
            Left=left, Top=top,
            Width=icon_size, Height=icon_size
        )

        # 自動再生設定
        play_settings = shape.AnimationSettings.PlaySettings
        play_settings.PlayOnEntry = True
        
        # msoAnimTriggerWithPrevious = 2 でスライド遷移と同時に実行
        effect = slide.TimeLine.MainSequence.AddEffect(
            shape,
            9,   # msoAnimEffectMediaPlay
            0,   # 第3引数 Level (ここでは0)
            2    # 第4引数 Trigger (msoAnimTriggerWithPrevious)
        )
#        effect.Timing.Duration = 600.0 # 600.0秒 (10分)

    pres.Save()
    try:
        pres.Close()
    except:
        print("Warning: PowerPoiintオブジェクトのClose()に失敗しました")
        pass
    try:
       ppt.Quit()
    except:
        print("Warning: PowerPoiintオブジェクトのQuit()に失敗しました")
        pass
    print(f"\n✅ PowerPoint更新完了: {output_pptx}")


def generate_audio_files_tktts(pptx_path, voice_map, args):
    """ tktts のインターフェースを使用して、PPTXノートから音声ファイルを生成する。 """
    
    tktts = tkTTS(tts_name = args.tts, config = args)
    tts_engine = args.tts.lower()
    output_dir = args.audio_dir
    os.makedirs(output_dir, exist_ok=True)
    
    # 1. PPTXからノートを取得
    print("--- 📝 PPTXからノートを取得中 ---")
    ppt = win32com.client.Dispatch("PowerPoint.Application")
    full_path = os.path.abspath(pptx_path)
    try:
        pres = ppt.Presentations.Open(full_path)
    except Exception as e:
        print(f"エラー: PowerPointでファイルを開けませんでした: {e}")
        ppt.Quit()
        return [], ""
    
    dialogue_map = {} # {slide_idx: (None, notes_text)}
    for idx in range(1, pres.Slides.Count + 1):
#        print()
#        print(f"Slide {idx} の音声を生成します:")
        slide = pres.Slides(idx)
        notes = ""
        try:
            for shp in slide.NotesPage.Shapes:
                if shp.Type == 14 and shp.PlaceholderFormat.Type == 2:
                    notes = shp.TextFrame.TextRange.Text.strip()
                    break
        except Exception:
            pass
        
        if notes:
            # tktts.speak_dialogue が期待する形式 (speaker=None, text)
            dialogue_map[idx] = (None, notes)
            
    pres.Close()
    ppt.Quit()
    
    if not dialogue_map:
        print("  ⚠️ ノートが記載されたスライドが見つかりませんでした。")
        return [], ""
        
    print(f"  ✅ ノートを検出: {len(dialogue_map)} スライド")

    # 2. tktts.speak_dialogue のためのデータ準備
#    dialogue_list = list(dialogue_map.values()) # [(None, text1), (None, text2), ...]
    replacements = {}

    # 3. エンジンモジュールを直接使用してスライドごとに音声を生成
    print(f"\n--- 🗣️ スライドごとに {tts_engine.upper()} で音声ファイルを生成中 ---")
    
    ext = "wav"
#    ext = "mp3"
    sorted_slide_indices = sorted(dialogue_map.keys())
    audio_tasks = []
    for slide_idx in sorted_slide_indices:
        print()
        print(f"スライド #{slide_idx} の音声ファイルを生成しています...")
        
        # スライドごとの読み上げテキストを (None, text) のタプルで渡す
        speaker, notes = dialogue_map[slide_idx]
        # 単一スライドの対話リスト
        single_dialogue = [(speaker, notes)]
        slide_outfile = os.path.join(output_dir, f"slide{slide_idx}.{ext}")
        
        try:
            args.outfile = slide_outfile
            generated_file = tktts.speak_dialogue(
                config = args, dialogue = single_dialogue, 
                voice_map = voice_map, replacements = replacements,
                output_format = ext,
                )

            if generated_file:
                if os.path.exists(slide_outfile):
                     audio_tasks.append((slide_idx, os.path.abspath(generated_file)))
                     print(f"  ✅ 生成完了: {os.path.basename(slide_outfile)} (スライド {slide_idx})")
                else:
                    # エラー処理
                    print(f"  ❌ ファイルが見つかりません: スライド {slide_idx} での生成失敗")
                    
            else:
                print(f"  ❌ 生成失敗: スライド {slide_idx}")
                
        except Exception as e:
            print(f"  ❌ TTSモジュール呼び出しエラー: スライド {slide_idx} - {e}")
            traceback.print_exc()

    # 4. 一時ファイルのクリーンアップ (tktts 内部で削除されない場合を考慮)
    if os.path.exists(args.temp_dir):
        shutil.rmtree(args.temp_dir)
        print(f"\n🗑️ 一時ディレクトリ {args.temp_dir} を削除しました。")


    print(f"✅ 音声ファイル生成完了 ({len(audio_tasks)}件)")
    return audio_tasks


def main():
    args = initialize()

# endpoint, aquestalk_pathはargsで渡す
    tktts = tkTTS(tts_name = args.tts, config = args)

    if args.mode == 'list':
        if args.tts.lower() == "voicevox":
            tktts.list_available_voices(args.tts, endpoint=args.endpoint)
        else:
            tktts.list_available_voices(args.tts)
        terminate()

    source_pptx = args.input_path
    narration_file = args.narration_txt
    output_pptx = args.output_path

    if not all([source_pptx, narration_file]):
        print("\nエラー: mapモードでは --input_path, --narration_txtの引数が必要です。")
        print(f"現在の設定: input={source_pptx}, text={narration_file}")
        terminate()

    if args.mode == 'map':
        tktts.show_voice_map(narration_file, args.voices, VOICE_MAPS = {}, is_monologue = args.monologue)
        terminate()

    if not all([source_pptx, narration_file, output_pptx]):
        print("\nエラー: map/convモードでは --input_path, --narration_txt, --output_path の全ての引数が必要です。")
        print(f"現在の設定: input={source_pptx}, text={narration_file}, output={output_pptx}")
        terminate()

    print("--- 💻 PowerPointナレーション作成プログラム (CONVモード) 開始 ---")
    print(f"  TTS engine  : {args.tts}")
    print(f"  is monologue: {args.monologue}")
    print(f"  入力PPTX    : {source_pptx}")
    print(f"  入力テキスト: {narration_file}")
    print(f"  出力PPTX    : {output_pptx}")
    print(f"  音声フォルダ: {args.audio_dir}")
    print("-" * 50)

    use_narration = narration_file and os.path.exists(narration_file)
    temp_pptx_file = source_pptx
    
    # 1 & 2. テキストファイルがあればノートを追加
    current_voice_map = None
    if use_narration:
        print()
        print(f"[{narration_file}]を解析します:")
        dialogue = tktts.load_text(narration_file, args.monologue, wait_for_clipboard = False)
        if not dialogue:
            print("エラー: 有効なテキストデータが取得できませんでした。")
            if not args.monologue:
                print("  対話形式でない場合は --monologue=1 オプションをつけてください。")
            terminate()

        speakers_in_file = tktts.get_speakers_from_dialogue(dialogue)
        print(f"  Speakers in [{narration_file}]")
        for idx, sp in enumerate(speakers_in_file):
            print(f"    {idx:02d}: {sp}")

        current_voice_map = tktts.update_voice_map(voice_map = VOICE_MAPS, 
                                voices = args.voices, speakers = speakers_in_file)

        print()
        print(f"Voice map updated:")
        for key, val in current_voice_map.items():
            if type(key) is str:
                print(f"  (speaker) {key}: (voice) {val}")
        for key, val in current_voice_map.items():
            if type(key) is not str and type(key) is not int:
                print(f"  (speaker) {key}: (voice) {val}")
        for key, val in current_voice_map.items():
            if type(key) is int:
                print(f"  (speaker) {key}: (voice) {val}")

        print("=== 検出された話者とvoice ===")
        for s in sorted(speakers_in_file):
            if s is None or s == "":
                voice = current_voice_map.get(s, None)
                if voice is None: voice = current_voice_map.get(0, None)
                print(f"  (独話): {voice}")
            else:
                s = tktts.normalize_speaker(s, args.tts)
                print(f"- {s}: {current_voice_map.get(s, '未設定')}")

    # ノート用にナレーションファイルを読みこみ
        slide_texts = parse_narration_file(narration_file, args.monologue)
        if not slide_texts: terminate()

        temp_pptx_file = "temp_notes_added.pptx"
        ret = add_notes_to_pptx_com(source_pptx, slide_texts, temp_pptx_file)
        if not ret: terminate()
    
    # 3. ノートが書き込まれた一時ファイルから音声ファイルを生成 (tktts版)
    # ノートが書き込まれたファイルからノートテキストを抽出して音声生成を行う
    print()
    print("音声ファイルを生成します:")
    audio_tasks = generate_audio_files_tktts(temp_pptx_file, voice_map = current_voice_map, 
                    args = args)

    # 4. 音声ファイルをリンクして出力PPTXを保存
    if audio_tasks:
        print()
        print(f"音声ファイルを[{temp_pptx_file}]にリンクします:")
        link_audio_autoplay(temp_pptx_file, audio_tasks, output_pptx)
    else:
        print("\n完了: 音声ファイルが生成されなかったため、リンク処理をスキップしました。")

    # クリーンアップ
    if use_narration and os.path.exists(temp_pptx_file) and temp_pptx_file != source_pptx:
        print()
        print("一時ファイル [{temp_pptx_file}] を削除します:")
        os.remove(temp_pptx_file)
        print(f"  クリーンアップ完了")

    print("--- 🎉 プログラム終了 ---")


if __name__ == "__main__":
    main()
    terminate()