package visualizer

import (
	
	
	
	
	
	
	
	
	
	
	
	

	merascii 
	
	
	
	
	
	
	
	
	d2log 
	

	

	
	am 
	
	dbgtypes 
)

var ErrEmptyTx = errors.New("empty tx")

type Transition struct {
	Tx         *dbg.DbgMsgTx
	PrevTx     *dbg.DbgMsgTx
	TxParsed   *dbgtypes.MsgTxParsed
	StateTrace []*dbgtypes.StateTraceItem
	Log        *slog.Logger
}

var p = message.NewPrinter(language.English)

// D2 will generate a D2 sequence diagram and an svg of a given transition.
func ( *Transition) (
	 context.Context,  am.S,
) (string, []byte, error) {
	// TODO accept prev tx, show activity from prev tx

	if .Tx.Steps == nil {
		return "", nil, ErrEmptyTx
	}

	// TODO extract
	//  - add stack trace
	//  - bg color like mach class
	//  - mark handler ID on called handlers
	//  - mark ticked multi states

	// build fragments

	 := .info()
	,  := .steps()
	 := .funcName(.PrevTx, , )
	 := .funcName(.Tx, , )

	// generate

	, ,  := .d2Gen(, , , , )
	if  != nil {
		return "", nil, 
	}

	// dump.Println(parts)

	return , []byte(), 
}

func ( *Transition) ( am.S) string {
	 := ""
	 += "**[" + .Tx.Type.String() + "] " +
		utils.J(.Tx.CalledStateNames()) + "**\n\n"
	 += fmt.Sprintf("- [%s](%s)\n", .Tx.Url(), .Tx.Url())

	// result
	if .Tx.Accepted {
		 += "- Executed "
	} else {
		 += "- Canceled "
	}
	 += p.Sprintf(
		"(added: %d, removed: %d, touched: %d)\n",
		len(.TxParsed.StatesAdded), len(.TxParsed.StatesRemoved),
		len(.TxParsed.StatesTouched),
	)

	// auto
	if .Tx.IsAuto {
		 += "- Auto transition "
		if !.Tx.IsIdx(.Tx.CalledStatesIdxs) && .Tx.Accepted {
			 += " (partially accepted)"
		}
		 += "\n"
	}

	// mach time
	 += p.Sprintf("- Mach time +%v: t%v -> t%v\n",
		.TxParsed.TimeDiff,
		.TxParsed.TimeSum-.TxParsed.TimeDiff,
		.TxParsed.TimeSum)
	 += "- " + .Tx.Time.UTC().Format(time.RFC3339Nano) + "\n"

	// state trace
	if len(.StateTrace) > 0 {
		 += "- State Trace:\n"
		for ,  := range .StateTrace {
			 += p.Sprintf(
				"  - [%s](%s) t%v\n",
				strings.ReplaceAll(strings.ReplaceAll(.Label,
					"[::b]", ""), "[::-]", ""),
				.Source.StringBase(),
				.Source.MachTime,
			)
		}
	}
	 = strings.ReplaceAll(strings.ReplaceAll(,
		"\n", "\n\t"),
		"|", "")
	return 
}

func ( *Transition) ( am.S) (bool, string) {
	 := false
	 := ""
	for ,  := range .Tx.Steps {
		 := strings.ReplaceAll(.StringFromIndex(), "**", "")
		 := strings.Split(, " ")
		 := ""

		switch len() {

		// "activate Requesting"
		case 2:
			 = [0]
			if  == "called" {
				continue
			}
			 := [1]
			// handle "handler FooState" -> Foo
			if  == "handler" {
				// TODO func suffix? "FooState(e)"
				 = helpers.HandlerToState()
				 +=  + " -> " +  + ": " + [1]
			} else {
				 +=  + " -> " +  + ": " + [0]
			}
			if  == am.StateAny {
				 = true
			}

		// "Foo require Bar"
		case 3:
			 += [0] + " -> " + [2] + ": " + [1]
			if [0] == am.StateAny || [2] == am.StateAny {
				 = true
			}
			 = [1]

		default:
			// TODO err
			continue
		}

		// line style
		 := ""
		switch  {
		case "handler":
			 = "handler"
		case "requested":
			fallthrough
		case "require":
			 = "req"
		case "activate":
			fallthrough
		case "add":
			 = "add"
		case "deactivate":
			fallthrough
		case "deactivate-passive":
			fallthrough
		case "remove":
			 = "rem"
		case "cancel":
			 = "cancel"
		}
		if  != "" {
			 += " {\n\tclass: " +  + "\n}\n"
		}

		 += "\n"
	}
	 = strings.ReplaceAll(, "*", "")
	return , 
}

