# File: src/noveler/application/adapters/cli_adapter_factory.py
# Purpose: CLI Adapter用ファクトリー（Application層でのDDD違反解決）
# Context: cli_adapter.py のDomain/Infrastructure層直接参照を隠蔽

"""CLIアダプター用ファクトリー。

Purpose:
    cli_adapter.py が必要とする Domain/Infrastructure層の依存を
    Application層経由で遅延インポートして提供する。
    Presentation層の DDD契約違反を解決し、層の依存方向を正規化。

Responsibility:
    - 設定マネージャーの提供（Infrastructure層）
    - Path Serviceの提供（Infrastructure Factory経由）
    - Universal Prompt Execution ValueObjects型の提供（Domain層）
    - Claude Code Integration Serviceの提供（Infrastructure層）
    - Enhanced FileManager の提供（Infrastructure層）
    - LLM IO Logger の提供（Infrastructure層）
    - MCPリポジトリおよびコンソール依存の提供（Application層経由）

Side Effects:
    遅延インポートにより、モジュール初期化時のインポートは遅延される。
    各メソッド呼び出し時点で必要な依存をインポート実行。
"""

from pathlib import Path
from typing import Any, Protocol, TYPE_CHECKING


if TYPE_CHECKING:
    from noveler.domain.repositories.episode_repository import EpisodeRepository
    from noveler.domain.repositories.plot_repository import PlotRepository
    from noveler.domain.repositories.yaml_prompt_repository import YamlPromptRepository
    from noveler.domain.utils.domain_console import DomainConsole


class ConfigManagerProtocol(Protocol):
    """設定マネージャー用 Protocol。

    Purpose:
        型チェック時に設定マネージャーの契約を定義。
    """

    def get(self, key: str, default: Any = None) -> Any:
        """設定値を取得。

        Purpose:
            Protocol契約としてキーに対応する設定値取得を定義。
        """


