// File: crates/common_enums/src/enums/ui.rs

use std::fmt;

use serde::{de::Visitor, Deserialize, Deserializer, Serialize};
use utoipa::ToSchema;

#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    Eq,
    Hash,
    PartialEq,
    Serialize,
    Deserialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
)]
#[router_derive::diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "lowercase")]
pub enum ElementPosition {
    Left,
    #[default]
    #[serde(rename = "top left")]
    TopLeft,
    Top,
    #[serde(rename = "top right")]
    TopRight,
    Right,
    #[serde(rename = "bottom right")]
    BottomRight,
    Bottom,
    #[serde(rename = "bottom left")]
    BottomLeft,
    Center,
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, strum::Display, strum::EnumString, ToSchema)]
#[router_derive::diesel_enum(storage_type = "db_enum")]
pub enum ElementSize {
    Variants(SizeVariants),
    Percentage(u32),
    Pixels(u32),
}

#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    Eq,
    Hash,
    PartialEq,
    Serialize,
    Deserialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
)]
#[router_derive::diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum SizeVariants {
    #[default]
    Cover,
    Contain,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    Eq,
    Hash,
    PartialEq,
    Serialize,
    Deserialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
)]
#[router_derive::diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum PaymentLinkDetailsLayout {
    #[default]
    Layout1,
    Layout2,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    Eq,
    Hash,
    PartialEq,
    Serialize,
    Deserialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
)]
#[router_derive::diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum PaymentLinkSdkLabelType {
    #[default]
    Above,
    Floating,
    Never,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    Eq,
    Hash,
    PartialEq,
    Serialize,
    Deserialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
)]
#[router_derive::diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum PaymentLinkShowSdkTerms {
    Always,
    #[default]
    Auto,
    Never,
}

impl<'de> Deserialize<'de> for ElementSize {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct ElementSizeVisitor;

        impl Visitor<'_> for ElementSizeVisitor {
            type Value = ElementSize;

            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                formatter.write_str("a string with possible values - contain, cover or values in percentage or pixels. For eg: 48px or 50%")
            }

            fn visit_str<E>(self, value: &str) -> Result<ElementSize, E>
            where
                E: serde::de::Error,
            {
                if let Some(percent) = value.strip_suffix('%') {
                    percent
                        .parse::<u32>()
                        .map(ElementSize::Percentage)
                        .map_err(E::custom)
                } else if let Some(px) = value.strip_suffix("px") {
                    px.parse::<u32>()
                        .map(ElementSize::Pixels)
                        .map_err(E::custom)
                } else {
                    match value {
                        "cover" => Ok(ElementSize::Variants(SizeVariants::Cover)),
                        "contain" => Ok(ElementSize::Variants(SizeVariants::Contain)),
                        _ => Err(E::custom("invalid size variant")),
                    }
                }
            }
        }

        deserializer.deserialize_str(ElementSizeVisitor)
    }
}

impl Serialize for ElementSize {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::ser::Serializer,
    {
        match self {
            Self::Variants(variant) => serializer.serialize_str(variant.to_string().as_str()),
            Self::Pixels(pixel_count) => {
                serializer.serialize_str(format!("{pixel_count}px").as_str())
            }
            Self::Percentage(pixel_count) => {
                serializer.serialize_str(format!("{pixel_count}%").as_str())
            }
        }
    }
}


// File: crates/common_enums/src/enums/payments.rs

use utoipa::ToSchema;

#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum ProductType {
    #[default]
    Physical,
    Digital,
    Travel,
    Ride,
    Event,
    Accommodation,
}


// File: crates/common_enums/src/enums/accounts.rs

use utoipa::ToSchema;
#[derive(
    Copy,
    Default,
    Clone,
    Debug,
    Eq,
    PartialEq,
    serde::Deserialize,
    serde::Serialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
    Hash,
)]
#[router_derive::diesel_enum(storage_type = "text")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum MerchantProductType {
    #[default]
    Orchestration,
    Vault,
    Recon,
    Recovery,
    CostObservability,
    DynamicRouting,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    Eq,
    PartialEq,
    serde::Deserialize,
    serde::Serialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
)]
#[router_derive::diesel_enum(storage_type = "text")]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum MerchantAccountType {
    #[default]
    Standard,
    Platform,
    Connected,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    Eq,
    PartialEq,
    serde::Deserialize,
    serde::Serialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
)]
#[router_derive::diesel_enum(storage_type = "text")]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum OrganizationType {
    #[default]
    Standard,
    Platform,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    Eq,
    PartialEq,
    serde::Deserialize,
    serde::Serialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
)]
#[router_derive::diesel_enum(storage_type = "text")]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum MerchantAccountRequestType {
    #[default]
    Standard,
    Connected,
}


// File: crates/hyperswitch_constraint_graph/Cargo.toml

[package]
name = "hyperswitch_constraint_graph"
description = "Constraint Graph Framework for modeling Domain-Specific Constraints"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true

[features]
viz = ["dep:graphviz-rust"]

[dependencies]
erased-serde = "0.3.31"
graphviz-rust = { version = "0.6.6", optional = true }
rustc-hash = "1.1.0"
serde = { version = "1.0.219", features = ["derive", "rc"] }
strum = { version = "0.25", features = ["derive"] }
thiserror = "1.0.69"

[lints]
workspace = true


// File: crates/hyperswitch_constraint_graph/src/types.rs

use std::{
    any::Any,
    fmt, hash,
    ops::{Deref, DerefMut},
    sync::Arc,
};

use rustc_hash::{FxHashMap, FxHashSet};

use crate::{dense_map::impl_entity, error::AnalysisTrace};

pub trait KeyNode: fmt::Debug + Clone + hash::Hash + serde::Serialize + PartialEq + Eq {}

pub trait ValueNode: fmt::Debug + Clone + hash::Hash + serde::Serialize + PartialEq + Eq {
    type Key: KeyNode;

    fn get_key(&self) -> Self::Key;
}

#[cfg(feature = "viz")]
pub trait NodeViz {
    fn viz(&self) -> String;
}

#[derive(Debug, Clone, Copy, serde::Serialize, PartialEq, Eq, Hash)]
#[serde(transparent)]
pub struct NodeId(usize);

impl_entity!(NodeId);

#[derive(Debug)]
pub struct Node<V: ValueNode> {
    pub node_type: NodeType<V>,
    pub preds: Vec<EdgeId>,
    pub succs: Vec<EdgeId>,
}

impl<V: ValueNode> Node<V> {
    pub(crate) fn new(node_type: NodeType<V>) -> Self {
        Self {
            node_type,
            preds: Vec::new(),
            succs: Vec::new(),
        }
    }
}

