package graph

import (
	
	
)

type directed[ comparable,  any] struct {
	hash   Hash[, ]
	traits *Traits
	store  Store[, ]
}

func newDirected[ comparable,  any]( Hash[, ],  *Traits,  Store[, ]) *directed[, ] {
	return &directed[, ]{
		hash:   ,
		traits: ,
		store:  ,
	}
}

func ( *directed[, ]) () *Traits {
	return .traits
}

func ( *directed[, ]) ( ,  ...func(*VertexProperties)) error {
	 := .hash()
	 := VertexProperties{
		Weight:     0,
		Attributes: make(map[string]string),
	}

	for ,  := range  {
		(&)
	}

	return .store.AddVertex(, , )
}

func ( *directed[, ]) ( Graph[, ]) error {
	,  := .AdjacencyMap()
	if  != nil {
		return fmt.Errorf("failed to get adjacency map: %w", )
	}

	for  := range  {
		, ,  := .VertexWithProperties()
		if  != nil {
			return fmt.Errorf("failed to get vertex %v: %w", , )
		}

		if  = .AddVertex(, copyVertexProperties());  != nil {
			return fmt.Errorf("failed to add vertex %v: %w", , )
		}
	}

	return nil
}

func ( *directed[, ]) ( ) (, error) {
	, ,  := .store.Vertex()
	return , 
}

func ( *directed[, ]) ( ) (, VertexProperties, error) {
	, ,  := .store.Vertex()
	if  != nil {
		return , VertexProperties{}, 
	}

	return , , nil
}

func ( *directed[, ]) ( ) error {
	return .store.RemoveVertex()
}

func ( *directed[, ]) (,  ,  ...func(*EdgeProperties)) error {
	, ,  := .store.Vertex()
	if  != nil {
		return fmt.Errorf("source vertex %v: %w", , )
	}

	_, _,  = .store.Vertex()
	if  != nil {
		return fmt.Errorf("target vertex %v: %w", , )
	}

	if ,  := .Edge(, ); !errors.Is(, ErrEdgeNotFound) {
		return ErrEdgeAlreadyExists
	}

	// If the user opted in to preventing cycles, run a cycle check.
	if .traits.PreventCycles {
		,  := .createsCycle(, )
		if  != nil {
			return fmt.Errorf("check for cycles: %w", )
		}
		if  {
			return ErrEdgeCreatesCycle
		}
	}

	 := Edge[]{
		Source: ,
		Target: ,
		Properties: EdgeProperties{
			Attributes: make(map[string]string),
		},
	}

	for ,  := range  {
		(&.Properties)
	}

	return .addEdge(, , )
}

func ( *directed[, ]) ( Graph[, ]) error {
	,  := .Edges()
	if  != nil {
		return fmt.Errorf("failed to get edges: %w", )
	}

	for ,  := range  {
		if  := .AddEdge(copyEdge());  != nil {
			return fmt.Errorf("failed to add (%v, %v): %w", .Source, .Target, )
		}
	}

	return nil
}

func ( *directed[, ]) (,  ) (Edge[], error) {
	,  := .store.Edge(, )
	if  != nil {
		return Edge[]{}, 
	}

	, ,  := .store.Vertex()
	if  != nil {
		return Edge[]{}, 
	}

	, ,  := .store.Vertex()
	if  != nil {
		return Edge[]{}, 
	}

	return Edge[]{
		Source: ,
		Target: ,
		Properties: EdgeProperties{
			Weight:     .Properties.Weight,
			Attributes: .Properties.Attributes,
			Data:       .Properties.Data,
		},
	}, nil
}

func ( *directed[, ]) () ([]Edge[], error) {
	return .store.ListEdges()
}

func ( *directed[, ]) (,  ,  ...func( *EdgeProperties)) error {
	,  := .store.Edge(, )
	if  != nil {
		return 
	}

	for ,  := range  {
		(&.Properties)
	}

	return .store.UpdateEdge(, , )
}

func ( *directed[, ]) (,  ) error {
	if ,  := .Edge(, );  != nil {
		return 
	}

	if  := .store.RemoveEdge(, );  != nil {
		return fmt.Errorf("failed to remove edge from %v to %v: %w", , , )
	}

	return nil
}

func ( *directed[, ]) () (map[]map[]Edge[], error) {
	,  := .store.ListVertices()
	if  != nil {
		return nil, fmt.Errorf("failed to list vertices: %w", )
	}

	,  := .store.ListEdges()
	if  != nil {
		return nil, fmt.Errorf("failed to list edges: %w", )
	}

	 := make(map[]map[]Edge[], len())

	for ,  := range  {
		[] = make(map[]Edge[])
	}

	for ,  := range  {
		[.Source][.Target] = 
	}

	return , nil
}

func ( *directed[, ]) () (map[]map[]Edge[], error) {
	,  := .store.ListVertices()
	if  != nil {
		return nil, fmt.Errorf("failed to list vertices: %w", )
	}

	,  := .store.ListEdges()
	if  != nil {
		return nil, fmt.Errorf("failed to list edges: %w", )
	}

	 := make(map[]map[]Edge[], len())

	for ,  := range  {
		[] = make(map[]Edge[])
	}

	for ,  := range  {
		if ,  := [.Target]; ! {
			[.Target] = make(map[]Edge[])
		}
		[.Target][.Source] = 
	}

	return , nil
}

func ( *directed[, ]) (,  ,  Edge[]) error {
	return .store.AddEdge(, , )
}

func ( *directed[, ]) () (Graph[, ], error) {
	 := &Traits{
		IsDirected:    .traits.IsDirected,
		IsAcyclic:     .traits.IsAcyclic,
		IsWeighted:    .traits.IsWeighted,
		IsRooted:      .traits.IsRooted,
		PreventCycles: .traits.PreventCycles,
	}

	 := &directed[, ]{
		hash:   .hash,
		traits: ,
		store:  newMemoryStore[, ](),
	}

	if  := .AddVerticesFrom();  != nil {
		return nil, fmt.Errorf("failed to add vertices: %w", )
	}

	if  := .AddEdgesFrom();  != nil {
		return nil, fmt.Errorf("failed to add edges: %w", )
	}

	return , nil
}

func ( *directed[, ]) () (int, error) {
	return .store.VertexCount()
}

func ( *directed[, ]) () (int, error) {
	 := 0
	,  := .AdjacencyMap()
	if  != nil {
		return 0, fmt.Errorf("failed to get adjacency map: %w", )
	}

	for ,  := range  {
		 += len()
	}

	return , nil
}

func ( *directed[, ]) (,  Edge[]) bool {
	 := .hash(.Source)
	 := .hash(.Target)
	 := .hash(.Source)
	 := .hash(.Target)

	return  ==  &&  == 
}

func ( *directed[, ]) (,  ) (bool, error) {
	// If the underlying store implements CreatesCycle, use that fast path.
	if ,  := .store.(interface {
		(,  ) (bool, error)
	});  {
		return .(, )
	}

	// Slow path.
	return CreatesCycle(Graph[, ](), , )
}

// copyEdge returns an argument list suitable for the Graph.AddEdge method. This
// argument list is derived from the given edge, hence the name copyEdge.
//
// The last argument is a custom functional option that sets the edge properties
// to the properties of the original edge.
func copyEdge[ comparable]( Edge[]) (, , func( *EdgeProperties)) {
	 := func( *EdgeProperties) {
		for ,  := range .Properties.Attributes {
			.Attributes[] = 
		}
		.Weight = .Properties.Weight
		.Data = .Properties.Data
	}

	return .Source, .Target, 
}