package compiler

import (
	"fmt"
	"strings"

	"c65gm/internal/preproc"
)

// VarKind represents the data type/size of a variable
type VarKind uint8

const (
	KindByte VarKind = iota
	KindWord
	// Future: KindDWord, Kind24bit, etc
)

// SymbolFlags represents properties of a symbol as a bitfield
type SymbolFlags uint16

const (
	FlagByte SymbolFlags = 1 << iota
	FlagWord
	FlagConst
	FlagAbsolute
	FlagZeroPage
	FlagLabelRef
	FlagRegister
)

// Symbol represents a variable, constant, or label reference
type Symbol struct {
	Name     string           // Variable name
	Scope    string           // Function scope (empty for global)
	Flags    SymbolFlags      // Type and properties
	Value    uint16           // Initial value for variables, constant value for constants
	AbsAddr  uint16           // Absolute address (for @ variables)
	LabelRef string           // Label reference (for label variables)
	Usage    uint8            // Usage tracking
	Line     preproc.Line     // Source line where declared (contains filename, line number, pragma index)
}

// Usage tracking for variables
const (
	UsageNone uint8 = 0
	UsageUsed uint8 = 1 << 0
	// Future flags can be added:
	// UsageRead      = 1 << 1
	// UsageWritten   = 1 << 2  
	// UsageAddress   = 1 << 3
)

// MarkUsed marks the symbol as used (should not be called for constants or absolutes)
func (s *Symbol) MarkUsed() {
	s.Usage |= UsageUsed
}

// IsUsed returns true if the symbol has been marked as used
func (s *Symbol) IsUsed() bool {
	return s.Usage&UsageUsed != 0
}

// Helper methods for Symbol
func (s *Symbol) Has(flag SymbolFlags) bool {
	return s.Flags&flag != 0
}

func (s *Symbol) HasAll(flags SymbolFlags) bool {
	return s.Flags&flags == flags
}

func (s *Symbol) IsByte() bool            { return s.Has(FlagByte) }
func (s *Symbol) IsWord() bool            { return s.Has(FlagWord) }
func (s *Symbol) IsConst() bool           { return s.Has(FlagConst) }
func (s *Symbol) IsAbsolute() bool        { return s.Has(FlagAbsolute) }
func (s *Symbol) IsZeroPage() bool        { return s.Has(FlagZeroPage) }
func (s *Symbol) IsRegister() bool       { return s.Has(FlagRegister) }
func (s *Symbol) IsZeroPagePointer() bool { return s.HasAll(FlagAbsolute | FlagZeroPage | FlagWord) }

// FullName returns the fully qualified name (scope.name or just name)
func (s *Symbol) FullName() string {
	if s.Scope == "" {
		return s.Name
	}
	return s.Scope + "_" + s.Name
}

// SymbolTable manages variable and constant declarations
type SymbolTable struct {
	symbols     []*Symbol                     // insertion order
	byFullName  map[string]*Symbol            // fullname -> symbol
	byScope     map[string]map[string]*Symbol // scope -> name -> symbol
	funcHandler FunctionHandlerInterface      // for notifying about absolute variables in functions
	pragma      *preproc.Pragma               // for pragma lookup during warning generation
}

// FunctionHandlerInterface allows SymbolTable to notify about absolute variables
// without creating a circular dependency
type FunctionHandlerInterface interface {
	RecordAbsoluteVar(funcScope string, addr uint16, isWord bool)
}

// NewSymbolTable creates a new symbol table
func NewSymbolTable() *SymbolTable {
	return &SymbolTable{
		symbols:    make([]*Symbol, 0),
		byFullName: make(map[string]*Symbol),
		byScope:    make(map[string]map[string]*Symbol),
	}
}

// SetFunctionHandler sets the function handler for absolute variable notifications
func (st *SymbolTable) SetFunctionHandler(fh FunctionHandlerInterface) {
	st.funcHandler = fh
}

// SetPragma sets the pragma reference for pragma lookup during warning generation
func (st *SymbolTable) SetPragma(p *preproc.Pragma) {
	st.pragma = p
}

// AddVar adds a regular variable (byte or word)
func (st *SymbolTable) AddVar(name, scope string, kind VarKind, initValue uint16, line preproc.Line) error {
	var flags SymbolFlags

	switch kind {
	case KindByte:
		flags = FlagByte
		if initValue > 255 {
			return fmt.Errorf("byte variable %q init value %d out of range", name, initValue)
		}
	case KindWord:
		flags = FlagWord
	default:
		return fmt.Errorf("unknown variable kind: %d", kind)
	}

	return st.add(&Symbol{
		Name:  name,
		Scope: scope,
		Flags: flags,
		Value: initValue,
		Line:  line,
	})
}