#[derive(Debug, PartialEq, Eq)]
pub enum NodeType<V: ValueNode> {
    AllAggregator,
    AnyAggregator,
    InAggregator(FxHashSet<V>),
    Value(NodeValue<V>),
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
pub enum NodeValue<V: ValueNode> {
    Key(<V as ValueNode>::Key),
    Value(V),
}

impl<V: ValueNode> From<V> for NodeValue<V> {
    fn from(value: V) -> Self {
        Self::Value(value)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EdgeId(usize);

impl_entity!(EdgeId);

#[derive(
    Debug, Clone, Copy, serde::Serialize, PartialEq, Eq, Hash, strum::Display, PartialOrd, Ord,
)]
pub enum Strength {
    Weak,
    Normal,
    Strong,
}

impl Strength {
    pub fn get_resolved_strength(prev_strength: Self, curr_strength: Self) -> Self {
        std::cmp::max(prev_strength, curr_strength)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Relation {
    Positive,
    Negative,
}

impl From<Relation> for bool {
    fn from(value: Relation) -> Self {
        matches!(value, Relation::Positive)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, serde::Serialize)]
pub enum RelationResolution {
    Positive,
    Negative,
    Contradiction,
}

impl From<Relation> for RelationResolution {
    fn from(value: Relation) -> Self {
        match value {
            Relation::Positive => Self::Positive,
            Relation::Negative => Self::Negative,
        }
    }
}

impl RelationResolution {
    pub fn get_resolved_relation(prev_relation: Self, curr_relation: Self) -> Self {
        if prev_relation != curr_relation {
            Self::Contradiction
        } else {
            curr_relation
        }
    }
}

#[derive(Debug, Clone)]
pub struct Edge {
    pub strength: Strength,
    pub relation: Relation,
    pub pred: NodeId,
    pub succ: NodeId,
    pub domain: Option<DomainId>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DomainId(usize);

impl_entity!(DomainId);

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DomainIdentifier(String);

impl DomainIdentifier {
    pub fn new(identifier: String) -> Self {
        Self(identifier)
    }

    pub fn into_inner(&self) -> String {
        self.0.clone()
    }
}

impl From<String> for DomainIdentifier {
    fn from(value: String) -> Self {
        Self(value)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DomainInfo {
    pub domain_identifier: DomainIdentifier,
    pub domain_description: String,
}

pub trait CheckingContext {
    type Value: ValueNode;

    fn from_node_values<L>(vals: impl IntoIterator<Item = L>) -> Self
    where
        L: Into<Self::Value>;

    fn check_presence(&self, value: &NodeValue<Self::Value>, strength: Strength) -> bool;

    fn get_values_by_key(
        &self,
        expected: &<Self::Value as ValueNode>::Key,
    ) -> Option<Vec<Self::Value>>;
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct Memoization<V: ValueNode>(
    #[allow(clippy::type_complexity)]
    FxHashMap<(NodeId, Relation, Strength), Result<(), Arc<AnalysisTrace<V>>>>,
);

impl<V: ValueNode> Memoization<V> {
    pub fn new() -> Self {
        Self(FxHashMap::default())
    }
}

impl<V: ValueNode> Default for Memoization<V> {
    #[inline]
    fn default() -> Self {
        Self::new()
    }
}

impl<V: ValueNode> Deref for Memoization<V> {
    type Target = FxHashMap<(NodeId, Relation, Strength), Result<(), Arc<AnalysisTrace<V>>>>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<V: ValueNode> DerefMut for Memoization<V> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

#[derive(Debug, Clone)]
pub struct CycleCheck(FxHashMap<NodeId, (Strength, RelationResolution)>);
impl CycleCheck {
    pub fn new() -> Self {
        Self(FxHashMap::default())
    }
}

impl Default for CycleCheck {
    #[inline]
    fn default() -> Self {
        Self::new()
    }
}

impl Deref for CycleCheck {
    type Target = FxHashMap<NodeId, (Strength, RelationResolution)>;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for CycleCheck {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

pub trait Metadata: erased_serde::Serialize + Any + Send + Sync + fmt::Debug {}
erased_serde::serialize_trait_object!(Metadata);

impl<M> Metadata for M where M: erased_serde::Serialize + Any + Send + Sync + fmt::Debug {}


// File: crates/hyperswitch_constraint_graph/src/graph.rs

use std::sync::{Arc, Weak};

use rustc_hash::{FxHashMap, FxHashSet};

use crate::{
    builder,
    dense_map::DenseMap,
    error::{self, AnalysisTrace, GraphError},
    types::{
        CheckingContext, CycleCheck, DomainId, DomainIdentifier, DomainInfo, Edge, EdgeId,
        Memoization, Metadata, Node, NodeId, NodeType, NodeValue, Relation, RelationResolution,
        Strength, ValueNode,
    },
};

#[derive(Debug)]
struct CheckNodeContext<'a, V: ValueNode, C: CheckingContext<Value = V>> {
    ctx: &'a C,
    node: &'a Node<V>,
    node_id: NodeId,
    relation: Relation,
    strength: Strength,
    memo: &'a mut Memoization<V>,
    cycle_map: &'a mut CycleCheck,
    domains: Option<&'a [DomainId]>,
}

#[derive(Debug)]
pub struct ConstraintGraph<V: ValueNode> {
    pub domain: DenseMap<DomainId, DomainInfo>,
    pub domain_identifier_map: FxHashMap<DomainIdentifier, DomainId>,
    pub nodes: DenseMap<NodeId, Node<V>>,
    pub edges: DenseMap<EdgeId, Edge>,
    pub value_map: FxHashMap<NodeValue<V>, NodeId>,
    pub node_info: DenseMap<NodeId, Option<&'static str>>,
    pub node_metadata: DenseMap<NodeId, Option<Arc<dyn Metadata>>>,
}

impl<V> ConstraintGraph<V>
where
    V: ValueNode,
{
    fn get_predecessor_edges_by_domain(
        &self,
        node_id: NodeId,
        domains: Option<&[DomainId]>,
    ) -> Result<Vec<&Edge>, GraphError<V>> {
        let node = self.nodes.get(node_id).ok_or(GraphError::NodeNotFound)?;
        let mut final_list = Vec::new();
        for &pred in &node.preds {
            let edge = self.edges.get(pred).ok_or(GraphError::EdgeNotFound)?;
            if let Some((domain_id, domains)) = edge.domain.zip(domains) {
                if domains.contains(&domain_id) {
                    final_list.push(edge);
                }
            } else if edge.domain.is_none() {
                final_list.push(edge);
            }
        }

        Ok(final_list)
    }

    #[allow(clippy::too_many_arguments)]
    pub fn check_node<C>(
        &self,
        ctx: &C,
        node_id: NodeId,
        relation: Relation,
        strength: Strength,
        memo: &mut Memoization<V>,
        cycle_map: &mut CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let domains = domains
            .map(|domain_idents| {
                domain_idents
                    .iter()
                    .map(|domain_ident| {
                        self.domain_identifier_map
                            .get(&DomainIdentifier::new(domain_ident.to_string()))
                            .copied()
                            .ok_or(GraphError::DomainNotFound)
                    })
                    .collect::<Result<Vec<_>, _>>()
            })
            .transpose()?;

        self.check_node_inner(
            ctx,
            node_id,
            relation,
            strength,
            memo,
            cycle_map,
            domains.as_deref(),
        )
    }

    #[allow(clippy::too_many_arguments)]
    pub fn check_node_inner<C>(
        &self,
        ctx: &C,
        node_id: NodeId,
        relation: Relation,
        strength: Strength,
        memo: &mut Memoization<V>,
        cycle_map: &mut CycleCheck,
        domains: Option<&[DomainId]>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let node = self.nodes.get(node_id).ok_or(GraphError::NodeNotFound)?;

        if let Some(already_memo) = memo.get(&(node_id, relation, strength)) {
            already_memo
                .clone()
                .map_err(|err| GraphError::AnalysisError(Arc::downgrade(&err)))
        } else if let Some((initial_strength, initial_relation)) = cycle_map.get(&node_id).copied()
        {
            let strength_relation = Strength::get_resolved_strength(initial_strength, strength);
            let relation_resolve =
                RelationResolution::get_resolved_relation(initial_relation, relation.into());
            cycle_map.entry(node_id).and_modify(|value| {
                value.0 = strength_relation;
                value.1 = relation_resolve
            });
            Ok(())
        } else {
            let check_node_context = CheckNodeContext {
                node,
                node_id,
                relation,
                strength,
                memo,
                cycle_map,
                ctx,
                domains,
            };

            match &node.node_type {
                NodeType::AllAggregator => self.validate_all_aggregator(check_node_context),

                NodeType::AnyAggregator => self.validate_any_aggregator(check_node_context),

                NodeType::InAggregator(expected) => {
                    self.validate_in_aggregator(check_node_context, expected)
                }
                NodeType::Value(val) => self.validate_value_node(check_node_context, val),
            }
        }
    }

    fn validate_all_aggregator<C>(
        &self,
        vald: CheckNodeContext<'_, V, C>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let mut unsatisfied = Vec::<Weak<AnalysisTrace<V>>>::new();

        for edge in self.get_predecessor_edges_by_domain(vald.node_id, vald.domains)? {
            vald.cycle_map
                .insert(vald.node_id, (vald.strength, vald.relation.into()));
            if let Err(e) = self.check_node_inner(
                vald.ctx,
                edge.pred,
                edge.relation,
                edge.strength,
                vald.memo,
                vald.cycle_map,
                vald.domains,
            ) {
                unsatisfied.push(e.get_analysis_trace()?);
            }
            if let Some((_resolved_strength, resolved_relation)) =
                vald.cycle_map.remove(&vald.node_id)
            {
                if resolved_relation == RelationResolution::Contradiction {
                    let err = Arc::new(AnalysisTrace::Contradiction {
                        relation: resolved_relation,
                    });
                    vald.memo.insert(
                        (vald.node_id, vald.relation, vald.strength),
                        Err(Arc::clone(&err)),
                    );
                    return Err(GraphError::AnalysisError(Arc::downgrade(&err)));
                }
            }
        }

        if !unsatisfied.is_empty() {
            let err = Arc::new(AnalysisTrace::AllAggregation {
                unsatisfied,
                info: self.node_info.get(vald.node_id).copied().flatten(),
                metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
            });

            vald.memo.insert(
                (vald.node_id, vald.relation, vald.strength),
                Err(Arc::clone(&err)),
            );
            Err(GraphError::AnalysisError(Arc::downgrade(&err)))
        } else {
            vald.memo
                .insert((vald.node_id, vald.relation, vald.strength), Ok(()));

            Ok(())
        }
    }

    fn validate_any_aggregator<C>(
        &self,
        vald: CheckNodeContext<'_, V, C>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let mut unsatisfied = Vec::<Weak<AnalysisTrace<V>>>::new();
        let mut matched_one = false;

        for edge in self.get_predecessor_edges_by_domain(vald.node_id, vald.domains)? {
            vald.cycle_map
                .insert(vald.node_id, (vald.strength, vald.relation.into()));
            if let Err(e) = self.check_node_inner(
                vald.ctx,
                edge.pred,
                edge.relation,
                edge.strength,
                vald.memo,
                vald.cycle_map,
                vald.domains,
            ) {
                unsatisfied.push(e.get_analysis_trace()?);
            } else {
                matched_one = true;
            }
            if let Some((_resolved_strength, resolved_relation)) =
                vald.cycle_map.remove(&vald.node_id)
            {
                if resolved_relation == RelationResolution::Contradiction {
                    let err = Arc::new(AnalysisTrace::Contradiction {
                        relation: resolved_relation,
                    });
                    vald.memo.insert(
                        (vald.node_id, vald.relation, vald.strength),
                        Err(Arc::clone(&err)),
                    );

                    return Err(GraphError::AnalysisError(Arc::downgrade(&err)));
                }
            }
        }

        if matched_one || vald.node.preds.is_empty() {
            vald.memo
                .insert((vald.node_id, vald.relation, vald.strength), Ok(()));
            Ok(())
        } else {
            let err = Arc::new(AnalysisTrace::AnyAggregation {
                unsatisfied: unsatisfied.clone(),
                info: self.node_info.get(vald.node_id).copied().flatten(),
                metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
            });

            vald.memo.insert(
                (vald.node_id, vald.relation, vald.strength),
                Err(Arc::clone(&err)),
            );
            Err(GraphError::AnalysisError(Arc::downgrade(&err)))
        }
    }

    fn validate_in_aggregator<C>(
        &self,
        vald: CheckNodeContext<'_, V, C>,
        expected: &FxHashSet<V>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let the_key = expected
            .iter()
            .next()
            .ok_or_else(|| GraphError::MalformedGraph {
                reason: "An OnlyIn aggregator node must have at least one expected value"
                    .to_string(),
            })?
            .get_key();

        let ctx_vals = if let Some(vals) = vald.ctx.get_values_by_key(&the_key) {
            vals
        } else {
            return if let Strength::Weak = vald.strength {
                vald.memo
                    .insert((vald.node_id, vald.relation, vald.strength), Ok(()));
                Ok(())
            } else {
                let err = Arc::new(AnalysisTrace::InAggregation {
                    expected: expected.iter().cloned().collect(),
                    found: None,
                    relation: vald.relation,
                    info: self.node_info.get(vald.node_id).copied().flatten(),
                    metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
                });

                vald.memo.insert(
                    (vald.node_id, vald.relation, vald.strength),
                    Err(Arc::clone(&err)),
                );
                Err(GraphError::AnalysisError(Arc::downgrade(&err)))
            };
        };

        let relation_bool: bool = vald.relation.into();
        for ctx_value in ctx_vals {
            if expected.contains(&ctx_value) != relation_bool {
                let err = Arc::new(AnalysisTrace::InAggregation {
                    expected: expected.iter().cloned().collect(),
                    found: Some(ctx_value.clone()),
                    relation: vald.relation,
                    info: self.node_info.get(vald.node_id).copied().flatten(),
                    metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
                });

                vald.memo.insert(
                    (vald.node_id, vald.relation, vald.strength),
                    Err(Arc::clone(&err)),
                );
                Err(GraphError::AnalysisError(Arc::downgrade(&err)))?;
            }
        }

        vald.memo
            .insert((vald.node_id, vald.relation, vald.strength), Ok(()));
        Ok(())
    }

    fn validate_value_node<C>(
        &self,
        vald: CheckNodeContext<'_, V, C>,
        val: &NodeValue<V>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let mut errors = Vec::<Weak<AnalysisTrace<V>>>::new();
        let mut matched_one = false;

        self.context_analysis(
            vald.node_id,
            vald.relation,
            vald.strength,
            vald.ctx,
            val,
            vald.memo,
        )?;

        for edge in self.get_predecessor_edges_by_domain(vald.node_id, vald.domains)? {
            vald.cycle_map
                .insert(vald.node_id, (vald.strength, vald.relation.into()));
            let result = self.check_node_inner(
                vald.ctx,
                edge.pred,
                edge.relation,
                edge.strength,
                vald.memo,
                vald.cycle_map,
                vald.domains,
            );

            if let Some((resolved_strength, resolved_relation)) =
                vald.cycle_map.remove(&vald.node_id)
            {
                if resolved_relation == RelationResolution::Contradiction {
                    let err = Arc::new(AnalysisTrace::Contradiction {
                        relation: resolved_relation,
                    });
                    vald.memo.insert(
                        (vald.node_id, vald.relation, vald.strength),
                        Err(Arc::clone(&err)),
                    );
                    return Err(GraphError::AnalysisError(Arc::downgrade(&err)));
                } else if resolved_strength != vald.strength {
                    self.context_analysis(
                        vald.node_id,
                        vald.relation,
                        resolved_strength,
                        vald.ctx,
                        val,
                        vald.memo,
                    )?
                }
            }
            match (edge.strength, result) {
                (Strength::Strong, Err(trace)) => {
                    let err = Arc::new(AnalysisTrace::Value {
                        value: val.clone(),
                        relation: vald.relation,
                        info: self.node_info.get(vald.node_id).copied().flatten(),
                        metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
                        predecessors: Some(error::ValueTracePredecessor::Mandatory(Box::new(
                            trace.get_analysis_trace()?,
                        ))),
                    });
                    vald.memo.insert(
                        (vald.node_id, vald.relation, vald.strength),
                        Err(Arc::clone(&err)),
                    );
                    Err(GraphError::AnalysisError(Arc::downgrade(&err)))?;
                }

                (Strength::Strong, Ok(_)) => {
                    matched_one = true;
                }

                (Strength::Normal | Strength::Weak, Err(trace)) => {
                    errors.push(trace.get_analysis_trace()?);
                }

                (Strength::Normal | Strength::Weak, Ok(_)) => {
                    matched_one = true;
                }
            }
        }

        if matched_one || vald.node.preds.is_empty() {
            vald.memo
                .insert((vald.node_id, vald.relation, vald.strength), Ok(()));
            Ok(())
        } else {
            let err = Arc::new(AnalysisTrace::Value {
                value: val.clone(),
                relation: vald.relation,
                info: self.node_info.get(vald.node_id).copied().flatten(),
                metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
                predecessors: Some(error::ValueTracePredecessor::OneOf(errors.clone())),
            });

            vald.memo.insert(
                (vald.node_id, vald.relation, vald.strength),
                Err(Arc::clone(&err)),
            );
            Err(GraphError::AnalysisError(Arc::downgrade(&err)))
        }
    }

    fn context_analysis<C>(
        &self,
        node_id: NodeId,
        relation: Relation,
        strength: Strength,
        ctx: &C,
        val: &NodeValue<V>,
        memo: &mut Memoization<V>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let in_context = ctx.check_presence(val, strength);
        let relation_bool: bool = relation.into();
        if in_context != relation_bool {
            let err = Arc::new(AnalysisTrace::Value {
                value: val.clone(),
                relation,
                predecessors: None,
                info: self.node_info.get(node_id).copied().flatten(),
                metadata: self.node_metadata.get(node_id).cloned().flatten(),
            });
            memo.insert((node_id, relation, strength), Err(Arc::clone(&err)));
            Err(GraphError::AnalysisError(Arc::downgrade(&err)))?;
        }
        if !relation_bool {
            memo.insert((node_id, relation, strength), Ok(()));
            return Ok(());
        }
        Ok(())
    }

    pub fn combine(g1: &Self, g2: &Self) -> Result<Self, GraphError<V>> {
        let mut node_builder = builder::ConstraintGraphBuilder::new();
        let mut g1_old2new_id = DenseMap::<NodeId, NodeId>::new();
        let mut g2_old2new_id = DenseMap::<NodeId, NodeId>::new();
        let mut g1_old2new_domain_id = DenseMap::<DomainId, DomainId>::new();
        let mut g2_old2new_domain_id = DenseMap::<DomainId, DomainId>::new();

        let add_domain = |node_builder: &mut builder::ConstraintGraphBuilder<V>,
                          domain: DomainInfo|
         -> Result<DomainId, GraphError<V>> {
            node_builder.make_domain(
                domain.domain_identifier.into_inner(),
                &domain.domain_description,
            )
        };

        let add_node = |node_builder: &mut builder::ConstraintGraphBuilder<V>,
                        node: &Node<V>|
         -> Result<NodeId, GraphError<V>> {
            match &node.node_type {
                NodeType::Value(node_value) => {
                    Ok(node_builder.make_value_node(node_value.clone(), None, None::<()>))
                }

                NodeType::AllAggregator => {
                    Ok(node_builder.make_all_aggregator(&[], None, None::<()>, None)?)
                }

                NodeType::AnyAggregator => {
                    Ok(node_builder.make_any_aggregator(&[], None, None::<()>, None)?)
                }

                NodeType::InAggregator(expected) => Ok(node_builder.make_in_aggregator(
                    expected.iter().cloned().collect(),
                    None,
                    None::<()>,
                )?),
            }
        };

        for (_old_domain_id, domain) in g1.domain.iter() {
            let new_domain_id = add_domain(&mut node_builder, domain.clone())?;
            g1_old2new_domain_id.push(new_domain_id);
        }

        for (_old_domain_id, domain) in g2.domain.iter() {
            let new_domain_id = add_domain(&mut node_builder, domain.clone())?;
            g2_old2new_domain_id.push(new_domain_id);
        }

        for (_old_node_id, node) in g1.nodes.iter() {
            let new_node_id = add_node(&mut node_builder, node)?;
            g1_old2new_id.push(new_node_id);
        }

        for (_old_node_id, node) in g2.nodes.iter() {
            let new_node_id = add_node(&mut node_builder, node)?;
            g2_old2new_id.push(new_node_id);
        }

        for edge in g1.edges.values() {
            let new_pred_id = g1_old2new_id
                .get(edge.pred)
                .ok_or(GraphError::NodeNotFound)?;
            let new_succ_id = g1_old2new_id
                .get(edge.succ)
                .ok_or(GraphError::NodeNotFound)?;
            let domain_ident = edge
                .domain
                .map(|domain_id| g1.domain.get(domain_id).ok_or(GraphError::DomainNotFound))
                .transpose()?
                .map(|domain| domain.domain_identifier.clone());

            node_builder.make_edge(
                *new_pred_id,
                *new_succ_id,
                edge.strength,
                edge.relation,
                domain_ident,
            )?;
        }

        for edge in g2.edges.values() {
            let new_pred_id = g2_old2new_id
                .get(edge.pred)
                .ok_or(GraphError::NodeNotFound)?;
            let new_succ_id = g2_old2new_id
                .get(edge.succ)
                .ok_or(GraphError::NodeNotFound)?;
            let domain_ident = edge
                .domain
                .map(|domain_id| g2.domain.get(domain_id).ok_or(GraphError::DomainNotFound))
                .transpose()?
                .map(|domain| domain.domain_identifier.clone());

            node_builder.make_edge(
                *new_pred_id,
                *new_succ_id,
                edge.strength,
                edge.relation,
                domain_ident,
            )?;
        }

        Ok(node_builder.build())
    }
}

#[cfg(feature = "viz")]
mod viz {
    use graphviz_rust::{
        dot_generator::*,
        dot_structures::*,
        printer::{DotPrinter, PrinterContext},
    };

    use crate::{dense_map::EntityId, types, ConstraintGraph, NodeViz, ValueNode};

    fn get_node_id(node_id: types::NodeId) -> String {
        format!("N{}", node_id.get_id())
    }

    impl<V> ConstraintGraph<V>
    where
        V: ValueNode + NodeViz,
        <V as ValueNode>::Key: NodeViz,
    {
        fn get_node_label(node: &types::Node<V>) -> String {
            let label = match &node.node_type {
                types::NodeType::Value(types::NodeValue::Key(key)) => format!("any {}", key.viz()),
                types::NodeType::Value(types::NodeValue::Value(val)) => {
                    format!("{} = {}", val.get_key().viz(), val.viz())
                }
                types::NodeType::AllAggregator => "&&".to_string(),
                types::NodeType::AnyAggregator => "| |".to_string(),
                types::NodeType::InAggregator(agg) => {
                    let key = if let Some(val) = agg.iter().next() {
                        val.get_key().viz()
                    } else {
                        return "empty in".to_string();
                    };

                    let nodes = agg.iter().map(NodeViz::viz).collect::<Vec<_>>();
                    format!("{key} in [{}]", nodes.join(", "))
                }
            };

            format!("\"{label}\"")
        }

        fn build_node(cg_node_id: types::NodeId, cg_node: &types::Node<V>) -> Node {
            let viz_node_id = get_node_id(cg_node_id);
            let viz_node_label = Self::get_node_label(cg_node);

            node!(viz_node_id; attr!("label", viz_node_label))
        }

        fn build_edge(cg_edge: &types::Edge) -> Edge {
            let pred_vertex = get_node_id(cg_edge.pred);
            let succ_vertex = get_node_id(cg_edge.succ);
            let arrowhead = match cg_edge.strength {
                types::Strength::Weak => "onormal",
                types::Strength::Normal => "normal",
                types::Strength::Strong => "normalnormal",
            };
            let color = match cg_edge.relation {
                types::Relation::Positive => "blue",
                types::Relation::Negative => "red",
            };

            edge!(
                node_id!(pred_vertex) => node_id!(succ_vertex);
                attr!("arrowhead", arrowhead),
                attr!("color", color)
            )
        }

        pub fn get_viz_digraph(&self) -> Graph {
            graph!(
                strict di id!("constraint_graph"),
                self.nodes
                    .iter()
                    .map(|(node_id, node)| Self::build_node(node_id, node))
                    .map(Stmt::Node)
                    .chain(self.edges.values().map(Self::build_edge).map(Stmt::Edge))
                    .collect::<Vec<_>>()
            )
        }

        pub fn get_viz_digraph_string(&self) -> String {
            let mut ctx = PrinterContext::default();
            let digraph = self.get_viz_digraph();
            digraph.print(&mut ctx)
        }
    }
}


// File: crates/hyperswitch_constraint_graph/src/error.rs

use std::sync::{Arc, Weak};

use crate::types::{Metadata, NodeValue, Relation, RelationResolution, ValueNode};

#[derive(Debug, Clone, serde::Serialize)]
#[serde(tag = "type", content = "predecessor", rename_all = "snake_case")]
pub enum ValueTracePredecessor<V: ValueNode> {
    Mandatory(Box<Weak<AnalysisTrace<V>>>),
    OneOf(Vec<Weak<AnalysisTrace<V>>>),
}

#[derive(Debug, Clone, serde::Serialize)]
#[serde(tag = "type", content = "trace", rename_all = "snake_case")]
pub enum AnalysisTrace<V: ValueNode> {
    Value {
        value: NodeValue<V>,
        relation: Relation,
        predecessors: Option<ValueTracePredecessor<V>>,
        info: Option<&'static str>,
        metadata: Option<Arc<dyn Metadata>>,
    },

    AllAggregation {
        unsatisfied: Vec<Weak<AnalysisTrace<V>>>,
        info: Option<&'static str>,
        metadata: Option<Arc<dyn Metadata>>,
    },

    AnyAggregation {
        unsatisfied: Vec<Weak<AnalysisTrace<V>>>,
        info: Option<&'static str>,
        metadata: Option<Arc<dyn Metadata>>,
    },

    InAggregation {
        expected: Vec<V>,
        found: Option<V>,
        relation: Relation,
        info: Option<&'static str>,
        metadata: Option<Arc<dyn Metadata>>,
    },
    Contradiction {
        relation: RelationResolution,
    },
}

#[derive(Debug, Clone, serde::Serialize, thiserror::Error)]
#[serde(tag = "type", content = "info", rename_all = "snake_case")]
pub enum GraphError<V: ValueNode> {
    #[error("An edge was not found in the graph")]
    EdgeNotFound,
    #[error("Attempted to create a conflicting edge between two nodes")]
    ConflictingEdgeCreated,
    #[error("Cycle detected in graph")]
    CycleDetected,
    #[error("Domain wasn't found in the Graph")]
    DomainNotFound,
    #[error("Malformed Graph: {reason}")]
    MalformedGraph { reason: String },
    #[error("A node was not found in the graph")]
    NodeNotFound,
    #[error("A value node was not found: {0:#?}")]
    ValueNodeNotFound(V),
    #[error("No values provided for an 'in' aggregator node")]
    NoInAggregatorValues,
    #[error("Error during analysis: {0:#?}")]
    AnalysisError(Weak<AnalysisTrace<V>>),
}

impl<V: ValueNode> GraphError<V> {
    pub fn get_analysis_trace(self) -> Result<Weak<AnalysisTrace<V>>, Self> {
        match self {
            Self::AnalysisError(trace) => Ok(trace),
            _ => Err(self),
        }
    }
}


// File: crates/hyperswitch_constraint_graph/src/lib.rs

pub mod builder;
mod dense_map;
pub mod error;
pub mod graph;
pub mod types;

pub use builder::ConstraintGraphBuilder;
pub use error::{AnalysisTrace, GraphError};
pub use graph::ConstraintGraph;
#[cfg(feature = "viz")]
pub use types::NodeViz;
pub use types::{
    CheckingContext, CycleCheck, DomainId, DomainIdentifier, Edge, EdgeId, KeyNode, Memoization,
    Node, NodeId, NodeValue, Relation, Strength, ValueNode,
};


// File: crates/hyperswitch_constraint_graph/src/dense_map.rs

use std::{fmt, iter, marker::PhantomData, ops, slice, vec};

pub trait EntityId {
    fn get_id(&self) -> usize;
    fn with_id(id: usize) -> Self;
}

macro_rules! impl_entity {
    ($name:ident) => {
        impl $crate::dense_map::EntityId for $name {
            #[inline]
            fn get_id(&self) -> usize {
                self.0
            }

            #[inline]
            fn with_id(id: usize) -> Self {
                Self(id)
            }
        }
    };
}

pub(crate) use impl_entity;

pub struct DenseMap<K, V> {
    data: Vec<V>,
    _marker: PhantomData<K>,
}

impl<K, V> DenseMap<K, V> {
    pub fn new() -> Self {
        Self {
            data: Vec::new(),
            _marker: PhantomData,
        }
    }
}

impl<K, V> Default for DenseMap<K, V> {
    fn default() -> Self {
        Self::new()
    }
}

impl<K, V> DenseMap<K, V>
where
    K: EntityId,
{
    pub fn push(&mut self, elem: V) -> K {
        let curr_len = self.data.len();
        self.data.push(elem);
        K::with_id(curr_len)
    }

    #[inline]
    pub fn get(&self, idx: K) -> Option<&V> {
        self.data.get(idx.get_id())
    }

    #[inline]
    pub fn get_mut(&mut self, idx: K) -> Option<&mut V> {
        self.data.get_mut(idx.get_id())
    }

    #[inline]
    pub fn contains_key(&self, key: K) -> bool {
        key.get_id() < self.data.len()
    }

    #[inline]
    pub fn keys(&self) -> Keys<K> {
        Keys::new(0..self.data.len())
    }

    #[inline]
    pub fn into_keys(self) -> Keys<K> {
        Keys::new(0..self.data.len())
    }

    #[inline]
    pub fn values(&self) -> slice::Iter<'_, V> {
        self.data.iter()
    }

    #[inline]
    pub fn values_mut(&mut self) -> slice::IterMut<'_, V> {
        self.data.iter_mut()
    }

    #[inline]
    pub fn into_values(self) -> vec::IntoIter<V> {
        self.data.into_iter()
    }

    #[inline]
    pub fn iter(&self) -> Iter<'_, K, V> {
        Iter::new(self.data.iter())
    }

    #[inline]
    pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
        IterMut::new(self.data.iter_mut())
    }
}

impl<K, V> fmt::Debug for DenseMap<K, V>
where
    K: EntityId + fmt::Debug,
    V: fmt::Debug,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_map().entries(self.iter()).finish()
    }
}

pub struct Keys<K> {
    inner: ops::Range<usize>,
    _marker: PhantomData<K>,
}

impl<K> Keys<K> {
    fn new(range: ops::Range<usize>) -> Self {
        Self {
            inner: range,
            _marker: PhantomData,
        }
    }
}

impl<K> Iterator for Keys<K>
where
    K: EntityId,
{
    type Item = K;

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().map(K::with_id)
    }
}

pub struct Iter<'a, K, V> {
    inner: iter::Enumerate<slice::Iter<'a, V>>,
    _marker: PhantomData<K>,
}

impl<'a, K, V> Iter<'a, K, V> {
    fn new(iter: slice::Iter<'a, V>) -> Self {
        Self {
            inner: iter.enumerate(),
            _marker: PhantomData,
        }
    }
}

impl<'a, K, V> Iterator for Iter<'a, K, V>
where
    K: EntityId,
{
    type Item = (K, &'a V);

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().map(|(id, val)| (K::with_id(id), val))
    }
}

pub struct IterMut<'a, K, V> {
    inner: iter::Enumerate<slice::IterMut<'a, V>>,
    _marker: PhantomData<K>,
}

