#!/usr/bin/env python3
"""A30準拠10段階エピソード執筆ユースケース

仕様書: SPEC-A30-001
max_turnsエラー根本解決のためのA30準拠10段階エピソード執筆システム

名称変更履歴:
- 旧名: FiveStageWritingUseCase (名称が実体と乖離)
- 新名: TenStageEpisodeWritingUseCase (10段階執筆の実態を正確に反映)
"""

import asyncio
import json
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import TYPE_CHECKING, Any, Optional

from noveler.application.base.abstract_use_case import AbstractUseCase
from noveler.application.services.a30_compatibility_adapter import (
    A30CompatibilityAdapter,
    CompatibilityMode,
)
from noveler.application.use_cases.ten_stage_progress_use_case import (
    ProgressStatus,
    TenStageProgressRequest,
)
from noveler.domain.services.configuration_loader_service import ConfigurationLoaderService, ProjectSettingsBundle
from noveler.domain.value_objects.detailed_execution_stage import DetailedExecutionStage
from noveler.domain.value_objects.five_stage_writing_execution import (
    ExecutionStage,
    FiveStageWritingRequest,
    FiveStageWritingResponse,
    StageExecutionResult,
    StageExecutionStatus,
)
from noveler.infrastructure.factories.path_service_factory import create_path_service
from noveler.infrastructure.integrations.claude_code_integration_service import ClaudeCodeIntegrationService
from noveler.infrastructure.services.unified_session_executor import UnifiedSessionExecutor


@dataclass
class StageResult:
    """ステージ実行結果"""

    stage_name: str
    success: bool
    turns_used: int = 0
    cost_usd: float = 0.0
    output: str = ""
    metadata: dict[str, Any] = field(default_factory=dict)

    def is_success(self) -> bool:
        """実行成功判定"""
        return self.success


# Console依存性注入: 直接インスタンス化を回避

# DDD Clean Architecture準拠: インターフェース依存
if TYPE_CHECKING:
    from noveler.domain.interfaces.console_service_protocol import IConsoleService
    from noveler.domain.interfaces.logger_service_protocol import ILoggerService
    from noveler.infrastructure.unit_of_work import IUnitOfWork

# DDD準拠: Infrastructure層への直接依存を除去
# from noveler.infrastructure.factories.configuration_service_factory import get_configuration_manager
# Phase 6+修正: Application → Infrastructure直接依存解消（DDD準拠）
# from noveler.infrastructure.integrations.claude_code_integration_service import ClaudeCodeIntegrationService
# DDD準拠: ドメインインターフェース経由でサービス呼出


# DDD Clean Architecture準拠: Presentation層への依存排除
# 依存性注入パターンで解決


