package optimizer

import (
	"fmt"
	"strconv"
	"strings"

	"c65gm/internal/preproc"
)

type Config struct {
	EnableLoad          bool
	EnableImm           bool
	EnableJmp           bool
	EnableSelf          bool
	EnableStoreLoad     bool
	EnableRegisterVars  bool
	Debug               bool
	ShowMarkers         bool
	IOMap               [65536]bool
	RegisterVars        map[string]bool
}

func NewConfig(ps preproc.PragmaSet) *Config {
	all := ps.GetPragma("_P_OPT_ALL") != "" && ps.GetPragma("_P_OPT_ALL") != "0"

	return &Config{
		EnableLoad:         all || (ps.GetPragma("_P_OPT_LOAD") != "" && ps.GetPragma("_P_OPT_LOAD") != "0"),
		EnableImm:          all || (ps.GetPragma("_P_OPT_IMM") != "" && ps.GetPragma("_P_OPT_IMM") != "0"),
		EnableJmp:          all || (ps.GetPragma("_P_OPT_JMP") != "" && ps.GetPragma("_P_OPT_JMP") != "0"),
		EnableSelf:         all || (ps.GetPragma("_P_OPT_SELF") != "" && ps.GetPragma("_P_OPT_SELF") != "0"),
		EnableStoreLoad:    all || (ps.GetPragma("_P_OPT_STLD") != "" && ps.GetPragma("_P_OPT_STLD") != "0"),
		EnableRegisterVars: all || (ps.GetPragma("_P_OPT_REGISTER_VARS") != "" && ps.GetPragma("_P_OPT_REGISTER_VARS") != "0"),
		Debug:              (ps.GetPragma("_P_OPT_DEBUG") != "" && ps.GetPragma("_P_OPT_DEBUG") != "0"),
		ShowMarkers:        (ps.GetPragma("_P_OPT_MARKERS") != "" && ps.GetPragma("_P_OPT_MARKERS") != "0"),
	}
}

func (c *Config) Any() bool {
	return c.EnableLoad || c.EnableImm || c.EnableJmp || c.EnableSelf || c.EnableStoreLoad || c.EnableRegisterVars
}

// BuildIOMap scans all pragma sets for _P_OPT_IO and marks I/O regions.
func (c *Config) BuildIOMap(pragma *preproc.Pragma) {
	for i := 0; i <= pragma.GetCurrentPragmaSetIndex(); i++ {
		val := pragma.GetPragmaSetByIndex(i).GetPragma("_P_OPT_IO")
		if val == "" {
			continue
		}
		parts := strings.Fields(val)
		if len(parts) >= 2 {
			start := parsePragmaAddr(parts[0])
			end := parsePragmaAddr(parts[1])
			if start >= 0 && end >= 0 && start <= end && start < 65536 {
				for a := start; a <= end && a < 65536; a++ {
					c.IOMap[a] = true
				}
			}
		}
	}
}

func parsePragmaAddr(s string) int {
	if strings.HasPrefix(s, "$") {
		v, err := strconv.ParseUint(s[1:], 16, 16)
		if err != nil {
			return -1
		}
		return int(v)
	}
	v, err := strconv.ParseUint(s, 10, 16)
	if err != nil {
		return -1
	}
	return int(v)
}

// IORegion defines an address range to exclude from optimization.
type IORegion struct {
	Start, End uint16
}

// AddIORegions marks all addresses in the given regions as I/O in the IOMap.
func (c *Config) AddIORegions(regions []IORegion) {
	for _, r := range regions {
		for a := r.Start; a <= r.End; a++ {
			c.IOMap[a] = true
		}
	}
}

// ParseExcludeRange parses a string like "0xD000:0xDFFF" or "53248:57343".
// Returns an IORegion or an error.
func ParseExcludeRange(s string) (IORegion, error) {
	parts := strings.SplitN(s, ":", 2)
	if len(parts) != 2 {
		return IORegion{}, fmt.Errorf("expected START:END, got %q", s)
	}

	start, err := parseExcludeAddr(parts[0])
	if err != nil {
		return IORegion{}, fmt.Errorf("invalid start %q: %w", parts[0], err)
	}
	end, err := parseExcludeAddr(parts[1])
	if err != nil {
		return IORegion{}, fmt.Errorf("invalid end %q: %w", parts[1], err)
	}
	if start > end {
		return IORegion{}, fmt.Errorf("start %s > end %s", parts[0], parts[1])
	}
	return IORegion{Start: start, End: end}, nil
}

func parseExcludeAddr(s string) (uint16, error) {
	s = strings.TrimSpace(s)
	// Hex: 0x prefix (case-insensitive)
	if len(s) > 2 && (s[:2] == "0x" || s[:2] == "0X") {
		v, err := strconv.ParseUint(s[2:], 16, 16)
		if err != nil {
			return 0, err
		}
		return uint16(v), nil
	}
	// Decimal
	v, err := strconv.ParseUint(s, 10, 16)
	if err != nil {
		return 0, err
	}
	return uint16(v), nil
}
