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

import enum
from collections.abc import Sequence
from collections.abc import Set as AbstractSet
from typing import Any, overload

import mqt.core.ir

class Arch(enum.Enum):
    IBM_QX4 = 0
    """5 qubit, directed bow tie layout"""

    IBM_QX5 = 1
    """16 qubit, directed ladder layout"""

    IBMQ_Yorktown = 2
    """5 qubit, undirected bow tie layout"""

    IBMQ_London = 3
    """5 qubit, undirected T-shape layout"""

    IBMQ_Bogota = 4
    """5 qubit, undirected linear chain layout"""

    IBMQ_Casablanca = 5
    """7 qubit, undirected H-shape layout"""

    IBMQ_Tokyo = 6
    """20 qubit, undirected brick-like layout"""

    Rigetti_Agave = 7
    """8 qubit, undirected ring layout"""

    Rigetti_Aspen = 8
    """16 qubit, undirected dumbbell layout"""

class Method(enum.Enum):
    heuristic = 2

    exact = 1

class InitialLayout(enum.Enum):
    identity = 0

    static = 1

    dynamic = 2

class Heuristic(enum.Enum):
    gate_count_max_distance = 0

    gate_count_sum_distance = 1

    gate_count_sum_distance_minus_shared_swaps = 2

    gate_count_max_distance_or_sum_distance_minus_shared_swaps = 3

    fidelity_best_location = 4

class LookaheadHeuristic(enum.Enum):
    none = 0

    gate_count_max_distance = 1

    gate_count_sum_distance = 2

class Layering(enum.Enum):
    individual_gates = 0

    disjoint_qubits = 1

    odd_gates = 2

    qubit_triangle = 3

    disjoint_2q_blocks = 4

class EarlyTermination(enum.Enum):
    none = 0

    expanded_nodes = 1

    expanded_nodes_after_first_solution = 2

    expanded_nodes_after_current_optimal_solution = 3

    solution_nodes = 4

    solution_nodes_after_current_optimal_solution = 5

class Encoding(enum.Enum):
    naive = 0

    commander = 1

    bimander = 2

class CommanderGrouping(enum.Enum):
    fixed2 = 1

    fixed3 = 2

    halves = 0

    logarithm = 3

