# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM
# Copyright (c) 2025 Munich Quantum Software Company GmbH
# All rights reserved.
#
# SPDX-License-Identifier: MIT
#
# Licensed under the MIT License

"""Python bindings for superconducting module."""

from typing import Any, ClassVar, overload

from mqt.core.ir import QuantumComputation

class Arch:
    __members__: ClassVar[dict[Arch, int]] = ...  # read-only
    IBMQ_Bogota: ClassVar[Arch] = ...
    IBMQ_Casablanca: ClassVar[Arch] = ...
    IBMQ_London: ClassVar[Arch] = ...
    IBMQ_Tokyo: ClassVar[Arch] = ...
    IBMQ_Yorktown: ClassVar[Arch] = ...
    IBM_QX4: ClassVar[Arch] = ...
    IBM_QX5: ClassVar[Arch] = ...
    Rigetti_Agave: ClassVar[Arch] = ...
    Rigetti_Aspen: ClassVar[Arch] = ...

    @overload
    def __init__(self, value: int) -> None: ...
    @overload
    def __init__(self, arg0: str) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...

class Architecture:
    """Class representing device/backend information."""
    class Properties:
        """Class representing properties of an architecture."""

        name: str
        num_qubits: int

        def __init__(self) -> None: ...
        def get_calibration_date(self, qubit: int) -> str: ...
        def get_frequency(self, qubit: int) -> float: ...
        def get_readout_error(self, qubit: int) -> float: ...
        def get_single_qubit_error(self, qubit: int, operation: str) -> float: ...
        def get_t1(self, qubit: int) -> float: ...
        def get_t2(self, qubit: int) -> float: ...
        def get_two_qubit_error(self, control: int, target: int, operation: str = ...) -> float: ...
        def json(self) -> dict[str, Any]: ...
        def set_calibration_date(self, qubit: int, calibration_date: str) -> None: ...
        def set_frequency(self, qubit: int, qubit_frequency: float) -> None: ...
        def set_readout_error(self, qubit: int, readout_error_rate: float) -> None: ...
        def set_single_qubit_error(self, qubit: int, operation: str, error_rate: float) -> None: ...
        def set_t1(self, qubit: int, t1: float) -> None: ...
        def set_t2(self, qubit: int, t2: float) -> None: ...
        def set_two_qubit_error(self, control: int, target: int, error_rate: float, operation: str = ...) -> None: ...

    coupling_map: set[tuple[int, int]]
    name: str
    num_qubits: int
    properties: Properties

    @overload
    def __init__(self) -> None: ...
    @overload
    def __init__(self, num_qubits: int, coupling_map: set[tuple[int, int]]) -> None: ...
    @overload
    def __init__(self, num_qubits: int, coupling_map: set[tuple[int, int]], properties: Properties) -> None: ...
    @overload
    def load_coupling_map(self, available_architecture: Arch) -> None: ...
    @overload
    def load_coupling_map(self, coupling_map_file: str) -> None: ...
    @overload
    def load_properties(self, properties: Properties) -> None: ...
    @overload
    def load_properties(self, properties: str) -> None: ...

class CircuitInfo:
    """Circuit information."""

    cnots: int
    direction_reverse: int
    gates: int
    layers: int
    total_fidelity: float
    total_log_fidelity: float
    name: str
    qubits: int
    single_qubit_gates: int
    swaps: int

    def __init__(self) -> None: ...

class CommanderGrouping:
    __members__: ClassVar[dict[CommanderGrouping, int]] = ...  # read-only
    fixed2: ClassVar[CommanderGrouping] = ...
    fixed3: ClassVar[CommanderGrouping] = ...
    halves: ClassVar[CommanderGrouping] = ...
    logarithm: ClassVar[CommanderGrouping] = ...

    @overload
    def __init__(self, value: int) -> None: ...
    @overload
    def __init__(self, arg0: str) -> None: ...
    @overload
    def __init__(self, arg0: CommanderGrouping) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...

class Configuration:
    """Class representing the configuration for the mapping."""

    add_measurements_to_mapped_circuit: bool
    add_barriers_between_layers: bool
    heuristic: Heuristic
    commander_grouping: CommanderGrouping
    enable_limits: bool
    encoding: Encoding
    first_lookahead_factor: float
    include_WCNF: bool  # noqa: N815
    initial_layout: InitialLayout
    iterative_bidirectional_routing: bool
    iterative_bidirectional_routing_passes: int
    layering: Layering
    automatic_layer_splits: bool
    automatic_layer_splits_node_limit: int
    early_termination: EarlyTermination
    early_termination_limit: int
    lookahead_heuristic: LookaheadHeuristic
    lookahead_factor: float
    lookaheads: int
    method: Method
    post_mapping_optimizations: bool
    pre_mapping_optimizations: bool
    subgraph: set[int]
    swap_limit: int
    swap_reduction: SwapReduction
    timeout: int
    use_subsets: bool
    verbose: bool
    debug: bool
    data_logging_path: str

    def __init__(self) -> None: ...
    def json(self) -> dict[str, Any]: ...

class EarlyTermination:
    __members__: ClassVar[dict[EarlyTermination, int]] = ...  # read-only
    none: ClassVar[EarlyTermination] = ...
    expanded_nodes: ClassVar[EarlyTermination] = ...
    expanded_nodes_after_first_solution: ClassVar[EarlyTermination] = ...
    expanded_nodes_after_current_optimal_solution: ClassVar[EarlyTermination] = ...
    solution_nodes: ClassVar[EarlyTermination] = ...
    solution_nodes_after_current_optimal_solution: ClassVar[EarlyTermination] = ...

    @overload
    def __init__(self, value: int) -> None: ...
    @overload
    def __init__(self, arg0: str) -> None: ...
    @overload
    def __init__(self, arg0: EarlyTermination) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...

