package preproc

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

type FileReader interface {
	ReadLines(includeSpec string, currentDir string) ([]string, string, error)
}

type DiskFileReader struct {
	cache   map[string][]string
	libPath string
}

func NewDiskFileReader() *DiskFileReader {
	return &DiskFileReader{
		cache:   make(map[string][]string),
		libPath: os.Getenv("C65LIBPATH"),
	}
}

func (d *DiskFileReader) ReadLines(includeSpec string, currentDir string) ([]string, string, error) {
	path, err := d.resolvePath(includeSpec, currentDir)
	if err != nil {
		return nil, "", err
	}

	abs, err := filepath.Abs(path)
	if err != nil {
		return nil, "", err
	}

	if lines, ok := d.cache[abs]; ok {
		return lines, abs, nil
	}

	data, err := os.ReadFile(abs)
	if err != nil {
		return nil, "", err
	}

	lines := strings.Split(string(data), "\n")
	d.cache[abs] = lines
	return lines, abs, nil
}

func (d *DiskFileReader) resolvePath(spec string, curDir string) (string, error) {
	if len(spec) == 0 {
		return "", errors.New("empty include path")
	}

	// <file> -> library include
	if strings.HasPrefix(spec, "<") && strings.HasSuffix(spec, ">") {
		base := strings.TrimSpace(spec[1 : len(spec)-1])
		if d.libPath == "" {
			return "", errors.New("C65LIBPATH not set for angle-bracket include")
		}
		path := filepath.Join(d.libPath, base)
		if _, err := os.Stat(path); err != nil {
			return "", fmt.Errorf("library include not found: %s", path)
		}
		return path, nil
	}

	// quoted or bare -> relative to current dir
	base := strings.Trim(spec, `"'`)
	path := filepath.Join(curDir, base)
	if _, err := os.Stat(path); err != nil {
		return "", fmt.Errorf("include not found: %s", path)
	}
	return path, nil
}

type MockFileReader struct {
	files map[string][]string
}

func NewMockFileReader(files map[string][]string) *MockFileReader {
	return &MockFileReader{
		files: files,
	}
}

func (m *MockFileReader) ReadLines(includeSpec string, _ string) ([]string, string, error) {
	lines, ok := m.files[includeSpec]
	if !ok {
		return nil, "", fmt.Errorf("open %s: no such file or directory", includeSpec)
	}
	return lines, includeSpec, nil
}

// NewDefaultFileReader creates the appropriate FileReader based on C65LIBPATH environment variable.
// If C65LIBPATH is set, returns a DiskFileReader for external library access.
// If C65LIBPATH is not set, returns a HybridFileReader that uses embedded standard library.
func NewDefaultFileReader() FileReader {
	libPath := os.Getenv("C65LIBPATH")
	if libPath != "" {
		fmt.Printf("Using external library from: %s\n", libPath)
		return NewDiskFileReader()
	}
	fmt.Println("Using embedded standard library")
	return NewHybridFileReader()
}