func ( *Transition) (
	 *dbg.DbgMsgTx,  am.S,  bool,
) string {
	 := ""
	for ,  := range slices.Concat(, am.S{am.StateAny}) {
		if !slices.Contains(.TxParsed.StatesTouched, ) &&
			!( &&  == am.StateAny) {

			continue
		}
		 := "; state; lifeline"
		if slices.Contains(.Tx.CalledStatesIdxs, ) {
			 += "; called"
		}
		switch {
		case  == am.StateStart:
			if  != nil && .Is1(, ) {
				 +=  + ".class: [_1s" +  + "]\n"
			} else {
				 +=  + ".class: [_0s" +  + "]\n"
			}
		case  == am.StateReady:
			if  != nil && .Is1(, ) {
				 +=  + ".class: [_1r" +  + "]\n"
			} else {
				 +=  + ".class: [_0r" +  + "]\n"
			}
		case  != nil && .Is1(, ):
			if IsStateInherited(, ) {
				 +=  + ".class: [_1i" +  + "]\n"
			} else {
				 +=  + ".class: [_1" +  + "]\n"
			}
		default:
			if IsStateInherited(, ) {
				 +=  + ".class: [_0i" +  + "]\n"
			} else {
				 +=  + ".class: [_0" +  + "]\n"
			}
		}
	}
	return 
}

func ( *Transition) (
	 context.Context,  string,  string,  string,
	 string,
) (string, string, error) {
	// D2

	 := utils.Sp(
		`
		shape: sequence_diagram
		style.fill: black
		
		%s
		explanation: |md
			%s
		| {
			near: top-center
			style.font-size: 28
		}
		
		%s
		
		%s
		
		`, d2Header, , , ,
	)

	// svg (multi-part)
	 := strings.Split(strings.Trim(, "\n"), "\n\n")
	 := pond.NewPool(10)
	 := .NewGroupContext()
	// TODO config, extract
	 := 15
	 := make([]string, 2+int(math.Ceil(
		float64(len())/float64(),
	)))
	 := sync.Mutex{}

	// START
	.SubmitErr(func() error {
		 := utils.Sp(`
		shape: sequence_diagram
		style.fill: black
		
		%s
		explanation: |md
			%s
		| {
			near: top-center
			style.font-size: 28
		}
		
		%s
		
		`, d2Header, , )
		,  := .d2Svg(, , .Log)
		if  != nil {
			return 
		}
		.Lock()
		defer .Unlock()
		[0] = string()

		return nil
	})

	// STEPS
	for  := 0;  < len();  +=  {
		.SubmitErr(func() error {
			 := [:min(+, len())]
			 := strings.Join(, "\n\n")
			if  == "" {
				return nil
			}
			 := utils.Sp(`
				shape: sequence_diagram
				style.fill: black
				
				%s
				
				%s
				
				%s
				
				`, d2Header, , )
			,  := .d2Svg(, , .Log)
			if  != nil {
				return 
			}
			.Lock()
			defer .Unlock()
			[/+1] = string()

			return nil
		})
	}

	// END
	.SubmitErr(func() error {
		 := utils.Sp(`
		shape: sequence_diagram
		style.fill: black
		
		%s
		
		%s
		`, d2Header, )
		,  := .d2Svg(, , .Log)
		if  != nil {
			return 
		}
		.Lock()
		defer .Unlock()
		[len()-1] = string()

		return nil
	})

	 := .Wait()
	if  != nil {
		return "", "", 
	}

	,  := mergeSvgs(, )
	if  != nil {
		return "", "", 
	}

	return , , nil
}

func ( *Transition) (
	 context.Context,  string,  *slog.Logger,
) ([]byte, error) {
	// set up
	,  := textmeasure.NewRuler()
	 = d2log.With(, )
	, ,  := d2lib.Compile(, , &d2lib.CompileOptions{
		Ruler: ,
		LayoutResolver: func( string) (d2graph.LayoutGraph, error) {
			return d2dagrelayout.DefaultLayout, nil
		},
	}, &d2svg.RenderOpts{})
	if  != nil {
		return nil, 
	}

	// render
	,  := d2svg.Render(, &d2svg.RenderOpts{
		ThemeID: &d2themescatalog.DarkMauve.ID,
	})
	return , 
}

func ( *Transition) ( am.S) (string, string, error) {
	if .Tx.Steps == nil {
		return "", "", ErrEmptyTx
	}

	 := ""
	 := make(map[string]bool)
	 := am.S{}
	for ,  := range .Tx.Steps {
		 := strings.Split(.StringFromIndex(), " ")
		 := ""

		switch len() {

		// "activate Requesting"
		case 2:
			 = [0]
			 := [1]
			// handle "handler FooState" -> Foo
			if  == "handler" {
				 = helpers.HandlerToState()
				 +=  + " ->> " +  + ": " + [1]
			} else {
				 +=  + " ->> " +  + ": " + [0]
			}
			[] = true

		// "Foo require Bar"
		case 3:
			 += "  " + [0] + " ->> " + [2] + ": " + [1]
			[[0]] = true
			[[2]] = true
			 = [1]

		default:
			// TODO err
			continue
		}

		// TODO line styles

		 += "\n"
	}

	// clean up
	for  := range  {
		 = append(, strings.ReplaceAll(, "*", ""))
	}

	 := ""
	for ,  := range slices.Concat(, am.S{am.StateAny}) {
		if !slices.Contains(, ) {
			continue
		}

		 += "participant " +  + "\n"
	}

	// mermaid
	 := utils.Sp(`
		sequenceDiagram
	
		%s
	
		%s
	`, , strings.ReplaceAll(, "*", ""))

	// ascii
	,  := merascii.Parse()
	if  != nil {
		return , "", 
	}
	,  := merascii.Render(, nil)

	return , , 
}

