package parser

import (
	"github.com/marzeq/quokka/shared"
	"github.com/marzeq/quokka/tokeniser"
)

func (p *Parser) ParseDeclaration() (*Node, error) {
	beginLoc := p.CurrLoc()

	kw, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_KEYWORD)
	if !ok || kw.Value != "let" && kw.Value != "var" {
		return nil, shared.NewError(p.PrevLoc(), "expected `let` or `var` keyword")
	}

	identLoc := p.CurrLoc()
	ident, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
	if !ok {
		return nil, shared.NewError(p.PrevLoc(), "expected name")
	}

	var tpe *Node
	if p.Match(tokeniser.TOKEN_TYPE_COLON) {
		p.Inc()

		ident, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
		if !ok {
			return nil, shared.NewError(p.PrevLoc(), "expected type name")
		}
		tpe = &Node{Type: NODE_TYPE_IDENTIFIER, Value: ident.Value}
	}

	if !p.Expect(tokeniser.TOKEN_TYPE_EQUALS) {
		return nil, shared.NewError(p.PrevLoc(), "expected '='")
	}

	for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
		p.Inc()
	}

	expr, err := p.ParseExpression()
	if err != nil {
		return nil, err
	}

	return &Node{
		Type: NODE_TYPE_DECLARATION,
		Value: &DeclarationValue{
			Mutable: kw.Value == "var",
			Type:    tpe,
		},
		Left: &Node{
			Type:  NODE_TYPE_IDENTIFIER,
			Value: ident.Value,
			Loc:   identLoc,
		},
		Right: expr,
		Loc:   beginLoc,
	}, nil
}

func (p *Parser) ParseAssignment() (*Node, error) {
	identLoc := p.CurrLoc()
	ident, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
	if !ok {
		return nil, shared.NewError(p.PrevLoc(), "expected name")
	}

	if !p.Expect(tokeniser.TOKEN_TYPE_EQUALS) {
		return nil, shared.NewError(p.PrevLoc(), "expected '='")
	}

	for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
		p.Inc()
	}

	expr, err := p.ParseExpression()
	if err != nil {
		return nil, err
	}

	return &Node{
		Type: NODE_TYPE_ASSIGNMENT,
		Left: &Node{
			Type:  NODE_TYPE_IDENTIFIER,
			Value: ident.Value,
			Loc:   identLoc,
		},
		Right: expr,
		Loc:   identLoc,
	}, err
}

func (p *Parser) ParseAssignmentBy() (*Node, error) {
	identLoc := p.CurrLoc()
	ident, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
	if !ok {
		return nil, shared.NewError(p.PrevLoc(), "expected name")
	}

	if !p.Match(tokeniser.TOKEN_TYPE_INC_BY, tokeniser.TOKEN_TYPE_DEC_BY, tokeniser.TOKEN_TYPE_MUL_BY, tokeniser.TOKEN_TYPE_DIV_BY) {
		return nil, shared.NewError(p.PrevLoc(), "expected '+=', '-=', '*=' or '/='")
	}

	opType := p.Consume()
	var op tokeniser.TokenType

	switch opType.Type {
	case tokeniser.TOKEN_TYPE_INC_BY:
		op = tokeniser.TOKEN_TYPE_PLUS
	case tokeniser.TOKEN_TYPE_DEC_BY:
		op = tokeniser.TOKEN_TYPE_MINUS
	case tokeniser.TOKEN_TYPE_MUL_BY:
		op = tokeniser.TOKEN_TYPE_ASTERISK
	case tokeniser.TOKEN_TYPE_DIV_BY:
		op = tokeniser.TOKEN_TYPE_SLASH
	}

	for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
		p.Inc()
	}

	expr, err := p.ParseExpression()
	if err != nil {
		return nil, err
	}

	identNode := &Node{
		Type:  NODE_TYPE_IDENTIFIER,
		Value: ident.Value,
		Loc:   identLoc,
	}

	return &Node{
		Type: NODE_TYPE_ASSIGNMENT,
		Left: identNode,
		Right: &Node{
			Type:  NODE_TYPE_BINARY_OP,
			Value: op,
			Left:  identNode,
			Right: expr,
		},
		Loc: identLoc,
	}, err
}

