package erlang

import (
	"bytes"
	"errors"
	"fmt"
	"io"

	"github.com/anchore/syft/syft/internal/parsing"
)

type erlangNode struct {
	value any
}

var errSkipComments = errors.New("")

func (e erlangNode) Slice() []erlangNode {
	out, ok := e.value.([]erlangNode)
	if ok {
		return out
	}
	return []erlangNode{}
}

func (e erlangNode) String() string {
	out, ok := e.value.(string)
	if ok {
		return out
	}
	return ""
}

func (e erlangNode) Get(index int) erlangNode {
	s := e.Slice()
	if len(s) > index {
		return s[index]
	}
	return erlangNode{}
}

func node(value any) erlangNode {
	return erlangNode{
		value: value,
	}
}

// parseErlang basic parser for erlang, used by rebar.lock
func parseErlang(reader io.Reader) (erlangNode, error) {
	data, err := io.ReadAll(reader) //nolint:gocritic // custom parser requires []byte
	if err != nil {
		return node(nil), err
	}

	out := erlangNode{
		value: []erlangNode{},
	}

	i := 0
	for i < len(data) {
		item, err := parseErlangBlock(data, &i)
		if err == errSkipComments {
			parsing.SkipWhitespace(data, &i)
			continue
		}
		if err != nil {
			return node(nil), fmt.Errorf("%w\n%s", err, parsing.PrintError(data, i))
		}

		parsing.SkipWhitespace(data, &i)

		if i, ok := item.value.(string); ok && i == "." {
			continue
		}

		out.value = append(out.value.([]erlangNode), item)
	}
	return out, nil
}

func parseErlangBlock(data []byte, i *int) (erlangNode, error) {
	block, err := parseErlangNode(data, i)
	if err != nil {
		return node(nil), err
	}

	parsing.SkipWhitespace(data, i)
	*i++ // skip the trailing .
	return block, nil
}

func parseErlangNode(data []byte, i *int) (erlangNode, error) {
	parsing.SkipWhitespace(data, i)
	c := data[*i]
	switch c {
	case '[', '{':
		offset := *i + 1
		parsing.SkipWhitespace(data, &offset)
		c2 := data[offset]

		// Add support for empty lists
		if (c == '[' && c2 == ']') || (c == '{' && c2 == '}') {
			*i = offset + 1
			return node(nil), nil
		}

		return parseErlangList(data, i)
	case '"':
		fallthrough
	case '\'':
		return parseErlangString(data, i)
	case '<':
		return parseErlangAngleString(data, i)
	case '%':
		parseErlangComment(data, i)
		return node(nil), errSkipComments
	}

	if parsing.IsLiteral(c) {
		return parseErlangLiteral(data, i)
	}

	return erlangNode{}, fmt.Errorf("invalid literal character: %s", string(c))
}

func parseErlangLiteral(data []byte, i *int) (erlangNode, error) {
	var buf bytes.Buffer
	for *i < len(data) {
		c := data[*i]
		if parsing.IsLiteral(c) {
			buf.WriteByte(c)
		} else {
			break
		}
		*i++
	}
	return node(buf.String()), nil
}

func parseErlangAngleString(data []byte, i *int) (erlangNode, error) {
	*i += 2
	out, err := parseErlangString(data, i)
	*i += 2
	return out, err
}

func parseErlangString(data []byte, i *int) (erlangNode, error) {
	delim := data[*i]
	*i++
	var buf bytes.Buffer
	for *i < len(data) {
		c := data[*i]
		if c == delim {
			*i++
			return node(buf.String()), nil
		}
		if c == '\\' {
			*i++
			if *i >= len(data) {
				return node(nil), fmt.Errorf("invalid escape without closed string at %d", *i)
			}
			c = data[*i]
		}
		buf.WriteByte(c)
		*i++
	}
	return node(nil), fmt.Errorf("unterminated string at %d", *i)
}

func parseErlangList(data []byte, i *int) (erlangNode, error) {
	*i++
	out := erlangNode{
		value: []erlangNode{},
	}
	for *i < len(data) {
		item, err := parseErlangNode(data, i)
		if err != nil {
			if err == errSkipComments {
				parsing.SkipWhitespace(data, i)
				continue
			}
			return node(nil), err
		}
		out.value = append(out.value.([]erlangNode), item)
		parsing.SkipWhitespace(data, i)
		c := data[*i]
		switch c {
		case ',':
			*i++
			continue
		case '%':
			// Starts a new comment node
			continue
		case ']', '}':
			*i++
			return out, nil
		default:
			return node(nil), fmt.Errorf("unexpected character: %s", string(c))
		}
	}
	return out, nil
}

func parseErlangComment(data []byte, i *int) {
	for *i < len(data) {
		c := data[*i]

		*i++

		// Rest of a line is a comment. Deals with CR, LF and CR/LF
		if c == '\n' {
			break
		} else if c == '\r' && data[*i] == '\n' {
			*i++
			break
		}
	}
}