class Encoding:
    __members__: ClassVar[dict[Encoding, int]] = ...  # read-only
    bimander: ClassVar[Encoding] = ...
    commander: ClassVar[Encoding] = ...
    naive: ClassVar[Encoding] = ...

    @overload
    def __init__(self, value: int) -> None: ...
    @overload
    def __init__(self, arg0: str) -> None: ...
    @overload
    def __init__(self, arg0: Encoding) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...

class InitialLayout:
    __members__: ClassVar[dict[InitialLayout, int]] = ...  # read-only
    dynamic: ClassVar[InitialLayout] = ...
    identity: ClassVar[InitialLayout] = ...
    static: ClassVar[InitialLayout] = ...

    @overload
    def __init__(self, value: int) -> None: ...
    @overload
    def __init__(self, arg0: str) -> None: ...
    @overload
    def __init__(self, arg0: InitialLayout) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...

class Heuristic:
    __members__: ClassVar[dict[Heuristic, int]] = ...  # read-only
    gate_count_max_distance: ClassVar[Heuristic] = ...
    gate_count_sum_distance: ClassVar[Heuristic] = ...
    gate_count_sum_distance_minus_shared_swaps: ClassVar[Heuristic] = ...
    gate_count_max_distance_or_sum_distance_minus_shared_swaps: ClassVar[Heuristic] = ...
    fidelity_best_location: ClassVar[Heuristic] = ...

    @overload
    def __init__(self, value: int) -> None: ...
    @overload
    def __init__(self, arg0: str) -> None: ...
    @overload
    def __init__(self, arg0: Heuristic) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...

class LookaheadHeuristic:
    __members__: ClassVar[dict[LookaheadHeuristic, int]] = ...  # read-only
    none: ClassVar[LookaheadHeuristic] = ...
    gate_count_max_distance: ClassVar[LookaheadHeuristic] = ...
    gate_count_sum_distance: ClassVar[LookaheadHeuristic] = ...

    @overload
    def __init__(self, value: int) -> None: ...
    @overload
    def __init__(self, arg0: str) -> None: ...
    @overload
    def __init__(self, arg0: LookaheadHeuristic) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...

class Layering:
    __members__: ClassVar[dict[Layering, int]] = ...  # read-only
    disjoint_qubits: ClassVar[Layering] = ...
    individual_gates: ClassVar[Layering] = ...
    odd_gates: ClassVar[Layering] = ...
    qubit_triangle: ClassVar[Layering] = ...
    disjoint_2q_blocks: ClassVar[Layering] = ...

    @overload
    def __init__(self, value: int) -> None: ...
    @overload
    def __init__(self, arg0: str) -> None: ...
    @overload
    def __init__(self, arg0: Layering) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...

class HeuristicBenchmarkInfo:
    expanded_nodes: int
    generated_nodes: int
    seconds_per_node: float
    average_branching_factor: float
    effective_branching_factor: float

    def __init__(self) -> None: ...
    def json(self) -> dict[str, Any]: ...

class LayerHeuristicBenchmarkInfo:
    expanded_nodes: int
    generated_nodes: int
    expanded_nodes_after_first_solution: int
    expanded_nodes_after_optimal_solution: int
    solution_nodes: int
    solution_nodes_after_optimal_solution: int
    solution_depth: int
    seconds_per_node: float
    average_branching_factor: float
    effective_branching_factor: float
    early_termination: bool

    def __init__(self) -> None: ...
    def json(self) -> dict[str, Any]: ...

class MappingResults:
    """Class representing the results of a mapping."""

    configuration: Configuration
    input: CircuitInfo
    mapped_circuit: str
    output: CircuitInfo
    time: float
    timeout: bool
    wcnf: str
    heuristic_benchmark: HeuristicBenchmarkInfo
    layer_heuristic_benchmark: LayerHeuristicBenchmarkInfo

    def __init__(self) -> None: ...
    def json(self) -> dict[str, Any]: ...

class Method:
    __members__: ClassVar[dict[Method, int]] = ...  # read-only
    exact: ClassVar[Method] = ...
    heuristic: ClassVar[Method] = ...

    @overload
    def __init__(self, value: int) -> None: ...
    @overload
    def __init__(self, arg0: str) -> None: ...
    @overload
    def __init__(self, arg0: Method) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...

class SwapReduction:
    __members__: ClassVar[dict[SwapReduction, int]] = ...  # read-only
    coupling_limit: ClassVar[SwapReduction] = ...
    custom: ClassVar[SwapReduction] = ...
    increasing: ClassVar[SwapReduction] = ...
    none: ClassVar[SwapReduction] = ...

    @overload
    def __init__(self, value: int) -> None: ...
    @overload
    def __init__(self, arg0: str) -> None: ...
    @overload
    def __init__(self, arg0: SwapReduction) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...

def map(  # noqa: A001
    circ: QuantumComputation, arch: Architecture, config: Configuration
) -> tuple[QuantumComputation, MappingResults]:
    """Map a quantum circuit to an architecture.

    Args:
        circ: The quantum circuit to map.
        arch: The architecture to map to.
        config: The mapping configuration.

    Returns:
        A tuple containing the mapped circuit and the mapping results.
    """
