package parser

import (
	"errors"

	"github.com/marzeq/qk/attributes"
	"github.com/marzeq/qk/shared"
	"github.com/marzeq/qk/tokeniser"
)

func (p *Parser) parseLinkItems(close tokeniser.TokenKind) ([]LinkItemNode, bool, error) {
	var items []LinkItemNode
	conditional := false
	for {
		for p.Match(tokeniser.TokenNewline, tokeniser.TokenComma) {
			p.Inc()
		}
		if p.Match(close) {
			return items, conditional, nil
		}
		if p.Match(tokeniser.TokenEof) {
			return nil, false, shared.NewError(p.CurrLoc(), "expected closing delimiter in @link")
		}
		if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordWhen) {
			when, err := p.parseLinkWhen()
			if err != nil {
				return nil, false, err
			}
			items = append(items, LinkItemNode{When: when})
			conditional = true
			continue
		}
		if p.MatchBuiltin("compiler_error") {
			directive, err := p.parseCompilerDirective()
			if err != nil {
				return nil, false, err
			}
			items = append(items, LinkItemNode{Directive: directive})
			conditional = true
			continue
		}
		kindToken, ok := p.ExpectGet(tokeniser.TokenIdentifier)
		if !ok {
			return nil, false, shared.NewError(p.PrevLoc(), "expected complete link entry or 'when' in @link")
		}
		var kind attributes.LinkKind
		switch kindToken.Value {
		case "system":
			kind = attributes.LinkSystem
		case "path":
			kind = attributes.LinkPath
		case "search":
			kind = attributes.LinkSearchPath
		case "framework":
			kind = attributes.LinkFramework
		default:
			return nil, false, shared.NewError(kindToken.Loc, "unknown @link entry kind %q", kindToken.Value)
		}
		value, ok := p.ExpectGet(tokeniser.TokenString)
		if !ok {
			return nil, false, shared.NewError(p.PrevLoc(), "expected string after %s in @link", kindToken.Value)
		}
		if value.Value == "" {
			return nil, false, shared.NewError(value.Loc, "@link values cannot be empty")
		}
		link := attributes.Link{Kind: kind, Value: value.Value}
		items = append(items, LinkItemNode{Link: &link})
	}
}

func (p *Parser) parseLinkWhen() (*LinkWhenNode, error) {
	begin := p.CurrLoc()
	node := &LinkWhenNode{}
	for {
		if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordWhen) {
			return nil, shared.NewError(p.CurrLoc(), "expected 'when' in @link")
		}
		p.Inc()
		condition, err := p.parseWhenCondition()
		if err != nil {
			return nil, err
		}
		if !p.Expect(tokeniser.TokenOpenCurly) {
			return nil, shared.NewError(p.PrevLoc(), "expected '{' after @link condition")
		}
		items, _, err := p.parseLinkItems(tokeniser.TokenCloseCurly)
		if err != nil {
			return nil, err
		}
		p.Inc()
		node.Branches = append(node.Branches, LinkWhenBranchNode{Condition: condition, Items: items})
		branchEnd := p.pos
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordElse) {
			p.pos = branchEnd
			break
		}
		p.Inc()
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordWhen) {
			continue
		}
		if !p.Expect(tokeniser.TokenOpenCurly) {
			return nil, shared.NewError(p.PrevLoc(), "expected '{' after else in @link")
		}
		node.ElseItems, _, err = p.parseLinkItems(tokeniser.TokenCloseCurly)
		if err != nil {
			return nil, err
		}
		p.Inc()
		break
	}
	node.Loc = p.SpanFrom(begin)
	return node, nil
}

func (p *Parser) parseWhen(context WhenContext) (*WhenNode, error) {
	begin := p.CurrLoc()
	node := &WhenNode{Context: context}
	for {
		if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordWhen) {
			return nil, shared.NewError(p.CurrLoc(), "expected 'when'")
		}
		branchBegin := p.CurrLoc()
		p.Inc()
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		condition, err := p.parseWhenCondition()
		if err != nil {
			return nil, err
		}
		body, value, err := p.parseWhenBody(context)
		if err != nil {
			return nil, err
		}
		node.Branches = append(node.Branches, WhenBranchNode{
			Condition: condition, Body: body, Value: value, Loc: branchBegin.WithEnd(p.PrevLoc()),
		})
		branchEnd := p.pos
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordElse) {
			p.pos = branchEnd
			break
		}
		p.Inc()
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordWhen) {
			continue
		}
		body, value, err = p.parseWhenBody(context)
		if err != nil {
			return nil, err
		}
		node.ElseBody, node.ElseValue = body, value
		break
	}
	node.Loc = p.SpanFrom(begin)
	return node, nil
}

