#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
slide2script.py

講義スライド(PPTX/PDF)と任意の講義文字起こしテキストから、
動画用のスライド読み上げ原稿を生成するアプリ。

想定ワークフロー:
    PPTX
      -> 必要なら pptx2pdf.py で PDF化
      -> OpenAI Responses API に PDF を input_file として渡す
      -> 必要なら講義音声の文字起こしテキストも統合
      -> スライド番号付き読み上げ原稿を生成

依存:
    pip install openai google-generativeai python-dotenv comtypes

同じディレクトリまたは PYTHONPATH 上に必要:
    tkai_movie.py
    pptx2pdf.py

使用例:
    python slide2script.py --in_pptx lecture.pptx
    python slide2script.py --in_pdf lecture.pdf --transcript lecture.txt
    python slide2script.py --in_pptx lecture.pptx --speaker zundamon --out lecture_script.txt
    python slide2script.py --in_pptx lecture.pptx --dialogue 1 --out lecture_dialogue.txt
"""

from __future__ import annotations

import os
import sys
import argparse
from pathlib import Path

try:
    from tkai_movie import AIConfig, AIRequest, run_ai_generation
except Exception as e:
    print("slide2script.py: Import error: tkai_movie", file=sys.stderr)
    print("tkai_movie.py を同じディレクトリ、または PYTHONPATH 上に置いてください。", file=sys.stderr)
    print(f"Original error: {e}", file=sys.stderr)
    input("\nPress ENTER to terminate>>\n")
    raise

try:
    from pptx2pdf import pptx_to_pdf
except Exception as e:
    print("slide2script.py: Import error: pptx2pdf", file=sys.stderr)
    print("pptx2pdf.py を同じディレクトリ、または PYTHONPATH 上に置いてください。", file=sys.stderr)
    print(f"Original error: {e}", file=sys.stderr)
    input("\nPress ENTER to terminate>>\n")
    raise


DEFAULT_OPENAI_MODEL5 = os.getenv("OPENAI_MODEL5", "gpt-5.4")
DEFAULT_GEMINI_MODEL = os.getenv("GOOGLE_MODEL", "gemini-3.1-pro-preview")


def read_text_file(path: str | os.PathLike | None) -> str:
    if not path:
        return ""

    p = Path(path)
    if not p.is_file():
        raise FileNotFoundError(f"file not found: {p}")

    return p.read_text(encoding="utf-8")


def default_output_name(in_pptx: str | None, in_pdf: str | None, suffix: str = "_script.txt") -> str:
    src = in_pdf or in_pptx
    if src:
        return str(Path(src).with_suffix("").name + suffix)
    return "slide_script.txt"


def build_system_prompt(args: argparse.Namespace) -> str:
    language_map = {
        "ja": "日本語",
        "jp": "日本語",
        "en": "米国英語",
        "zh": "標準中国語",
        "cn": "標準中国語",
        "ko": "韓国語",
        "kr": "韓国語",
    }
    language = language_map.get(args.lang, args.lang)

    if args.dialogue:
        style_instruction = f"""
出力は、ずんだもん・四国めたん等の対話形式の動画原稿にしてください。
話者名は以下を基本にしてください:
- 解説役: {args.speaker_main}
- 聞き手/ツッコミ役: {args.speaker_sub}

各発話は必ず次の形式にしてください:
話者名,内容
"""
    else:
        style_instruction = f"""
出力は、1人のナレーターが読み上げる動画原稿にしてください。
ナレーター名を付ける場合は「{args.speaker_main}」を使用してください。
ただし、通常は本文のみで自然な読み上げ原稿にしてください。
"""

    slide_format_instruction = """
スライドごとに必ず以下の区切りを入れてください:

---

# Slide N

（このスライドの読み上げ原稿）

N はスライド番号です。
"""

    visual_instruction = """
PPTX/PDFの視覚情報を重視してください。
特に、タイトル、箇条書き、図、表、矢印、強調表示、スライド間の流れを読み取り、
単なる文字の読み上げではなく、講義として自然につながる説明にしてください。
前後スライドの文脈を考慮し、同じ説明の重複は避けてください。
ただし、スライドにない重要な前提を補う場合は、講義として自然な範囲に留めてください。
"""

    transcript_instruction = """
