package visualizer

import (
	
	
	
	
	
	
	
	

	
	
	
	
	
	
	
	d2log 
	
	

	amgraph 

	am 
	ssam 
)

const d2Header = `
	vars: {
		d2-config: {
			theme-id: 201
			theme-overrides: {
				N7: black

				# mach border
				B1: "#5F5C5C"
				B2: grey
				B3: "#6C7086"
				# mach background
				B4: "#262424"
				B5: "#45475A"
				B6: "#313244"

				AA2: "#f38BA8"
				AA4: "#45475A"
				AA5: "#313244"

				AB4: "#45475A"
				AB5: "#313244"
			}
		}
	}
	classes: {
		# active
		_1: {
			style: {
				font-color: black
				fill: yellow
				border-radius: 999
				double-border: true
			}
		}
		# inactive
		_0: {
			style: {
				stroke: white
				border-radius: 999
				double-border: true
			}
		}

		# active inherited
		_1i: {
			style: {
				font-color: black
				fill: "yellow"
				border-radius: 999
			}
		}
		# inactive inherited
		_0i: {
			style: {
				border-radius: 999
			}
		}

		# active Start
		_1s: {
			style: {
				font-color: black
				fill: "#329241"
				border-radius: 999
			}
		}
		# inactive Start
		_0s: {
			style: {
				border-radius: 999
			}
		}

		# active Ready
		_1r: {
			style: {
				font-color: black
				fill: deepskyblue
				border-radius: 999
			}
		}
		# inactive Ready
		_0r: {
			style: {
				border-radius: 999
			}
		}
	}
	direction: right

	`

func ( *Renderer) ( context.Context) error {
	.log("Generating D2 %s", .OutputFilename)

	.cleanBuffer()

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

	// header
	.buf.WriteString(dedent.Dedent(d2Header))

	// 1st pass - requested machs and neighbours
	for  := range .adjMap {
		if .Err() != nil {
			return nil
		}

		,  := .graph.G.Vertex()
		if  != nil {
			return fmt.Errorf("failed to get vertex for source %s: %w", , )
		}

		// render machines
		if .StateName == "" {
			 = .outputD2Mach(, .MachId)
			if  != nil {
				return fmt.Errorf("failed to render D2 mach %s: %w", , )
			}
		}
	}

	// collect what was rendered
	 := slices.Collect(maps.Keys(.renderedMachs))

	// 2nd pass - render adjecents as halfs
	 := .adjsMachsToRender
	for ,  := range  {
		// only not already rendered
		if ,  := .renderedMachs[];  {
			continue
		}

		 = .outputD2HalfMach(, )
		if  != nil {
			return fmt.Errorf("failed to render D2 half mach %s: %w", , )
		}
	}

	// 3rd pass - render predecesors as halfs
	 := len(.RenderMachs) > 0 || len(.RenderMachsRe) > 0
	 := .RenderPipes && .RenderHalfPipes ||
		.RenderConns && .RenderHalfConns ||
		.RenderParentRel && .RenderHalfHierarchy

	if  &&  {
		,  := .graph.G.PredecessorMap()
		if  != nil {
			return fmt.Errorf("failed to get predecessor map: %w", )
		}
		for ,  := range  {
			if .Err() != nil {
				return nil
			}

			for  := range [] {
				,  := .graph.G.Vertex()
				if  != nil {
					return 
				}
				// machs only
				if .StateName != "" {
					continue
				}
				// only if not already rendered
				if ,  := .renderedMachs[];  {
					continue
				}

				 = .outputD2HalfMach(, )
				if  != nil {
					return fmt.Errorf("failed to render D2 half mach %s: %w", , )
				}
			}
		}
	}

	// build str diag
	 := .buf.String()

	// generate D2
	.log("Generating %s.d2\n", .OutputFilename)
	 = os.WriteFile(.OutputFilename+".d2", []byte(), 0o644)
	if  != nil {
		return fmt.Errorf("failed to write D2 file: %w", )
	}

	// generate D2 graph
	// Initialize the slog logger
	 := slog.HandlerOptions{
		AddSource: true,           // Include source file and line number
		Level:     slog.LevelInfo, // Set default log level
	}

	 := slog.New(slog.NewJSONHandler(os.Stdout, &))
	 = d2log.With(, )
	,  := textmeasure.NewRuler()
	 := func( string) (d2graph.LayoutGraph, error) {
		if .OutputElk {
			return d2elklayout.DefaultLayout, nil
		}
		return d2dagrelayout.DefaultLayout, nil
	}
	 := &d2svg.RenderOpts{
		Pad:     go2.Pointer(int64(5)),
		ThemeID: &d2themescatalog.DarkMauve.ID,
	}
	 := &d2lib.CompileOptions{
		LayoutResolver: ,
		Ruler:          ,
	}
	.log("Generating %s.svg\n", .OutputFilename)
	, ,  := d2lib.Compile(, ,
		, )
	if  != nil {
		return fmt.Errorf("failed to compile D2: %w", )
	}
	.log("Edges: %d Objects: %d\n", len(.Edges),
		len(.Objects))

	// render SVG
	if .OutputD2Svg {
		,  := d2svg.Render(, )
		if  != nil {
			return fmt.Errorf("failed to render D2: %w", )
		}
		 = os.WriteFile(filepath.Join(.OutputFilename+".svg"), , 0o600)
		if  != nil {
			return fmt.Errorf("failed to write D2 file: %w", )
		}
	}

	return nil
}

