package compiler

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"c65gm/internal/optimizer"
	"c65gm/internal/preproc"
)

// Compiler orchestrates the compilation process
type Compiler struct {
	ctx              *CompilerContext
	registry         *CommandRegistry
	deferredAsm      []string            // ASM blocks with _P_ASM_AFTER_VARS pragma
	dissolvedVars    map[string]bool     // REGISTER vars dissolved by optimizer
	CmdlineOpt       bool                // --opt enables all passes
	CmdlineDebug     bool                // --opt-debug enables debug output
	CmdlineIORegions []optimizer.IORegion // --opt-exclude ranges
}

// NewCompiler creates a new compiler with initialized context and registry
func NewCompiler(pragma *preproc.Pragma) *Compiler {
	return &Compiler{
		ctx:      NewCompilerContext(pragma),
		registry: NewCommandRegistry(),
	}
}

// Context returns the compiler context (for registering commands that need it)
func (c *Compiler) Context() *CompilerContext {
	return c.ctx
}

// Registry returns the command registry (for registering commands)
func (c *Compiler) Registry() *CommandRegistry {
	return c.registry
}

// Compile processes preprocessed lines and generates assembly output
func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
	var codeOutput []string
	var lastKind = preproc.Source
	var scriptBuffer []preproc.Line
	var scriptIsLibrary bool
	var macroBuffer []string
	var currentMacroName string
	var currentMacroParams []string
	var currentMacroSourceFile string
	var currentMacroStartLine int
	var currentAsmTarget *[]string // nil = no active ASM block, or points to target slice

	// Reset deferred ASM storage for this compilation
	c.deferredAsm = nil

	for i, line := range lines {
		// Detect kind transitions and emit markers
		if line.Kind != lastKind {
			// Execute and close previous Script or ScriptLibrary block
			if lastKind == preproc.Script || lastKind == preproc.ScriptLibrary {
				scriptOutput, err := executeScript(scriptBuffer, c.ctx, scriptIsLibrary)
				if err != nil {
					return nil, fmt.Errorf("script execution failed: %w", err)
				}
				codeOutput = append(codeOutput, scriptOutput...)
				scriptBuffer = nil
				if scriptIsLibrary {
					codeOutput = append(codeOutput, "; ENDSCRIPT LIBRARY")
				} else {
					codeOutput = append(codeOutput, "; ENDSCRIPT")
				}
			}

			// Store previous ScriptMacroDef block
			if lastKind == preproc.ScriptMacroDef {
				if currentMacroName != "" {
					c.ctx.ScriptMacros[currentMacroName] = &ScriptMacro{
						Name:       currentMacroName,
						Params:     currentMacroParams,
						Body:       macroBuffer,
						SourceFile: currentMacroSourceFile,
						StartLine:  currentMacroStartLine,
					}
					codeOutput = append(codeOutput, fmt.Sprintf("; ENDSCRIPT MACRO %s", currentMacroName))
				}
				macroBuffer = nil
				currentMacroName = ""
				currentMacroParams = nil
				currentMacroSourceFile = ""
				currentMacroStartLine = 0
			}

			// Close previous Assembler block
			if lastKind == preproc.Assembler && currentAsmTarget != nil {
				*currentAsmTarget = append(*currentAsmTarget, "; ENDASM")
				currentAsmTarget = nil
			}

			// Open new block
			if line.Kind == preproc.Assembler {
				// Check if ASM block should be deferred to end
				pragmaSet := c.ctx.Pragma.GetPragmaSetByIndex(line.PragmaSetIndex)
				asmAfterVars := pragmaSet.GetPragma("_P_ASM_AFTER_VARS") != "" && 
				                pragmaSet.GetPragma("_P_ASM_AFTER_VARS") != "0"
				
				if asmAfterVars {
					// Add inline comment and defer ASM block
					codeOutput = append(codeOutput, "; ASM block deferred to end of source")
					c.deferredAsm = append(c.deferredAsm, 
						fmt.Sprintf("; ASM Block from %s, Line %d", line.Filename, line.LineNo))
					currentAsmTarget = &c.deferredAsm
				} else {
					// Normal ASM block
					codeOutput = append(codeOutput, "; ASM")
					currentAsmTarget = &codeOutput
				}
			} else if line.Kind == preproc.Script {
				codeOutput = append(codeOutput, "; SCRIPT")
				scriptIsLibrary = false
			} else if line.Kind == preproc.ScriptLibrary {
				codeOutput = append(codeOutput, "; SCRIPT LIBRARY")
				scriptIsLibrary = true
			} else if line.Kind == preproc.ScriptMacroDef {
				// First line is the header - parse it
				name, params, err := parseMacroHeader(line.Text)
				if err != nil {
					c.printErrorWithContext(lines, i, err)
					return nil, fmt.Errorf("compilation failed")
				}
				currentMacroName = name
				currentMacroParams = params
				codeOutput = append(codeOutput, fmt.Sprintf("; %s", line.Text))
			}

			lastKind = line.Kind
		}

		// Handle non-source lines
		if line.Kind != preproc.Source {
			if line.Kind == preproc.Assembler {
				// Safety check: currentAsmTarget should be set when processing assembler lines
				if currentAsmTarget == nil {
					c.printErrorWithContext(lines, i, fmt.Errorf("internal error: ASM line without active ASM block"))
					return nil, fmt.Errorf("compilation failed")
				}
				
				text := line.Text

				// Find comment boundary - only process |...| patterns in the code portion
				commentPos := findAsmCommentStart(text)
				codePart := text
				commentPart := ""
				if commentPos != -1 {
					codePart = text[:commentPos]
					commentPart = text[commentPos:]
				}

				// Check for |@macro()| pattern first (only in code portion, outside strings)
				macroStart := findPipeOutsideStrings(codePart, 0)
				if macroStart != -1 && macroStart+1 < len(codePart) && codePart[macroStart+1] == '@' {
					macroEnd := findPipeOutsideStrings(codePart, macroStart+1)
					if macroEnd != -1 {
						invocation := codePart[macroStart+1 : macroEnd] // @name(args)

						macroName, args, err := ParseMacroInvocation(invocation)
						if err != nil {
							c.printErrorWithContext(lines, i, err)
							return nil, fmt.Errorf("compilation failed")
						}

						macroOutput, err := ExecuteMacro(macroName, args, c.ctx, line.PragmaSetIndex)
						if err != nil {
							c.printErrorWithContext(lines, i, fmt.Errorf("macro %s: %w", macroName, err))
							return nil, fmt.Errorf("compilation failed")
						}

						// Emit with comments showing invocation
						*currentAsmTarget = append(*currentAsmTarget, fmt.Sprintf("; %s", text))
						*currentAsmTarget = append(*currentAsmTarget, macroOutput...)
						*currentAsmTarget = append(*currentAsmTarget, fmt.Sprintf("; end @%s", macroName))
						continue
					}
				}

				// Expand |varname| -> scoped_varname for local variables in ASM blocks (only in code portion, outside strings)
				searchFrom := 0
				for {
					start := findPipeOutsideStrings(codePart, searchFrom)
					if start == -1 {
						break
					}
					end := findPipeOutsideStrings(codePart, start+1)
					if end == -1 {
						c.printErrorWithContext(lines, i, fmt.Errorf("unclosed | in assembler line"))
						return nil, fmt.Errorf("compilation failed")
					}

					varName := codePart[start+1 : end]
					sym := c.ctx.SymbolTable.LookupWithoutUsage(varName, c.ctx.CurrentScope())
					if sym != nil && sym.IsRegister() {
						c.printErrorWithContext(lines, i, fmt.Errorf("REGISTER variable %q cannot be referenced from ASM blocks", varName))
						return nil, fmt.Errorf("compilation failed")
					}
					expandedName := c.ctx.SymbolTable.ExpandName(varName, c.ctx.CurrentScope())
					codePart = codePart[:start] + expandedName + codePart[end+1:]
					// Continue searching after the replacement
					searchFrom = start + len(expandedName)
				}
				*currentAsmTarget = append(*currentAsmTarget, codePart+commentPart)
			} else if line.Kind == preproc.Script || line.Kind == preproc.ScriptLibrary {
				// Collect script lines for execution
				scriptBuffer = append(scriptBuffer, line)
			} else if line.Kind == preproc.ScriptMacroDef {
				// Skip the header line (already parsed in transition)
				if strings.HasPrefix(strings.TrimSpace(line.Text), "SCRIPT MACRO ") {
					continue
				}
				// Capture source provenance from first body line
				if len(macroBuffer) == 0 {
					currentMacroSourceFile = line.Filename
					currentMacroStartLine = line.LineNo
				}
				// Collect macro body lines
				macroBuffer = append(macroBuffer, line.Text)
			}
			continue
		}

		// Skip empty/whitespace-only lines
		if strings.TrimSpace(line.Text) == "" {
			continue
		}

		// Find handler for this line
		cmd, found := c.registry.FindHandler(line)
		if !found {
			c.printErrorWithContext(lines, i, fmt.Errorf("unhandled line (no matching command)"))
			return nil, fmt.Errorf("compilation failed")
		}

		// Interpret the line
		if err := cmd.Interpret(line, c.ctx); err != nil {
			c.printErrorWithContext(lines, i, err)
			return nil, fmt.Errorf("compilation failed")
		}

		// Generate assembly
		asmLines, err := cmd.Generate(c.ctx)
		if err != nil {
			c.printErrorWithContext(lines, i, err)
			return nil, fmt.Errorf("compilation failed")
		}

		codeOutput = append(codeOutput, fmt.Sprintf("; %s", line.Text))
		if len(asmLines) > 0 && c.isMarkersEnabled() {
			codeOutput = append(codeOutput, fmt.Sprintf("; @@OPT:%s:%s", classString(cmd.GetClass()), cmd.GetName()))
		}
		codeOutput = append(codeOutput, asmLines...)
	}

	// Close any open block
	if lastKind == preproc.Assembler {
		// Close the final ASM block if still open
		if currentAsmTarget != nil {
			*currentAsmTarget = append(*currentAsmTarget, "; ENDASM")
		}
		return nil, fmt.Errorf("Unclosed ASM block.")
	} else if lastKind == preproc.Script {
		return nil, fmt.Errorf("Unclosed SCRIPT block.")
	} else if lastKind == preproc.ScriptLibrary {
		return nil, fmt.Errorf("Unclosed SCRIPT LIBRARY block.")
	} else if lastKind == preproc.ScriptMacroDef {
		return nil, fmt.Errorf("Unclosed SCRIPT MACRO block.")
	}

	// Peephole optimization pass
	if cfg := c.getOptimizerConfig(); cfg != nil {
		var dissolved map[string]bool
		codeOutput, dissolved = optimizer.Optimize(codeOutput, cfg)
		c.dissolvedVars = dissolved
	}

	// Analyze for overlapping absolute addresses in function call chains
	c.checkAbsoluteOverlaps()

	// Get functions with _P_REMOVE_UNUSED pragma (for suppressing variable warnings)
	funcsWithRemovePragma := c.ctx.FunctionHandler.GetFunctionsWithRemovePragma()
	
	// Check for unused variables and print warnings (skip variables in functions with remove pragma)
	warnings := c.ctx.SymbolTable.CheckUnused(funcsWithRemovePragma, c.dissolvedVars)
	for _, warning := range warnings {
		_, _ = fmt.Fprintf(os.Stderr, "%s\n", warning)
	}

	// Check for unused functions and print warnings
	funcWarnings := c.ctx.FunctionHandler.CheckUnusedFunctions()
	for _, warning := range funcWarnings {
		_, _ = fmt.Fprintf(os.Stderr, "%s\n", warning)
	}

	// Remove unused functions with _P_REMOVE_UNUSED pragma
	codeOutput, removedFuncs := c.removeUnusedFunctions(codeOutput)

	// Update peephole header to match actual [removed] count after function removal
	codeOutput = updatePeepholeHeader(codeOutput)

	// Assemble final output with headers and footers
	return c.assembleOutput(codeOutput, removedFuncs), nil
}