func (p *Parser) ParseBlock() (*Node, error) {
	beginLoc := p.CurrLoc()
	if !p.Expect(tokeniser.TOKEN_TYPE_OPEN_CURLY) {
		return nil, shared.NewError(p.PrevLoc(), "expected '{' to start block")
	}

	for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
		p.Inc()
	}

	var children []*Node
	for !p.Match(tokeniser.TOKEN_TYPE_CLOSE_CURLY, tokeniser.TOKEN_TYPE_EOF) {
		stmt, err := p.ParseStatement()
		if err != nil {
			return nil, err
		}
		children = append(children, stmt)

		if p.Match(tokeniser.TOKEN_TYPE_CLOSE_CURLY) {
			break
		}

		if !p.Match(tokeniser.TOKEN_TYPE_SEMICOLON, tokeniser.TOKEN_TYPE_NEWLINE) {
			return nil, shared.NewError(p.CurrLoc(), "expected ';' or '\\n' to end statement")
		}

		for p.Match(tokeniser.TOKEN_TYPE_SEMICOLON, tokeniser.TOKEN_TYPE_NEWLINE) {
			p.Inc()
		}
	}

	if !p.Expect(tokeniser.TOKEN_TYPE_CLOSE_CURLY) {
		return nil, shared.NewError(p.PrevLoc(), "expected '}' to close block")
	}

	return &Node{
		Type:     NODE_TYPE_BLOCK,
		Children: children,
		Loc:      beginLoc,
	}, nil
}

func (p *Parser) ParseFunctionDefinition() (*Node, error) {
	beginLoc := p.CurrLoc()
	if !p.Expect(tokeniser.TOKEN_TYPE_KEYWORD) {
		return nil, shared.NewError(p.PrevLoc(), "expected 'let' keyword")
	}

	nameLoc := p.CurrLoc()
	name, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
	if !ok {
		return nil, shared.NewError(p.PrevLoc(), "expected function name")
	}

	if !p.Expect(tokeniser.TOKEN_TYPE_OPEN_PAREN) {
		return nil, shared.NewError(p.PrevLoc(), "expected '('")
	}

	var args []shared.Pair[*Node, *Node]
	for !p.Match(tokeniser.TOKEN_TYPE_CLOSE_PAREN) {
		argNameLoc := p.CurrLoc()
		argName, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
		if !ok {
			return nil, shared.NewError(p.PrevLoc(), "expected argument name")
		}

		if !p.Expect(tokeniser.TOKEN_TYPE_COLON) {
			return nil, shared.NewError(p.PrevLoc(), "expected ':'")
		}

		argTypeLoc := p.CurrLoc()
		argType, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
		if !ok {
			return nil, shared.NewError(p.PrevLoc(), "expected argument type")
		}

		args = append(args, shared.Pair[*Node, *Node]{
			L: &Node{Type: NODE_TYPE_IDENTIFIER, Value: argName.Value, Loc: argNameLoc},
			R: &Node{Type: NODE_TYPE_IDENTIFIER, Value: argType.Value, Loc: argTypeLoc},
		})

		if !p.Match(tokeniser.TOKEN_TYPE_COMMA) {
			break
		}
		p.Consume()
	}

	if !p.Expect(tokeniser.TOKEN_TYPE_CLOSE_PAREN) {
		return nil, shared.NewError(p.PrevLoc(), "expected ')'")
	}

	if !p.Expect(tokeniser.TOKEN_TYPE_COLON) {
		return nil, shared.NewError(p.PrevLoc(), "expected ':'")
	}

	retTypeLoc := p.CurrLoc()
	returnType, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
	if !ok {
		return nil, shared.NewError(p.PrevLoc(), "expected return type")
	}

	if !p.Expect(tokeniser.TOKEN_TYPE_EQUALS) {
		return nil, shared.NewError(p.PrevLoc(), "expected '='")
	}

	for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
		p.Inc()
	}

	var body *Node

	if p.Match(tokeniser.TOKEN_TYPE_OPEN_CURLY) {
		b, err := p.ParseBlock()
		if err != nil {
			return nil, err
		}
		body = b
	} else {
		b, err := p.ParseExpression()
		if err != nil {
			return nil, err
		}
		body = b
	}

	return &Node{
		Type: NODE_TYPE_FUNCTION_DEF,
		Value: &FunctionValue{
			Name:    &Node{Type: NODE_TYPE_IDENTIFIER, Value: name.Value, Loc: nameLoc},
			Args:    args,
			RetType: &Node{Type: NODE_TYPE_IDENTIFIER, Value: returnType.Value, Loc: retTypeLoc},
		},
		Children: []*Node{body},
		Loc:      beginLoc,
	}, nil
}