impl<'a, K, V> IterMut<'a, K, V> {
    fn new(iter: slice::IterMut<'a, V>) -> Self {
        Self {
            inner: iter.enumerate(),
            _marker: PhantomData,
        }
    }
}

impl<'a, K, V> Iterator for IterMut<'a, K, V>
where
    K: EntityId,
{
    type Item = (K, &'a mut V);

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().map(|(id, val)| (K::with_id(id), val))
    }
}

pub struct IntoIter<K, V> {
    inner: iter::Enumerate<vec::IntoIter<V>>,
    _marker: PhantomData<K>,
}

impl<K, V> IntoIter<K, V> {
    fn new(iter: vec::IntoIter<V>) -> Self {
        Self {
            inner: iter.enumerate(),
            _marker: PhantomData,
        }
    }
}

impl<K, V> Iterator for IntoIter<K, V>
where
    K: EntityId,
{
    type Item = (K, V);

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().map(|(id, val)| (K::with_id(id), val))
    }
}

impl<K, V> IntoIterator for DenseMap<K, V>
where
    K: EntityId,
{
    type Item = (K, V);
    type IntoIter = IntoIter<K, V>;

    fn into_iter(self) -> Self::IntoIter {
        IntoIter::new(self.data.into_iter())
    }
}

impl<K, V> FromIterator<V> for DenseMap<K, V>
where
    K: EntityId,
{
    fn from_iter<T>(iter: T) -> Self
    where
        T: IntoIterator<Item = V>,
    {
        Self {
            data: Vec::from_iter(iter),
            _marker: PhantomData,
        }
    }
}