// isOptimizing returns true if any peephole optimization pragma is active
func (c *Compiler) isOptimizing() bool {
	if c.CmdlineOpt {
		return true
	}
	if c.ctx == nil || c.ctx.Pragma == nil {
		return false
	}
	idx := c.ctx.Pragma.GetCurrentPragmaSetIndex()
	ps := c.ctx.Pragma.GetPragmaSetByIndex(idx)
	cfg := optimizer.NewConfig(ps)
	return cfg.Any()
}

// isMarkersEnabled returns true if _P_OPT_MARKERS is active
func (c *Compiler) isMarkersEnabled() bool {
	if c.ctx == nil || c.ctx.Pragma == nil {
		return false
	}
	idx := c.ctx.Pragma.GetCurrentPragmaSetIndex()
	ps := c.ctx.Pragma.GetPragmaSetByIndex(idx)
	return ps.GetPragma("_P_OPT_MARKERS") != "" && ps.GetPragma("_P_OPT_MARKERS") != "0"
}

// getOptimizerConfig returns optimizer config if optimization is enabled, nil otherwise
func (c *Compiler) getOptimizerConfig() *optimizer.Config {
	if c.ctx == nil || c.ctx.Pragma == nil {
		return nil
	}
	idx := c.ctx.Pragma.GetCurrentPragmaSetIndex()
	ps := c.ctx.Pragma.GetPragmaSetByIndex(idx)
	cfg := optimizer.NewConfig(ps)

	// Command-line overrides
	if c.CmdlineOpt {
		cfg.EnableLoad = true
		cfg.EnableImm = true
		cfg.EnableJmp = true
		cfg.EnableSelf = true
		cfg.EnableStoreLoad = true
		cfg.EnableRegisterVars = true
	}
	if c.CmdlineDebug {
		cfg.Debug = true
	}

	// Populate register variable names for the optimizer
	cfg.RegisterVars = make(map[string]bool)
	for _, sym := range c.ctx.SymbolTable.Symbols() {
		if sym.IsRegister() {
			cfg.RegisterVars[sym.FullName()] = true
		}
	}

	if !cfg.Any() {
		return nil
	}

	// Apply CLI I/O regions before pragma-based ones
	if len(c.CmdlineIORegions) > 0 {
		cfg.AddIORegions(c.CmdlineIORegions)
	}
	cfg.BuildIOMap(c.ctx.Pragma)
	return cfg
}