func (p *Parser) ParseImport() (*Node, error) {
	beginLoc := p.CurrLoc()
	if kw, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_KEYWORD); !ok || kw.Value != "import" {
		return nil, shared.NewError(p.PrevLoc(), "expected 'import' keyword")
	}

	strLoc := p.CurrLoc()
	path, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_STRING)
	if !ok {
		return nil, shared.NewError(p.PrevLoc(), "expected pat")
	}

	return &Node{
		Type:  NODE_TYPE_IMPORT,
		Right: &Node{Type: NODE_TYPE_STRING_LITERAL, Value: path.Value, Loc: strLoc},
		Loc:   beginLoc,
	}, nil
}

func (p *Parser) ParseStatement() (*Node, error) {
	if p.Match(tokeniser.TOKEN_TYPE_KEYWORD) {
		kw := p.Peek().Value
		switch kw {
		case "let":
			p.Inc()
			if !p.Expect(tokeniser.TOKEN_TYPE_IDENT) {
				return nil, shared.NewError(p.PrevLoc(), "expected name")
			}
			p.Dec()
			isParen := p.Next().Type == tokeniser.TOKEN_TYPE_OPEN_PAREN
			p.Dec()
			if isParen {
				return p.ParseFunctionDefinition()
			}
			return p.ParseDeclaration()
		case "var":
			return p.ParseDeclaration()
		case "return", "naked_return", "break", "continue":
			return p.ParseControlKeyword()
		case "if":
			return p.ParseIfStatement()
		case "for":
			return p.ParseForLoop()
		default:
			return nil, shared.NewError(p.CurrLoc(), "unexpected keyword: %s", kw)
		}
	}

	if p.Match(tokeniser.TOKEN_TYPE_IDENT) {
		ident := p.Consume()

		if p.Match(tokeniser.TOKEN_TYPE_EQUALS) {
			p.Dec()
			return p.ParseAssignment()
		} else if p.Match(tokeniser.TOKEN_TYPE_INC_BY, tokeniser.TOKEN_TYPE_DEC_BY, tokeniser.TOKEN_TYPE_MUL_BY, tokeniser.TOKEN_TYPE_DIV_BY) {
			p.Dec()
			return p.ParseAssignmentBy()
		} else if p.Match(tokeniser.TOKEN_TYPE_OPEN_PAREN) {
			return p.ParseFunctionCall(ident)
		}

		return nil, shared.NewError(p.CurrLoc(), "unexpected token after identifier %s", p.Peek())
	}

	if p.Match(tokeniser.TOKEN_TYPE_OPEN_CURLY) {
		return p.ParseBlock()
	}

	return nil, shared.NewError(p.CurrLoc(), "unexpected token %s", p.Peek())
}