func ( *Renderer) ( context.Context,  string) error {
	// blacklist
	if slices.Contains(.RenderSkipMachs, ) {
		return nil
	}

	// whitelist & neighbours
	if !.shouldRenderMach() {
		return nil
	}
	// render once
	if ,  := .renderedMachs[];  {
		return nil
	}
	.renderedMachs[] = struct{}{}

	// TAGS
	 := .graph.Clients[]
	 := "\n"
	if .RenderTags && len(.MsgSchema.Tags) > 0 {
		 := "#" + strings.Join(.MsgSchema.Tags, "\n#")
		 = "\texplanation: |text\n\t\tTags\n" +  +
			"\n\t| { style.stroke: transparent }\n\n"
	}

	// PARENT NESTING
	 := .shortId()
	if .RenderNestSubmachines {
		// TODO fix non-rendered machines in the nested ID (remove? when?)
		 = strings.Join(.fullIdPath(, true), ".")
	}
	 := ""
	if slices.Contains(.renderMachIds(), ) {
		 = "\tstyle.stroke: yellow\n"
	} else if len(.renderMachIds()) > 0 {
		 = "\tstyle.stroke: white\n"
	}
	.buf.WriteString( + ": " +  + " {\n" +
		"\tlabel.near: top-center\n" +
		"\tstyle.font-size: 40\n" +
		 + )

	 := ""
	 := ""
	 := ""
	 := map[string]struct{}{}
	// TODO extract & split
	for ,  := range .adjMap[] {
		if .Err() != nil {
			return nil
		}

		,  := .graph.G.Vertex(.Target)
		if  != nil {
			return fmt.Errorf("failed to get vertex for target %s: %w",
				.Target, )
		}
		 := .Properties.Data.(*amgraph.EdgeData)
		 := .StateName

		// STATES TODO extract
		if  != "" && (.shouldRenderState(, ) ||
			.RenderPipeStates && .stateHasRenderedPipes(, )) {

			 := .shortId()
			 := "_0"
			if .RenderActive {
				 := slices.Index(.MsgSchema.StatesIndex, )
				if .LatestClock.Is1() {
					 = "_1"
				}
			}

			// INHERITED
			 := .isStateInherited(, .MsgSchema.StatesIndex)
			 := ""
			if .RenderMarkInherited &&  {
				 += "i"
			}

			// START & READY
			if .RenderReady &&  == ssam.BasicStates.Ready {
				 = "r"
			} else if .RenderStart &&  == ssam.BasicStates.Start {
				 = "s"
			}

			.buf.WriteString("\t" +  + ":" +  + "\n")
			.buf.WriteString("\t" +  + ".class: " +  +
				 + "\n")

			.renderD2Relations(, , )
		}

		 += .renderD2Parent(, , , .RenderHalfHierarchy,
			false)
		 += .renderD2Pipes(, , , .RenderHalfPipes, false)
		 += .renderD2Conns(, , , .RenderHalfConns, false)
	}

	.buf.WriteString("}\n")

	if  != "" {
		.buf.WriteString()
	}

	if  != "" {
		.buf.WriteString()
	}

	if  != "" {
		.buf.WriteString()
	}

	.buf.WriteString("\n\n")

	return nil
}