// File: crates/hyperswitch_constraint_graph/src/builder.rs

use std::sync::Arc;

use rustc_hash::{FxHashMap, FxHashSet};

use crate::{
    dense_map::DenseMap,
    error::GraphError,
    graph::ConstraintGraph,
    types::{
        DomainId, DomainIdentifier, DomainInfo, Edge, EdgeId, Metadata, Node, NodeId, NodeType,
        NodeValue, Relation, Strength, ValueNode,
    },
};

pub enum DomainIdOrIdentifier {
    DomainId(DomainId),
    DomainIdentifier(DomainIdentifier),
}

impl From<String> for DomainIdOrIdentifier {
    fn from(value: String) -> Self {
        Self::DomainIdentifier(DomainIdentifier::new(value))
    }
}

impl From<DomainIdentifier> for DomainIdOrIdentifier {
    fn from(value: DomainIdentifier) -> Self {
        Self::DomainIdentifier(value)
    }
}

impl From<DomainId> for DomainIdOrIdentifier {
    fn from(value: DomainId) -> Self {
        Self::DomainId(value)
    }
}
#[derive(Debug)]
pub struct ConstraintGraphBuilder<V: ValueNode> {
    domain: DenseMap<DomainId, DomainInfo>,
    nodes: DenseMap<NodeId, Node<V>>,
    edges: DenseMap<EdgeId, Edge>,
    domain_identifier_map: FxHashMap<DomainIdentifier, DomainId>,
    value_map: FxHashMap<NodeValue<V>, NodeId>,
    edges_map: FxHashMap<(NodeId, NodeId, Option<DomainId>), EdgeId>,
    node_info: DenseMap<NodeId, Option<&'static str>>,
    node_metadata: DenseMap<NodeId, Option<Arc<dyn Metadata>>>,
}