// AddRegisterVar adds a REGISTER-hinted byte variable (function-local only, BYTE only)
func (st *SymbolTable) AddRegisterVar(name, scope string, initValue uint16, line preproc.Line) error {
	if scope == "" {
		return fmt.Errorf("BYTE REGISTER %q is only valid inside a FUNC block (remove REGISTER or move inside a function)", name)
	}
	return st.add(&Symbol{
		Name:  name,
		Scope: scope,
		Flags: FlagByte | FlagRegister,
		Value: initValue,
		Line:  line,
	})
}

// AddConst adds a constant (byte or word)
func (st *SymbolTable) AddConst(name, scope string, kind VarKind, value uint16, line preproc.Line) error {
	var flags SymbolFlags

	switch kind {
	case KindByte:
		flags = FlagByte | FlagConst
		if value > 255 {
			return fmt.Errorf("byte constant %q value %d out of range", name, value)
		}
	case KindWord:
		flags = FlagWord | FlagConst
	default:
		return fmt.Errorf("unknown variable kind: %d", kind)
	}

	return st.add(&Symbol{
		Name:  name,
		Scope: scope,
		Flags: flags,
		Value: value,
		Line:  line,
	})
}

// AddAbsolute adds a variable at a fixed memory address
func (st *SymbolTable) AddAbsolute(name, scope string, kind VarKind, addr uint16, line preproc.Line) error {
	if addr > 0xFFFF {
		return fmt.Errorf("absolute address %d exceeds 16-bit range", addr)
	}

	var flags SymbolFlags

	switch kind {
	case KindByte:
		flags = FlagByte | FlagAbsolute
		// Zero page check for bytes
		if addr < 0x100 {
			flags |= FlagZeroPage
		}
	case KindWord:
		flags = FlagWord | FlagAbsolute
		// Zero page check for words (pointer must fit in ZP)
		if addr < 0xFF {
			flags |= FlagZeroPage
		}
	default:
		return fmt.Errorf("unknown variable kind: %d", kind)
	}

	// Notify function handler about absolute variable in function scope
	if st.funcHandler != nil && scope != "" {
		st.funcHandler.RecordAbsoluteVar(scope, addr, kind == KindWord)
	}

	return st.add(&Symbol{
		Name:    name,
		Scope:   scope,
		Flags:   flags,
		AbsAddr: addr,
		Line:    line,
	})
}

// AddLabel adds a word variable that references a label
func (st *SymbolTable) AddLabel(name, scope string, labelRef string, line preproc.Line) error {
	return st.add(&Symbol{
		Name:     name,
		Scope:    scope,
		Flags:    FlagWord | FlagLabelRef,
		LabelRef: labelRef,
		Line:     line,
	})
}

// add is the internal method that actually adds a symbol
func (st *SymbolTable) add(sym *Symbol) error {
	fullName := sym.FullName()

	// Check for redeclaration
	if _, exists := st.byFullName[fullName]; exists {
		return fmt.Errorf("symbol %q already declared", fullName)
	}

	// Add to all indexes
	st.symbols = append(st.symbols, sym)
	st.byFullName[fullName] = sym

	// Add to scope index
	if st.byScope[sym.Scope] == nil {
		st.byScope[sym.Scope] = make(map[string]*Symbol)
	}
	st.byScope[sym.Scope][sym.Name] = sym

	return nil
}

// Lookup finds a symbol by name, resolving scope
// Searches local scope first (if currentScopes provided), then global
// Marks non-constant, non-absolute variables as used
func (st *SymbolTable) Lookup(name string, currentScopes []string) *Symbol {
	// Try local scopes first (innermost to outermost)
	for i := len(currentScopes) - 1; i >= 0; i-- {
		scope := currentScopes[i]
		if scopeMap, ok := st.byScope[scope]; ok {
			if sym, ok := scopeMap[name]; ok {
				// Mark as used if it's a regular variable (not constant, not absolute)
				if !sym.IsConst() && !sym.IsAbsolute() {
					sym.MarkUsed()
				}
				return sym
			}
		}
	}

	// Try global scope
	if scopeMap, ok := st.byScope[""]; ok {
		if sym, ok := scopeMap[name]; ok {
			// Mark as used if it's a regular variable (not constant, not absolute)
			if !sym.IsConst() && !sym.IsAbsolute() {
				sym.MarkUsed()
			}
			return sym
		}
	}

	return nil
}

