# File: src/mcp_servers/noveler/tools/emotion/emotion_pipeline_coordinator.py
# Purpose: Coordinate emotion-analysis MCP tools into a cohesive optimisation pipeline.
# Context: Orchestrates SPEC-A38 STEP 8 by combining specialised emotion tools in noveler server workflows.

"""Emotion pipeline coordinator that orchestrates multiple MCP tools.

Purpose:
    Provide a unified pipeline that runs individual emotion tools, aggregates their insights, and surfaces guidance.
Context:
    Invoked by the noveler MCP server after upstream validation to design and adjust emotional story arcs.
Side Effects:
    Manages in-memory caches, async semaphores, performance metrics, and logs execution progress.
"""

import asyncio
import hashlib
import time
from dataclasses import dataclass
from enum import Enum
from typing import Any

from .base_emotion_tool import BaseEmotionTool, EmotionToolInput, EmotionToolOutput
from .cliche_detector_tool import CliqueDetectorTool
from .contextual_cue_retriever_tool import ContextualCueRetrieverTool
from .metaphor_diversity_scorer_tool import MetaphorDiversityScorerTool
from .micro_ab_emotion_test_tool import MicroABEmotionTestTool
from .physiology_checker_tool import PhysiologyCheckerTool
from .register_verifier_tool import RegisterVerifierTool


class PipelineMode(Enum):
    """Enumerates supported execution strategies for the emotion pipeline.

    Purpose:
        Expose pipeline strategies so callers can control concurrency and tool selection logic.
    Side Effects:
        None.
    """
    SEQUENTIAL = "sequential"    # 順次実行
    PARALLEL = "parallel"       # 並列実行
    SELECTIVE = "selective"     # 選択的実行


@dataclass
class PipelineConfig:
    """Configuration values controlling pipeline execution behaviour.

    Purpose:
        Capture runtime knobs for tool selection, caching, thresholds, and iteration limits.
    Side Effects:
        None.
    """
    mode: PipelineMode = PipelineMode.PARALLEL  # 並列実行をデフォルト
    enabled_tools: list[str] | None = None  # Noneの場合は全ツール有効
    context_metadata: dict[str, Any] | None = None
    quality_threshold: float = 70.0
    max_iterations: int = 3
    enable_caching: bool = True  # キャッシュ有効化
    cache_ttl_seconds: int = 300  # キャッシュTTL（5分）
    parallel_chunk_size: int = 3  # 並列実行時のチャンクサイズ


@dataclass
class PipelineResult:
    """Aggregate output produced by a pipeline execution.

    Purpose:
        Convey tool-level outcomes, integrated analyses, and actionable guidance back to callers.
    Side Effects:
        None.
    """
    success: bool
    overall_score: float
    tool_results: dict[str, EmotionToolOutput]
    integrated_analysis: dict[str, Any]
    recommendations: list[str]
    final_text_suggestions: list[str]
    execution_metadata: dict[str, Any]