#[allow(clippy::new_without_default)]
impl<V> ConstraintGraphBuilder<V>
where
    V: ValueNode,
{
    pub fn new() -> Self {
        Self {
            domain: DenseMap::new(),
            nodes: DenseMap::new(),
            edges: DenseMap::new(),
            domain_identifier_map: FxHashMap::default(),
            value_map: FxHashMap::default(),
            edges_map: FxHashMap::default(),
            node_info: DenseMap::new(),
            node_metadata: DenseMap::new(),
        }
    }

    pub fn build(self) -> ConstraintGraph<V> {
        ConstraintGraph {
            domain: self.domain,
            domain_identifier_map: self.domain_identifier_map,
            nodes: self.nodes,
            edges: self.edges,
            value_map: self.value_map,
            node_info: self.node_info,
            node_metadata: self.node_metadata,
        }
    }

    fn retrieve_domain_from_identifier(
        &self,
        domain_ident: DomainIdentifier,
    ) -> Result<DomainId, GraphError<V>> {
        self.domain_identifier_map
            .get(&domain_ident)
            .copied()
            .ok_or(GraphError::DomainNotFound)
    }

    pub fn make_domain(
        &mut self,
        domain_identifier: String,
        domain_description: &str,
    ) -> Result<DomainId, GraphError<V>> {
        let domain_identifier = DomainIdentifier::new(domain_identifier);
        Ok(self
            .domain_identifier_map
            .clone()
            .get(&domain_identifier)
            .map_or_else(
                || {
                    let domain_id = self.domain.push(DomainInfo {
                        domain_identifier: domain_identifier.clone(),
                        domain_description: domain_description.to_string(),
                    });
                    self.domain_identifier_map
                        .insert(domain_identifier, domain_id);
                    domain_id
                },
                |domain_id| *domain_id,
            ))
    }

    pub fn make_value_node<M: Metadata>(
        &mut self,
        value: NodeValue<V>,
        info: Option<&'static str>,
        metadata: Option<M>,
    ) -> NodeId {
        self.value_map.get(&value).copied().unwrap_or_else(|| {
            let node_id = self.nodes.push(Node::new(NodeType::Value(value.clone())));
            let _node_info_id = self.node_info.push(info);

            let _node_metadata_id = self
                .node_metadata
                .push(metadata.map(|meta| -> Arc<dyn Metadata> { Arc::new(meta) }));

            self.value_map.insert(value, node_id);
            node_id
        })
    }

    pub fn make_edge<T: Into<DomainIdOrIdentifier>>(
        &mut self,
        pred_id: NodeId,
        succ_id: NodeId,
        strength: Strength,
        relation: Relation,
        domain: Option<T>,
    ) -> Result<EdgeId, GraphError<V>> {
        self.ensure_node_exists(pred_id)?;
        self.ensure_node_exists(succ_id)?;
        let domain_id = domain
            .map(|d| match d.into() {
                DomainIdOrIdentifier::DomainIdentifier(ident) => {
                    self.retrieve_domain_from_identifier(ident)
                }
                DomainIdOrIdentifier::DomainId(domain_id) => {
                    self.ensure_domain_exists(domain_id).map(|_| domain_id)
                }
            })
            .transpose()?;
        self.edges_map
            .get(&(pred_id, succ_id, domain_id))
            .copied()
            .and_then(|edge_id| self.edges.get(edge_id).cloned().map(|edge| (edge_id, edge)))
            .map_or_else(
                || {
                    let edge_id = self.edges.push(Edge {
                        strength,
                        relation,
                        pred: pred_id,
                        succ: succ_id,
                        domain: domain_id,
                    });
                    self.edges_map
                        .insert((pred_id, succ_id, domain_id), edge_id);

                    let pred = self
                        .nodes
                        .get_mut(pred_id)
                        .ok_or(GraphError::NodeNotFound)?;
                    pred.succs.push(edge_id);

                    let succ = self
                        .nodes
                        .get_mut(succ_id)
                        .ok_or(GraphError::NodeNotFound)?;
                    succ.preds.push(edge_id);

                    Ok(edge_id)
                },
                |(edge_id, edge)| {
                    if edge.strength == strength && edge.relation == relation {
                        Ok(edge_id)
                    } else {
                        Err(GraphError::ConflictingEdgeCreated)
                    }
                },
            )
    }

    pub fn make_all_aggregator<M: Metadata>(
        &mut self,
        nodes: &[(NodeId, Relation, Strength)],
        info: Option<&'static str>,
        metadata: Option<M>,
        domain_id: Option<DomainId>,
    ) -> Result<NodeId, GraphError<V>> {
        nodes
            .iter()
            .try_for_each(|(node_id, _, _)| self.ensure_node_exists(*node_id))?;

        let aggregator_id = self.nodes.push(Node::new(NodeType::AllAggregator));
        let _aggregator_info_id = self.node_info.push(info);

        let _node_metadata_id = self
            .node_metadata
            .push(metadata.map(|meta| -> Arc<dyn Metadata> { Arc::new(meta) }));

        for (node_id, relation, strength) in nodes {
            self.make_edge(*node_id, aggregator_id, *strength, *relation, domain_id)?;
        }

        Ok(aggregator_id)
    }

    pub fn make_any_aggregator<M: Metadata>(
        &mut self,
        nodes: &[(NodeId, Relation, Strength)],
        info: Option<&'static str>,
        metadata: Option<M>,
        domain_id: Option<DomainId>,
    ) -> Result<NodeId, GraphError<V>> {
        nodes
            .iter()
            .try_for_each(|(node_id, _, _)| self.ensure_node_exists(*node_id))?;

        let aggregator_id = self.nodes.push(Node::new(NodeType::AnyAggregator));
        let _aggregator_info_id = self.node_info.push(info);

        let _node_metadata_id = self
            .node_metadata
            .push(metadata.map(|meta| -> Arc<dyn Metadata> { Arc::new(meta) }));

        for (node_id, relation, strength) in nodes {
            self.make_edge(*node_id, aggregator_id, *strength, *relation, domain_id)?;
        }

        Ok(aggregator_id)
    }

    pub fn make_in_aggregator<M: Metadata>(
        &mut self,
        values: Vec<V>,
        info: Option<&'static str>,
        metadata: Option<M>,
    ) -> Result<NodeId, GraphError<V>> {
        let key = values
            .first()
            .ok_or(GraphError::NoInAggregatorValues)?
            .get_key();

        for val in &values {
            if val.get_key() != key {
                Err(GraphError::MalformedGraph {
                    reason: "Values for 'In' aggregator not of same key".to_string(),
                })?;
            }
        }
        let node_id = self
            .nodes
            .push(Node::new(NodeType::InAggregator(FxHashSet::from_iter(
                values,
            ))));
        let _aggregator_info_id = self.node_info.push(info);

        let _node_metadata_id = self
            .node_metadata
            .push(metadata.map(|meta| -> Arc<dyn Metadata> { Arc::new(meta) }));

        Ok(node_id)
    }

    fn ensure_node_exists(&self, id: NodeId) -> Result<(), GraphError<V>> {
        if self.nodes.contains_key(id) {
            Ok(())
        } else {
            Err(GraphError::NodeNotFound)
        }
    }

    fn ensure_domain_exists(&self, id: DomainId) -> Result<(), GraphError<V>> {
        if self.domain.contains_key(id) {
            Ok(())
        } else {
            Err(GraphError::DomainNotFound)
        }
    }
}


