# 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 module for MQT QMAP."""

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.
    """

class TargetMetric:
    __members__: ClassVar[dict[TargetMetric, int]] = ...  # read-only
    depth: ClassVar[TargetMetric] = ...
    gates: ClassVar[TargetMetric] = ...
    two_qubit_gates: ClassVar[TargetMetric] = ...

    @overload
    def __init__(self, value: int) -> None: ...
    @overload
    def __init__(self, arg0: str) -> None: ...
    @overload
    def __init__(self, arg0: TargetMetric) -> 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 Verbosity:
    __members__: ClassVar[dict[Verbosity, int]] = ...  # read-only
    none: ClassVar[Verbosity] = ...
    fatal: ClassVar[Verbosity] = ...
    error: ClassVar[Verbosity] = ...
    warning: ClassVar[Verbosity] = ...
    info: ClassVar[Verbosity] = ...
    debug: ClassVar[Verbosity] = ...
    verbose: ClassVar[Verbosity] = ...

    @overload
    def __init__(self, value: int) -> None: ...
    @overload
    def __init__(self, arg0: str) -> None: ...
    @overload
    def __init__(self, arg0: Verbosity) -> 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 SynthesisConfiguration:
    """Class representing the configuration for the Clifford synthesis tehcniques."""

    dump_intermediate_results: bool
    gate_limit_factor: float
    initial_timestep_limit: int
    intermediate_results_path: str
    minimize_gates_after_depth_optimization: bool
    minimize_gates_after_two_qubit_gate_optimization: bool
    solver_parameters: dict[str, bool | int | float | str]
    target_metric: TargetMetric
    try_higher_gate_limit_for_two_qubit_gate_optimization: bool
    use_maxsat: bool
    use_symmetry_breaking: bool
    verbosity: Verbosity
    heuristic: bool
    split_size: int
    linear_search: bool

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

class SynthesisResults:
    """Class representing the results of the Clifford synthesis techniques."""
    def __init__(self) -> None: ...
    def sat(self) -> bool: ...
    def unsat(self) -> bool: ...
    @property
    def circuit(self) -> str: ...
    @property
    def depth(self) -> int: ...
    @property
    def gates(self) -> int: ...
    @property
    def runtime(self) -> float: ...
    @property
    def single_qubit_gates(self) -> int: ...
    @property
    def solver_calls(self) -> int: ...
    @property
    def tableau(self) -> str: ...
    @property
    def two_qubit_gates(self) -> int: ...

class Tableau:
    """Class representing a Clifford tableau."""
    @overload
    def __init__(self, n: int, include_stabilizers: bool = False) -> None: ...
    @overload
    def __init__(self, description: str) -> None: ...
    @overload
    def __init__(self, stabilizers: str, destabilizers: str) -> None: ...

class CliffordSynthesizer:
    """The main class for the Clifford synthesis techniques."""
    @overload
    def __init__(self, initial_tableau: Tableau, target_tableau: Tableau) -> None: ...
    @overload
    def __init__(self, target_tableau: Tableau) -> None: ...
    @overload
    def __init__(self, qc: QuantumComputation, use_destabilizers: bool) -> None: ...
    @overload
    def __init__(self, initial_tableau: Tableau, qc: QuantumComputation) -> None: ...
    def synthesize(self, config: SynthesisConfiguration = ...) -> None: ...
    @property
    def results(self) -> SynthesisResults: ...
    @property
    def result_circuit(self) -> QuantumComputation: ...

class InitialCoordinateMapping:
    __members__: ClassVar[dict[str, int]] = ...  # read-only
    __entries: ClassVar[dict[str, int]] = ...
    random: ClassVar[InitialCoordinateMapping] = ...
    trivial: ClassVar[InitialCoordinateMapping] = ...

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

class InitialCircuitMapping:
    __members__: ClassVar[dict[str, int]] = ...  # read-only
    __entries: ClassVar[dict[str, int]] = ...
    identity: ClassVar[InitialCircuitMapping] = ...

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

class HybridMapperParameters:
    decay: float
    gate_weight: float
    initial_mapping: InitialCoordinateMapping
    lookahead_weight_moves: float
    lookahead_weight_swaps: float
    seed: int
    shuttling_time_weight: float
    shuttling_weight: float
    verbose: bool
    @overload
    def __init__(self) -> None: ...
    @overload
    def __init__(
        self,
        lookahead_weight_swaps: float = ...,
        lookahead_weight_moves: float = ...,
        decay: float = ...,
        shuttling_time_weight: float = ...,
        gate_weight: float = ...,
        shuttling_weight: float = ...,
    ) -> None: ...

class HybridNAMapper:
    """The hybrid mapper for Neutral Atom Quantum Computers."""
    def __init__(self, arch: NeutralAtomHybridArchitecture, params: HybridMapperParameters = ...) -> None: ...
    def get_animation_csv(self) -> str: ...
    def get_init_hw_pos(self) -> dict[int, int]: ...
    def get_mapped_qc(self) -> str: ...
    def get_mapped_qc_aod(self) -> str: ...
    def map(
        self, circ: QuantumComputation, initial_mapping: InitialCircuitMapping = ..., verbose: bool = ...
    ) -> None: ...
    def map_qasm_file(
        self, filename: str, initial_mapping: InitialCircuitMapping = ..., verbose: bool = ...
    ) -> None: ...
    def save_animation_csv(self, filename: str) -> None: ...
    def save_mapped_qc(self, filename: str) -> None: ...
    def save_mapped_qc_aod(self, filename: str) -> None: ...
    def schedule(
        self, verbose: bool = ..., create_animation_csv: bool = ..., shuttling_speed_factor: float = ...
    ) -> dict[str, float]: ...
    def set_parameters(self, params: HybridMapperParameters) -> None: ...

class NeutralAtomHybridArchitecture:
    """Class representing the architecture of a Neutral Atom Quantum Computer."""

    name: str

    def __init__(self, filename: str) -> None: ...
    def compute_swap_distance(self, idx1: int, idx2: int) -> float: ...
    def get_animation_csv(self) -> str: ...
    def get_gate_average_fidelity(self, s: str) -> float: ...
    def get_gate_time(self, s: str) -> float: ...
    def get_nearby_coordinates(self, idx: int) -> set[int]: ...
    def load_json(self, json_filename: str) -> None: ...
    def save_animation_csv(self, filename: str) -> None: ...
    @property
    def blocking_factor(self) -> float: ...
    @property
    def decoherence_time(self) -> float: ...
    @property
    def inter_qubit_distance(self) -> float: ...
    @property
    def interaction_radius(self) -> float: ...
    @property
    def naod_coordinates(self) -> int: ...
    @property
    def naod_intermediate_levels(self) -> int: ...
    @property
    def naods(self) -> int: ...
    @property
    def ncolumns(self) -> int: ...
    @property
    def npositions(self) -> int: ...
    @property
    def nqubits(self) -> int: ...
    @property
    def nrows(self) -> int: ...