func (p *Parser) ParseIfStatement() (*Node, error) {
	beginLoc := p.CurrLoc()
	if !p.Expect(tokeniser.TOKEN_TYPE_KEYWORD) {
		return nil, shared.NewError(p.PrevLoc(), "expected 'if'")
	}

	for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
		p.Inc()
	}

	condition, err := p.ParseExpression()
	if err != nil {
		return nil, err
	}

	for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
		p.Inc()
	}

	thenBlock, err := p.ParseBlock()
	if err != nil {
		return nil, err
	}

	for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
		p.Inc()
	}

	ifBranch := &IfBranch{
		Condition: condition,
		Node:      thenBlock,
	}

	ifNodeValue := &IfNodeValue{
		IfBranch:       ifBranch,
		ElseIfBranches: []*IfBranch{},
	}

	for p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "else" {
		p.Consume()

		for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
			p.Inc()
		}

		if p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "if" {
			p.Consume()

			for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
				p.Inc()
			}

			elseifCondition, err := p.ParseExpression()
			if err != nil {
				return nil, err
			}

			for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
				p.Inc()
			}

			elseifBlock, err := p.ParseBlock()
			if err != nil {
				return nil, err
			}

			elseIfBranch := &IfBranch{
				Condition: elseifCondition,
				Node:      elseifBlock,
			}
			ifNodeValue.ElseIfBranches = append(ifNodeValue.ElseIfBranches, elseIfBranch)
		} else {
			elseBlock, err := p.ParseBlock()
			if err != nil {
				return nil, err
			}

			ifNodeValue.ElseBranch = &IfBranch{
				Condition: nil,
				Node:      elseBlock,
			}
			break
		}
	}

	return &Node{
		Type:  NODE_TYPE_IF,
		Value: ifNodeValue,
		Loc:   beginLoc,
	}, nil
}

func (p *Parser) ParseControlKeyword() (*Node, error) {
	loc := p.CurrLoc()
	kw, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_KEYWORD)
	if !ok {
		return nil, shared.NewError(p.PrevLoc(), "expected 'return', 'break' or 'continue'")
	}

	var expr *Node
	if kw.Value == "return" && !p.Match(tokeniser.TOKEN_TYPE_NEWLINE, tokeniser.TOKEN_TYPE_SEMICOLON) {
		got, err := p.ParseExpression()
		if err != nil {
			return nil, err
		}
		expr = got
	}

	return &Node{
		Type:  NODE_TYPE_CONTROL_KEYWORD,
		Value: kw.Value,
		Right: expr,
		Loc:   loc,
	}, nil
}

func (p *Parser) ParseForLoop() (*Node, error) {
	beginLoc := p.CurrLoc()
	if !p.Expect(tokeniser.TOKEN_TYPE_KEYWORD) {
		return nil, shared.NewError(p.PrevLoc(), "expected 'for'")
	}

	for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
		p.Inc()
	}

	var err error

	exprsOrStmts := []*Node{}

	for !p.Match(tokeniser.TOKEN_TYPE_OPEN_CURLY) {
		ogLoc := p.CurrLoc()
		ogPos := p.pos
		exOrSt, err := p.ParseStatement()
		if err != nil {
			p.pos = ogPos
			exOrSt, err = p.ParseExpression()
			if err != nil {
				return nil, shared.NewError(ogLoc, "expected a valid statement or expression")
			}
		}

		exprsOrStmts = append(exprsOrStmts, exOrSt)

		if p.Match(tokeniser.TOKEN_TYPE_OPEN_CURLY) {
			break
		}

		if !p.Expect(tokeniser.TOKEN_TYPE_SEMICOLON) {
			return nil, shared.NewError(p.PrevLoc(), "expected ';' to end statement or expression")
		}

		for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
			p.Inc()
		}
	}

	for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
		p.Inc()
	}

	body, err := p.ParseBlock()
	if err != nil {
		return nil, err
	}

	return &Node{
		Type: NODE_TYPE_FOR,
		Value: &ForLoopValue{
			ExprsOrStmts: exprsOrStmts,
			Body:         body,
		},
		Loc: beginLoc,
	}, nil
}