#
# Copyright © 2022-2024 University of Strasbourg. All Rights Reserved.
# Copyright © 2023-2025 QPerfect. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#


from mimiqcircuits.instruction import Instruction
from mimiqcircuits.operations.operation import Operation
from collections.abc import Iterable
from itertools import repeat
import numpy as np


def push_instruction_container(container, operation, *args, check_fn=None):
    """
    Shared push logic for Circuit or Block.

    Args:
        container: the object with `.instructions` list.
        operation: Operation or Instruction to push.
        args: target qubits/bits/zvars.
        check_fn: optional validator function (e.g., container._check_instruction_block).
    """
    args = tuple(int(arg) if isinstance(arg, np.integer) else arg for arg in args)
    nq = nc = nz = 0
    L = len(args)

    if isinstance(operation, Instruction):
        if L != 0:
            raise ValueError("No extra arguments allowed when pushing an instruction.")
        if check_fn:
            check_fn(operation)
        container.instructions.append(operation)
        return container

    if not isinstance(operation, Operation):
        raise TypeError("Non Operation object passed to push.")

    nq = operation.num_qubits
    nc = operation.num_bits
    nz = operation.num_zvars

    if L != nq + nc + nz:
        raise ValueError(
            f"Wrong number of target qubits/bits/zvars: got {L}, expected {nq}+{nc}+{nz}"
        )

    targets = []
    hasiterable = False

    for arg in args[:-1]:
        if isinstance(arg, int):
            targets.append(repeat(arg))
        elif isinstance(arg, Iterable):
            targets.append(arg)
            hasiterable = True
        else:
            raise TypeError(f"Invalid target type: {arg}")

    if args:
        larg = args[-1]
        if isinstance(larg, int):
            targets.append(repeat(larg) if hasiterable else [larg])
        elif isinstance(larg, Iterable):
            targets.append(larg)
        else:
            raise TypeError(f"Invalid target type: {larg}")

        for tg in zip(*targets):
            inst = Instruction(
                operation, tuple(tg[:nq]), tuple(tg[nq : nq + nc]), tuple(tg[nq + nc :])
            )
            if check_fn:
                check_fn(inst)
            container.instructions.append(inst)
    else:
        inst = Instruction(operation)
        if check_fn:
            check_fn(inst)
        container.instructions.append(inst)

    return container


__all__ = ["push_instruction_container"]