class EmotionPipelineCoordinator(BaseEmotionTool):
    """Coordinates specialised emotion tools into a unified optimisation pipeline.

    Purpose:
        Execute the emotion tool suite, merge their analyses, and recommend narrative adjustments.
    Side Effects:
        Maintains caches, async primitives, and logging state inherited from the base tool.
    """

    def __init__(self) -> None:
        """Initialise the pipeline coordinator and preload supporting structures.

        Purpose:
            Register the tool name, instantiate dependant tools, and prepare integration metadata.
        Side Effects:
            Populates dictionaries for tools, dependencies, weights, caches, and performance metrics.
        """
        super().__init__("emotion_pipeline_coordinator")
        self._initialize_tools()
        self._initialize_integration_rules()
        self._initialize_performance_optimizations()

    def _initialize_tool(self) -> None:
        """Emit lifecycle trace when the base tool initialises.

        Purpose:
            Provide visibility that the coordinator finished base-class initialisation.
        Side Effects:
            Writes an informational log entry.
        """
        self.logger.info("感情表現統合パイプライン初期化")

    def _initialize_tools(self) -> None:
        """Instantiate and register component emotion tools.

        Purpose:
            Provide ready-to-use tool instances keyed by identifier for later orchestration.
        Side Effects:
            Stores tool instances on `self.tools`.
        """
        self.tools = {
            "cliche_detector": CliqueDetectorTool(),
            "contextual_cue_retriever": ContextualCueRetrieverTool(),
            "physiology_checker": PhysiologyCheckerTool(),
            "metaphor_diversity_scorer": MetaphorDiversityScorerTool(),
            "register_verifier": RegisterVerifierTool(),
            "micro_ab_emotion_test": MicroABEmotionTestTool()
        }

    def _initialize_integration_rules(self) -> None:
        """Define inter-tool dependencies and scoring weights.

        Purpose:
            Capture execution ordering constraints and contribution weights for aggregation.
        Side Effects:
            Stores dependency and weight mappings on the instance.
        """
        # ツール間の依存関係定義
        self.tool_dependencies = {
            "contextual_cue_retriever": [],  # 最初に実行
            "cliche_detector": [],
            "physiology_checker": ["contextual_cue_retriever"],
            "metaphor_diversity_scorer": ["cliche_detector"],
            "register_verifier": ["contextual_cue_retriever"],
            "micro_ab_emotion_test": ["cliche_detector", "metaphor_diversity_scorer"]
        }

        # 統合重み（最終スコア計算用）
        self.integration_weights = {
            "cliche_detector": 0.20,
            "contextual_cue_retriever": 0.15,
            "physiology_checker": 0.20,
            "metaphor_diversity_scorer": 0.15,
            "register_verifier": 0.15,
            "micro_ab_emotion_test": 0.15
        }

    def _initialize_performance_optimizations(self) -> None:
        """Prepare caching and performance-tracking structures.

        Purpose:
            Enable cache reuse, concurrency control, and metrics collection for pipeline runs.
        Side Effects:
            Allocates caches, timestamps, semaphores, and metrics dictionaries on the instance.
        """
        # 結果キャッシュ（メモリベース、TTL付き）
        self.result_cache: dict[str, dict[str, EmotionToolOutput | float]] = {}
        self.cache_timestamps: dict[str, float] = {}

        # 並列実行用セマフォ（同時実行数制限）
        self.execution_semaphore = asyncio.Semaphore(6)  # 最大6ツール同時実行

        # パフォーマンスメトリクス
        self.performance_metrics = {
            "total_executions": 0,
            "cache_hits": 0,
            "cache_misses": 0,
            "average_execution_time": 0.0,
            "parallel_execution_count": 0
        }

        # 品質基準
        self.quality_thresholds = {
            "minimum_acceptable": 60.0,
            "good_quality": 75.0,
            "excellent_quality": 85.0
        }

    async def execute(self, input_data: EmotionToolInput) -> EmotionToolOutput:
        """Run the optimised emotion pipeline end to end.

        Purpose:
            Execute orchestrated emotion tools, fuse their outputs, and deliver a consolidated result.

        Args:
            input_data: EmotionToolInput containing manuscript text, metadata, and optional pipeline overrides.

        Returns:
            EmotionToolOutput with aggregated analyses, recommendations, and execution metadata.
        Side Effects:
            Updates cache usage metrics, writes logs, and may populate in-memory cache entries.
        Preconditions:
            Component tools must be initialised via `_initialize_tools`.
        """
        start_time = time.time()

        try:
            # パフォーマンスメトリクス更新
            self.performance_metrics["total_executions"] += 1

            # キャッシュキー生成
            cache_key = self._generate_cache_key(input_data)

            # キャッシュチェック
            if cached_result := self._get_cached_result(cache_key):
                self.performance_metrics["cache_hits"] += 1
                self.logger.info(
                    "キャッシュヒット: %s...",
                    cache_key[:16],
                )
                return cached_result

            self.performance_metrics["cache_misses"] += 1

            # パイプライン設定の抽出
            config = self._extract_pipeline_config(input_data.metadata or {})

            # 最適化されたパイプライン実行
            pipeline_result = await self._execute_optimized_pipeline(input_data, config)

            if not pipeline_result.success:
                return self.create_error_output(
                    "パイプライン実行エラー",
                    ["統合処理中にエラーが発生しました"]
                )

            # 統合分析結果の構築
            integrated_analysis = self._build_integrated_analysis(pipeline_result)

            # 最終推奨事項の生成
            final_recommendations = self._generate_final_recommendations(pipeline_result)

            # 実行時間計算
            execution_time = (time.time() - start_time) * 1000
            pipeline_result.execution_metadata["execution_time_ms"] = execution_time

            # 結果作成
            result = self.create_success_output(
                score=pipeline_result.overall_score,
                analysis=integrated_analysis,
                suggestions=final_recommendations,
                metadata=pipeline_result.execution_metadata
            )

            # 結果キャッシュ
            if config.enable_caching:
                self._cache_result(cache_key, result)

            # パフォーマンス統計更新
            self._update_performance_stats(execution_time)

            self.logger.info(
                "パイプライン実行完了: %.2fms, スコア: %.2f",
                execution_time,
                pipeline_result.overall_score,
            )
            return result

        except Exception as e:
            execution_time = (time.time() - start_time) * 1000
            self.logger.exception(
                "パイプライン実行エラー: %s, 時間: %.2fms",
                e,
                execution_time,
            )
            return self.create_error_output(
                f"パイプライン実行エラー: {e!s}",
                ["統合処理中にエラーが発生しました"]
            )

    async def execute_pipeline(self, text: str, config: PipelineConfig | None = None) -> PipelineResult:
        """Execute the pipeline using raw text input for convenience.

        Purpose:
            Offer a simple interface for callers that bypasses `EmotionToolInput` construction.

        Args:
            text: Narrative text passed to underlying tools.
            config: Optional PipelineConfig overriding default behaviour.

        Returns:
            PipelineResult describing tool outcomes, scores, and recommendations.
        Side Effects:
            Generates an EmotionToolInput and logs execution steps.
        Preconditions:
            `config` must either be None or a fully initialised PipelineConfig instance.
        """
        input_data = EmotionToolInput(
            text=text,
            metadata={"pipeline_config": config.__dict__ if config else {}}
        )

        config = config or PipelineConfig()
        return await self._execute_pipeline(input_data, config)

    def _extract_pipeline_config(self, metadata: dict[str, Any]) -> PipelineConfig:
        """Build a PipelineConfig from incoming metadata overrides.

        Purpose:
            Normalise caller-provided configuration dictionaries into strongly typed objects.

        Args:
            metadata: Input metadata dictionary provided with the tool request.

        Returns:
            PipelineConfig populated with defaults when values are missing.
        Side Effects:
            None.
        Preconditions:
            `metadata` should contain a `pipeline_config` mapping if overrides are desired.
        """
        config_data = metadata.get("pipeline_config", {})

        return PipelineConfig(
            mode=PipelineMode(config_data.get("mode", "sequential")),
            enabled_tools=config_data.get("enabled_tools"),
            context_metadata=config_data.get("context_metadata", {}),
            quality_threshold=config_data.get("quality_threshold", 70.0),
            max_iterations=config_data.get("max_iterations", 3)
        )

    async def _execute_pipeline(self, input_data: EmotionToolInput,
                              config: PipelineConfig) -> PipelineResult:
        """Execute the pipeline according to the selected mode.

        Purpose:
            Route execution through sequential, parallel, or selective strategies as configured.

        Args:
            input_data: Normalised EmotionToolInput for the current request.
            config: PipelineConfig specifying execution parameters.

        Returns:
            PipelineResult summarising per-tool outputs and preliminary aggregations.
        Side Effects:
            Emits informational logs and may raise exceptions for unrecoverable errors.
        Preconditions:
            Component tool set must already be registered in `self.tools`.
        """
        self.logger.info(
            "パイプライン実行開始: モード=%s",
            config.mode.value,
        )

        try:
            if config.mode == PipelineMode.PARALLEL:
                tool_results = await self._execute_parallel(input_data, config)
            elif config.mode == PipelineMode.SELECTIVE:
                tool_results = await self._execute_selective(input_data, config)
            else:  # SEQUENTIAL
                tool_results = await self._execute_sequential(input_data, config)

            # 統合スコア計算
            overall_score = self._calculate_overall_score(tool_results)

            # 推奨事項統合
            recommendations = self._integrate_recommendations(tool_results)

            # テキスト改善提案
            text_suggestions = self._generate_text_suggestions(tool_results, input_data.text)

            return PipelineResult(
                success=True,
                overall_score=overall_score,
                tool_results=tool_results,
                integrated_analysis={},  # 後で構築
                recommendations=recommendations,
                final_text_suggestions=text_suggestions,
                execution_metadata={
                    "mode": config.mode.value,
                    "tools_executed": list(tool_results.keys()),
                    "execution_time": 0  # 実際の実行時間を記録する場合
                }
            )

        except Exception as e:
            self.logger.exception(
                "パイプライン実行エラー: %s",
                e,
            )
            return PipelineResult(
                success=False,
                overall_score=0,
                tool_results={},
                integrated_analysis={},
                recommendations=[f"エラー: {e!s}"],
                final_text_suggestions=[],
                execution_metadata={}
            )

    async def _execute_sequential(self, input_data: EmotionToolInput,
                                config: PipelineConfig) -> dict[str, EmotionToolOutput]:
        """Execute tools sequentially respecting dependency order.

        Purpose:
            Provide a safe baseline execution path when concurrency is unnecessary or undesirable.

        Args:
            input_data: EmotionToolInput shared across all tools.
            config: PipelineConfig indicating enabled tools and metadata.

        Returns:
            Dictionary mapping tool identifiers to their outputs.
        Side Effects:
            Logs execution progress for each tool.
        Preconditions:
            Dependencies must be resolvable via `_resolve_execution_order`.
        """
        results = {}
        enabled_tools = config.enabled_tools or list(self.tools.keys())

        # 依存関係順序でツールを実行
        execution_order = self._resolve_execution_order(enabled_tools)

        for tool_name in execution_order:
            if tool_name in self.tools:
                self.logger.info(
                    "ツール実行: %s",
                    tool_name,
                )

                # 前の結果を考慮した入力データ調整
                adjusted_input = self._adjust_input_for_tool(
                    input_data, tool_name, results, config.context_metadata
                )

                # ツール実行
                result = await self.tools[tool_name].safe_execute(adjusted_input)
                results[tool_name] = result

                self.logger.info(
                    "ツール完了: %s, スコア: %s",
                    tool_name,
                    result.score,
                )

        return results

    async def _execute_parallel(self, input_data: EmotionToolInput,
                              config: PipelineConfig) -> dict[str, EmotionToolOutput]:
        """Execute tools concurrently without cross-pipeline optimisation.

        Purpose:
            Provide a simpler parallel pathway used by legacy `_execute_pipeline`.

        Args:
            input_data: EmotionToolInput shared across tasks.
            config: PipelineConfig specifying enabled tool identifiers.

        Returns:
            Dictionary of tool outputs keyed by identifier.
        Side Effects:
            Schedules asynchronous tasks and logs errors for failures.
        Preconditions:
            Caller must manage semaphore limits if required.
        """
        enabled_tools = config.enabled_tools or list(self.tools.keys())

        # 依存関係のないツールを並列実行
        tasks = []
        for tool_name in enabled_tools:
            if tool_name in self.tools:
                adjusted_input = self._adjust_input_for_tool(
                    input_data, tool_name, {}, config.context_metadata
                )
                task = self.tools[tool_name].safe_execute(adjusted_input)
                tasks.append((tool_name, task))

        # 並列実行
        results = {}
        completed_tasks = await asyncio.gather(*[task for _, task in tasks], return_exceptions=True)

        for i, (tool_name, _) in enumerate(tasks):
            result = completed_tasks[i]
            if isinstance(result, Exception):
                self.logger.error(
                    "ツール実行エラー: %s, %s",
                    tool_name,
                    result,
                )
                results[tool_name] = self.tools[tool_name].create_error_output(str(result))
            else:
                results[tool_name] = result

        return results

    async def _execute_selective(self, input_data: EmotionToolInput,
                                config: PipelineConfig) -> dict[str, EmotionToolOutput]:
        """Run only the tools deemed relevant for the provided context.

        Purpose:
            Optimise runtime by executing a curated subset of tools when selective mode is chosen.

        Args:
            input_data: EmotionToolInput for the current request.
            config: PipelineConfig whose metadata informs tool selection.

        Returns:
            Mapping of tool identifiers to their EmotionToolOutput.
        Side Effects:
            None beyond logging performed within subordinate methods.
        Preconditions:
            `_select_tools_by_content` must enumerate at least the core tool set.
        """
        # テキストの特徴に基づいてツールを選択
        selected_tools = self._select_tools_by_content(input_data.text, config)

        # 選択されたツールで順次実行
        temp_config = PipelineConfig(
            mode=PipelineMode.SEQUENTIAL,
            enabled_tools=selected_tools,
            context_metadata=config.context_metadata,
            quality_threshold=config.quality_threshold
        )

        return await self._execute_sequential(input_data, temp_config)

    def _resolve_execution_order(self, enabled_tools: list[str]) -> list[str]:
        """Determine an execution order that satisfies dependency constraints.

        Purpose:
            Ensure prerequisite tools run before dependants without requiring a full topological sort.

        Args:
            enabled_tools: Tool identifiers requested for the current pipeline run.

        Returns:
            Ordered list of tools respecting declared dependencies.
        Side Effects:
            None.
        Preconditions:
            `self.tool_dependencies` must define dependency lists for registered tools.
        """
        # 簡化したトポロジカルソート
        ordered = []
        remaining = enabled_tools.copy()

        while remaining:
            # 依存関係がない、または依存先が既に実行済みのツールを探す
            ready_tools = []
            for tool in remaining:
                dependencies = self.tool_dependencies.get(tool, [])
                if all(dep in ordered or dep not in enabled_tools for dep in dependencies):
                    ready_tools.append(tool)

            if not ready_tools:
                # デッドロックを避けるため、残りのツールを追加
                ordered.extend(remaining)
                break

            # 最初の準備完了ツールを実行順序に追加
            ordered.extend(ready_tools)
            for tool in ready_tools:
                remaining.remove(tool)

        return ordered

    def _adjust_input_for_tool(self, base_input: EmotionToolInput, tool_name: str,
                             previous_results: dict[str, EmotionToolOutput],
                             context_metadata: dict[str, Any]) -> EmotionToolInput:
        """Derive tool-specific input from the shared base payload.

        Purpose:
            Apply per-tool contextual tweaks while preserving the original request data.

        Args:
            base_input: Original EmotionToolInput supplied to the pipeline.
            tool_name: Identifier of the tool about to execute.
            previous_results: Outputs from already executed tools, if any.
            context_metadata: Additional metadata configured for the pipeline run.

        Returns:
            EmotionToolInput tailored for the requested tool.
        Side Effects:
            May inject extra metadata fields (e.g., A/B variants) for specific tools.
        Preconditions:
            `base_input` must include the text to analyse.
        """
        metadata = (base_input.metadata or {}).copy()
        metadata.update(context_metadata or {})

        # ツール固有の調整
        if tool_name == "micro_ab_emotion_test" and previous_results:
            # A/Bテストには複数の候補が必要（簡化実装）
            metadata["variant_a"] = base_input.text
            metadata["variant_b"] = base_input.text + "（改良版）"  # 実際には他の結果から生成

        return EmotionToolInput(
            text=base_input.text,
            emotion_layer=base_input.emotion_layer,
            intensity=base_input.intensity,
            context=base_input.context,
            metadata=metadata
        )

    def _select_tools_by_content(self, text: str, config: PipelineConfig) -> list[str]:
        """Choose a subset of tools based on content heuristics.

        Purpose:
            Reduce execution time by skipping tools unlikely to add value for the given text.

        Args:
            text: Narrative text analysed for heuristic matches.
            config: Current PipelineConfig, allowing future tuning via metadata.

        Returns:
            List of tool identifiers to execute in selective mode.
        Side Effects:
            None.
        Preconditions:
            Core quality tools should always be included regardless of heuristics.
        """
        selected = []

        # 基本チェック系は常に実行
        selected.extend(["cliche_detector", "contextual_cue_retriever"])

        # 生理学的記述があれば PhysiologyChecker を追加
        if any(word in text for word in ["心臓", "脈", "息", "血", "汗", "震え"]):
            selected.append("physiology_checker")

        # 比喩表現があれば MetaphorDiversityScorer を追加
        if any(word in text for word in ["のような", "のように", "まるで", "あたかも"]):
            selected.append("metaphor_diversity_scorer")

        # 長いテキストなら文体チェック
        if len(text) > 200:
            selected.append("register_verifier")

        return selected

    def _calculate_overall_score(self, tool_results: dict[str, EmotionToolOutput]) -> float:
        """Calculate the weighted overall score for the pipeline.

        Purpose:
            Aggregate individual tool scores using predefined weighting factors.

        Args:
            tool_results: Mapping of tool identifiers to their outputs.

        Returns:
            Weighted score normalised to the 0–100 range.
        Side Effects:
            None.
        Preconditions:
            `self.integration_weights` must contain weights for executed tools.
        """
        total_weighted_score = 0
        total_weight = 0

        for tool_name, result in tool_results.items():
            if result.success and result.score is not None:
                weight = self.integration_weights.get(tool_name, 0.1)
                total_weighted_score += result.score * weight
                total_weight += weight

        if total_weight == 0:
            return 0

        return total_weighted_score / total_weight

    def _integrate_recommendations(self, tool_results: dict[str, EmotionToolOutput]) -> list[str]:
        """Combine top-level recommendations from all successful tools.

        Purpose:
            Provide a concise list of actionable guidance with tool attribution.

        Args:
            tool_results: Mapping of tool identifiers to their outputs.

        Returns:
            List of recommendation strings capped to a manageable length.
        Side Effects:
            None.
        Preconditions:
            Tool outputs should present suggestions in `suggestions`.
        """
        all_recommendations = []

        for tool_name, result in tool_results.items():
            if result.success and result.suggestions:
                # ツール名プレフィックス付きで追加
                prefixed = [f"[{tool_name}] {suggestion}" for suggestion in result.suggestions[:2]]
                all_recommendations.extend(prefixed)

        return all_recommendations[:10]  # 最大10個の推奨事項

    def _generate_text_suggestions(self, tool_results: dict[str, EmotionToolOutput],
                                 original_text: str) -> list[str]:
        """Create narrative improvement suggestions derived from tool findings.

        Purpose:
            Translate diagnostic results into concrete author-facing guidance.

        Args:
            tool_results: Mapping of tool identifiers to their outputs.
            original_text: Manuscript text analysed in the pipeline.

        Returns:
            List of high-priority suggestion strings.
        Side Effects:
            None.
        Preconditions:
            Relevant tools must populate analysis or score fields used in heuristics.
        """
        suggestions = []

        # 陳腐表現チェック結果に基づく改善提案
        if "cliche_detector" in tool_results:
            cliche_result = tool_results["cliche_detector"]
            if cliche_result.success and cliche_result.analysis:
                flagged = cliche_result.analysis.get("flagged_phrases", [])
                if flagged:
                    suggestions.append(f"「{flagged[0]}」をより独創的な表現に変更")

        # 生理反応チェック結果に基づく提案
        if "physiology_checker" in tool_results:
            physio_result = tool_results["physiology_checker"]
            if physio_result.success and physio_result.score and physio_result.score < 70:
                suggestions.append("生理学的描写をより正確で詳細な表現に改良")

        # 比喩多様性に基づく提案
        if "metaphor_diversity_scorer" in tool_results:
            metaphor_result = tool_results["metaphor_diversity_scorer"]
            if metaphor_result.success and metaphor_result.score and metaphor_result.score < 60:
                suggestions.append("比喩表現の多様性を高めて独創性を向上")

        return suggestions[:5]  # 最大5個の提案

    def _build_integrated_analysis(self, pipeline_result: PipelineResult) -> dict[str, Any]:
        """Construct integrated analysis payload from pipeline results.

        Purpose:
            Summarise per-tool performance and extract cross-tool insights for downstream use.

        Args:
            pipeline_result: Completed PipelineResult produced by orchestration.

        Returns:
            Dictionary containing structured analysis data.
        Side Effects:
            None.
        Preconditions:
            `pipeline_result.tool_results` must include outputs for executed tools.
        """
        analysis = {
            "overall_assessment": {
                "score": pipeline_result.overall_score,
                "quality_level": self._get_quality_level(pipeline_result.overall_score),
                "tools_executed": len(pipeline_result.tool_results)
            },
            "tool_summary": {},
            "cross_tool_insights": []
        }

        # ツール別サマリー
        for tool_name, result in pipeline_result.tool_results.items():
            analysis["tool_summary"][tool_name] = {
                "success": result.success,
                "score": result.score,
                "key_findings": result.suggestions[:2] if result.suggestions else []
            }

        # クロスツール洞察（簡化実装）
        if len(pipeline_result.tool_results) >= 3:
            analysis["cross_tool_insights"].append(
                "複数のツールを通じた包括的分析により、バランスの取れた改善提案を提供"
            )

        return analysis

    def _get_quality_level(self, score: float) -> str:
        """Classify score ranges into qualitative labels.

        Purpose:
            Provide human-readable quality tiers for final reporting.

        Args:
            score: Aggregated pipeline score between 0 and 100.

        Returns:
            String label describing the quality band.
        Side Effects:
            None.
        Preconditions:
            Thresholds must be defined in `self.quality_thresholds`.
        """
        if score >= self.quality_thresholds["excellent_quality"]:
            return "excellent"
        if score >= self.quality_thresholds["good_quality"]:
            return "good"
        if score >= self.quality_thresholds["minimum_acceptable"]:
            return "acceptable"
        return "needs_improvement"

    def _generate_final_recommendations(self, pipeline_result: PipelineResult) -> list[str]:
        """Compile the final recommendation list surfaced to callers.

        Purpose:
            Merge quality-level guidance with cross-tool and text-level suggestions.

        Args:
            pipeline_result: PipelineResult containing aggregated findings.

        Returns:
            List of prioritised recommendations capped for brevity.
        Side Effects:
            None.
        Preconditions:
            `pipeline_result.overall_score` and suggestion lists must be populated.
        """
        recommendations = []

        # 品質レベルに基づく総合提案
        quality_level = self._get_quality_level(pipeline_result.overall_score)

        if quality_level == "needs_improvement":
            recommendations.append("感情表現の全体的な改善が必要です。特に独創性と生理学的正確性を重視してください")
        elif quality_level == "acceptable":
            recommendations.append("基本的な品質は確保されています。比喩の多様性と文体の一貫性を向上させてください")
        elif quality_level == "good":
            recommendations.append("良好な品質です。微細な調整で更なる向上が期待できます")
        else:
            recommendations.append("優秀な感情表現です。現在の水準を維持しつつ、さらなる独創性を追求してください")

        # ツール横断的な提案
        recommendations.extend(pipeline_result.recommendations[:3])

        # テキスト改善提案
        recommendations.extend(pipeline_result.final_text_suggestions[:2])

        return recommendations[:8]  # 最大8個の最終提案

    # ========================================
    # パフォーマンス最適化メソッド群
    # ========================================

    def _generate_cache_key(self, input_data: EmotionToolInput) -> str:
        """Create a deterministic cache key for the given input payload.

        Purpose:
            Identify cached results for identical requests to avoid recomputation.

        Args:
            input_data: EmotionToolInput describing the manuscript and metadata.

        Returns:
            Hex string produced by hashing the salient parts of the request.
        Side Effects:
            None.
        Preconditions:
            Input text and metadata should be serialisable to strings.
        """
        # テキストとメタデータからハッシュ生成
        content = f"{input_data.text}_{input_data.emotion_layer}_{input_data.intensity}"
        if input_data.metadata:
            metadata_str = str(sorted(input_data.metadata.items()))
            content += f"_{metadata_str}"

        return hashlib.sha256(content.encode()).hexdigest()

    def _get_cached_result(self, cache_key: str) -> EmotionToolOutput | None:
        """Retrieve a cached result when the entry remains within TTL.

        Purpose:
            Serve previously computed outputs to accelerate repeat requests.

        Args:
            cache_key: Key produced by `_generate_cache_key`.

        Returns:
            Cached EmotionToolOutput if present and fresh, otherwise None.
        Side Effects:
            Removes expired cache entries.
        Preconditions:
            Cache dictionaries must be initialised via `_initialize_performance_optimizations`.
        """
        if cache_key not in self.result_cache:
            return None

        # TTLチェック
        current_time = time.time()
        cache_time = self.cache_timestamps.get(cache_key, 0)

        if current_time - cache_time > 300:  # 5分でキャッシュ期限切れ
            del self.result_cache[cache_key]
            del self.cache_timestamps[cache_key]
            return None

        return self.result_cache[cache_key]

    def _cache_result(self, cache_key: str, result: EmotionToolOutput) -> None:
        """Persist a pipeline result in the in-memory cache.

        Purpose:
            Allow identical future requests to reuse the computed EmotionToolOutput.

        Args:
            cache_key: Hash key identifying the cached payload.
            result: EmotionToolOutput to store.
        Side Effects:
            May evict the oldest cache entry when capacity is reached.
        Preconditions:
            Cache structures must exist and the result must be serialisable if persisted elsewhere.
        """
        # メモリ使用量制限（最大100エントリ）
        if len(self.result_cache) >= 100:
            # 最も古いエントリを削除
            oldest_key = min(self.cache_timestamps.keys(),
                           key=self.cache_timestamps.get)
            del self.result_cache[oldest_key]
            del self.cache_timestamps[oldest_key]

        self.result_cache[cache_key] = result
        self.cache_timestamps[cache_key] = time.time()

    def _update_performance_stats(self, execution_time: float) -> None:
        """Update running averages and counters for pipeline performance metrics.

        Purpose:
            Track long-term performance characteristics for monitoring and optimisation.

        Args:
            execution_time: Pipeline execution time in milliseconds.

        Returns:
            None.
        Side Effects:
            Mutates `self.performance_metrics`.
        Preconditions:
            The `total_executions` counter must be incremented prior to calling.
        """
        total_executions = self.performance_metrics["total_executions"]
        current_avg = self.performance_metrics["average_execution_time"]

        # 移動平均計算
        new_avg = ((current_avg * (total_executions - 1)) + execution_time) / total_executions
        self.performance_metrics["average_execution_time"] = new_avg

    async def _execute_optimized_pipeline(
        self,
        input_data: EmotionToolInput,
        config: PipelineConfig
    ) -> PipelineResult:
        """Dispatch execution to the appropriate optimised path.

        Purpose:
            Route pipeline execution through parallel, selective, or sequential implementations.

        Args:
            input_data: EmotionToolInput for the current request.
            config: PipelineConfig detailing mode and auxiliary settings.

        Returns:
            PipelineResult produced by the chosen execution strategy.
        Side Effects:
            None beyond logging in downstream methods.
        Preconditions:
            Execution mode must be a member of PipelineMode.
        """
        if config.mode == PipelineMode.PARALLEL:
            return await self._execute_parallel_pipeline(input_data, config)
        if config.mode == PipelineMode.SELECTIVE:
            return await self._execute_selective_pipeline(input_data, config)
        return await self._execute_sequential_pipeline(input_data, config)

    async def _execute_parallel_pipeline(
        self,
        input_data: EmotionToolInput,
        config: PipelineConfig
    ) -> PipelineResult:
        """Execute all enabled tools in parallel with concurrency limits.

        Purpose:
            Reduce runtime by leveraging asynchronous execution while respecting resource bounds.

        Args:
            input_data: EmotionToolInput shared across tools.
            config: PipelineConfig specifying enabled tools and chunk sizes.

        Returns:
            PipelineResult capturing aggregated outcomes of the parallel run.
        Side Effects:
            Updates performance counters and logs debug timing information.
        Preconditions:
            Async event loop must be running and `self.tools` populated.
        """
        self.performance_metrics["parallel_execution_count"] += 1
        start_time = time.time()

        # 有効ツール取得
        enabled_tools = config.enabled_tools or list(self.tools.keys())
        tool_results = {}

        try:
            # 並列実行（セマフォで同時実行数制御）
            async def execute_tool_with_semaphore(tool_name: str):
                """Run a single tool within the semaphore guard.

                Purpose:
                    Limit concurrent executions while capturing timing metrics per tool.

                Args:
                    tool_name: Identifier of the tool to execute.

                Returns:
                    Tuple of the tool name and its EmotionToolOutput.
                Side Effects:
                    Occupies the execution semaphore and logs debug timing information.
                Preconditions:
                    `self.execution_semaphore` must be initialised.
                """
                async with self.execution_semaphore:
                    tool = self.tools[tool_name]
                    tool_start = time.time()
                    result = await tool.execute(input_data)
                    tool_time = (time.time() - tool_start) * 1000
                    self.logger.debug(
                        "%s実行時間: %.2fms",
                        tool_name,
                        tool_time,
                    )
                    return tool_name, result

            # 並列タスク作成・実行
            tasks = [
                execute_tool_with_semaphore(tool_name)
                for tool_name in enabled_tools
            ]

            # 全タスクを並列実行
            results = await asyncio.gather(*tasks, return_exceptions=True)

            # 結果処理
            for result in results:
                if isinstance(result, Exception):
                    self.logger.error(
                        "ツール実行エラー: %s",
                        result,
                    )
                    continue

                tool_name, tool_output = result
                tool_results[tool_name] = tool_output

            # 統合スコア計算
            overall_score = self._calculate_weighted_score(tool_results)

            # 実行メタデータ
            execution_time = (time.time() - start_time) * 1000
            metadata = {
                "execution_mode": "parallel",
                "tools_executed": len(tool_results),
                "successful_tools": len([r for r in tool_results.values() if r.success]),
                "total_execution_time": execution_time,
                "parallel_efficiency": len(enabled_tools) / execution_time * 1000
            }

            return PipelineResult(
                success=len(tool_results) > 0,
                overall_score=overall_score,
                tool_results=tool_results,
                integrated_analysis=self._create_integrated_analysis(tool_results),
                recommendations=self._extract_cross_tool_recommendations(tool_results),
                final_text_suggestions=self._generate_text_improvements(tool_results),
                execution_metadata=metadata
            )

        except Exception as e:
            self.logger.exception(
                "並列パイプライン実行エラー: %s",
                e,
            )
            return PipelineResult(
                success=False,
                overall_score=0.0,
                tool_results={},
                integrated_analysis={},
                recommendations=[f"実行エラー: {e!s}"],
                final_text_suggestions=[],
                execution_metadata={"error": str(e)}
            )

    def _calculate_weighted_score(self, tool_results: dict[str, EmotionToolOutput]) -> float:
        """Compute an overall score using integration weights.

        Purpose:
            Combine tool scores while honouring the configured contribution weights.

        Args:
            tool_results: Mapping of tool identifiers to outputs generated in parallel mode.

        Returns:
            Weighted overall score or 0.0 when no weights are applicable.
        Side Effects:
            None.
        Preconditions:
            `self.integration_weights` must contain weights for relevant tools.
        """
        total_weighted_score = 0.0
        total_weight = 0.0

        for tool_name, result in tool_results.items():
            if result.success and tool_name in self.integration_weights:
                weight = self.integration_weights[tool_name]
                total_weighted_score += result.score * weight
                total_weight += weight

        return total_weighted_score / total_weight if total_weight > 0 else 0.0

    def get_performance_metrics(self) -> dict[str, int | float]:
        """Expose current performance metrics for monitoring.

        Purpose:
            Provide callers with insight into cache usage and execution efficiency.

        Returns:
            Dictionary containing cumulative counters and derived rates.
        Side Effects:
            None.
        Preconditions:
            Metrics must be initialised via `_initialize_performance_optimizations`.
        """
        cache_hit_rate = (
            self.performance_metrics["cache_hits"] /
            max(self.performance_metrics["total_executions"], 1)
        ) * 100

        return {
            **self.performance_metrics,
            "cache_hit_rate_percent": cache_hit_rate,
            "cached_entries": len(self.result_cache)
        }