class TenStageEpisodeWritingUseCase(AbstractUseCase[FiveStageWritingRequest, FiveStageWritingResponse]):
    """A30準拠10段階エピソード執筆ユースケース

    従来のmax_turns制限問題を根本解決するA30準拠10段階エピソード執筆システム

    名称変更理由:
    - 旧名「FiveStage」は実際の10段階構成と乖離していた
    - 「Episode」を追加してエピソード単位の執筆であることを明確化
    - 段階名もより具体的で理解しやすい名称に統一

    A30準拠10段階構成（SPEC-EPISODE-011感情重視改善版）:
    1. PlotDataPreparationStage (2ターン想定) - プロット・データ準備
    2. PlotAnalysisDesignStage (2ターン想定) - プロット分析・設計
    3. EmotionalRelationshipDesignStage (2ターン想定) - 感情・関係性設計（最優先）
    4. HumorCharmDesignStage (2ターン想定) - ユーモア・魅力設計
    5. CharacterPsychologyDialogueDesignStage (2ターン想定) - キャラ心理・対話設計
    6. SceneDirectionAtmosphereDesignStage (2ターン想定) - 場面演出・雰囲気設計
    7. LogicConsistencyAdjustmentStage (2ターン想定) - 論理整合性調整（最小限）
    8. ManuscriptWritingStage (3ターン想定) - 原稿執筆
    9. QualityRefinementStage (2ターン想定) - 品質仕上げ
    10. FinalAdjustmentStage (1ターン想定) - 最終調整

    合計: 20ターン想定 (従来10ターン制限の2.0倍で高品質な実行)
    """

    def __init__(
        self,
        logger_service: Optional["ILoggerService"] = None,
        unit_of_work: Optional["IUnitOfWork"] = None,
        console_service: Optional["IConsoleService"] = None,
        claude_code_service: ClaudeCodeIntegrationService | None = None,
        **kwargs: Any,  # noqa: ANN401
    ) -> None:
        """初期化

        Args:
            logger_service: ロガーサービス（DI注入）
            unit_of_work: ユニットオブワーク（DI注入）
            console_service: コンソールサービス（DI注入）
            claude_code_service: Claude Code統合サービス（DI注入）
            **kwargs: 追加のキーワード引数
        """
        progress_use_case = kwargs.pop("progress_use_case", None)
        compatibility_adapter = kwargs.pop("compatibility_adapter", None)
        session_executor = kwargs.pop("session_executor", None)

        super().__init__(console_service=console_service, **kwargs)
        # B20準拠: 標準DIサービス
        self._logger_service = logger_service
        self._unit_of_work = unit_of_work

        self._claude_code_service = claude_code_service
        self._configuration_manager: Any = None
        self._config_loader = ConfigurationLoaderService()
        self._progress_use_case = progress_use_case
        self._compatibility_adapter = compatibility_adapter or A30CompatibilityAdapter(
            CompatibilityMode.A30_DETAILED_TEN_STAGE
        )

        if session_executor is not None:
            self._session_executor = session_executor
        else:
            self._session_executor = UnifiedSessionExecutor(
                claude_service=self._get_claude_code_service(),
                compatibility_adapter=self._compatibility_adapter,
                progress_use_case=self._progress_use_case,
            )

        # 設定ファイルから読み込みが必要なフラグは除去（21段階ワークフローに一本化）

    def _get_configuration_manager(self) -> Any:  # noqa: ANN401
        """設定マネージャー取得（遅延初期化）

        Returns:
            Any: 設定マネージャーインスタンス
        """
        if self._configuration_manager is None:
            # 型チェッカー用の明示的インポートとキャスト
            self._configuration_manager = self.config_service
        return self._configuration_manager

    def _get_claude_code_service(self) -> ClaudeCodeIntegrationService:
        """Claude Codeサービス取得（遅延初期化）

        Returns:
            ClaudeCodeIntegrationService: Claude Code統合サービスインスタンス
        """
        if self._claude_code_service is None:
            # ClaudeCodeIntegrationConfigを作成
            from noveler.infrastructure.integrations.claude_code_integration_service import ClaudeCodeIntegrationConfig

            config = ClaudeCodeIntegrationConfig(
                claude_executable_path="claude",
                default_output_format="json",
                default_max_turns=3,
                timeout_seconds=300,
            )
            self._claude_code_service = ClaudeCodeIntegrationService(config)
        return self._claude_code_service

    @property
    def claude_code_service(self) -> ClaudeCodeIntegrationService:
        """Claude Codeサービス（プロパティ）"""
        return self._get_claude_code_service()

    @property
    def config(self) -> Any:  # noqa: ANN401
        """設定（プロパティ）"""
        return self._get_configuration_manager().get_configuration()

    async def execute(self, request: FiveStageWritingRequest) -> FiveStageWritingResponse:  # type: ignore[override]
        """A30準拠10段階執筆ユースケース実行

        Args:
            request: A30準拠10段階執筆リクエスト

        Returns:
            FiveStageWritingResponse: 実行結果
        """
        self.logger.info("A30準拠10段階エピソード執筆ユースケース開始 - 第%03d話", request.episode_number)

        # 前処理実行
        early_return_response = await self._handle_preprocessing(request)
        if early_return_response:
            return early_return_response

        try:
            return await self._execute_main_workflow(request)
        except Exception as e:
            return await self._handle_execution_error(e, request)

    async def _handle_preprocessing(self, request: FiveStageWritingRequest) -> FiveStageWritingResponse | None:
        """前処理ハンドラー（デバッグ、ドライラン、プロット表示、Claude Code利用可能性チェック）

        Args:
            request: 執筆リクエスト

        Returns:
            Optional[FiveStageWritingResponse]: 早期リターンが必要な場合のレスポンス
        """
        # デバッグモード処理
        if request.debug_mode:
            self.console_service.print_warning("処理中...")
            # 統一ロガーのレベル定数を使用
            from noveler.infrastructure.logging.unified_logger import LogLevel  # type: ignore
            self.logger.setLevel(LogLevel.DEBUG.value)

        # ドライランモード処理
        if request.dry_run:
            self.console_service.print_info("処理中...")
            return await self._execute_dry_run(request)

        # プロット要素表示（SPEC-PLOT-ADHERENCE-001 Section 2.1）
        await self._display_plot_elements_before_writing(request)

        # Claude Code利用可能性チェック
        claude_available = await self._check_claude_availability()
        if not claude_available:
            error_message = "Claude Codeが利用できません。21段階テンプレート実行には Claude Code の稼働が必須です。"
            self.logger.error(error_message)
            self.console_service.print_error(error_message)
            return FiveStageWritingResponse(
                success=False,
                session_id="claude_unavailable",
                stage_results={},
                error_message=error_message,
                recovery_suggestions=[
                    "Claude Code サービスの状態とAPIキーを確認してください",
                    "必要に応じてリトライ後に再度実行してください",
                ],
            )

        return None

    async def _check_claude_availability(self) -> bool:
        """Claude Code利用可能性チェック

        Returns:
            bool: 利用可能かどうか
        """
        try:
            return self.claude_code_service._validate_claude_code_availability()
        except Exception as e:
            self.logger.warning("Claude Code利用可能性チェックエラー: %s", e)
            return False

    async def _execute_main_workflow(self, request: FiveStageWritingRequest) -> FiveStageWritingResponse:
        """メインワークフロー実行

        Args:
            request: 執筆リクエスト

        Returns:
            FiveStageWritingResponse: 実行結果
        """
        # DDD準拠: セッション実行サービスは依存性注入で提供されるべき
        if hasattr(self, "_session_executor") and self._session_executor:
            unified_executor = self._session_executor
        else:
            raise RuntimeError(
                "Unified ten-stage execution executor is not configured. "
                "The legacy fallback path has been removed; please provide a compliant executor."
            )

        # 統一セッションで実行
        unified_response = await unified_executor.execute_unified_session(request)
        response = self._convert_unified_response(unified_response)

        # 成功時のStage 9品質仕上げ時にプロット準拠検証統合
        if response.success and response.stage_results:
            await self._handle_stage_9_plot_adherence_integration(request, response)

        # 後処理実行
        await self._handle_postprocessing(response, request)

        return response

    async def _execute_ten_stage_workflow(self, request: FiveStageWritingRequest) -> FiveStageWritingResponse:
        """Backward compatible wrapper for legacy test suites calling the previous private method name."""
        return await self._execute_main_workflow(request)

    def _convert_unified_response(self, unified_response) -> FiveStageWritingResponse:
        """Convert UnifiedSessionResponse to FiveStageWritingResponse."""

        # Guard: Support legacy FiveStageWritingResponse for backward compatibility with test fixtures
        if isinstance(unified_response, FiveStageWritingResponse):
            return unified_response

        success = str(getattr(unified_response, "status", "")).lower() == "success"
        metadata: dict[str, Any] = getattr(unified_response, "metadata", {}) or {}
        raw_responses = getattr(unified_response, "raw_responses", []) or []

        raw_by_stage: dict[str, dict[str, Any]] = {}
        for entry in raw_responses:
            if not isinstance(entry, dict):
                continue
            stage_name = entry.get("stage")
            payload = entry.get("response", entry)
            if isinstance(stage_name, str) and isinstance(payload, dict):
                raw_by_stage[stage_name] = payload

        legacy_outputs: dict[ExecutionStage, str] = {}
        raw_legacy = getattr(unified_response, "legacy_stage_outputs", {}) or {}
        for stage, output in dict(raw_legacy).items():
            if isinstance(stage, ExecutionStage):
                legacy_outputs[stage] = output

        stage_outputs = getattr(unified_response, "stage_outputs", {}) or {}
        if not legacy_outputs and stage_outputs:
            for stage, output in dict(stage_outputs).items():
                if isinstance(stage, ExecutionStage):
                    legacy_outputs[stage] = output

        def _parse_output_text(stage_name: str, text: str) -> dict[str, Any]:
            if not text:
                return {}
            stripped = text.strip()
            if stripped.startswith("{") and stripped.endswith("}"):
                try:
                    parsed = json.loads(stripped)
                    if isinstance(parsed, dict):
                        return parsed
                except json.JSONDecodeError:
                    return {"raw_output": text}
            return {"raw_output": text}

        stage_results: dict[ExecutionStage, StageExecutionResult] = {}

        for stage in ExecutionStage:
            output_text = legacy_outputs.get(stage)
            raw_entry = raw_by_stage.get(stage.value, {})

            stage_success = raw_entry.get("success")
            if stage_success is None:
                stage_success = success
            status = StageExecutionStatus.COMPLETED if stage_success else StageExecutionStatus.FAILED

            execution_time_ms = float(metadata.get("stage_timings", {}).get(stage.value, 0.0))
            turns_used = int(metadata.get("stage_turns", {}).get(stage.value, raw_entry.get("turns_used", 0)) or 0)
            error_message = raw_entry.get("error") or raw_entry.get("error_message")

            output_data: dict[str, Any] = {}
            if isinstance(raw_entry.get("output_data"), dict):
                output_data.update(raw_entry["output_data"])

            parsed_output = _parse_output_text(stage.value, output_text or "")
            if parsed_output:
                output_data.update(parsed_output)

            manuscript_candidate = (
                output_data.get("manuscript")
                or raw_entry.get("manuscript")
                or raw_entry.get("manuscript_markdown")
                or raw_entry.get("draft")
                or raw_entry.get("content")
                or raw_entry.get("result", {}).get("manuscript") if isinstance(raw_entry.get("result"), dict) else None
                or parsed_output.get("manuscript")
            )
            if not manuscript_candidate and stage in (ExecutionStage.MANUSCRIPT_WRITING, ExecutionStage.QUALITY_FINALIZATION):
                raw_text = output_data.get("raw_output") or output_text or ""
                if isinstance(raw_text, str) and raw_text.strip():
                    # 粗い判定だがMarkdownとして扱える本文をそのまま保存する
                    manuscript_candidate = raw_text
            if stage in (ExecutionStage.MANUSCRIPT_WRITING, ExecutionStage.QUALITY_FINALIZATION) and manuscript_candidate:
                output_data["manuscript"] = manuscript_candidate

            stage_result = StageExecutionResult(
                stage=stage,
                status=status,
                execution_time_ms=execution_time_ms,
                turns_used=turns_used,
                error_message=error_message,
                output_data=output_data,
            )

            structured_payload = raw_entry.get("structured_output")
            structured_data = None
            quality_score = 0.0
            if isinstance(structured_payload, dict):
                structured_data = structured_payload.get("structured_data") or structured_payload
                quality_score = float(structured_payload.get("quality_score") or 0.0)
            elif isinstance(raw_entry.get("structured_data"), dict):
                structured_data = raw_entry["structured_data"]

            if structured_data:
                try:
                    stage_result.create_structured_output(stage.value, structured_data, quality_score=quality_score)
                except Exception:
                    pass
            elif stage in (ExecutionStage.MANUSCRIPT_WRITING, ExecutionStage.QUALITY_FINALIZATION):
                manuscript_value = output_data.get("manuscript")
                if manuscript_value:
                    try:
                        stage_result.create_structured_output(
                            stage.value,
                            {"manuscript": manuscript_value},
                            quality_score=quality_score,
                        )
                    except Exception:
                        pass

            stage_results[stage] = stage_result

        response = FiveStageWritingResponse(
            success=success,
            session_id=getattr(unified_response, "session_id", ""),
            stage_results=stage_results,
            total_turns_used=getattr(unified_response, "turns_used", 0),
            total_execution_time_ms=float(metadata.get("execution_time_ms", 0.0)),
            error_message=None if success else metadata.get("error_message"),
        )

        if not success:
            response.failed_stage = next(
                (stage for stage, result in stage_results.items() if result.status == StageExecutionStatus.FAILED),
                None,
            )
            if response.error_message is None:
                response.error_message = metadata.get("error") or "unified session failed"

        return response

    async def _handle_postprocessing(
        self, response: FiveStageWritingResponse, request: FiveStageWritingRequest
    ) -> None:
        """後処理ハンドラー

        Args:
            response: 実行レスポンス
            request: 実行リクエスト
        """
        if response.success:
            await self._post_success_processing(response, request)
            self.console_service.print_success("処理中...")
        else:
            # 失敗時の後処理とフォールバック検討
            await self._post_failure_processing(response, request)

            # フォールバック実行を試行
    async def _handle_execution_error(
        self, error: Exception, request: FiveStageWritingRequest
    ) -> FiveStageWritingResponse:
        """実行エラーハンドラー

        Args:
            error: 発生したエラー
            request: 実行リクエスト

        Returns:
            FiveStageWritingResponse: エラー時のレスポンス
        """
        self.logger.exception("A30準拠10段階エピソード執筆ユースケース実行エラー")

        # 致命的エラー時のレスポンス
        return FiveStageWritingResponse(
            success=False,
            session_id="execution_error",
            stage_results={},
            error_message=f"実行エラー: {error!s}",
            recovery_suggestions=[
                "プロジェクトルートパスを確認してください",
                "必要な設定ファイルが存在するか確認してください",
                "システム管理者に連絡してください",
            ],
        )

    async def _handle_stage_9_plot_adherence_integration(
        self, request: FiveStageWritingRequest, response: FiveStageWritingResponse
    ) -> None:
        """Stage 9品質仕上げ時のプロット準拠検証統合

        SPEC-PLOT-ADHERENCE-001準拠実装
        TenStageEpisodeWritingUseCase Stage 9統合対応

        Args:
            request: 執筆リクエスト
            response: 実行レスポンス
        """
        try:
            self.logger.info("Stage 9品質仕上げ: プロット準拠検証統合開始")

            # Stage 9の実行結果から原稿内容を取得（品質仕上げ段階を使用）
            from noveler.domain.value_objects.five_stage_writing_execution import ExecutionStage

            stage_quality_key = ExecutionStage.QUALITY_FINALIZATION  # 品質仕上げ段階に対応
            stage_result = response.stage_results.get(stage_quality_key)

            if not stage_result:
                self.logger.warning("品質仕上げ段階の実行結果が見つかりません - プロット準拠検証をスキップ")
                return

            # structured_outputからmanuscriptを取得
            manuscript_content = ""
            if stage_result.structured_output and stage_result.structured_output.structured_data:
                manuscript_content = stage_result.structured_output.structured_data.get("manuscript", "")
            elif stage_result.output_data:
                manuscript_content = stage_result.output_data.get("manuscript", "")

            if not manuscript_content:
                self.logger.warning("品質仕上げ段階の原稿内容が見つかりません - プロット準拠検証をスキップ")
                return

            # プロット準拠検証ユースケース実行
            from noveler.application.use_cases.validate_plot_adherence_use_case import (
                PlotAdherenceRequest,
                ValidatePlotAdherenceUseCase,
            )

            plot_adherence_use_case = ValidatePlotAdherenceUseCase(  # type: ignore[no-untyped-call]
                logger_service=self._logger_service, unit_of_work=self._unit_of_work
            )

            # プロット準拠検証リクエスト構築
            adherence_request = PlotAdherenceRequest(
                episode_number=request.episode_number,
                manuscript_content=manuscript_content,
                project_root=request.project_root,
                include_suggestions=True,
                minimum_score_threshold=95.0,
            )

            # プロット準拠検証実行
            adherence_response = await plot_adherence_use_case.execute(adherence_request)

            # 検証結果をstage_resultのoutput_dataに追加
            if not stage_result.output_data:
                stage_result.output_data = {}

            stage_result.output_data["quality_checks"] = {
                "plot_adherence": {
                    "adherence_score": adherence_response.adherence_score,
                    "plot_elements_checked": adherence_response.plot_elements_checked,
                    "missing_elements": adherence_response.missing_elements,
                    "suggestions": adherence_response.suggestions,
                    "is_acceptable_quality": adherence_response.is_acceptable_quality(),
                    "quality_summary": adherence_response.get_quality_summary(),
                }
            }

            # 可視化レポート生成・表示
            from noveler.application.visualizers.plot_adherence_visualizer import PlotAdherenceVisualizer

            visualizer = PlotAdherenceVisualizer()
            if adherence_response.validation_result:
                visualizer.display_adherence_report(adherence_response.validation_result)

            # 品質基準未達時の警告
            if not adherence_response.is_acceptable_quality():
                self.console_service.print_warning(
                    f"⚠️ プロット準拠率が基準未達: {adherence_response.adherence_score:.1f}%"
                )
                self.console_service.print_info("改善提案を確認して再執筆を検討してください")
            else:
                self.console_service.print_success(
                    f"✅ プロット準拠検証完了: {adherence_response.get_quality_summary()}"
                )

            self.logger.info("品質仕上げ段階: プロット準拠検証統合完了")

        except Exception as e:
            self.logger.exception("品質仕上げ段階プロット準拠検証統合エラー")
            self.console_service.print_warning(f"プロット準拠検証でエラーが発生しましたが、執筆処理は継続します: {e}")

    async def resume_execution(self, session_id: str, project_root: Path) -> FiveStageWritingResponse:
        """A30準拠10段階実行再開

        Args:
            session_id: 再開するセッションID
            project_root: プロジェクトルートパス

        Returns:
            FiveStageWritingResponse: 実行結果
        """
        self.logger.info("A30準拠10段階実行再開 - セッション: %s", (session_id[:8]))

        # 再開リクエスト作成
        resume_request = FiveStageWritingRequest(
            episode_number=1,  # セッションから取得すべき
            project_root=project_root,
            resume_session_id=session_id,
        )

        return await self.execute(resume_request)

    async def get_execution_status(self, session_id: str, project_root: Path) -> dict[str, Any]:  # noqa: ARG002
        """実行ステータス取得

        Args:
            session_id: セッションID
            project_root: プロジェクトルートパス

        Returns:
            dict: ステータス情報
        """
        # Phase 6+修正: DDD準拠 - Infrastructure直接依存を除去
        # DI経由でサービス取得
        try:
            # TODO: DI - 依存性注入でIFiveStageExecutionServiceを取得
            # execution_service = self._five_stage_execution_service

            # DDD準拠修正：Infrastructure依存除去により暫定mock実装
            return {
                "status": "found",
                "session_id": session_id,
                "current_stage": "ddd_compliance_fixed",
                "total_stages": 10,
                "completed_stages": 0,
                "estimated_remaining_time": "DI実装後に復旧予定",
                "metadata": {"ddd_compliant": True, "fixed_phase6": True},
            }

        except Exception as e:
            self.logger.exception("ステータス取得エラー")
            return {"status": "error", "session_id": session_id, "error": f"ステータス取得エラー: {e!s}"}

    async def _execute_dry_run(self, request: FiveStageWritingRequest) -> FiveStageWritingResponse:
        """ドライラン実行"""
        import time

        start_time = time.time()
        self.console_service.print(f"[blue]🧪 ドライラン: 第{request.episode_number:03d}話 A30準拠10段階執筆[/blue]")

        # ドライラン用の仮結果生成（実際の処理時間をシミュレート）
        await asyncio.sleep(0.5)  # 実行時間のシミュレート

        # ExecutionStageとStageExecutionResultを使用（実際の5段階を使用）
        from noveler.domain.value_objects.five_stage_writing_execution import (
            ExecutionStage,
            StageExecutionResult,
            StageExecutionStatus,
        )

        stage_results: dict[ExecutionStage, StageExecutionResult] = {}

        # 実際のExecutionStageの5段階を使用
        execution_stages = [
            ExecutionStage.DATA_COLLECTION,
            ExecutionStage.PLOT_ANALYSIS,
            ExecutionStage.EPISODE_DESIGN,
            ExecutionStage.MANUSCRIPT_WRITING,
            ExecutionStage.QUALITY_FINALIZATION,
        ]

        stage_names = [
            "データ収集・準備段階",
            "プロット分析・設計段階",
            "エピソード設計段階",
            "原稿執筆段階",
            "品質チェック・仕上げ段階",
        ]

        for stage, stage_name in zip(execution_stages, stage_names, strict=False):
            stage_results[stage] = StageExecutionResult(
                stage=stage,
                status=StageExecutionStatus.COMPLETED,
                execution_time_ms=100.0,
                turns_used=stage.expected_turns,
                output_data={"dry_run": True, "stage_name": stage_name, "output": f"ドライラン仮出力: {stage_name}"},
            )

        end_time = time.time()
        execution_time_ms = (end_time - start_time) * 1000

        # 総ターン数を計算
        total_turns = sum(stage.expected_turns for stage in execution_stages)
        total_cost = total_turns * 0.025  # ターンあたり$0.025と仮定

        self.console_service.print(
            f"[green]✅ ドライラン完了 - 推定{total_turns}ターン、推定コスト${total_cost:.2f}[/green]"
        )
        self.console_service.print(f"[dim]実行時間: {execution_time_ms:.1f}ms[/dim]")

        # ドライラン用原稿ファイル生成（実際に作成）
        # B20準拠: パス管理はPathServiceを使用
        path_service = create_path_service()
        manuscript_dir = path_service.get_manuscript_dir()
        await asyncio.to_thread(manuscript_dir.mkdir, exist_ok=True)  # ディレクトリを確実に作成

        # B20準拠: ドライランも標準原稿パスに保存
        dry_run_manuscript_path = path_service.get_manuscript_path(request.episode_number)

        # ドライラン用のサンプル原稿コンテンツを生成
        sample_content = f"""# 第{request.episode_number:03d}話 ドライラン原稿

## 概要
この原稿はドライランモードで生成されたサンプルです。

## 本文

　これはA30準拠10段階執筆システム（内部5段階マッピング）によって生成されたドライラン原稿です。

　実際の執筆では、以下の5段階を経て高品質な原稿が作成されます：

1. データ収集・準備段階 ({ExecutionStage.DATA_COLLECTION.expected_turns}ターン)
2. プロット分析・設計段階 ({ExecutionStage.PLOT_ANALYSIS.expected_turns}ターン)
3. エピソード設計段階 ({ExecutionStage.EPISODE_DESIGN.expected_turns}ターン)
4. 原稿執筆段階 ({ExecutionStage.MANUSCRIPT_WRITING.expected_turns}ターン)
5. 品質チェック・仕上げ段階 ({ExecutionStage.QUALITY_FINALIZATION.expected_turns}ターン)

　合計{total_turns}ターンの処理によって、従来の10ターン制限を回避し、高品質な原稿を生成します。

---
*生成時刻: {datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")}*
*実行時間: {execution_time_ms:.1f}ms*
*推定コスト: ${total_cost:.2f}*
"""

        # 実際にファイルを書き込み
        await asyncio.to_thread(dry_run_manuscript_path.write_text, sample_content, encoding="utf-8")

        return FiveStageWritingResponse(
            success=True,
            session_id="dry_run_session",
            stage_results=stage_results,
            manuscript_path=dry_run_manuscript_path,  # Path型に修正
            error_message=None,
            total_turns_used=total_turns,
            total_execution_time_ms=execution_time_ms,
            total_cost_usd=total_cost,
            turns_saved_vs_single_execution=max(0, total_turns - 10),
        )

    async def _post_success_processing(self, response: FiveStageWritingResponse, request: FiveStageWritingRequest) -> None:
        """成功時後処理"""
        self.logger.info("A30準拠10段階執筆成功完了 - セッション: %s", (response.session_id[:8]))

        # パフォーマンス改善メトリクス表示
        if response.turns_saved_vs_single_execution > 0:
            self.console_service.print(
                f"[green]💡 パフォーマンス改善: {response.turns_saved_vs_single_execution}ターン節約[/green]"
            )

        # 原稿保存処理を実行
        try:
            await self._save_generated_manuscript(response, request)
        except Exception:
            self.logger.exception("原稿保存エラー")

        try:
            await self._update_progress_from_response(request, response)
        except Exception:
            self.logger.exception("進捗更新エラー")

        # 成功メトリクス更新
        try:
            await self._update_success_metrics(response)
        except Exception:
            self.logger.exception("成功メトリクス更新エラー")

    async def _post_failure_processing(
        self, response: FiveStageWritingResponse, request: FiveStageWritingRequest
    ) -> None:
        """失敗時後処理"""
        self.logger.warning("A30準拠10段階執筆失敗 - セッション: %s", (response.session_id[:8]))

        # 失敗パターン分析（今後の改善のため）
        # FiveStageWritingResponseにはmetadata属性がないため、failed_stageとerror_messageを使用
        failure_stage = response.failed_stage.value if response.failed_stage else "unknown"
        failure_reason = response.error_message or "unknown"

        self.logger.info("失敗分析: 段階=%s, 理由=%s", failure_stage, failure_reason)

    async def _update_success_metrics(self, response: FiveStageWritingResponse) -> None:
        """成功メトリクス更新"""
        try:
            metrics_dir = self.path_service.project_root / "metrics"
            await asyncio.to_thread(metrics_dir.mkdir, exist_ok=True)
            metrics_file = metrics_dir / "five_stage_success_metrics.json"

            # 既存メトリクス読み込み
            if await asyncio.to_thread(metrics_file.exists):
                metrics = await asyncio.to_thread(
                    json.loads, await asyncio.to_thread(metrics_file.read_text, encoding="utf-8")
                )
            else:
                metrics = {
                    "total_executions": 0,
                    "successful_executions": 0,
                    "total_turns_used": 0,
                    "total_cost_usd": 0.0,
                    "average_turns_per_execution": 0.0,
                    "average_cost_per_execution": 0.0,
                    "last_updated": None,
                }

            # メトリクス更新
            metrics["total_executions"] += 1
            metrics["successful_executions"] += 1
            metrics["total_turns_used"] += response.total_turns_used
            metrics["total_cost_usd"] += response.total_cost_usd
            metrics["average_turns_per_execution"] = metrics["total_turns_used"] / metrics["successful_executions"]
            metrics["average_cost_per_execution"] = metrics["total_cost_usd"] / metrics["successful_executions"]
            metrics["last_updated"] = datetime.now(timezone.utc).isoformat()

            # メトリクス保存
            metrics_content = json.dumps(metrics, ensure_ascii=False, indent=2)
            await asyncio.to_thread(metrics_file.write_text, metrics_content, encoding="utf-8")

        except Exception:
            self.logger.exception("成功メトリクス更新エラー")

    async def _save_generated_manuscript(self, response: FiveStageWritingResponse, request: FiveStageWritingRequest) -> None:
        """生成された原稿を保存

        Args:
            response: 実行レスポンス
            request: 実行リクエスト
        """
        try:
            if not response.stage_results:
                self.logger.warning("原稿保存: stage_resultsが空です")
                return

            # 最終段階（品質仕上げまたは最終調整）から原稿内容を取得
            from noveler.domain.value_objects.five_stage_writing_execution import ExecutionStage

            manuscript_content = ""
            episode_number = request.episode_number  # リクエストから取得

            # 品質仕上げ段階から原稿を取得（優先）
            quality_stage = response.stage_results.get(ExecutionStage.QUALITY_FINALIZATION)
            if quality_stage and quality_stage.structured_output:
                if quality_stage.structured_output.structured_data:
                    manuscript_content = quality_stage.structured_output.structured_data.get("manuscript", "")
                    episode_number = quality_stage.structured_output.structured_data.get("episode_number", 1)

            # 品質仕上げ段階で見つからない場合は原稿執筆段階から取得
            if not manuscript_content:
                writing_stage = response.stage_results.get(ExecutionStage.MANUSCRIPT_WRITING)
                if writing_stage and writing_stage.structured_output:
                    if writing_stage.structured_output.structured_data:
                        manuscript_content = writing_stage.structured_output.structured_data.get("manuscript", "")
                        episode_number = writing_stage.structured_output.structured_data.get("episode_number", 1)

            # それでも見つからない場合は各段階のoutput_dataから検索
            if not manuscript_content:
                for stage_result in response.stage_results.values():
                    if stage_result.output_data and "manuscript" in stage_result.output_data:
                        manuscript_content = stage_result.output_data["manuscript"]
                        episode_number = stage_result.output_data.get("episode_number", 1)
                        break

            if not manuscript_content:
                self.logger.warning("原稿保存: 原稿内容が見つかりません")
                return

            # MarkdownManuscriptRepository を使用して保存
            from noveler.infrastructure.repositories.markdown_manuscript_repository import MarkdownManuscriptRepository

            # 原稿ディレクトリの設定
            # B20準拠: パス管理はPathServiceを使用
            path_service = create_path_service()
            manuscript_dir = path_service.get_manuscript_dir()
            await asyncio.to_thread(manuscript_dir.mkdir, exist_ok=True)

            # リポジトリ初期化
            manuscript_repo = MarkdownManuscriptRepository(manuscript_dir)

            # メタデータの作成
            metadata = {
                "title": f"第{episode_number:03d}話",
                "episode_number": episode_number,
                "created_at": datetime.now(timezone.utc).isoformat(),
                "session_id": response.session_id,
                "status": "completed",
                "word_count": len(manuscript_content),
                "total_turns_used": response.total_turns_used,
                "total_cost_usd": response.total_cost_usd,
                "generated_by": "TenStageEpisodeWritingUseCase",
            }

            # 原稿保存（非同期）
            success = await asyncio.to_thread(
                manuscript_repo.save_manuscript_with_metadata,
                str(episode_number),
                manuscript_content,
                metadata
            )

            if success:
                # 共通基盤の命名規則に基づく原稿パスを設定
                manuscript_path = path_service.get_manuscript_path(episode_number)
                # responseのmanuscript_pathを更新
                response.manuscript_path = manuscript_path
                self.console_service.print(f"[green]💾 原稿保存完了: {manuscript_path}[/green]")
                self.logger.info("原稿保存完了: エピソード%s, パス: %s", episode_number, manuscript_path)
            else:
                self.console_service.print(f"[red]❌ 原稿保存失敗: 第{episode_number}話[/red]")
                self.logger.error("原稿保存失敗: 第%s話", episode_number)

        except Exception as e:
            self.logger.exception("原稿保存処理エラー")
            self.console_service.print(f"[red]⚠️ 原稿保存でエラーが発生しましたが、処理は継続します: {e}[/red]")

    def _load_project_settings(self) -> ProjectSettingsBundle:
        """プロジェクト設定を読み込み

        Returns:
            ProjectSettingsBundle: 読み込まれた設定データ
        """
        try:
            return self._config_loader.load_project_settings()
        except Exception as e:
            self.logger.warning("設定ファイル読み込みに失敗。デフォルト設定を使用: %s", e)
            return ProjectSettingsBundle(
                character_voice_patterns={},
                quality_rules_summary={},
                emotion_expression_rules="感情三層表現（身体反応+比喩+内面独白）を最低3回実装してください。",
                dialogue_ratio_targets="会話比率: 対話60%、独白40%を目標としてください。",
                character_interaction_requirements="サブキャラクターの能動的関与: 2シーン/話、関係性を示す雑談: 3回/話",
                explanation_limits="情報解説は2文以内とし、その後は必ずキャラクターの反応または会話に変換してください。",
                quantitative_check_criteria={},
                quality_scoring_rubric={},
                quality_rules_application={},
            )

    def _enhance_request_with_settings(
        self, request: FiveStageWritingRequest, settings: ProjectSettingsBundle
    ) -> FiveStageWritingRequest:
        """設定データでリクエストを拡張

        Args:
            request: 元のリクエスト
            settings: プロジェクト設定

        Returns:
            FiveStageWritingRequest: 拡張されたリクエスト
        """
        # 設定データをcustom_requirementsに追加
        # custom_requirementsがlist[str]の場合は拡張
        if isinstance(request.custom_requirements, list):
            enhanced_requirements_list = request.custom_requirements.copy()
            enhanced_requirements_list.extend(
                [
                    f"\n【キャラクター口調設定適用】\n{settings.character_voice_patterns}",
                    f"【執筆品質ルール適用】\n{settings.emotion_expression_rules}",
                ]
            )
        else:
            # custom_requirementsが文字列またはNoneの場合  # type: ignore[unreachable]
            base_requirements = request.custom_requirements or ""  # type: ignore[unreachable]
            enhanced_requirements_list = [
                base_requirements,
                f"\n【キャラクター口調設定適用】\n{settings.character_voice_patterns}",
                f"【執筆品質ルール適用】\n{settings.emotion_expression_rules}",
            ]

        # 拡張されたリクエストを作成（immutableなので新規作成）
        return FiveStageWritingRequest(
            episode_number=request.episode_number,
            project_root=request.project_root,  # project_rootを追加
            genre=request.genre,
            viewpoint=request.viewpoint,
            viewpoint_character=request.viewpoint_character,
            word_count_target=request.word_count_target,
            custom_requirements=enhanced_requirements_list,  # list[str]型で統一
            resume_session_id=request.resume_session_id,
            skip_completed_stages=request.skip_completed_stages,
            user_interaction_mode=request.user_interaction_mode,
            debug_mode=request.debug_mode,
            dry_run=request.dry_run,
        )

    async def _update_progress_from_response(
        self, request: FiveStageWritingRequest, response: FiveStageWritingResponse
    ) -> None:
        """成功レスポンスから進捗情報を更新する。"""
        progress_use_case = getattr(self, "_progress_use_case", None)
        if progress_use_case is None:
            return

        if not response.success or not response.stage_results:
            return

        adapter = getattr(self, "_compatibility_adapter", None)
        if adapter is None:
            return

        episode_number = request.episode_number
        progress_cache = getattr(progress_use_case, "_stage_progress_cache", {})
        if episode_number not in progress_cache:
            await progress_use_case.execute(
                TenStageProgressRequest(
                    episode_number=episode_number,
                    project_root=request.project_root,
                    operation="start",
                    force_reset=True,
                )
            )

        turns_map: dict[DetailedExecutionStage, int] = {}

        for legacy_stage, stage_result in response.stage_results.items():
            try:
                detailed_stages = adapter.convert_legacy_to_detailed(legacy_stage)
            except Exception:
                detailed_stages = []

            if not detailed_stages:
                continue

            stage_output_payload = stage_result.structured_output or stage_result.output_data or {
                "stage": legacy_stage.name,
                "status": stage_result.status.value,
            }

            for detailed_stage in detailed_stages:
                await progress_use_case.execute(
                    TenStageProgressRequest(
                        episode_number=episode_number,
                        project_root=request.project_root,
                        operation="update",
                        stage=detailed_stage,
                        stage_output=stage_output_payload,
                    )
                )
                turns_map[detailed_stage] = stage_result.turns_used

        stage_progresses = getattr(progress_use_case, "_stage_progress_cache", {}).get(episode_number)
        if not stage_progresses:
            return

        now = datetime.now(timezone.utc)
        for stage in stage_progresses.values():
            stage.status = ProgressStatus.COMPLETED
            if stage.started_at is None:
                stage.started_at = now
            stage.completed_at = now
            stage.turns_used = turns_map.get(stage.stage, stage.turns_used or max(1, response.total_turns_used // len(stage_progresses)))
            stage.estimated_turns_remaining = 0

    def get_stage_execution_estimates(self) -> dict[str, Any]:
        """段階実行見積もり情報取得

        Returns:
            dict: 見積もり情報
        """
        total_turns = sum(stage.expected_turns for stage in DetailedExecutionStage)
        cost_per_turn = 0.04
        estimated_cost = round(total_turns * cost_per_turn, 2)
        estimated_duration = round(45 * (total_turns / 20))

        return {
            "total_estimated_turns": total_turns,
            "estimated_cost_usd": estimated_cost,
            "estimated_duration_minutes": estimated_duration,
            "vs_single_execution": {
                "improvement_ratio": 2.0,
                "reliability_improvement": "高",
                "max_turns_avoidance": "完全回避",
            },
            "stage_breakdown": [
                {"stage": detailed_stage.value, "estimated_turns": detailed_stage.expected_turns}
                for detailed_stage in DetailedExecutionStage
            ],
        }

    async def _display_plot_elements_before_writing(self, request: FiveStageWritingRequest) -> None:
        """執筆前プロット要素表示（SPEC-PLOT-ADHERENCE-001 Section 2.1）

        Args:
            request: 執筆リクエスト

        Raises:
            FileNotFoundError: プロットファイルが見つからない場合
            ValueError: プロットデータが不正な場合
        """
        # プロットデータ読み込み
        plot_data = self._load_episode_plot_data(request.project_root, request.episode_number)

        # プロット表示サービスを使って表示
        from noveler.application.services.plot_display_service import PlotDisplayService

        display_service = PlotDisplayService(
            console_service=self.console_service,
            logger_service=self._logger_service,
        )
        display_service.display_plot_elements(
            episode_number=request.episode_number,
            plot_data=plot_data,
        )

    def _load_episode_plot_data(self, project_root: Path, episode_number: int) -> dict[str, Any]:
        """エピソードプロットデータを読み込む

        Args:
            project_root: プロジェクトルート
            episode_number: エピソード番号

        Returns:
            dict: プロットデータ

        Raises:
            FileNotFoundError: プロットファイルが見つからない場合
            ValueError: プロットデータが不正な場合
        """
        from noveler.infrastructure.adapters.path_service_adapter import create_path_service
        import re
        import yaml

        ps = create_path_service(project_root)
        ep_dir = ps.get_episode_plots_dir()
        pattern = re.compile(rf"^第{episode_number:03d}話_.*\.yaml$")
        candidates = [p for p in ep_dir.glob("*.yaml") if pattern.match(p.name)]

        if not candidates:
            msg = f"第{episode_number:03d}話のプロットファイルが見つかりません: {ep_dir}"
            raise FileNotFoundError(msg)

        plot_file = candidates[0]
        try:
            with plot_file.open("r", encoding="utf-8") as f:
                data = yaml.safe_load(f)
        except Exception as e:
            msg = f"プロットファイルの読み込みに失敗しました: {plot_file}"
            raise ValueError(msg) from e

        if not isinstance(data, dict):
            msg = f"プロットデータが不正です（辞書型である必要があります）: {plot_file}"
            raise ValueError(msg)

        return {
            "key_events": data.get("key_events", []) or [],
            "character_development": data.get("character_development", []) or [],
            "world_building": data.get("world_building", []) or [],
            "foreshadowing": data.get("foreshadowing", []) or [],
        }