// updatePeepholeHeader recalculates the peephole header saved count
// from the actual [removed] annotations present after removeUnusedFunctions.
func updatePeepholeHeader(codeOutput []string) []string {
	headerIdx := -1
	var header string
	removedCount := 0

	for i, line := range codeOutput {
		if strings.HasPrefix(line, "; --- peephole:") {
			headerIdx = i
			header = line
		}
		if strings.HasPrefix(line, "; [removed]") {
			removedCount++
		}
	}

	if headerIdx < 0 {
		return codeOutput
	}

	// Extract orig count from the existing header
	var origCount, oldOptCount int
	if _, err := fmt.Sscanf(header, "; --- peephole: %d asm lines → %d",
		&origCount, &oldOptCount); err != nil {
		return codeOutput
	}

	newHeader := fmt.Sprintf("; --- peephole: %d asm lines → %d (%d saved) ---",
		origCount, origCount-removedCount, removedCount)
	codeOutput[headerIdx] = newHeader
	return codeOutput
}

func classString(cls CmdClass) string {
	switch cls {
	case CmdLinear:
		return "LINEAR"
	case CmdFlowCtrl:
		return "FLOWCTRL"
	case CmdBoundary:
		return "BOUNDARY"
	default:
		return "UNKNOWN"
	}
}