func ( *Renderer) (
	 context.Context,  string,
) error {
	.renderedMachs[] = struct{}{}
	 := .shortId()
	if .RenderNestSubmachines {
		 = strings.Join(.fullIdPath(, true), ".")
	}
	.buf.WriteString( + ": " +  + " {\n" +
		"\tlabel.near: top-center\n" +
		"\tstyle.font-size: 40\n")

	 := ""
	 := ""
	 := ""
	for ,  := range .adjMap[] {
		if .Err() != nil {
			return nil
		}

		,  := .graph.Connection(, .Target)
		if  != nil {
			return 
		}
		 := .Target
		 := .Edge

		 += .renderD2Parent(, , , false, true)
		 += .renderD2Pipes(, , , false, true)
		 += .renderD2Conns(, , , false, true)
	}

	.buf.WriteString("}\n")

	if  != "" {
		.buf.WriteString()
	}

	if  != "" {
		.buf.WriteString()
	}

	if  != "" {
		.buf.WriteString()
	}

	.buf.WriteString("\n\n")

	return nil
}

func ( *Renderer) (
	 string,  string,  map[string]struct{},
) {
	 := .shortId()
	 := .graph.Clients[]
	 := .MsgSchema.States[]

	if !.RenderRelations {
		return
	}

	// require
	for ,  := range .Require {
		if !.shouldRenderState(, ) {
			continue
		}

		// TODO class
		.buf.WriteString("\t" +  + " --> " +
			.shortId() + ": require {\n" +
			"\t\tstyle.stroke: white\n" +
			"\t\ttarget-arrowhead.style.filled: true\n" +
			"\t\ttarget-arrowhead.shape: circle\n" +
			"\t}\n")
	}

	// add
	for ,  := range .Add {
		if !.shouldRenderState(, ) {
			continue
		}

		// TODO class
		.buf.WriteString("\t" +  + " --> " +
			.shortId() + ": add {\n" +
			"\t\tstyle.stroke: yellow\n" +
			"\t\ttarget-arrowhead.shape: triangle\n" +
			"\t\ttarget-arrowhead.style.filled: true\n" +
			"\t}\n")
	}

	// remove
	for ,  := range .Remove {
		if !.shouldRenderState(, ) {
			continue
		}

		// no self removal
		if  ==  {
			continue
		}

		// merge double remove
		 := " --> "
		 := "remove"
		 := "2"
		if slices.Contains(.MsgSchema.States[].Remove, ) {
			,  := [+":"+]
			,  := [+":"+]
			if  ||  {
				continue
			}
			 = " <--> "
			 = "double remove"
			 = "4"

			// mark
			[+":"+] = struct{}{}
			[+":"+] = struct{}{}
		}

		// TODO class
		.buf.WriteString("\t" +  +  +
			.shortId() + ": " +  + " {\n\t\tstyle.stroke: red\n" +
			"\t\tstyle.stroke-width: " +  + "\n" +
			"\t\ttarget-arrowhead.style.filled: true\n" +
			"\t\ttarget-arrowhead.shape: diamond\n" +
			"\t\tsource-arrowhead.style.filled: true\n" +
			"\t\tsource-arrowhead.shape: diamond\n" +
			"\t}\n")
	}
}

func ( *Renderer) (
	 *amgraph.EdgeData,  string,  *amgraph.Vertex, ,
	 bool,
) string {
	 := ""
	 := .shortId()
	 := .shortId(.MachId)
	if .RenderNestSubmachines {
		 = strings.Join(.fullIdPath(.MachId, true), ".")
		 = strings.Join(.fullIdPath(, true), ".")
	}

	if .RenderNestSubmachines || !.RenderParentRel || !.MachChildOf {
		return 
	}

	// render halfs
	if !.shouldRenderMach(.MachId) && ! {
		return ""
	}

	// render once
	 :=  + ":" + 
	if ,  := .renderedParents[];  {
		return ""
	}
	.renderedParents[] = struct{}{}

	// render this half later
	.adjsMachsToRender = append(.adjsMachsToRender, .MachId)

	return  + " -> " +  +
		": parent {\n" +
		"\tstyle.stroke: white\n" +
		"\tstyle.stroke-width: 8\n" +
		"\ttarget-arrowhead.shape: circle\n}\n"
}