func (p *Parser) parseWhenCondition() (ExpressionNode, error) {
	start := p.pos
	parenDepth, squareDepth := 0, 0
	for !p.Match(tokeniser.TokenEof) {
		switch p.Peek().Type {
		case tokeniser.TokenOpenParen:
			parenDepth++
		case tokeniser.TokenCloseParen:
			parenDepth--
		case tokeniser.TokenOpenSquare:
			squareDepth++
		case tokeniser.TokenCloseSquare:
			squareDepth--
		case tokeniser.TokenOpenCurly:
			if parenDepth == 0 && squareDepth == 0 {
				tokens := make([]tokeniser.Token, 0, p.pos-start+1)
				for _, token := range p.tokens[start:p.pos] {
					if token.Type != tokeniser.TokenNewline {
						tokens = append(tokens, token)
					}
				}
				if len(tokens) == 0 {
					return nil, shared.NewError(p.CurrLoc(), "expected condition after 'when'")
				}
				tokens = append(tokens, tokeniser.Token{Type: tokeniser.TokenEof, Loc: tokens[len(tokens)-1].Loc})
				return NewParser(tokens).ParseWholeExpression()
			}
		}
		p.Inc()
	}
	return nil, shared.NewError(p.CurrLoc(), "expected '{' after compile-time condition")
}

func (p *Parser) parseWhenBody(context WhenContext) ([]Node, Node, error) {
	switch context {
	case WhenDeclarations:
		body, err := p.parseWhenDeclarationBody()
		return body, nil, err
	case WhenStatements:
		block, err := p.ParseBlock()
		if err != nil {
			return nil, nil, err
		}
		return block.Body, nil, nil
	case WhenExpression:
		block, err := p.ParseBlockExpression()
		return nil, block, err
	case WhenType:
		if !p.Expect(tokeniser.TokenOpenCurly) {
			return nil, nil, shared.NewError(p.PrevLoc(), "expected '{' after compile-time condition")
		}
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if p.MatchBuiltin("compiler_error") || p.MatchBuiltin("compiler_assert") {
			directive, err := p.parseCompilerDirective()
			if err != nil {
				return nil, nil, err
			}
			for p.Match(tokeniser.TokenNewline, tokeniser.TokenSemicolon) {
				p.Inc()
			}
			if !p.Expect(tokeniser.TokenCloseCurly) {
				return nil, nil, shared.NewError(p.PrevLoc(), "expected '}' after compiler directive")
			}
			return nil, directive, nil
		}
		typeNode, err := p.ParseType()
		if err != nil {
			return nil, nil, err
		}
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if !p.Expect(tokeniser.TokenCloseCurly) {
			return nil, nil, shared.NewError(p.PrevLoc(), "expected '}' after selected type")
		}
		return nil, typeNode, nil
	default:
		panic("unknown when context")
	}
}

func (p *Parser) parseWhenDeclarationBody() ([]Node, error) {
	if !p.Expect(tokeniser.TokenOpenCurly) {
		return nil, shared.NewError(p.PrevLoc(), "expected '{' after compile-time condition")
	}
	var body []Node
	var parseErrors []error
	for !p.Match(tokeniser.TokenCloseCurly, tokeniser.TokenEof) {
		for p.Match(tokeniser.TokenNewline, tokeniser.TokenSemicolon) {
			p.Inc()
		}
		if p.Match(tokeniser.TokenCloseCurly) {
			break
		}
		node, err := p.parseTopLevel()
		if err != nil {
			parseErrors = append(parseErrors, err)
			p.synchroniseTopLevel()
			continue
		}
		body = append(body, node)
		if !p.Match(tokeniser.TokenCloseCurly, tokeniser.TokenNewline, tokeniser.TokenSemicolon) {
			parseErrors = append(parseErrors, shared.NewError(p.CurrLoc(), "expected declaration separator in when block"))
			p.synchroniseTopLevel()
		}
	}
	if !p.Expect(tokeniser.TokenCloseCurly) {
		parseErrors = append(parseErrors, shared.NewError(p.PrevLoc(), "expected '}' after when declarations"))
	}
	return body, errors.Join(parseErrors...)
}

func (p *Parser) parseCompilerDirective() (*CompilerDirectiveNode, error) {
	begin := p.CurrLoc()
	name := "compiler_error"
	if p.MatchBuiltin("compiler_assert") {
		name = "compiler_assert"
	}
	if !p.ConsumeBuiltin(name) || !p.Expect(tokeniser.TokenOpenParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected '(' after @%s", name)
	}
	nameEnd := p.tokens[p.pos-2].Loc
	node := &CompilerDirectiveNode{Name: name, NameLoc: begin.WithEnd(nameEnd)}
	if name == "compiler_assert" {
		condition, err := p.ParseExpression()
		if err != nil {
			return nil, err
		}
		node.Condition = condition
		if !p.Expect(tokeniser.TokenComma) {
			return nil, shared.NewError(p.PrevLoc(), "expected ',' after @compiler_assert condition")
		}
	}
	message, ok := p.ExpectGet(tokeniser.TokenString)
	if !ok {
		return nil, shared.NewError(p.PrevLoc(), "expected string message in @%s", name)
	}
	node.Message = message.Value
	if !p.Expect(tokeniser.TokenCloseParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected ')' after @%s", name)
	}
	node.Loc = p.SpanFrom(begin)
	return node, nil
}