class CliAdapterFactory:
    """CLIアダプター用ファクトリー（Application層）

    Purpose:
        Domain層インターフェースおよび Infrastructure層ファクトリー・
        サービスへの参照を Application層経由で提供し、Presentation層の
        DDD契約違反を解決。

    Responsibility:
        - 設定マネージャー提供
        - Path Service生成
        - Universal Prompt Execution型提供
        - Claude Code Integration Service提供
        - Enhanced FileManager提供
        - LLM IO Logger提供

    Side Effects:
        遅延インポート - 各メソッド呼び出し時にインポート実行
    """

    _domain_console: "DomainConsole | None" = None

    @staticmethod
    def create_config_manager():
        """設定マネージャー取得（Infrastructure層からのラップ）

        Purpose:
            Infrastructure層の具体的な設定マネージャー実装を、
            Application層経由で提供する。

        Returns:
            ConfigManager: Noveler プロジェクトの設定マネージャー

        Raises:
            ImportError: Infrastructure層の依存関係取得失敗時

        Side Effects:
            遅延インポートで提供（グローバル初期化時ではなく呼び出し時）
        """
        from noveler.infrastructure.config.config_manager import (
            get_config_manager,
        )

        return get_config_manager()

    @staticmethod
    def create_path_service(project_root: str | None = None):
        """Path Service生成（Infrastructure Factory経由）

        Purpose:
            Infrastructure層の Path Service ファクトリを通じて、
            Path Service インスタンスを生成・提供する。

        Args:
            project_root: プロジェクトルートパス（オプション）

        Returns:
            PathService: パスサービスインスタンス

        Raises:
            ImportError: Infrastructure層の依存関係取得失敗時

        Side Effects:
            遅延インポートで Path Service を生成
        """
        from noveler.infrastructure.factories.path_service_factory import (
            create_common_path_service,
        )

        if project_root:
            return create_common_path_service(project_root)
        return create_common_path_service()

    @staticmethod
    def get_console():
        """Return the domain-managed console for CLI handlers.

        Purpose:
            Provide presentation-safe console access through the application layer boundary.
        """

        if CliAdapterFactory._domain_console is None:
            from noveler.domain.utils.domain_console import get_console as _get_console

            CliAdapterFactory._domain_console = _get_console()

        # Retry acquisition if the cached console is a NullConsole or has lost its delegate.
        console = CliAdapterFactory._domain_console
        needs_refresh = False
        try:
            from noveler.domain.utils.domain_console import NullConsole  # type: ignore

            if isinstance(console, NullConsole):
                needs_refresh = True
        except Exception:
            # If NullConsole is not importable treat only the delegate check.
            pass

        if not needs_refresh and hasattr(console, "_delegate") and getattr(console, "_delegate", None) is None:
            needs_refresh = True

        if needs_refresh:
            from noveler.domain.utils.domain_console import get_console as _get_console

            CliAdapterFactory._domain_console = _get_console()

        return CliAdapterFactory._domain_console

    @staticmethod
    def reset_console_cache() -> None:
        """Reset the cached DomainConsole instance.

        Purpose:
            Allow callers (tests or recovery flows) to force reinitialisation of the domain console.

        Side Effects:
            Clears the class-level console cache.
        """

        CliAdapterFactory._domain_console = None

    @staticmethod
    def get_universal_prompt_types() -> dict[str, type]:
        """Universal Prompt Execution 型群（Domain ValueObjectsのラップ）

        Purpose:
            Domain層の Universal Prompt Execution ValueObjects を
            型辞書として提供。Presentation層が型を取得する際の窓口。

        Returns:
            dict[str, type]: ValueObjects型群
                - ProjectContext: プロジェクト文脈
                - PromptType: プロンプト型列挙
                - UniversalPromptRequest: Universal プロンプトリクエスト

        Raises:
            ImportError: Domain層の依存関係取得失敗時

        Side Effects:
            遅延インポート - 呼び出し時点で Domain層をインポート
        """
        from noveler.domain.value_objects.universal_prompt_execution import (
            ProjectContext,
            PromptType,
            UniversalPromptRequest,
        )

        return {
            "ProjectContext": ProjectContext,
            "PromptType": PromptType,
            "UniversalPromptRequest": UniversalPromptRequest,
        }

    @staticmethod
    def create_universal_claude_code_service():
        """Claude Code Integration Service生成

        Purpose:
            Infrastructure層の Claude Code Integration Service を
            生成・提供する。

        Returns:
            UniversalClaudeCodeService: Claude Code インテグレーション

        Raises:
            ImportError: Infrastructure層の依存関係取得失敗時

        Side Effects:
            遅延インポート - 呼び出し時点で Infrastructure層をインポート
        """
        from noveler.infrastructure.integrations.universal_claude_code_service import (
            UniversalClaudeCodeService,
        )

        return UniversalClaudeCodeService()

    @staticmethod
    def create_enhanced_file_manager():
        """Enhanced FileManager生成

        Purpose:
            Infrastructure層の Enhanced FileManager を生成・提供する。

        Returns:
            EnhancedFileManager: ファイル管理インスタンス

        Raises:
            ImportError: Infrastructure層の依存関係取得失敗時

        Side Effects:
            遅延インポート - 呼び出し時点で Infrastructure層をインポート
        """
        from noveler.infrastructure.json.file_managers.enhanced_file_manager import (
            EnhancedFileManager,
        )

        return EnhancedFileManager()

    @staticmethod
    def get_domain_logging_util():
        """Domain logging utility取得（handlers.py用）

        Purpose:
            handlers.py が使用する Domain層logging utility関数を
            遅延インポートで提供する。

        Returns:
            function: capture_domain_logs 関数

        Raises:
            ImportError: Domain層の依存関係取得失敗時

        Side Effects:
            遅延インポート - 呼び出し時点で Domain層をインポート
        """
        from noveler.domain.utils.domain_logging import capture_domain_logs

        return capture_domain_logs

    @staticmethod
    def create_artifact_store(logger_service=None, storage_dir=None):
        """Artifact Store Service生成（handlers.py用）

        Purpose:
            Domain層のartifact store serviceをApplication層経由で
            生成・提供する。

        Args:
            logger_service: ロガーサービス（オプション）
            storage_dir: ストレージディレクトリ（オプション）

        Returns:
            ArtifactStoreService: artifact store インスタンス

        Raises:
            ImportError: Domain層の依存関係取得失敗時

        Side Effects:
            遅延インポート - 呼び出し時点で Domain層をインポート
        """
        from noveler.domain.services.artifact_store_service import (
            create_artifact_store,
        )

        return create_artifact_store(logger_service=logger_service, storage_dir=storage_dir)

    @staticmethod
    def get_creative_intention_types() -> dict[str, type]:
        """Creative Intention ValueObjects型群取得（handlers.py用）

        Purpose:
            Domain層のCreative IntentionValueObjectsを型辞書として
            提供。handlers.py が型を取得する際の窓口。

        Returns:
            dict[str, type]: ValueObjects型群
                - CreativeIntention: 創作意図ValueObject
                - CharacterArc: キャラクターアークValueObject

        Raises:
            ImportError: Domain層の依存関係取得失敗時

        Side Effects:
            遅延インポート - 呼び出し時点で Domain層をインポート
        """
        from noveler.domain.value_objects.creative_intention import (
            CharacterArc,
            CreativeIntention,
        )

        return {
            "CharacterArc": CharacterArc,
            "CreativeIntention": CreativeIntention,
        }

    @staticmethod
    def create_di_factory():
        """DI Container Factory取得（ten_stage_adapter.py用）

        Purpose:
            Infrastructure層のDI Container Factoryを
            Application層経由で提供する。

        Returns:
            IDIContainerFactory: DI Containerファクトリーインスタンス

        Raises:
            ImportError: Infrastructure層の依存関係取得失敗時

        Side Effects:
            遅延インポート - 呼び出し時点で Infrastructure層をインポート
        """
        from noveler.infrastructure.di.domain_di_container_factory import (
            get_domain_di_factory,
        )

        return get_domain_di_factory()

    @staticmethod
    def create_logger(name: str):
        """Logger取得（ten_stage_adapter.py用）

        Purpose:
            Infrastructure層のUnified Loggerを
            Application層経由で提供する。

        Args:
            name: ロガー名（通常は __name__）

        Returns:
            Logger: ロガーインスタンス

        Raises:
            ImportError: Infrastructure層の依存関係取得失敗時

        Side Effects:
            遅延インポート - 呼び出し時点で Infrastructure層をインポート
        """
        from noveler.infrastructure.logging.unified_logger import get_logger

        return get_logger(name)

    @staticmethod
    def create_yaml_prompt_repository(project_root: Path) -> "YamlPromptRepository | None":
        """Provision the YAML prompt repository for MCP writing commands.

        Purpose:
            Resolve infrastructure repositories via the application layer while
            keeping presentation code free from direct imports.
        """

        project_root = CliAdapterFactory._validate_project_root(project_root)
        console = CliAdapterFactory.get_console()

        template_name = "A30_執筆ガイド.yaml"
        guide_template_path = project_root / "docs" / template_name
        fallback_path = Path(__file__).resolve().parents[4] / "docs" / template_name

        try:
            from noveler.infrastructure.repositories.ruamel_yaml_prompt_repository import (
                RuamelYamlPromptRepository,
            )

            if not guide_template_path.exists():
                if not fallback_path.exists():
                    console.print_error("❌ A30ガイドテンプレート未発見")
                    console.print_warning(f"   検索パス1: {guide_template_path}")
                    console.print_warning(f"   検索パス2: {fallback_path}")
                    console.print_warning("📝 A28プロンプトシステム使用不可 - 精度が低下します")
                    console.print_info("💡 セットアップ: docs/A30_執筆ガイド.yaml を配置してください")
                    return None

                guide_template_path = fallback_path
                console.print_info(f"📄 A30ガイド使用: {guide_template_path}")

            return RuamelYamlPromptRepository(guide_template_path=guide_template_path)
        except Exception as exc:  # pragma: no cover - defensive logging
            console.print_warning(f"⚠️ YamlPromptRepository初期化失敗: {exc}")
            console.print_warning("📝 簡易YAMLフォールバックモードで実行します（精度低下）")
            return None

    @staticmethod
    def create_episode_repository(project_root: Path) -> "EpisodeRepository | None":
        """Create the episode repository used by MCP writing flows.

        Purpose:
            Provide FileEpisodeRepository instances without exposing infrastructure
            constructors to presentation modules.
        """

        project_root = CliAdapterFactory._validate_project_root(project_root)
        console = CliAdapterFactory.get_console()

        try:
            from noveler.infrastructure.adapters.file_episode_repository import (
                FileEpisodeRepository,
            )

            episode_data_dir = project_root / "temp" / "ddd_repo"
            return FileEpisodeRepository(base_dir=episode_data_dir)
        except Exception as exc:  # pragma: no cover - defensive logging
            console.print_warning(f"⚠️ EpisodeRepository初期化失敗: {exc}")
            return None

    @staticmethod
    def create_plot_repository(project_root: Path) -> "PlotRepository | None":
        """Create the plot repository used by MCP writing flows.

        Purpose:
            Supply the YAML-based plot repository through an application facade to
            maintain DDD layering boundaries.
        """

        project_root = CliAdapterFactory._validate_project_root(project_root)
        console = CliAdapterFactory.get_console()

        try:
            from noveler.infrastructure.repositories.yaml_plot_repository import (
                YamlPlotRepository,
            )

            return YamlPlotRepository(base_path=project_root)
        except Exception as exc:  # pragma: no cover - defensive logging
            console.print_warning(f"⚠️ PlotRepository初期化失敗: {exc}")
            return None

    @staticmethod
    def _validate_project_root(project_root: Path) -> Path:
        """Ensure the project root exists before repository creation.

        Purpose:
            Normalise incoming paths and guard against missing directories ahead
            of repository factory execution.
        """

        path = project_root if isinstance(project_root, Path) else Path(project_root)
        if not path.exists():
            raise FileNotFoundError(f"Project root not found: {path}")
        if not path.is_dir():
            raise NotADirectoryError(f"Project root is not a directory: {path}")
        return path