// printErrorWithContext prints an error with source code context to stderr
func (c *Compiler) printErrorWithContext(lines []preproc.Line, lineIndex int, err error) {
	if lineIndex < 0 || lineIndex >= len(lines) {
		// Shouldn't happen, but be safe
		_, _ = fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		return
	}

	line := lines[lineIndex]
	const contextLines = 3

	// Print error header
	_, _ = fmt.Fprintf(os.Stderr, "\nError: %v\n", err)
	_, _ = fmt.Fprintf(os.Stderr, "  --> %s:%d\n\n", line.Filename, line.LineNo)

	// Find all lines from the same file for context
	// Group consecutive lines from the same file
	fileLines := make(map[string][]int)
	for i, l := range lines {
		if l.Filename == line.Filename {
			fileLines[l.Filename] = append(fileLines[l.Filename], i)
		}
	}

	// Get context range (lineIndex within our lines array)
	startIdx := lineIndex - contextLines
	if startIdx < 0 {
		startIdx = 0
	}
	endIdx := lineIndex + contextLines
	if endIdx >= len(lines) {
		endIdx = len(lines) - 1
	}

	// Calculate width for line numbers based on actual source line numbers
	maxLineNo := 0
	for i := startIdx; i <= endIdx; i++ {
		if lines[i].LineNo > maxLineNo {
			maxLineNo = lines[i].LineNo
		}
	}
	maxLineNumWidth := len(fmt.Sprintf("%d", maxLineNo))

	// Print context
	for i := startIdx; i <= endIdx; i++ {
		l := lines[i]

		// Skip lines from different files
		if l.Filename != line.Filename {
			continue
		}

		if i == lineIndex {
			// Error line - highlight it
			_, _ = fmt.Fprintf(os.Stderr, ">> %*d | %s\n", maxLineNumWidth, l.LineNo, l.Text)
		} else {
			// Context line
			_, _ = fmt.Fprintf(os.Stderr, "   %*d | %s\n", maxLineNumWidth, l.LineNo, l.Text)
		}
	}

	_, _ = fmt.Fprintf(os.Stderr, "\n")
}

