# qsys/circuit/quantum_circuit.py
from __future__ import annotations
from typing import List

from ..ir.types import Instruction
from ..errors import ValidationError


class QuantumCircuit:
    """
    Minimal QuantumCircuit IR with lightweight builder-time validation.

    Builder-time checks are intentionally *lightweight*:
      - only validate argument types and non-negativity here
      - upper-bound checks (index < n_qubits / n_clbits) are deferred to the runtime
        (execute/transpiler) so passes and tooling can construct/transform circuits
        without immediate failure.
    """

    def __init__(self, n_qubits: int, n_clbits: int = 0):
        # constructor argument validation (strict)
        if not isinstance(n_qubits, int) or n_qubits <= 0:
            raise ValidationError("n_qubits must be a positive integer")
        if not isinstance(n_clbits, int) or n_clbits < 0:
            raise ValidationError("n_clbits must be a non-negative integer")

        self.n_qubits: int = n_qubits
        self.n_clbits: int = n_clbits
        self._instructions: List[Instruction] = []

    def _check_qubits(self, *qs: int) -> None:
        """
        Builder-time lightweight checks:
          - qubit indices must be ints
          - qubit indices must be >= 0
        Upper-bound checks (q < self.n_qubits) are intentionally deferred to execute().
        """
        for q in qs:
            if not isinstance(q, int):
                raise ValidationError(f"qubit index must be int, got {type(q)}")
            if q < 0:
                raise ValidationError(f"qubit index must be non-negative, got {q}")

    def draw(self, mode: str = "text") -> str:
        """Render the circuit as ASCII. Usage: print(qc.draw())."""
        if mode != "text":
            raise ValueError("Only 'text' mode is supported for now.")
        from ..viz.text_drawer import draw_ascii

        return draw_ascii(self.n_qubits, self._instructions)

    # ----- gate builders (chainable) -----
    def x(self, q: int) -> "QuantumCircuit":
        self._check_qubits(q)
        self._instructions.append(Instruction(name="x", qubits=(q,)))
        return self

    def sx(self, q: int) -> "QuantumCircuit":
        self._check_qubits(q)
        self._instructions.append(Instruction(name="sx", qubits=(q,)))
        return self

    def rz(self, q: int, theta: float) -> "QuantumCircuit":
        self._check_qubits(q)
        self._instructions.append(
            Instruction(name="rz", qubits=(q,), params=(float(theta),))
        )
        return self

    def cx(self, control: int, target: int) -> "QuantumCircuit":
        if control == target:
            raise ValidationError("control and target must differ")
        self._check_qubits(control, target)
        self._instructions.append(Instruction(name="cx", qubits=(control, target)))
        return self

    def measure(self, q: int, c: int) -> "QuantumCircuit":
        """
        Keep classical mapping in IR but validate types/non-negativity only here.
        Upper-bound checks for classical bit index are deferred to execute().
        """
        # validate qubit type and non-negativity (no upper-bound check here)
        self._check_qubits(q)

        # classical index: type and non-negative only (defer upper-bound)
        if not isinstance(c, int):
            raise ValidationError(f"classical bit index must be int, got {type(c)}")
        if c < 0:
            raise ValidationError(f"classical bit index must be non-negative, got {c}")

        self._instructions.append(Instruction(name="measure", qubits=(q,), clbits=(c,)))
        return self

    # convenience non-native gate (will be decomposed by transpiler)
    def h(self, q: int) -> "QuantumCircuit":
        self._check_qubits(q)
        self._instructions.append(Instruction("h", (q,), ()))
        return self

    @property
    def instructions(self) -> List[Instruction]:
        # return a copy to protect internal list
        return list(self._instructions)
