package compiler

import (
	"fmt"
	"strings"

	"c65gm/internal/preproc"
)

// Compiler orchestrates the compilation process
type Compiler struct {
	ctx      *CompilerContext
	registry *CommandRegistry
}

// 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 []string

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

			// Close previous Assembler block
			if lastKind == preproc.Assembler {
				codeOutput = append(codeOutput, "; ENDASM")
			}

			// Open new block
			if line.Kind == preproc.Assembler {
				codeOutput = append(codeOutput, "; ASM")
			} else if line.Kind == preproc.Script {
				codeOutput = append(codeOutput, "; SCRIPT")
			}

			lastKind = line.Kind
		}

		// Handle non-source lines
		if line.Kind != preproc.Source {
			if line.Kind == preproc.Assembler {
				// Expand |varname| -> scoped_varname for local variables in ASM blocks
				text := line.Text
				for {
					start := strings.IndexByte(text, '|')
					if start == -1 {
						break
					}
					end := strings.IndexByte(text[start+1:], '|')
					if end == -1 {
						return nil, fmt.Errorf("%s:%d: unclosed | in assembler line", line.Filename, line.LineNo)
					}
					end += start + 1

					varName := text[start+1 : end]
					expandedName := c.ctx.SymbolTable.ExpandName(varName, c.ctx.CurrentScope())
					text = text[:start] + expandedName + text[end+1:]
				}
				codeOutput = append(codeOutput, text)
			} else if line.Kind == preproc.Script {
				// Collect script lines for execution
				scriptBuffer = append(scriptBuffer, 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 {
			return nil, &UnhandledLineError{Line: line}
		}

		// Interpret the line
		if err := cmd.Interpret(line, c.ctx); err != nil {
			return nil, fmt.Errorf("%s:%d: %w", line.Filename, line.LineNo, err)
		}

		// Generate assembly
		asmLines, err := cmd.Generate(c.ctx)
		if err != nil {
			return nil, fmt.Errorf("%s:%d: %w", line.Filename, line.LineNo, err)
		}

		codeOutput = append(codeOutput, fmt.Sprintf("; %s", line.Text))
		codeOutput = append(codeOutput, asmLines...)
	}

	// Close any open block
	if lastKind == preproc.Assembler {
		//codeOutput = append(codeOutput, "; ENDASM")
		return nil, fmt.Errorf("Unclosed ASM block.")
	} else if lastKind == preproc.Script {
		//codeOutput = append(codeOutput, "; ENDSCRIPT")
		return nil, fmt.Errorf("Unclosed SCRIPT block.")
	}

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

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

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

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

	// Absolute addresses section
	if absLines := GenerateAbsolutes(c.ctx.SymbolTable); 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); 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, "")
	}

	return output
}