// checkAbsoluteOverlaps analyzes and warns about overlapping absolute addresses
func (c *Compiler) checkAbsoluteOverlaps() {
	c.ctx.FunctionHandler.ReportAbsoluteOverlaps()
}

// parseMacroHeader parses "SCRIPT MACRO name(param1, param2, ...)" and returns name and params
func parseMacroHeader(header string) (string, []string, error) {
	// Expected format: "SCRIPT MACRO name(param1, param2, ...)"
	header = strings.TrimSpace(header)

	// Remove "SCRIPT MACRO " prefix
	if !strings.HasPrefix(header, "SCRIPT MACRO ") {
		return "", nil, fmt.Errorf("invalid macro header: %s", header)
	}
	rest := strings.TrimSpace(header[len("SCRIPT MACRO "):])

	// Find opening paren
	parenStart := strings.IndexByte(rest, '(')
	if parenStart == -1 {
		return "", nil, fmt.Errorf("macro header missing '(': %s", header)
	}

	// Find closing paren
	parenEnd := strings.LastIndexByte(rest, ')')
	if parenEnd == -1 || parenEnd < parenStart {
		return "", nil, fmt.Errorf("macro header missing ')': %s", header)
	}

	name := strings.TrimSpace(rest[:parenStart])
	if name == "" {
		return "", nil, fmt.Errorf("macro name is empty")
	}

	// Parse parameters
	paramStr := strings.TrimSpace(rest[parenStart+1 : parenEnd])
	var params []string
	if paramStr != "" {
		parts := strings.Split(paramStr, ",")
		for _, p := range parts {
			param := strings.TrimSpace(p)
			if param == "" {
				return "", nil, fmt.Errorf("empty parameter in macro definition")
			}
			params = append(params, param)
		}
	}

	return name, params, nil
}

// findAsmCommentStart finds the position of ';' that starts a comment in an ASM line,
// taking into account that ';' inside strings (single or double quoted) should be ignored.
// Supports ACME-style escape sequences (backslash escapes) inside strings.
// Returns -1 if no comment is found.
func findAsmCommentStart(line string) int {
	inString := false
	escaped := false
	var quoteChar rune

	for i, ch := range line {
		if escaped {
			escaped = false
			continue
		}
		if inString {
			if ch == '\\' {
				escaped = true
				continue
			}
			if ch == quoteChar {
				inString = false
			}
			continue
		}
		// Not in string
		if ch == '"' || ch == '\'' {
			inString = true
			quoteChar = ch
			continue
		}
		if ch == ';' {
			return i
		}
	}
	return -1
}

// findPipeOutsideStrings finds the position of '|' that is not inside a string.
// Supports ACME-style escape sequences (backslash escapes) inside strings.
// Starts searching from 'startFrom' position.
// Returns -1 if no such '|' is found.
func findPipeOutsideStrings(line string, startFrom int) int {
	inString := false
	escaped := false
	var quoteChar rune

	for i, ch := range line {
		if i < startFrom {
			// Track string state even before startFrom
			if escaped {
				escaped = false
				continue
			}
			if inString {
				if ch == '\\' {
					escaped = true
					continue
				}
				if ch == quoteChar {
					inString = false
				}
				continue
			}
			if ch == '"' || ch == '\'' {
				inString = true
				quoteChar = ch
			}
			continue
		}

		if escaped {
			escaped = false
			continue
		}
		if inString {
			if ch == '\\' {
				escaped = true
				continue
			}
			if ch == quoteChar {
				inString = false
			}
			continue
		}
		// Not in string
		if ch == '"' || ch == '\'' {
			inString = true
			quoteChar = ch
			continue
		}
		if ch == '|' {
			return i
		}
	}
	return -1
}

