"""Batch-parallel Gemma text encoder builder.
Each rank materialises a full :class:`GemmaTextEncoder` on its own local
CUDA device via the standard :class:`SingleGPUModelBuilder` pipeline (the
same code path used by the non-MGPU pipelines). No Accelerate, no
``device_map``, no per-layer dispatch hooks.
Every ``build()`` reconstructs the encoder through
:class:`SingleGPUModelBuilder`: a fresh meta module is created, the
``GEMMA_MODEL_OPS`` chain re-runs (recomputing the rotary / position
buffers that live outside the safetensors file), and the trained weights
are bound from the provided :class:`Registry`. The registry caches the
loaded state dict so subsequent calls skip disk I/O while still rebuilding
the module tree -- mirroring the rebuild logic of
:class:`AccelerateGemmaBuilder` on this branch. Encoder-instance caching is
intentionally left out; it will arrive later as a global builder refactor.
The result is wrapped in :class:`BatchParallelGemmaWrapper`, which
partitions prompt lists across ranks in ``encode`` and routes
non-deterministic sampling (``enhance_t2v`` / ``enhance_i2v``) through a
single ``src_rank``.
"""

from __future__ import annotations

import logging

import torch
import torch.distributed as dist

from ltx_core.loader.primitives import BuilderProtocol
from ltx_core.loader.registry import Registry
from ltx_core.loader.single_gpu_model_builder import SingleGPUModelBuilder as Builder
from ltx_core.multigpu.gemma.batch_parallel_wrapper import BatchParallelGemmaWrapper
from ltx_core.text_encoders.gemma import (
    GEMMA_LLM_KEY_OPS,
    GEMMA_MODEL_OPS,
    GemmaTextEncoderConfigurator,
    module_ops_from_gemma_root,
)
from ltx_core.utils import find_matching_file

logger = logging.getLogger(__name__)


class BatchParallelGemmaBuilder(BuilderProtocol):
    """Per-rank Gemma replica builder for the batch-parallel encode path.
    Mirrors the inline Gemma builder construction inside
    :class:`PromptEncoder` (single-GPU path) and adds the MGPU wiring --
    broadcast group + source rank for non-deterministic methods. Each
    ``build()`` reconstructs the encoder via :class:`SingleGPUModelBuilder`;
    the registry caches the state dict so only disk I/O is skipped across
    calls.
    """

    def __init__(
        self,
        gemma_root_path: str,
        broadcast_group: dist.ProcessGroup | None,
        registry: Registry,
        *,
        src_rank: int,
        dtype: torch.dtype = torch.bfloat16,
    ) -> None:
        model_folder = find_matching_file(gemma_root_path, "model*.safetensors").parent
        weight_paths = tuple(str(p) for p in model_folder.rglob("*.safetensors"))
        self._inner = Builder(
            model_path=weight_paths,
            model_class_configurator=GemmaTextEncoderConfigurator,
            model_sd_ops=GEMMA_LLM_KEY_OPS,
            module_ops=(GEMMA_MODEL_OPS, *module_ops_from_gemma_root(gemma_root_path)),
            registry=registry,
        )
        self._broadcast_group = broadcast_group
        self._src_rank = src_rank
        self._dtype = dtype

    def model_config(self) -> dict:
        return {}

    def build(
        self,
        device: torch.device | None = None,
        dtype: torch.dtype | None = None,
    ) -> BatchParallelGemmaWrapper:
        dtype = dtype or self._dtype
        encoder = self._inner.build(device=device, dtype=dtype).eval()
        return BatchParallelGemmaWrapper(
            encoder=encoder,
            broadcast_group=self._broadcast_group,
            src_rank=dist.get_group_rank(self._broadcast_group, self._src_rank),
            dtype=dtype,
            device=device,
        )
