"""Music analysis client with SSE streaming support."""

from __future__ import annotations

from collections.abc import AsyncGenerator, Generator
from typing import Any

from melogenai._http import AsyncHttpClient, HttpClient
from melogenai.types import AnalysisResult

SUPPORTED_LANGUAGES = frozenset(
    {"en", "zh", "tw", "ja", "ko", "de", "fr", "es", "pt", "it", "ru", "pl", "el", "vi"}
)


def _build_payload(
    mxl_file_url: str | None,
    image_url: str | None,
    pdf_file_url: str | None,
    language: str,
) -> dict[str, Any]:
    if not any([mxl_file_url, image_url, pdf_file_url]):
        raise ValueError(
            "At least one of mxl_file_url, image_url, or pdf_file_url must be provided."
        )
    if language not in SUPPORTED_LANGUAGES:
        raise ValueError(
            f"Unsupported language '{language}'. Supported: {', '.join(sorted(SUPPORTED_LANGUAGES))}"
        )

    payload: dict[str, Any] = {"lang": language}
    if mxl_file_url:
        payload["mxl_file_url"] = mxl_file_url
    if image_url:
        payload["image_url"] = image_url
    if pdf_file_url:
        payload["pdf_file_url"] = pdf_file_url
    return payload


class AnalysisClient:
    """Synchronous client for music analysis."""

    def __init__(self, http: HttpClient):
        self._http = http

    def analyze(
        self,
        *,
        mxl_file_url: str | None = None,
        image_url: str | None = None,
        pdf_file_url: str | None = None,
        language: str = "en",
        timeout: float | None = None,
    ) -> AnalysisResult:
        """Analyze music structure, returning the full result.

        At least one of mxl_file_url, image_url, or pdf_file_url must be provided.

        Args:
            mxl_file_url: URL of a MusicXML (.mxl) file.
            image_url: URL of a sheet music image.
            pdf_file_url: URL of a PDF score.
            language: Language for analysis results (e.g. "en", "zh", "ja").
            timeout: Request timeout in seconds.
        """
        payload = _build_payload(mxl_file_url, image_url, pdf_file_url, language)
        text = self._http.consume_sse("/api/analysis/analyze", payload, timeout=timeout or 300)
        return AnalysisResult(status="completed", analysis=text, language=language)

    def analyze_stream(
        self,
        *,
        mxl_file_url: str | None = None,
        image_url: str | None = None,
        pdf_file_url: str | None = None,
        language: str = "en",
        timeout: float | None = None,
    ) -> Generator[str, None, None]:
        """Stream analysis results chunk by chunk."""
        payload = _build_payload(mxl_file_url, image_url, pdf_file_url, language)
        yield from self._http.stream_sse("/api/analysis/analyze", payload, timeout=timeout or 300)


class AsyncAnalysisClient:
    """Asynchronous client for music analysis."""

    def __init__(self, http: AsyncHttpClient):
        self._http = http

    async def analyze(
        self,
        *,
        mxl_file_url: str | None = None,
        image_url: str | None = None,
        pdf_file_url: str | None = None,
        language: str = "en",
        timeout: float | None = None,
    ) -> AnalysisResult:
        """Analyze music structure, returning the full result."""
        payload = _build_payload(mxl_file_url, image_url, pdf_file_url, language)
        text = await self._http.consume_sse(
            "/api/analysis/analyze", payload, timeout=timeout or 300
        )
        return AnalysisResult(status="completed", analysis=text, language=language)

    async def analyze_stream(
        self,
        *,
        mxl_file_url: str | None = None,
        image_url: str | None = None,
        pdf_file_url: str | None = None,
        language: str = "en",
        timeout: float | None = None,
    ) -> AsyncGenerator[str, None]:
        """Stream analysis results chunk by chunk."""
        payload = _build_payload(mxl_file_url, image_url, pdf_file_url, language)
        async for chunk in self._http.stream_sse(
            "/api/analysis/analyze", payload, timeout=timeout or 300
        ):
            yield chunk