// File: crates/connector_configs/Cargo.toml

[package]
name = "connector_configs"
description = "Connector Integration Dashboard"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

[features]
default = ["payouts", "dummy_connector"]
production = []
sandbox = []
dummy_connector = ["api_models/dummy_connector"]
payouts = ["api_models/payouts"]
v1 = ["api_models/v1", "common_utils/v1"]

[dependencies]
# First party crates
api_models = { version = "0.1.0", path = "../api_models", package = "api_models" }
common_utils = { version = "0.1.0", path = "../common_utils" }

# Third party crates
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
serde_with = "3.12.0"
toml = "0.8.22"
utoipa = { version = "4.2.3", features = ["preserve_order", "preserve_path_order"] }

[lints]
workspace = true


// File: crates/connector_configs/src/lib.rs

pub mod common_config;
pub mod connector;
pub mod response_modifier;
pub mod transformer;


// File: crates/connector_configs/src/response_modifier.rs

use crate::common_config::{
    CardProvider, ConnectorApiIntegrationPayload, DashboardPaymentMethodPayload,
    DashboardRequestPayload, Provider,
};

impl ConnectorApiIntegrationPayload {
    pub fn get_transformed_response_payload(response: Self) -> DashboardRequestPayload {
        let mut wallet_details: Vec<Provider> = Vec::new();
        let mut bank_redirect_details: Vec<Provider> = Vec::new();
        let mut pay_later_details: Vec<Provider> = Vec::new();
        let mut debit_details: Vec<CardProvider> = Vec::new();
        let mut credit_details: Vec<CardProvider> = Vec::new();
        let mut bank_transfer_details: Vec<Provider> = Vec::new();
        let mut crypto_details: Vec<Provider> = Vec::new();
        let mut bank_debit_details: Vec<Provider> = Vec::new();
        let mut reward_details: Vec<Provider> = Vec::new();
        let mut real_time_payment_details: Vec<Provider> = Vec::new();
        let mut upi_details: Vec<Provider> = Vec::new();
        let mut voucher_details: Vec<Provider> = Vec::new();
        let mut gift_card_details: Vec<Provider> = Vec::new();
        let mut card_redirect_details: Vec<Provider> = Vec::new();
        let mut open_banking_details: Vec<Provider> = Vec::new();
        let mut mobile_payment_details: Vec<Provider> = Vec::new();

        if let Some(payment_methods_enabled) = response.payment_methods_enabled.clone() {
            for methods in payment_methods_enabled {
                match methods.payment_method {
                    api_models::enums::PaymentMethod::Card => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                match method_type.payment_method_type {
                                    api_models::enums::PaymentMethodType::Credit => {
                                        if let Some(card_networks) = method_type.card_networks {
                                            for card in card_networks {
                                                credit_details.push(CardProvider {
                                                    payment_method_type: card,
                                                    accepted_currencies: method_type
                                                        .accepted_currencies
                                                        .clone(),
                                                    accepted_countries: method_type
                                                        .accepted_countries
                                                        .clone(),
                                                })
                                            }
                                        }
                                    }
                                    api_models::enums::PaymentMethodType::Debit => {
                                        if let Some(card_networks) = method_type.card_networks {
                                            for card in card_networks {
                                                // debit_details.push(card)
                                                debit_details.push(CardProvider {
                                                    payment_method_type: card,
                                                    accepted_currencies: method_type
                                                        .accepted_currencies
                                                        .clone(),
                                                    accepted_countries: method_type
                                                        .accepted_countries
                                                        .clone(),
                                                })
                                            }
                                        }
                                    }
                                    _ => (),
                                }
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::Wallet => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                // wallet_details.push(method_type.payment_method_type)
                                wallet_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::BankRedirect => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                bank_redirect_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::PayLater => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                pay_later_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::BankTransfer => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                bank_transfer_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::Crypto => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                crypto_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::BankDebit => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                bank_debit_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::Reward => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                reward_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::RealTimePayment => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                real_time_payment_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::OpenBanking => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                open_banking_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::Upi => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                upi_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::Voucher => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                voucher_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::GiftCard => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                gift_card_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::CardRedirect => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                card_redirect_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                    api_models::enums::PaymentMethod::MobilePayment => {
                        if let Some(payment_method_types) = methods.payment_method_types {
                            for method_type in payment_method_types {
                                mobile_payment_details.push(Provider {
                                    payment_method_type: method_type.payment_method_type,
                                    accepted_currencies: method_type.accepted_currencies.clone(),
                                    accepted_countries: method_type.accepted_countries.clone(),
                                    payment_experience: method_type.payment_experience,
                                })
                            }
                        }
                    }
                }
            }
        }

        let open_banking = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::OpenBanking,
            payment_method_type: api_models::enums::PaymentMethod::OpenBanking.to_string(),
            provider: Some(open_banking_details),
            card_provider: None,
        };