class SwapReduction(enum.Enum):
    none = 0

    coupling_limit = 1

    custom = 2

    increasing = 3

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

    def __init__(self) -> None: ...
    @property
    def method(self) -> Method: ...
    @method.setter
    def method(self, arg: Method, /) -> None: ...
    @property
    def heuristic(self) -> Heuristic: ...
    @heuristic.setter
    def heuristic(self, arg: Heuristic, /) -> None: ...
    @property
    def verbose(self) -> bool: ...
    @verbose.setter
    def verbose(self, arg: bool, /) -> None: ...
    @property
    def debug(self) -> bool: ...
    @debug.setter
    def debug(self, arg: bool, /) -> None: ...
    @property
    def data_logging_path(self) -> str: ...
    @data_logging_path.setter
    def data_logging_path(self, arg: str, /) -> None: ...
    @property
    def layering(self) -> Layering: ...
    @layering.setter
    def layering(self, arg: Layering, /) -> None: ...
    @property
    def automatic_layer_splits(self) -> bool: ...
    @automatic_layer_splits.setter
    def automatic_layer_splits(self, arg: bool, /) -> None: ...
    @property
    def automatic_layer_splits_node_limit(self) -> int: ...
    @automatic_layer_splits_node_limit.setter
    def automatic_layer_splits_node_limit(self, arg: int, /) -> None: ...
    @property
    def early_termination(self) -> EarlyTermination: ...
    @early_termination.setter
    def early_termination(self, arg: EarlyTermination, /) -> None: ...
    @property
    def early_termination_limit(self) -> int: ...
    @early_termination_limit.setter
    def early_termination_limit(self, arg: int, /) -> None: ...
    @property
    def initial_layout(self) -> InitialLayout: ...
    @initial_layout.setter
    def initial_layout(self, arg: InitialLayout, /) -> None: ...
    @property
    def iterative_bidirectional_routing(self) -> bool: ...
    @iterative_bidirectional_routing.setter
    def iterative_bidirectional_routing(self, arg: bool, /) -> None: ...
    @property
    def iterative_bidirectional_routing_passes(self) -> int: ...
    @iterative_bidirectional_routing_passes.setter
    def iterative_bidirectional_routing_passes(self, arg: int, /) -> None: ...
    @property
    def lookahead_heuristic(self) -> LookaheadHeuristic: ...
    @lookahead_heuristic.setter
    def lookahead_heuristic(self, arg: LookaheadHeuristic, /) -> None: ...
    @property
    def lookaheads(self) -> int: ...
    @lookaheads.setter
    def lookaheads(self, arg: int, /) -> None: ...
    @property
    def first_lookahead_factor(self) -> float: ...
    @first_lookahead_factor.setter
    def first_lookahead_factor(self, arg: float, /) -> None: ...
    @property
    def lookahead_factor(self) -> float: ...
    @lookahead_factor.setter
    def lookahead_factor(self, arg: float, /) -> None: ...
    @property
    def timeout(self) -> int: ...
    @timeout.setter
    def timeout(self, arg: int, /) -> None: ...
    @property
    def encoding(self) -> Encoding: ...
    @encoding.setter
    def encoding(self, arg: Encoding, /) -> None: ...
    @property
    def commander_grouping(self) -> CommanderGrouping: ...
    @commander_grouping.setter
    def commander_grouping(self, arg: CommanderGrouping, /) -> None: ...
    @property
    def use_subsets(self) -> bool: ...
    @use_subsets.setter
    def use_subsets(self, arg: bool, /) -> None: ...
    @property
    def include_wcnf(self) -> bool: ...
    @include_wcnf.setter
    def include_wcnf(self, arg: bool, /) -> None: ...
    @property
    def enable_limits(self) -> bool: ...
    @enable_limits.setter
    def enable_limits(self, arg: bool, /) -> None: ...
    @property
    def swap_reduction(self) -> SwapReduction: ...
    @swap_reduction.setter
    def swap_reduction(self, arg: SwapReduction, /) -> None: ...
    @property
    def swap_limit(self) -> int: ...
    @swap_limit.setter
    def swap_limit(self, arg: int, /) -> None: ...
    @property
    def subgraph(self) -> set[int]: ...
    @subgraph.setter
    def subgraph(self, arg: AbstractSet[int], /) -> None: ...
    @property
    def pre_mapping_optimizations(self) -> bool: ...
    @pre_mapping_optimizations.setter
    def pre_mapping_optimizations(self, arg: bool, /) -> None: ...
    @property
    def post_mapping_optimizations(self) -> bool: ...
    @post_mapping_optimizations.setter
    def post_mapping_optimizations(self, arg: bool, /) -> None: ...
    @property
    def add_measurements_to_mapped_circuit(self) -> bool: ...
    @add_measurements_to_mapped_circuit.setter
    def add_measurements_to_mapped_circuit(self, arg: bool, /) -> None: ...
    @property
    def add_barriers_between_layers(self) -> bool: ...
    @add_barriers_between_layers.setter
    def add_barriers_between_layers(self, arg: bool, /) -> None: ...
    def json(self) -> dict[str, Any]: ...

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

    def __init__(self) -> None: ...
    @property
    def input_(self) -> CircuitInfo: ...
    @input_.setter
    def input_(self, arg: CircuitInfo, /) -> None: ...
    @property
    def output(self) -> CircuitInfo: ...
    @output.setter
    def output(self, arg: CircuitInfo, /) -> None: ...
    @property
    def configuration(self) -> Configuration: ...
    @configuration.setter
    def configuration(self, arg: Configuration, /) -> None: ...
    @property
    def time(self) -> float: ...
    @time.setter
    def time(self, arg: float, /) -> None: ...
    @property
    def timeout(self) -> bool: ...
    @timeout.setter
    def timeout(self, arg: bool, /) -> None: ...
    @property
    def mapped_circuit(self) -> str: ...
    @mapped_circuit.setter
    def mapped_circuit(self, arg: str, /) -> None: ...
    @property
    def heuristic_benchmark(self) -> HeuristicBenchmarkInfo: ...
    @heuristic_benchmark.setter
    def heuristic_benchmark(self, arg: HeuristicBenchmarkInfo, /) -> None: ...
    @property
    def layer_heuristic_benchmark(self) -> list[LayerHeuristicBenchmarkInfo]: ...
    @layer_heuristic_benchmark.setter
    def layer_heuristic_benchmark(self, arg: Sequence[LayerHeuristicBenchmarkInfo], /) -> None: ...
    @property
    def wcnf(self) -> str: ...
    @wcnf.setter
    def wcnf(self, arg: str, /) -> None: ...
    def json(self) -> dict[str, Any]: ...