// Get retrieves a symbol by its full name
func (st *SymbolTable) Get(fullName string) *Symbol {
	return st.byFullName[fullName]
}

// Symbols returns all symbols in insertion order
func (st *SymbolTable) Symbols() []*Symbol {
	return st.symbols
}

// Count returns the number of symbols
func (st *SymbolTable) Count() int {
	return len(st.symbols)
}

// ExpandName resolves a local name to its full name using scope resolution
func (st *SymbolTable) ExpandName(name string, currentScopes []string) string {
	sym := st.Lookup(name, currentScopes)
	if sym != nil {
		return sym.FullName()
	}
	return name
}

// LookupWithoutUsage finds a symbol by name without marking it as used
// Used for validation-only lookups (e.g., checking if variable exists)
func (st *SymbolTable) LookupWithoutUsage(name string, currentScopes []string) *Symbol {
	// Try local scopes first (innermost to outermost)
	for i := len(currentScopes) - 1; i >= 0; i-- {
		scope := currentScopes[i]
		if scopeMap, ok := st.byScope[scope]; ok {
			if sym, ok := scopeMap[name]; ok {
				return sym
			}
		}
	}

	// Try global scope
	if scopeMap, ok := st.byScope[""]; ok {
		if sym, ok := scopeMap[name]; ok {
			return sym
		}
	}

	return nil
}

// LookupConstant looks up a constant by name and returns its value if found
// Returns (value, true) if symbol exists and is a constant, (0, false) otherwise
func (st *SymbolTable) LookupConstant(name string, currentScopes []string) (int64, bool) {
	sym := st.LookupWithoutUsage(name, currentScopes)
	if sym != nil && sym.IsConst() {
		return int64(sym.Value), true
	}
	return 0, false
}

// ConstantLookupFunc returns a ConstantLookup function that captures the current scopes
// This can be used directly with utils.EvaluateExpression
func (st *SymbolTable) ConstantLookupFunc(currentScopes []string) func(string) (int64, bool) {
	return func(name string) (int64, bool) {
		return st.LookupConstant(name, currentScopes)
	}
}

// CheckUnused returns warnings for unused variables
// Returns slice of warning messages for regular variables (not constants, not absolutes) that were never used
// excludeFuncs is a map of function names that will be removed (e.g., have _P_REMOVE_UNUSED pragma)
// dissolvedVars is a set of REGISTER variables dissolved by the optimizer (do not warn)
func (st *SymbolTable) CheckUnused(excludeFuncs map[string]bool, dissolvedVars map[string]bool) []string {
	var warnings []string
	for _, sym := range st.symbols {
		// Skip constants and absolute variables (they shouldn't track usage)
		if sym.IsConst() || sym.IsAbsolute() {
			if sym.IsUsed() {
				continue
			}
			continue
		}
		
		// Skip variables in functions that will be removed
		if sym.Scope != "" && excludeFuncs != nil && excludeFuncs[sym.Scope] {
			continue
		}
		
		// Skip dissolved REGISTER variables (dissolved is the intended outcome)
		if sym.IsRegister() && dissolvedVars != nil && dissolvedVars[sym.FullName()] {
			continue
		}
		
		// Check if pragma indicates we should ignore unused warnings for this variable
		if st.pragma != nil {
			pragmaSet := st.pragma.GetPragmaSetByIndex(sym.Line.PragmaSetIndex)
			value := pragmaSet.GetPragma("_P_IGNORE_UNUSED")
			if value != "" && value != "0" {
				continue // Skip warning for this variable
			}
		}
		
		// Check if variable was never used
		if !sym.IsUsed() {
			var scopeInfo string
			if sym.Scope != "" {
				scopeInfo = fmt.Sprintf(" in function '%s'", sym.Scope)
			}
			warning := fmt.Sprintf("%s:%d: warning: variable '%s'%s declared but never used", 
				sym.Line.Filename, sym.Line.LineNo, sym.Name, scopeInfo)
			warnings = append(warnings, warning)
		}
	}
	return warnings
}

// String representation for debugging
func (s *Symbol) String() string {
	var parts []string
	parts = append(parts, fmt.Sprintf("Name=%s", s.FullName()))

	if s.IsByte() {
		parts = append(parts, "BYTE")
	} else if s.IsWord() {
		parts = append(parts, "WORD")
	}

	if s.IsConst() {
		parts = append(parts, fmt.Sprintf("CONST=%d", s.Value))
	} else if s.IsAbsolute() {
		parts = append(parts, fmt.Sprintf("@$%04X", s.AbsAddr))
		if s.IsZeroPage() {
			parts = append(parts, "ZP")
		}
	} else if s.Has(FlagLabelRef) {
		parts = append(parts, fmt.Sprintf("->%s", s.LabelRef))
	} else if s.Value != 0 {
		parts = append(parts, fmt.Sprintf("=%d", s.Value))
	}

	// Add location info if available
	if s.Line.Filename != "" {
		parts = append(parts, fmt.Sprintf("@%s:%d", s.Line.Filename, s.Line.LineNo))
	}

	return strings.Join(parts, " ")
}

