"""Tiled data parallel transformer builder.
Wrapping builder that produces a transformer model with tiled data parallelism applied.
"""

from __future__ import annotations

from typing import Generic

import torch
import torch.distributed as dist

from ltx_core.loader.primitives import ModelBuilderProtocol
from ltx_core.loader.registry import Registry
from ltx_core.loader.single_gpu_model_builder import SingleGPUModelBuilder as Builder
from ltx_core.model.model_protocol import LTXModelProtocol
from ltx_core.multigpu.transformer.tiled_data_parallel import (
    TiledDataParallelModelWrapper,
)
from ltx_core.tiling import TileCountConfig
from ltx_core.tools import VideoLatentTools
from ltx_pipelines.multigpu.delegating_builder import DelegatingBuilder, InnerModelT
from ltx_pipelines.multigpu.weight_tracker import TransformerWeightTracker


class TiledDataParallelBuilder(DelegatingBuilder[InnerModelT], Generic[InnerModelT]):
    """Builder conforming to :class:`ModelBuilderProtocol` that wraps with
    :class:`TiledDataParallelModelWrapper`.
    Requires ``video_tools`` as a keyword argument to :meth:`build` so the
    wrapper can compute the tile for this rank.
    The underlying model must accept ``(video, audio, perturbations)`` and return
    ``(denoised_video, denoised_audio)`` — i.e. conform to the ``X0Model`` forward
    signature used by the LTX transformer.
    """

    def __init__(
        self,
        inner: ModelBuilderProtocol[LTXModelProtocol],
        group: dist.ProcessGroup,
        tiling: TileCountConfig,
        registry: Registry,
        tracker: TransformerWeightTracker,
        normalize_positions: bool = True,
    ) -> None:
        if not isinstance(inner, Builder):
            raise TypeError(f"TiledDataParallelBuilder wraps a SingleGPUModelBuilder, got {type(inner).__name__}")
        cuda_device = torch.device(f"cuda:{torch.cuda.current_device()}")
        self._inner = inner.with_registry(registry).with_lora_load_device(cuda_device)
        self._tracker = tracker
        self._group = group
        self._tiling = tiling
        self._normalize_positions = normalize_positions

    def build(
        self,
        device: torch.device | None = None,
        dtype: torch.dtype | None = None,
        *,
        video_tools: VideoLatentTools | None = None,
        **_kwargs: object,
    ) -> TiledDataParallelModelWrapper:
        if video_tools is None:
            raise ValueError("TiledDataParallelBuilder.build() requires video_tools")
        model = self._tracker.build(self._inner, device=device, dtype=dtype, **_kwargs)
        return TiledDataParallelModelWrapper(
            model,
            video_tools=video_tools,
            tiling=self._tiling,
            group=self._group,
            normalize_positions=self._normalize_positions,
        )