class CircuitInfo:
    """Class containing circuit information."""

    def __init__(self) -> None: ...
    @property
    def name(self) -> str: ...
    @name.setter
    def name(self, arg: str, /) -> None: ...
    @property
    def qubits(self) -> int: ...
    @qubits.setter
    def qubits(self, arg: int, /) -> None: ...
    @property
    def gates(self) -> int: ...
    @gates.setter
    def gates(self, arg: int, /) -> None: ...
    @property
    def single_qubit_gates(self) -> int: ...
    @single_qubit_gates.setter
    def single_qubit_gates(self, arg: int, /) -> None: ...
    @property
    def cnots(self) -> int: ...
    @cnots.setter
    def cnots(self, arg: int, /) -> None: ...
    @property
    def layers(self) -> int: ...
    @layers.setter
    def layers(self, arg: int, /) -> None: ...
    @property
    def total_fidelity(self) -> float: ...
    @total_fidelity.setter
    def total_fidelity(self, arg: float, /) -> None: ...
    @property
    def total_log_fidelity(self) -> float: ...
    @total_log_fidelity.setter
    def total_log_fidelity(self, arg: float, /) -> None: ...
    @property
    def swaps(self) -> int: ...
    @swaps.setter
    def swaps(self, arg: int, /) -> None: ...
    @property
    def direction_reverse(self) -> int: ...
    @direction_reverse.setter
    def direction_reverse(self, arg: int, /) -> None: ...

class HeuristicBenchmarkInfo:
    """Heuristic benchmark information."""

    def __init__(self) -> None: ...
    @property
    def expanded_nodes(self) -> int: ...
    @expanded_nodes.setter
    def expanded_nodes(self, arg: int, /) -> None: ...
    @property
    def generated_nodes(self) -> int: ...
    @generated_nodes.setter
    def generated_nodes(self, arg: int, /) -> None: ...
    @property
    def time_per_node(self) -> float: ...
    @time_per_node.setter
    def time_per_node(self, arg: float, /) -> None: ...
    @property
    def average_branching_factor(self) -> float: ...
    @average_branching_factor.setter
    def average_branching_factor(self, arg: float, /) -> None: ...
    @property
    def effective_branching_factor(self) -> float: ...
    @effective_branching_factor.setter
    def effective_branching_factor(self, arg: float, /) -> None: ...
    def json(self) -> dict[str, Any]: ...