// removeUnusedFunctions removes functions marked with _P_REMOVE_UNUSED pragma from assembly output
// Returns the filtered code lines and a map of removed function names
func (c *Compiler) removeUnusedFunctions(codeLines []string) ([]string, map[string]bool) {
	toRemove := c.ctx.FunctionHandler.GetFunctionsToRemove()
	if len(toRemove) == 0 {
		return codeLines, nil
	}

	var result []string
	i := 0
	for i < len(codeLines) {
		line := codeLines[i]

		// Check for function start marker
		if strings.HasPrefix(line, "; @@FUNC_BEGIN ") {
			parts := strings.Fields(line)
			if len(parts) >= 3 && toRemove[parts[2]] {
				// Collect all consecutive @@FUNC_BEGIN markers in this group (all removable)
				groupNames := []string{parts[2]}
				j := i + 1
				for j < len(codeLines) {
					if strings.HasPrefix(codeLines[j], "; @@FUNC_BEGIN ") {
						p := strings.Fields(codeLines[j])
						if len(p) >= 3 && toRemove[p[2]] {
							groupNames = append(groupNames, p[2])
							j++
						} else {
							break
						}
					} else {
						break
					}
				}

				// Print info messages for all removed functions
				for _, funcName := range groupNames {
					var filename string
					var lineNo int
					for _, funcDecl := range c.ctx.FunctionHandler.functions {
						if funcDecl.Name == funcName {
							filename = funcDecl.Line.Filename
							lineNo = funcDecl.Line.LineNo
							break
						}
					}
					if filename != "" {
						baseFilename := filepath.Base(filename)
						fmt.Printf("info:%s:%d:FUNC %s removed.\n", baseFilename, lineNo, funcName)
					} else {
						fmt.Printf("info:unknown:0:FUNC %s removed.\n", funcName)
					}
				}

				// Build set of end markers to find
				endSet := make(map[string]bool)
				for _, name := range groupNames {
					endSet[name] = true
				}

				// Skip past all FUNC_BEGIN markers
				i = j

				// Skip body until all corresponding @@FUNC_END markers are found
				for i < len(codeLines) && len(endSet) > 0 {
					if strings.HasPrefix(codeLines[i], "; @@FUNC_END ") {
						p := strings.Fields(codeLines[i])
						if len(p) >= 3 && endSet[p[2]] {
							delete(endSet, p[2])
						}
					}
					i++
				}
				continue
			}
		}

		result = append(result, line)
		i++
	}
	return result, toRemove
}

// assembleOutput combines all generated sections into final assembly
func (c *Compiler) assembleOutput(codeLines []string, removedFuncs map[string]bool) []string {
	var output []string

	// Header comment
	output = append(output, ";Generated by c65gm")
	output = append(output, "")

	// Constants section
	if constLines := GenerateConstants(c.ctx.SymbolTable, removedFuncs); len(constLines) > 0 {
		output = append(output, constLines...)
	}

	// Absolute addresses section
	if absLines := GenerateAbsolutes(c.ctx.SymbolTable, removedFuncs); len(absLines) > 0 {
		output = append(output, absLines...)
	}

	// Main code section
	output = append(output, ";Main code")
	output = append(output, "")
	output = append(output, codeLines...)
	output = append(output, "")

	// Variables section
	if varLines := GenerateVariables(c.ctx.SymbolTable, removedFuncs, c.dissolvedVars); len(varLines) > 0 {
		output = append(output, varLines...)
	}

	// Constant strings section
	if strLines := c.ctx.ConstStrHandler.GenerateConstStrDecls(); len(strLines) > 0 {
		output = append(output, ";Constant strings (from c65gm)")
		output = append(output, "")
		output = append(output, strLines...)
		output = append(output, "")
	}

	// Deferred ASM blocks (with _P_ASM_AFTER_VARS pragma)
	if len(c.deferredAsm) > 0 {
		output = append(output, "; Deferred ASM blocks (after variables)")
		output = append(output, "")
		output = append(output, c.deferredAsm...)
		output = append(output, "")
	}

	return output
}
