"""Build-time API — resolve entry names to pip dependencies.

CLI calls this (via subprocess or direct import) to determine
what pip packages to install for the matched frameworks.

    from edgeone_agent_observability.build import resolve
    result = resolve(["langchain", "openai"])
    # result.entries → pass to setup() at runtime
    # result.required_deps → pip packages to install
"""
from __future__ import annotations

from dataclasses import dataclass, field


@dataclass(frozen=True)
class ResolveResult:
    """What CLI needs to install and pass to setup()."""
    entries: list[str]
    required_deps: list[str]


# Framework → pip packages needed for instrumentation.
# Mirrors pyproject.toml [project.optional-dependencies] but exposed
# programmatically so CLI doesn't have to parse TOML.
_INSTRUMENTATION_DEPS: dict[str, list[str]] = {
    "langchain": ["openinference-instrumentation-langchain>=0.1.30"],
    "crewai": [
        "openinference-instrumentation-crewai>=0.1.8",
        "openinference-instrumentation-litellm>=0.1",
    ],
    "llama-index": ["openinference-instrumentation-llama-index>=4.0"],
    "openai": ["openinference-instrumentation-openai>=0.1.15"],
    "openai-agents": ["openinference-instrumentation-openai-agents>=1.5.0"],
    "anthropic": ["openinference-instrumentation-anthropic>=1.0.5"],
    "beeai": ["openinference-instrumentation-beeai>=0.1.19"],
    "agno": ["openinference-instrumentation-agno>=0.1.34"],
    "autogen-agentchat": ["openinference-instrumentation-autogen-agentchat>=0.1.9"],
    "autogen": ["openinference-instrumentation-autogen>=0.1.14"],
    "bedrock": ["openinference-instrumentation-bedrock>=0.1.39"],
    "claude-agent-sdk": ["openinference-instrumentation-claude-agent-sdk>=0.1.4"],
    "dspy": ["openinference-instrumentation-dspy>=0.1.37"],
    "google-adk": ["openinference-instrumentation-google-adk>=0.1.14"],
    "google-genai": ["openinference-instrumentation-google-genai>=1.0.2"],
    "groq": ["openinference-instrumentation-groq>=0.1.16"],
    "guardrails": ["openinference-instrumentation-guardrails>=0.1.14"],
    "mistralai": ["openinference-instrumentation-mistralai>=2.0.4"],
}

# Python import patterns that identify each framework.
# CLI scans .py files for these patterns to determine entry_names.
IMPORT_PATTERNS: dict[str, list[str]] = {
    "langchain": ["langchain", "langgraph", "langchain_openai", "langchain_core"],
    "crewai": ["crewai"],
    "llama-index": ["llama_index"],
    "openai": ["openai"],
    "openai-agents": ["agents"],
    "anthropic": ["anthropic"],
    "beeai": ["beeai_framework", "beeai"],
    "agno": ["agno"],
    "autogen-agentchat": ["autogen_agentchat"],
    "autogen": ["autogen"],
    "bedrock": ["boto3", "botocore"],
    "claude-agent-sdk": ["claude_agent_sdk"],
    "dspy": ["dspy"],
    "google-adk": ["google.adk", "google_adk"],
    "google-genai": ["google.generativeai", "google_generativeai", "google.genai"],
    "groq": ["groq"],
    "guardrails": ["guardrails"],
    "mistralai": ["mistralai"],
}

# Base OTel packages always needed.
_OTEL_DEPS: list[str] = [
    "opentelemetry-api>=1.28",
    "opentelemetry-sdk>=1.28",
    "opentelemetry-exporter-otlp-proto-http>=1.28",
    "opentelemetry-semantic-conventions>=0.49b0",
]


# When a higher-level framework is active, suppress the lower-level openai
# instrumentor to avoid duplicate LLM spans.
# Key: entry name to suppress → set of entry names that trigger suppression.
_SUPPRESSED_BY: dict[str, list[str]] = {
    # openai-agents SDK already traces LLM calls at the framework level;
    # openai instrumentor on top produces duplicate ChatCompletion spans.
    "openai": ["openai-agents"],
}


def resolve(entry_names: list[str]) -> ResolveResult:
    """Given a list of framework names, return what to install.

    Pure function — no side effects.

    Args:
        entry_names: Frameworks detected in user code (e.g. ["langchain", "openai"])

    Returns:
        ResolveResult with entries (pass to setup) and required pip packages.
    """
    if not entry_names:
        return ResolveResult(entries=[], required_deps=[])

    name_set = set(entry_names)
    matched: list[str] = []
    deps: list[str] = list(_OTEL_DEPS)

    for name in entry_names:
        if name not in _INSTRUMENTATION_DEPS:
            continue
        suppressors = _SUPPRESSED_BY.get(name, [])
        if any(s in name_set for s in suppressors):
            continue
        matched.append(name)
        deps.extend(_INSTRUMENTATION_DEPS[name])

    return ResolveResult(entries=matched, required_deps=deps)