class LayerHeuristicBenchmarkInfo:
    """Heuristic benchmark information."""

    def __init__(self) -> None: ...
    @property
    def expanded_nodes(self) -> int: ...
    @expanded_nodes.setter
    def expanded_nodes(self, arg: int, /) -> None: ...
    @property
    def generated_nodes(self) -> int: ...
    @generated_nodes.setter
    def generated_nodes(self, arg: int, /) -> None: ...
    @property
    def expanded_nodes_after_first_solution(self) -> int: ...
    @expanded_nodes_after_first_solution.setter
    def expanded_nodes_after_first_solution(self, arg: int, /) -> None: ...
    @property
    def expanded_nodes_after_optimal_solution(self) -> int: ...
    @expanded_nodes_after_optimal_solution.setter
    def expanded_nodes_after_optimal_solution(self, arg: int, /) -> None: ...
    @property
    def solution_nodes(self) -> int: ...
    @solution_nodes.setter
    def solution_nodes(self, arg: int, /) -> None: ...
    @property
    def solution_nodes_after_optimal_solution(self) -> int: ...
    @solution_nodes_after_optimal_solution.setter
    def solution_nodes_after_optimal_solution(self, arg: int, /) -> None: ...
    @property
    def solution_depth(self) -> int: ...
    @solution_depth.setter
    def solution_depth(self, arg: int, /) -> None: ...
    @property
    def time_per_node(self) -> float: ...
    @time_per_node.setter
    def time_per_node(self, arg: float, /) -> None: ...
    @property
    def average_branching_factor(self) -> float: ...
    @average_branching_factor.setter
    def average_branching_factor(self, arg: float, /) -> None: ...
    @property
    def effective_branching_factor(self) -> float: ...
    @effective_branching_factor.setter
    def effective_branching_factor(self, arg: float, /) -> None: ...
    @property
    def early_termination(self) -> bool: ...
    @early_termination.setter
    def early_termination(self, arg: bool, /) -> None: ...
    def json(self) -> dict[str, Any]: ...

class Architecture:
    """Class representing device/backend information."""

    @overload
    def __init__(self) -> None: ...
    @overload
    def __init__(self, num_qubits: int, coupling_map: AbstractSet[tuple[int, int]]) -> None: ...
    @overload
    def __init__(
        self, num_qubits: int, coupling_map: AbstractSet[tuple[int, int]], properties: Architecture.Properties
    ) -> None: ...

    class Properties:
        """Class representing properties of an architecture."""

        def __init__(self) -> None: ...
        @property
        def name(self) -> str: ...
        @name.setter
        def name(self, arg: str, /) -> None: ...
        @property
        def num_qubits(self) -> int: ...
        @num_qubits.setter
        def num_qubits(self, arg: int, /) -> None: ...
        def get_single_qubit_error(self, qubit: int, operation: str) -> float: ...
        def set_single_qubit_error(self, qubit: int, operation: str, error_rate: float) -> None: ...
        def get_two_qubit_error(self, control: int, target: int, operation: str = "cx") -> float: ...
        def set_two_qubit_error(self, control: int, target: int, error_rate: float, operation: str = "cx") -> None: ...
        def get_readout_error(self, qubit: int) -> float: ...
        def set_readout_error(self, qubit: int, readout_error_rate: float) -> None: ...
        def get_t1(self, qubit: int) -> float: ...
        def set_t1(self, qubit: int, t1: float) -> None: ...
        def get_t2(self, qubit: int) -> float: ...
        def set_t2(self, qubit: int, t2: float) -> None: ...
        def get_frequency(self, qubit: int) -> float: ...
        def set_frequency(self, qubit: int, qubit_frequency: float) -> None: ...
        def get_calibration_date(self, qubit: int) -> str: ...
        def set_calibration_date(self, qubit: int, calibration_date: str) -> None: ...
        def json(self) -> dict[str, Any]:
            """Returns a JSON-style dictionary of all the information present in the :class:`.Properties`."""

    @property
    def name(self) -> str: ...
    @name.setter
    def name(self, arg: str, /) -> None: ...
    @property
    def num_qubits(self) -> int: ...
    @num_qubits.setter
    def num_qubits(self, arg: int, /) -> None: ...
    @property
    def coupling_map(self) -> set[tuple[int, int]]: ...
    @coupling_map.setter
    def coupling_map(self, arg: AbstractSet[tuple[int, int]], /) -> None: ...
    @property
    def properties(self) -> Architecture.Properties: ...
    @properties.setter
    def properties(self, arg: Architecture.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: Architecture.Properties) -> None: ...
    @overload
    def load_properties(self, properties: str) -> None: ...

def map_(
    circ: mqt.core.ir.QuantumComputation, arch: Architecture, config: Configuration
) -> tuple[mqt.core.ir.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.
    """