// Code generation functions for ACME assembler syntax

// GenerateConstants generates constant definitions (name = $value)
func GenerateConstants(st *SymbolTable, excludeScopes map[string]bool) []string {
	var lines []string
	hasConsts := false

	for _, sym := range st.Symbols() {
		if !sym.IsConst() {
			continue
		}
		// Skip constants scoped to removed functions
		if excludeScopes != nil && sym.Scope != "" && excludeScopes[sym.Scope] {
			continue
		}

		hasConsts = true
		var line string

		if sym.IsByte() {
			// Byte constant with decimal comment
			line = fmt.Sprintf("%s = $%02x\t; %d", sym.FullName(), sym.Value, sym.Value)
		} else {
			// Word constant
			line = fmt.Sprintf("%s = $%04x", sym.FullName(), sym.Value)
		}

		lines = append(lines, line)
	}

	if hasConsts {
		// Prepend header
		result := []string{
			";Constant values (from c65gm)",
			"",
		}
		result = append(result, lines...)
		result = append(result, "") // blank line after
		return result
	}

	return nil
}

// GenerateAbsolutes generates absolute address assignments (name = $addr)
func GenerateAbsolutes(st *SymbolTable, excludeScopes map[string]bool) []string {
	var lines []string
	hasAbsolutes := false

	for _, sym := range st.Symbols() {
		if !sym.IsAbsolute() {
			continue
		}
		// Skip absolutes scoped to removed functions
		if excludeScopes != nil && sym.Scope != "" && excludeScopes[sym.Scope] {
			continue
		}

		hasAbsolutes = true
		var line string

		if sym.IsZeroPage() {
			// Zero-page: 2 hex digits
			line = fmt.Sprintf("%s = $%02x", sym.FullName(), sym.AbsAddr)
		} else {
			// Non-zero-page: 4 hex digits
			line = fmt.Sprintf("%s = $%04x", sym.FullName(), sym.AbsAddr)
		}

		lines = append(lines, line)
	}

	if hasAbsolutes {
		// Prepend header
		result := []string{
			";Absolute variable definitions (from c65gm)",
			"",
		}
		result = append(result, lines...)
		result = append(result, "") // blank line after
		return result
	}

	return nil
}

// GenerateVariables generates variable declarations (name !8 $value)
func GenerateVariables(st *SymbolTable, excludeScopes map[string]bool, dissolvedVars map[string]bool) []string {
	var lines []string
	hasVars := false

	for _, sym := range st.Symbols() {
		// Skip constants and absolutes - they're handled separately
		if sym.IsConst() || sym.IsAbsolute() {
			continue
		}
		// Skip variables scoped to removed functions
		if excludeScopes != nil && sym.Scope != "" && excludeScopes[sym.Scope] {
			continue
		}
		// Skip dissolved REGISTER variables (optimizer eliminated all references)
		if sym.IsRegister() && dissolvedVars != nil && dissolvedVars[sym.FullName()] {
			continue
		}

		hasVars = true
		var line string

		if sym.IsByte() {
			// Byte variable with decimal comment
			line = fmt.Sprintf("%s\t!8 $%02x\t; %d", sym.FullName(), sym.Value&0xFF, sym.Value&0xFF)
		} else if sym.Has(FlagLabelRef) {
			// Word with label reference
			line = fmt.Sprintf("%s\t!8 <%s, >%s", sym.FullName(), sym.LabelRef, sym.LabelRef)
		} else {
			// Word variable (split into low byte, high byte)
			lo := sym.Value & 0xFF
			hi := (sym.Value >> 8) & 0xFF
			line = fmt.Sprintf("%s\t!8 $%02x, $%02x", sym.FullName(), lo, hi)
		}

		lines = append(lines, line)
	}

	if hasVars {
		// Prepend header
		result := []string{
			";Variables (from c65gm)",
			"",
		}
		result = append(result, lines...)
		result = append(result, "") // blank line after
		return result
	}

	return nil
}

// GetVarKind extracts VarKind from Symbol
func (s *Symbol) GetVarKind() VarKind {
	if s.IsByte() {
		return KindByte
	}
	return KindWord
}