func ( *Renderer) (
	 *amgraph.EdgeData,  string,  *amgraph.Vertex, ,
	 bool,
) string {
	 := ""
	 := .shortId()
	 := .shortId(.MachId)
	if .RenderNestSubmachines {
		 = strings.Join(.fullIdPath(.MachId, true), ".")
		 = strings.Join(.fullIdPath(, true), ".")
	}

	if !.RenderConns || !.MachConnectedTo {
		return 
	}

	// render halfs
	if !.shouldRenderMach(.MachId) && ! {
		return ""
	}

	// render once
	 :=  + ":" + 
	if ,  := .renderedConns[];  {
		return ""
	}
	 +=  + " -> " +  + ": rpc {\n" +
		"\tstyle.stroke: green\n" +
		"\tstyle.stroke-width: 4\n" +
		"}\n"

	// render this half later
	.adjsMachsToRender = append(.adjsMachsToRender, .MachId)

	// remember
	.renderedConns[] = struct{}{}

	return 
}

func ( *Renderer) (
	 *amgraph.EdgeData,  string,  *amgraph.Vertex, ,
	 bool,
) string {
	if !.RenderPipes ||  == .MachId {
		return ""
	}

	 := ""
	 := .shortId()
	 := .shortId(.MachId)
	if .RenderNestSubmachines {
		 = strings.Join(.fullIdPath(.MachId, true), ".")
		 = strings.Join(.fullIdPath(, true), ".")
	}

	for ,  := range .MachPipesTo {
		// mach.state -> mach.state
		if .RenderDetailedPipes {
			 :=  + "." + .shortId(.FromState)
			 :=  + "." + .shortId(.ToState)

			// check the source state
			if !.shouldRenderState(, .FromState) &&
				(!.RenderPipeStates ||  ||
					!.shouldRenderMach(.MachId)) {

				if ! && !.shouldRenderMach() &&
					!.shouldRenderMach(.MachId) {
					continue
				}

				// point from the current machine (not state)
				 = 
			}

			// check the target state
			if !.shouldRenderState(.MachId, .ToState) {
				if .shouldRenderState(, .FromState) &&
					(!.RenderPipeStates || ) {

					// pass (full target render)
				} else if ! && !.shouldRenderMach(.MachId) &&
					!.shouldRenderMach(.MachId) {

					continue
				} else {
					// point to the target machine (not state)
					 = 

					// render this half later
					.adjsMachsToRender = append(.adjsMachsToRender, .MachId)
				}
			}

			// render once
			if ,  := .renderedPipes[+":"+];  {
				continue
			}

			// TODO class
			 := "\tstyle.stroke: yellow\n" +
				"\tstyle.stroke-dash: 3\n" +
				"\ttarget-arrowhead.style.filled: false\n"
			 := "add"
			if .MutType == am.MutationRemove {
				 = "\tstyle.stroke: red\n" +
					"\ttarget-arrowhead.shape: diamond\n" +
					"\tstyle.stroke-dash: 3\n"
				 = "remove"
			}
			 +=  + " -> " +  + ":" +  + " {\n" +
				 + "}\n"

			// remember
			.renderedPipes[+":"+] = struct{}{}

		} else {
			// mach -> mach
			 := 
			if !.shouldRenderMach(.MachId) {
				if ! && !.shouldRenderMach(.MachId) {
					continue
				}
			}

			// render once
			if ,  := .renderedPipes[+":"+];  {
				continue
			}

			// TODO class, number of pipes
			 +=  + " -> " +  + ": pipes { " +
				"style.stroke: white }\n"

			// remember
			.renderedPipes[+":"+] = struct{}{}
		}
	}

	return 
}