講義文字起こしが与えられている場合は、スライドに書かれていない口頭補足を積極的に反映してください。
ただし、文字起こしには言い間違い、重複、フィラーが含まれる可能性があるため、
必要に応じて整理し、読み上げ原稿として自然な文章にしてください。
"""

    length_instruction = f"""
各スライドの原稿量は、目安として {args.words_per_slide} 文字程度にしてください。
重要なスライドは少し長く、導入・区切りスライドは短くして構いません。
"""

    system_prompt = f"""
あなたは、{args.field}を専門とする{args.role}です。
講義スライドと任意の講義文字起こしをもとに、動画用の読み上げ原稿を作成してください。

出力言語: {language}

{style_instruction}

{slide_format_instruction}

{visual_instruction}

{transcript_instruction}

{length_instruction}

注意:
- スライド番号を必ず維持してください。
- 数式は、読み上げやすい日本語説明を優先してください。
- 厳密な教科書Markdownではなく、動画で聞いて理解しやすい原稿を優先してください。
- 断定しすぎず、講義として自然な補足説明にしてください。
- 出力は指定された SCRIPT ブロック内だけに入れてください。
""".strip()

    return system_prompt


def prepare_pdf(args: argparse.Namespace) -> str:
    """
    入力PDFを決定する。
    - --in_pdf があればそれを使用
    - --in_pptx があれば pptx2pdf でPDF化
    """
    if args.in_pdf:
        pdf = Path(args.in_pdf).expanduser().resolve()
        if not pdf.is_file():
            raise FileNotFoundError(f"PDF file not found: {pdf}")
        return str(pdf)

    if args.in_pptx:
        pptx = Path(args.in_pptx).expanduser().resolve()
        if not pptx.is_file():
            raise FileNotFoundError(f"PPTX file not found: {pptx}")

        if args.pdf_out:
            pdf_out = Path(args.pdf_out).expanduser().resolve()
        else:
            pdf_out = pptx.with_suffix(".pdf")

        if args.convert_pptx:
            print("📄 PPTXをPDFに変換します...")
            pdf = pptx_to_pdf(
                pptx,
                pdf_out,
                overwrite=bool(args.overwrite_pdf),
                quiet=bool(args.quiet),
                visible=bool(args.ppt_visible),
            )
            if pdf is None:
                raise RuntimeError("PPTX to PDF conversion failed")
            return pdf

        if not pdf_out.is_file():
            raise FileNotFoundError(
                f"--convert_pptx 0 が指定されていますが、PDFが見つかりません: {pdf_out}"
            )
        return str(pdf_out)

    raise ValueError("--in_pptx または --in_pdf を指定してください")


def parse_args() -> argparse.Namespace:
    p = argparse.ArgumentParser(
        description="PPTX/PDF + 任意の講義文字起こしから、動画用スライド読み上げ原稿を生成します。",
        formatter_class=argparse.RawTextHelpFormatter,
    )

    inp = p.add_argument_group("入力")
    inp.add_argument("--in_pptx", "-ip", default=None, help="入力PowerPointファイル (.pptx/.ppt)")
    inp.add_argument("--in_pdf", "-if", default=None, help="入力PDFファイル。指定時はPPTX変換を行わない")
    inp.add_argument("--transcript", "-t", default=None, help="講義音声の文字起こしテキストファイル")
    inp.add_argument("--extra_text", default=None, help="追加メモ・指示テキストファイル")
    inp.add_argument("--pdf_out", default=None, help="PPTX変換後のPDF出力先")

    out = p.add_argument_group("出力")
    out.add_argument("--out", "-o", default=None, help="出力スクリプトファイル")
    out.add_argument("--raw_out", default=None, help="AI生応答の保存先")
    out.add_argument("--log", default=None, help="AIログJSONの保存先")

    ai = p.add_argument_group("AI設定")
    ai.add_argument("--api", "-a", choices=["openai5", "gemini", "google"], default="openai5", help="使用API")
    ai.add_argument("--model", default=None, help="モデル名を明示指定")
    ai.add_argument("--lang", default="ja", choices=["ja", "jp", "en", "zh", "cn", "ko", "kr"], help="出力言語")
    ai.add_argument("--field", default="半導体工学", help="専門分野")
    ai.add_argument("--role", default="大学教授", help="AIの役割")

    style = p.add_argument_group("原稿スタイル")
    style.add_argument("--dialogue", type=int, default=0, choices=[0, 1], help="1: 対話形式, 0: ナレーション形式")
    style.add_argument("--speaker_main", default="四国めたん", help="主解説者/ナレーター名")
    style.add_argument("--speaker_sub", default="ずんだもん", help="対話形式の聞き手/ツッコミ役")
    style.add_argument("--words_per_slide", type=int, default=250, help="各スライドあたりの目安文字数")

    conv = p.add_argument_group("PPTX→PDF変換")
    conv.add_argument("--convert_pptx", type=int, default=1, choices=[0, 1], help="PPTX入力時にPDF変換するか")
    conv.add_argument("--overwrite_pdf", type=int, default=1, choices=[0, 1], help="既存PDFを上書きするか")
    conv.add_argument("--ppt_visible", type=int, default=1, choices=[0, 1], help="PowerPointを表示して変換するか")

    misc = p.add_argument_group("その他")
    misc.add_argument("--quiet", type=int, default=0, choices=[0, 1], help="進行メッセージを抑制")
    misc.add_argument("--pause", type=int, default=1, choices=[0, 1], help="終了時にENTER待ちするか。Launcher用 default=1")

    args = p.parse_args()

    if not args.in_pptx and not args.in_pdf:
        p.error("--in_pptx または --in_pdf のどちらかを指定してください。")

    return args


def main() -> int:
    args = parse_args()

    try:
        pdf_path = prepare_pdf(args)

        transcript_text = read_text_file(args.transcript)
        extra_text = read_text_file(args.extra_text)

        system_prompt = build_system_prompt(args)

        text_inputs = {}
        if transcript_text:
            text_inputs["講義音声文字起こし"] = transcript_text
        else:
            text_inputs["講義音声文字起こし"] = "講義音声文字起こしはありません。スライド情報を中心に原稿を作成してください。"

        if extra_text:
            text_inputs["追加メモ"] = extra_text

        if args.model:
            if args.api == "openai5":
                openai_model5 = args.model
                gemini_model = DEFAULT_GEMINI_MODEL
            else:
                openai_model5 = DEFAULT_OPENAI_MODEL5
                gemini_model = args.model
        else:
            openai_model5 = DEFAULT_OPENAI_MODEL5
            gemini_model = DEFAULT_GEMINI_MODEL

        config = AIConfig.from_env(api=args.api)
        config.openai_model5 = openai_model5
        config.gemini_model = gemini_model
        config.pause = args.pause

        request = AIRequest(
            system_prompt=system_prompt,
            text_inputs=text_inputs,
            file_inputs=[pdf_path],
            output_blocks=["SCRIPT"],
        )

        out_file = args.out or default_output_name(args.in_pptx, args.in_pdf, "_script.txt")
        log_file = args.log or str(Path(out_file).with_suffix(".log"))

        print()
        print("生成AIで動画原稿を作成します...")
        print(f"  PDF入力       : {pdf_path}")
        print(f"  transcript   : {args.transcript}")
        print(f"  api          : {args.api}")
        print(f"  output       : {out_file}")
        print()

        result = run_ai_generation(
            request,
            config,
            save_log=True,
            logfile=log_file,
        )

        script = result.get_block("SCRIPT")
        if not script:
            print("⚠ SCRIPTブロックを抽出できませんでした。raw応答を出力します。", file=sys.stderr)
            script = result.raw_text

        Path(out_file).write_text(script.strip() + "\n", encoding="utf-8")
        print(f"✅ script written: {out_file}")

        if args.raw_out:
            Path(args.raw_out).write_text(result.raw_text, encoding="utf-8")
            print(f"✅ raw response written: {args.raw_out}")

        if args.pause:
            input("\nPress ENTER to terminate>>\n")

        return 0

    except Exception as e:
        print(f"\n❌ Error: {e}", file=sys.stderr)
        if args.pause:
            input("\nPress ENTER to terminate>>\n")
        return 1


if __name__ == "__main__":
    raise SystemExit(main())
