package graph

import (
	
)

// Union combines two given graphs into a new graph. The vertex hashes in both
// graphs are expected to be unique. The two input graphs will remain unchanged.
//
// Both graphs should be either directed or undirected. All traits for the new
// graph will be derived from g.
func [ comparable,  any](,  Graph[, ]) (Graph[, ], error) {
	,  := .Clone()
	if  != nil {
		return , fmt.Errorf("failed to clone g: %w", )
	}

	,  := .AdjacencyMap()
	if  != nil {
		return , fmt.Errorf("failed to get adjacency map: %w", )
	}

	 := make(map[]map[]struct{})

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

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

	for ,  := range  {
		for ,  := range  {
			if ,  := [.Source];  {
				if ,  := [.Source][.Target];  {
					// If the edge addedEdges[source][target] exists, the edge
					// has already been created and thus can be skipped here.
					continue
				}
			}

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

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

	return , nil
}

// unionFind implements a union-find or disjoint set data structure that works
// with vertex hashes as vertices. It's an internal helper type at the moment,
// but could perhaps be exposed publicly in the future.
//
// unionFind is not related to the Union function.
type unionFind[ comparable] struct {
	parents map[]
}

func newUnionFind[ comparable]( ...) *unionFind[] {
	 := &unionFind[]{
		parents: make(map[], len()),
	}

	for ,  := range  {
		.parents[] = 
	}

	return 
}

func ( *unionFind[]) ( ) {
	.parents[] = 
}

func ( *unionFind[]) (,  ) {
	 := .find()
	 := .find()

	if  ==  {
		return
	}

	.parents[] = 
}

func ( *unionFind[]) ( )  {
	 := 

	for .parents[] !=  {
		 = .parents[]
	}

	// Perform a path compression in order to optimize of future find calls.
	 := 

	for .parents[] !=  {
		 := .parents[]
		.parents[] = 
		 = 
	}

	return 
}

func copyVertexProperties( VertexProperties) func(*VertexProperties) {
	return func( *VertexProperties) {
		for ,  := range .Attributes {
			.Attributes[] = 
		}
		.Weight = .Weight
	}
}