// ///// ///// /////

// ///// SVG merge

// ///// ///// /////

// svg represents the root svg element to extract attributes and inner content
type svg struct {
	XMLName xml.Name `xml:"svg"`
	Width   string   `xml:"width,attr"`
	Height  string   `xml:"height,attr"`
	ViewBox string   `xml:"viewBox,attr"`
	Content string   `xml:",innerxml"` // Grabs everything inside the <svg> tags
}

// parseDimension removes common suffixes and converts to float
func parseDimension( string) float64 {
	 = strings.TrimSuffix(strings.TrimSpace(), "px")
	,  := strconv.ParseFloat(, 64)
	return 
}

// getDimensions tries to figure out the width and height of an svg
func getDimensions( *svg) (float64, float64) {
	,  := parseDimension(.Width), parseDimension(.Height)

	// If width/height aren't explicitly set, fallback to the viewBox
	if  == 0 ||  == 0 {
		 := strings.Fields(.ViewBox)
		if len() >= 4 {
			, _ = strconv.ParseFloat([2], 64)
			, _ = strconv.ParseFloat([3], 64)
		}
	}
	return , 
}

// mergeSvgs takes a slice of svg strings and stacks them
func mergeSvgs( context.Context,  []string) (string, error) {
	// Helper struct to hold parsed data and viewBox coordinates
	type  struct {
		  svg
		    float64
		    float64
		 float64
		 float64
		  float64
		  float64
	}

	 := make([]*, len())
	 := pond.NewPool(10)
	 := .NewGroupContext()
	 := sync.Mutex{}

	// 1. Parse all SVGs and extract dimensions and viewBoxes
	for ,  := range  {
		if  == "" {
			continue
		}
		,  := , 
		.SubmitErr(func() error {
			var  svg
			 := xml.Unmarshal([]byte(), &)
			if  != nil {
				return fmt.Errorf("failed to parse svg: %w", )
			}

			,  := getDimensions(&)

			// Clean and parse the viewBox string to allow us to do math on it
			 := strings.ReplaceAll(.ViewBox, ",", " ")
			var , , ,  float64
			_,  = fmt.Sscanf(, "%f %f %f %f", &, &, &, &)
			if  != nil {
				// Fallback if viewBox parsing fails or is missing
				, , ,  = 0, 0, , 
			}

			.Lock()
			[] = &{
				:  ,
				:    ,
				:    ,
				: ,
				: ,
				:  ,
				:  ,
			}
			.Unlock()

			return nil
		})
	}
	if  := .Wait();  != nil {
		return "", 
	}

	var  []
	var  float64
	var  float64
	for ,  := range  {
		if  == nil {
			continue
		}
		 = append(, *)
		if . >  {
			 = .
		}
		 += .
	}

	// 2. Calculate the trimmed dimensions and write the outer wrapper
	// Every join has two sides. (len(parsed) - 1) joins = that many * 200px
	// removed.
	// TODO config
	 := 100.0
	 := float64(len() - 1)
	if  < 0 {
		 = 0
	}
	 :=  - ( * ( * 2))

	var  bytes.Buffer
	.WriteString(fmt.Sprintf(
		"<svg xmlns=\"http://www.w3.org/2000/svg\" "+
			"viewBox=\"0 0 %g %g\" width=\"%g\" height=\"%g\">\n",
		, , , ,
	))

	// 3. Insert each parsed svg, trimming joining edges and offsetting Y
	 := 0.0
	for ,  := range  {
		 := 0.0
		 := 0.0

		// If it's not the first svg, trim the top
		if  > 0 {
			 = 
		}
		// If it's not the last svg, trim the bottom
		if  < len()-1 {
			 = 
		}
		// fix double header states
		if  == 0 {
			// TODO config
			 = 260
		}

		// Calculate the new ViewBox and Height for this specific nested piece
		 := . + 
		 := . -  - 
		 := . -  - 

		// Safety check to prevent negative heights if an svg is very small
		if  < 0 {
			 = 0
		}
		if  < 0 {
			 = 0
		}

		.WriteString(fmt.Sprintf(
			`  <svg y="%g" width="%g" height="%g" viewBox="%g %g %g %g">`+"\n",
			, ., , ., , ., ,
		))
		.WriteString(..Content)
		.WriteString("\n  </svg>\n")

		// Move the Y cursor down by the visible (trimmed) height of the current svg
		 += 
	}

	.WriteString("</svg>")
	return .String(), nil
}