from typing import TYPE_CHECKING

import mlx.nn as nn

from mflux.config.config import Config
from mflux.weights.quantization_util import QuantizationUtil

if TYPE_CHECKING:
    from mflux.controlnet.weight_handler_controlnet import WeightHandlerControlnet
    from mflux.weights.weight_handler import WeightHandler


class WeightUtil:
    @staticmethod
    def flatten(params):
        return [(k, v) for p in params for (k, v) in p]

    @staticmethod
    def reshape_weights(key, value):
        if len(value.shape) == 4:
            value = value.transpose(0, 2, 3, 1)
        value = value.reshape(-1).reshape(value.shape).astype(Config.precision)
        return [(key, value)]

    @staticmethod
    def set_weights_and_quantize(
        quantize_arg: int | None,
        weights: "WeightHandler",
        vae: nn.Module,
        transformer: nn.Module,
        t5_text_encoder: nn.Module,
        clip_text_encoder: nn.Module,
    ) -> int | None:
        if weights.meta_data.quantization_level is None and quantize_arg is None:
            WeightUtil._set_model_weights(weights, vae, transformer, t5_text_encoder, clip_text_encoder)
            return None

        if weights.meta_data.quantization_level is None and quantize_arg is not None:
            bits = quantize_arg
            WeightUtil._set_model_weights(weights, vae, transformer, t5_text_encoder, clip_text_encoder)
            QuantizationUtil.quantize_model(vae, transformer, t5_text_encoder, clip_text_encoder, bits, weights)  # fmt:off
            return bits

        if weights.meta_data.quantization_level is not None:
            bits = weights.meta_data.quantization_level
            QuantizationUtil.quantize_model(vae, transformer, t5_text_encoder, clip_text_encoder, bits, weights)  # fmt:off
            WeightUtil._set_model_weights(weights, vae, transformer, t5_text_encoder, clip_text_encoder)
            return bits

        raise Exception("Error setting weights")

    @staticmethod
    def set_controlnet_weights_and_quantize(
        quantize_arg: int | None,
        weights: "WeightHandlerControlnet",
        transformer_controlnet: nn.Module,
    ) -> int | None:
        if weights.meta_data.quantization_level is None and quantize_arg is None:
            transformer_controlnet.update(weights.controlnet_transformer)
            return None

        if weights.meta_data.quantization_level is None and quantize_arg is not None:
            bits = quantize_arg
            transformer_controlnet.update(weights.controlnet_transformer)
            QuantizationUtil.quantize_controlnet(bits, weights, transformer_controlnet)
            return bits

        if weights.meta_data.quantization_level is not None:
            bits = weights.meta_data.quantization_level
            QuantizationUtil.quantize_controlnet(bits, weights, transformer_controlnet)
            transformer_controlnet.update(weights.controlnet_transformer)
            return bits

    @staticmethod
    def _set_model_weights(
        weights: "WeightHandler",
        vae: nn.Module,
        transformer: nn.Module,
        t5_text_encoder: nn.Module,
        clip_text_encoder: nn.Module,
    ):
        vae.update(weights.vae)
        transformer.update(weights.transformer)
        t5_text_encoder.update(weights.t5_encoder)
        clip_text_encoder.update(weights.clip_encoder)