        let upi = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::Upi,
            payment_method_type: api_models::enums::PaymentMethod::Upi.to_string(),
            provider: Some(upi_details),
            card_provider: None,
        };

        let voucher: DashboardPaymentMethodPayload = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::Voucher,
            payment_method_type: api_models::enums::PaymentMethod::Voucher.to_string(),
            provider: Some(voucher_details),
            card_provider: None,
        };

        let gift_card: DashboardPaymentMethodPayload = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::GiftCard,
            payment_method_type: api_models::enums::PaymentMethod::GiftCard.to_string(),
            provider: Some(gift_card_details),
            card_provider: None,
        };

        let reward = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::Reward,
            payment_method_type: api_models::enums::PaymentMethod::Reward.to_string(),
            provider: Some(reward_details),
            card_provider: None,
        };

        let real_time_payment = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::RealTimePayment,
            payment_method_type: api_models::enums::PaymentMethod::RealTimePayment.to_string(),
            provider: Some(real_time_payment_details),
            card_provider: None,
        };

        let wallet = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::Wallet,
            payment_method_type: api_models::enums::PaymentMethod::Wallet.to_string(),
            provider: Some(wallet_details),
            card_provider: None,
        };
        let bank_redirect = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::BankRedirect,
            payment_method_type: api_models::enums::PaymentMethod::BankRedirect.to_string(),
            provider: Some(bank_redirect_details),
            card_provider: None,
        };

        let bank_debit = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::BankDebit,
            payment_method_type: api_models::enums::PaymentMethod::BankDebit.to_string(),
            provider: Some(bank_debit_details),
            card_provider: None,
        };

        let bank_transfer = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::BankTransfer,
            payment_method_type: api_models::enums::PaymentMethod::BankTransfer.to_string(),
            provider: Some(bank_transfer_details),
            card_provider: None,
        };

        let crypto = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::Crypto,
            payment_method_type: api_models::enums::PaymentMethod::Crypto.to_string(),
            provider: Some(crypto_details),
            card_provider: None,
        };

        let card_redirect = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::CardRedirect,
            payment_method_type: api_models::enums::PaymentMethod::CardRedirect.to_string(),
            provider: Some(card_redirect_details),
            card_provider: None,
        };
        let pay_later = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::PayLater,
            payment_method_type: api_models::enums::PaymentMethod::PayLater.to_string(),
            provider: Some(pay_later_details),
            card_provider: None,
        };
        let debit_details = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::Card,
            payment_method_type: api_models::enums::PaymentMethodType::Debit.to_string(),
            provider: None,
            card_provider: Some(debit_details),
        };
        let credit_details = DashboardPaymentMethodPayload {
            payment_method: api_models::enums::PaymentMethod::Card,
            payment_method_type: api_models::enums::PaymentMethodType::Credit.to_string(),
            provider: None,
            card_provider: Some(credit_details),
        };

        DashboardRequestPayload {
            connector: response.connector_name,
            payment_methods_enabled: Some(vec![
                open_banking,
                upi,
                voucher,
                reward,
                real_time_payment,
                wallet,
                bank_redirect,
                bank_debit,
                bank_transfer,
                crypto,
                card_redirect,
                pay_later,
                debit_details,
                credit_details,
                gift_card,
            ]),
            metadata: response.metadata,
        }
    }
}


// File: crates/connector_configs/src/common_config.rs

use api_models::{payment_methods, payments};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

#[serde_with::skip_serializing_none]
#[derive(Debug, Deserialize, serde::Serialize, Clone)]
#[serde(rename_all = "snake_case")]
pub struct ZenApplePay {
    pub terminal_uuid: Option<String>,
    pub pay_wall_secret: Option<String>,
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Deserialize, serde::Serialize, Clone)]
#[serde(untagged)]
pub enum ApplePayData {
    ApplePay(payments::ApplePayMetadata),
    ApplePayCombined(payments::ApplePayCombinedMetadata),
    Zen(ZenApplePay),
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct GpayDashboardPayLoad {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub gateway_merchant_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none", rename = "stripe:version")]
    pub stripe_version: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(rename(
        serialize = "stripe_publishable_key",
        deserialize = "stripe:publishable_key"
    ))]
    #[serde(alias = "stripe:publishable_key")]
    #[serde(alias = "stripe_publishable_key")]
    pub stripe_publishable_key: Option<String>,
    pub merchant_name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub merchant_id: Option<String>,
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Deserialize, serde::Serialize, Clone)]
#[serde(rename_all = "snake_case")]
pub struct ZenGooglePay {
    pub terminal_uuid: Option<String>,
    pub pay_wall_secret: Option<String>,
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Deserialize, serde::Serialize, Clone)]
#[serde(untagged)]
pub enum GooglePayData {
    Standard(GpayDashboardPayLoad),
    Zen(ZenGooglePay),
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PaypalSdkData {
    pub client_id: Option<String>,
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Deserialize, serde::Serialize, Clone)]
#[serde(untagged)]
pub enum GoogleApiModelData {
    Standard(payments::GpayMetaData),
    Zen(ZenGooglePay),
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct PaymentMethodsEnabled {
    pub payment_method: api_models::enums::PaymentMethod,
    pub payment_method_types: Option<Vec<payment_methods::RequestPaymentMethodTypes>>,
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct ApiModelMetaData {
    pub merchant_config_currency: Option<api_models::enums::Currency>,
    pub merchant_account_id: Option<String>,
    pub account_name: Option<String>,
    pub terminal_id: Option<String>,
    pub merchant_id: Option<String>,
    pub google_pay: Option<GoogleApiModelData>,
    pub paypal_sdk: Option<PaypalSdkData>,
    pub apple_pay: Option<ApplePayData>,
    pub apple_pay_combined: Option<ApplePayData>,
    pub endpoint_prefix: Option<String>,
    pub mcc: Option<String>,
    pub merchant_country_code: Option<String>,
    pub merchant_name: Option<String>,
    pub acquirer_bin: Option<String>,
    pub acquirer_merchant_id: Option<String>,
    pub acquirer_country_code: Option<String>,
    pub three_ds_requestor_name: Option<String>,
    pub three_ds_requestor_id: Option<String>,
    pub pull_mechanism_for_external_3ds_enabled: Option<bool>,
    pub klarna_region: Option<KlarnaEndpoint>,
    pub source_balance_account: Option<String>,
    pub brand_id: Option<String>,
    pub destination_account_number: Option<String>,
    pub dpa_id: Option<String>,
    pub dpa_name: Option<String>,
    pub locale: Option<String>,
    pub card_brands: Option<Vec<String>>,
    pub merchant_category_code: Option<String>,
    pub merchant_configuration_id: Option<String>,
    pub tenant_id: Option<String>,
    pub platform_url: Option<String>,
    pub account_id: Option<serde_json::Value>,
    pub site: Option<String>,
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub enum KlarnaEndpoint {
    Europe,
    NorthAmerica,
    Oceania,
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, ToSchema, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct CardProvider {
    pub payment_method_type: api_models::enums::CardNetwork,
    /// List of currencies accepted or has the processing capabilities of the processor
    #[schema(example = json!(
            {
                "type": "specific_accepted",
                "list": ["USD", "INR"]
            }
        ), value_type = Option<AcceptedCurrencies>)]
    pub accepted_currencies: Option<api_models::admin::AcceptedCurrencies>,
    #[schema(example = json!(
        {
            "type": "specific_accepted",
            "list": ["UK", "AU"]
        }
    ), value_type = Option<AcceptedCountries>)]
    pub accepted_countries: Option<api_models::admin::AcceptedCountries>,
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, ToSchema, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct Provider {
    pub payment_method_type: api_models::enums::PaymentMethodType,
    /// List of currencies accepted or has the processing capabilities of the processor
    #[schema(example = json!(
            {
                "type": "specific_accepted",
                "list": ["USD", "INR"]
            }
        ), value_type = Option<AcceptedCurrencies>)]
    pub accepted_currencies: Option<api_models::admin::AcceptedCurrencies>,
    #[schema(example = json!(
        {
            "type": "specific_accepted",
            "list": ["UK", "AU"]
        }
    ), value_type = Option<AcceptedCountries>)]
    pub accepted_countries: Option<api_models::admin::AcceptedCountries>,
    pub payment_experience: Option<api_models::enums::PaymentExperience>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct ConnectorApiIntegrationPayload {
    pub connector_type: String,
    pub profile_id: common_utils::id_type::ProfileId,
    pub connector_name: api_models::enums::Connector,
    #[serde(skip_deserializing)]
    #[schema(example = "stripe_US_travel")]
    pub connector_label: Option<String>,
    pub merchant_connector_id: Option<String>,
    pub disabled: bool,
    pub test_mode: bool,
    pub payment_methods_enabled: Option<Vec<PaymentMethodsEnabled>>,
    pub metadata: Option<ApiModelMetaData>,
    pub connector_webhook_details: Option<api_models::admin::MerchantConnectorWebhookDetails>,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct DashboardPaymentMethodPayload {
    pub payment_method: api_models::enums::PaymentMethod,
    pub payment_method_type: String,
    pub provider: Option<Vec<Provider>>,
    pub card_provider: Option<Vec<CardProvider>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde_with::skip_serializing_none]
#[serde(rename_all = "snake_case")]
pub struct DashboardRequestPayload {
    pub connector: api_models::enums::Connector,
    pub payment_methods_enabled: Option<Vec<DashboardPaymentMethodPayload>>,
    pub metadata: Option<ApiModelMetaData>,
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Deserialize, serde::Serialize, Clone)]
#[serde(tag = "type", content = "options")]
pub enum InputType {
    Text,
    Number,
    Toggle,
    Radio(Vec<String>),
    Select(Vec<String>),
    MultiSelect(Vec<String>),
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Deserialize, serde::Serialize, Clone)]
#[serde(rename_all = "snake_case")]
pub struct InputData {
    pub name: String,
    pub label: String,
    pub placeholder: String,
    pub required: bool,
    #[serde(flatten)]
    pub input_type: InputType,
}


