// Package azalea implements a proof-of-concept, domain-specific language for
// Rosa OS software packaging.
package azalea

import (
	"errors"
	"io"
	"strconv"
	"text/scanner"
)

// idents are runes accepted in an identifier.
var idents = [...]bool{
	'0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true,
	'7': true, '8': true, '9': true,

	'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true,
	'H': true, 'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true,
	'O': true, 'P': true, 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true,
	'V': true, 'W': true, 'X': true, 'Y': true, 'Z': true,

	'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true,
	'h': true, 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true,
	'o': true, 'p': true, 'q': true, 'r': true, 's': true, 't': true, 'u': true,
	'v': true, 'w': true, 'x': true, 'y': true, 'z': true,

	'-': true, '_': true,
}

// TokenError describes an unexpected token.
type TokenError [2]rune

func (e TokenError) Error() string {
	return "expected " + scanner.TokenString(e[0]) +
		", found " + scanner.TokenString(e[1])
}

// ExprError is an unexpected token encountered while parsing an expression.
type ExprError rune

func (e ExprError) Error() string {
	return "unexpected token " + scanner.TokenString(rune(e))
}

// must1 returns v, or panics if err is not nil.
func must1[T any](v T, err error) T {
	if err != nil {
		panic(err)
	}
	return v
}

// parser retains the current token.
type parser struct {
	s   scanner.Scanner
	tok rune
}

// scan advances the underlying scanner to the next token, storing its result.
func (p *parser) scan() rune { p.tok = p.s.Scan(); return p.tok }

// expects panics with [TokenError] for an unexpected tok.
func (p *parser) expects(expects rune) {
	if p.tok != expects {
		panic(TokenError{expects, p.tok})
	}
}

// scanAs advances the scanner for an expected token.
func (p *parser) scanAs(expects rune) { p.scan(); p.expects(expects) }

// An Int is the value represented by an integer literal.
type Int int64

func (v Int) GoString() string {
	return "azalea.Int(" + strconv.FormatInt(int64(v), 10) + ")"
}

// parseInt parses the current token as a base 10 representation of a 64-bit
// signed integer.
func (p *parser) parseInt() Int {
	v, err := strconv.ParseInt(p.s.TokenText(), 10, 64)
	return must1(Int(v), err)
}

// A String holds the unquoted content of a string literal.
type String string

func (v String) GoString() string {
	return "azalea.String(" + strconv.Quote(string(v)) + ")"
}

// parseString parses the current token as a string.
func (p *parser) parseString() String {
	s, err := strconv.Unquote(p.s.TokenText())
	return must1(String(s), err)
}

// An Ident holds the name of an identifier.
type Ident string

func (v Ident) GoString() string {
	return "azalea.Ident(" + strconv.Quote(string(v)) + ")"
}

// A Val are statements joined by the '+' operator. Only the [String] type
// supports concatenation.
type Val []any

// parseVal parses until the end of the [Val].
func (p *parser) parseVal() (v Val) {
	v = append(v, p.parseExpr())
	for p.tok == '+' {
		p.scan()
		v = append(v, p.parseExpr())
	}
	return
}

// An Array holds statements in an array.
type Array []Val

// A KV holds a key/value pair.
type KV struct {
	K String
	V Val
}

// An Arg represents an argument of [Func].
type Arg struct {
	K []Ident
	V Val
	R bool
}

// Func is a function call or package declaration.
type Func struct {
	// Function or package identifier.
	Ident Ident
	// Whether this is a package declaration.
	Package bool
	// Key-value arguments.
	Args []Arg
}

// parseExpr parses the current expression.
func (p *parser) parseExpr() any {
	switch p.tok {
	case scanner.Int:
		v := p.parseInt()
		p.scan()
		return v

	case scanner.String, scanner.RawString:
		v := p.parseString()
		p.scan()
		return v

	case scanner.Ident:
		var v Func
		v.Ident = Ident(p.s.TokenText())
		if v.Package = v.Ident == "package"; v.Package {
			p.scanAs(scanner.Ident)
			v.Ident = Ident(p.s.TokenText())
		}

		p.scan()
		switch p.tok {
		case '{':
			for {
				p.scan()
				switch p.tok {
				case '}':
					p.scan()
					return v

				case scanner.Ident:
					break

				default:
					panic(TokenError{scanner.Ident, p.tok})
				}

				arg := Arg{K: []Ident{Ident(p.s.TokenText())}}
				delim := true
			arg:
				for {
					p.scan()
					switch p.tok {
					case ',':
						if delim {
							delim = false
							continue
						}
						panic(ExprError(p.tok))

					case scanner.Ident:
						if delim {
							panic(TokenError{',', p.tok})
						}
						delim = true
						arg.K = append(arg.K, Ident(p.s.TokenText()))

					default:
						break arg
					}
				}
				switch p.tok {
				case '=':
					break

				case '#':
					arg.R = true
					p.scanAs('=')

				default:
					panic(TokenError{'=', p.tok})
				}
				p.scan()
				arg.V = p.parseVal()
				v.Args = append(v.Args, arg)
				p.expects(';')
			}

		default:
			return v.Ident
		}

	case '{':
		var v []KV
		for {
			p.scan()
			switch p.tok {
			case '}':
				p.scan()
				return v

			case scanner.String:
				pair := KV{K: p.parseString()}
				p.scan()
				switch p.tok {
				case ';':
					break

				case ':':
					p.scan()
					pair.V = p.parseVal()
					p.expects(';')
					break

				default:
					panic(ExprError(p.tok))
				}
				v = append(v, pair)

			default:
				panic(ExprError(p.tok))
			}
		}

	case '[':
		var (
			v     Array
			delim bool
		)
		p.scan()
		for {
			switch p.tok {
			case ',':
				if delim {
					p.scan()
					delim = false
					continue
				}
				panic(ExprError(','))
			case ']':
				p.scan()
				return v
			case scanner.EOF:
				panic(ExprError(scanner.EOF))
			default:
				if delim {
					panic(TokenError{',', p.tok})
				}
				delim = true
				break
			}
			v = append(v, p.parseVal())
		}

	default:
		panic(ExprError(p.tok))
	}
}

// ScanError is the error count parsing all expressions.
type ScanError int

func (ScanError) Error() string {
	return "aborting due to scanning errors"
}

// Parse parses expressions from r.
func Parse(r io.Reader) (e []any, err error) {
	var p parser
	p.s.Init(r)

	p.s.Mode = scanner.ScanIdents |
		scanner.ScanInts |
		scanner.ScanStrings |
		scanner.ScanRawStrings |
		scanner.ScanComments |
		scanner.SkipComments
	p.s.IsIdentRune = func(ch rune, i int) bool {
		if i == 0 && ch >= '0' && ch <= '9' {
			return false
		}
		return ch > 0 && ch < rune(len(idents)) && idents[ch]
	}

	defer func() {
		v := recover()
		if v == nil {
			return
		}

		_err, ok := v.(error)
		if !ok {
			panic(v)
		}

		if err == nil {
			err = _err
			return
		}
		err = errors.Join(err, _err)
	}()

	p.scan()
	for p.tok != scanner.EOF {
		e = append(e, p.parseExpr())
	}

	if p.s.ErrorCount != 0 {
		err = ScanError(p.s.ErrorCount)
	}
	return
}