// File: crates/connector_configs/src/transformer.rs

use std::str::FromStr;

use api_models::{
    enums::{
        Connector, PaymentMethod,
        PaymentMethodType::{self, AliPay, ApplePay, GooglePay, Klarna, Paypal, WeChatPay},
    },
    payment_methods,
    refunds::MinorUnit,
};

use crate::common_config::{
    ConnectorApiIntegrationPayload, DashboardRequestPayload, PaymentMethodsEnabled, Provider,
};

impl DashboardRequestPayload {
    pub fn transform_card(
        payment_method_type: PaymentMethodType,
        card_provider: Vec<api_models::enums::CardNetwork>,
    ) -> payment_methods::RequestPaymentMethodTypes {
        payment_methods::RequestPaymentMethodTypes {
            payment_method_type,
            card_networks: Some(card_provider),
            minimum_amount: Some(MinorUnit::zero()),
            maximum_amount: Some(MinorUnit::new(68607706)),
            recurring_enabled: Some(true),
            installment_payment_enabled: Some(false),
            accepted_currencies: None,
            accepted_countries: None,
            payment_experience: None,
        }
    }

    pub fn get_payment_experience(
        connector: Connector,
        payment_method_type: PaymentMethodType,
        payment_method: PaymentMethod,
        payment_experience: Option<api_models::enums::PaymentExperience>,
    ) -> Option<api_models::enums::PaymentExperience> {
        match payment_method {
            PaymentMethod::BankRedirect => None,
            _ => match (connector, payment_method_type) {
                #[cfg(feature = "dummy_connector")]
                (Connector::DummyConnector4, _) | (Connector::DummyConnector7, _) => {
                    Some(api_models::enums::PaymentExperience::RedirectToUrl)
                }
                (Connector::Paypal, Paypal) => payment_experience,
                (Connector::Klarna, Klarna) => payment_experience,
                (Connector::Zen, GooglePay) | (Connector::Zen, ApplePay) => {
                    Some(api_models::enums::PaymentExperience::RedirectToUrl)
                }
                (Connector::Braintree, Paypal) => {
                    Some(api_models::enums::PaymentExperience::InvokeSdkClient)
                }
                (Connector::Globepay, AliPay)
                | (Connector::Globepay, WeChatPay)
                | (Connector::Stripe, WeChatPay) => {
                    Some(api_models::enums::PaymentExperience::DisplayQrCode)
                }
                (_, GooglePay)
                | (_, ApplePay)
                | (_, PaymentMethodType::SamsungPay)
                | (_, PaymentMethodType::Paze)
                | (_, PaymentMethodType::AmazonPay) => {
                    Some(api_models::enums::PaymentExperience::InvokeSdkClient)
                }
                (_, PaymentMethodType::DirectCarrierBilling) => {
                    Some(api_models::enums::PaymentExperience::CollectOtp)
                }
                (_, PaymentMethodType::Cashapp) | (_, PaymentMethodType::Swish) => {
                    Some(api_models::enums::PaymentExperience::DisplayQrCode)
                }
                _ => Some(api_models::enums::PaymentExperience::RedirectToUrl),
            },
        }
    }

    pub fn transform_payment_method(
        connector: Connector,
        provider: Vec<Provider>,
        payment_method: PaymentMethod,
    ) -> Vec<payment_methods::RequestPaymentMethodTypes> {
        let mut payment_method_types = Vec::new();
        for method_type in provider {
            let data = payment_methods::RequestPaymentMethodTypes {
                payment_method_type: method_type.payment_method_type,
                card_networks: None,
                minimum_amount: Some(MinorUnit::zero()),
                maximum_amount: Some(MinorUnit::new(68607706)),
                recurring_enabled: Some(true),
                installment_payment_enabled: Some(false),
                accepted_currencies: method_type.accepted_currencies,
                accepted_countries: method_type.accepted_countries,
                payment_experience: Self::get_payment_experience(
                    connector,
                    method_type.payment_method_type,
                    payment_method,
                    method_type.payment_experience,
                ),
            };
            payment_method_types.push(data)
        }
        payment_method_types
    }

    pub fn create_connector_request(
        request: Self,
        api_response: ConnectorApiIntegrationPayload,
    ) -> ConnectorApiIntegrationPayload {
        let mut card_payment_method_types = Vec::new();
        let mut payment_method_enabled = Vec::new();

        if let Some(payment_methods_enabled) = request.payment_methods_enabled.clone() {
            for payload in payment_methods_enabled {
                match payload.payment_method {
                    PaymentMethod::Card => {
                        if let Some(card_provider) = payload.card_provider {
                            let payment_type =
                                PaymentMethodType::from_str(&payload.payment_method_type)
                                    .map_err(|_| "Invalid key received".to_string());

                            if let Ok(payment_type) = payment_type {
                                for method in card_provider {
                                    let data = payment_methods::RequestPaymentMethodTypes {
                                        payment_method_type: payment_type,
                                        card_networks: Some(vec![method.payment_method_type]),
                                        minimum_amount: Some(MinorUnit::zero()),
                                        maximum_amount: Some(MinorUnit::new(68607706)),
                                        recurring_enabled: Some(true),
                                        installment_payment_enabled: Some(false),
                                        accepted_currencies: method.accepted_currencies,
                                        accepted_countries: method.accepted_countries,
                                        payment_experience: None,
                                    };
                                    card_payment_method_types.push(data)
                                }
                            }
                        }
                    }

                    PaymentMethod::BankRedirect
                    | PaymentMethod::Wallet
                    | PaymentMethod::PayLater
                    | PaymentMethod::BankTransfer
                    | PaymentMethod::Crypto
                    | PaymentMethod::BankDebit
                    | PaymentMethod::Reward
                    | PaymentMethod::RealTimePayment
                    | PaymentMethod::Upi
                    | PaymentMethod::Voucher
                    | PaymentMethod::GiftCard
                    | PaymentMethod::OpenBanking
                    | PaymentMethod::CardRedirect
                    | PaymentMethod::MobilePayment => {
                        if let Some(provider) = payload.provider {
                            let val = Self::transform_payment_method(
                                request.connector,
                                provider,
                                payload.payment_method,
                            );
                            if !val.is_empty() {
                                let methods = PaymentMethodsEnabled {
                                    payment_method: payload.payment_method,
                                    payment_method_types: Some(val),
                                };
                                payment_method_enabled.push(methods);
                            }
                        }
                    }
                };
            }
            if !card_payment_method_types.is_empty() {
                let card = PaymentMethodsEnabled {
                    payment_method: PaymentMethod::Card,
                    payment_method_types: Some(card_payment_method_types),
                };
                payment_method_enabled.push(card);
            }
        }

        ConnectorApiIntegrationPayload {
            connector_type: api_response.connector_type,
            profile_id: api_response.profile_id,
            connector_name: api_response.connector_name,
            connector_label: api_response.connector_label,
            merchant_connector_id: api_response.merchant_connector_id,
            disabled: api_response.disabled,
            test_mode: api_response.test_mode,
            payment_methods_enabled: Some(payment_method_enabled),
            connector_webhook_details: api_response.connector_webhook_details,
            metadata: request.metadata,
        }
    }
}