package parsers

import (
	"fmt"
	"os"
	"strings"

	"github.com/johnkerl/pgpg/go/lib/pkg/asts"
	liblexers "github.com/johnkerl/pgpg/go/lib/pkg/lexers"
	"github.com/johnkerl/pgpg/go/lib/pkg/tokens"
)

type StatementsParser struct {
	Trace            *StatementsParserTraceHooks
	stashedLookahead *tokens.Token
}

type StatementsParserTraceHooks struct {
	OnToken  func(tok *tokens.Token)
	OnAction func(state int, action StatementsParserAction, lookahead *tokens.Token)
	OnStack  func(stateStack []int, nodeStack []*asts.ASTNode)
}

func NewStatementsParser() *StatementsParser { return &StatementsParser{} }

// noASTSentinel is used as a placeholder on the node stack when astMode == "noast".
var StatementsParserNoASTSentinel = &asts.ASTNode{}

func (parser *StatementsParser) Parse(lexer liblexers.AbstractLexer, astMode string) (*asts.AST, error) {
	if lexer == nil {
		return nil, fmt.Errorf("parser: nil lexer")
	}
	stateStack := []int{0}
	nodeStack := []*asts.ASTNode{}
	lookahead := lexer.Scan()
	if parser.Trace != nil && parser.Trace.OnToken != nil {
		parser.Trace.OnToken(lookahead)
	}
	for {
		if lookahead == nil {
			return nil, fmt.Errorf("parser: lexer returned nil token")
		}
		if lookahead.Type == tokens.TokenTypeError {
			return nil, fmt.Errorf("lexer error: %s", string(lookahead.Lexeme))
		}
		state := stateStack[len(stateStack)-1]
		action, ok := StatementsParserActions[state][lookahead.Type]
		if !ok {
			return nil, fmt.Errorf("parse error: unexpected %s (%q)", lookahead.Type, string(lookahead.Lexeme))
		}
		if parser.Trace != nil && parser.Trace.OnAction != nil {
			parser.Trace.OnAction(state, action, lookahead)
		}
		switch action.Kind {
		case StatementsParserActionShift:
			if astMode == "noast" {
				nodeStack = append(nodeStack, StatementsParserNoASTSentinel)
			} else {
				nodeStack = append(nodeStack, asts.NewASTNodeTerminal(lookahead, asts.NodeType(lookahead.Type)))
			}
			stateStack = append(stateStack, action.Target)
			lookahead = lexer.Scan()
			if parser.Trace != nil && parser.Trace.OnToken != nil {
				parser.Trace.OnToken(lookahead)
			}
			if parser.Trace != nil && parser.Trace.OnStack != nil {
				parser.Trace.OnStack(stateStack, nodeStack)
			}
		case StatementsParserActionReduce:
			prod := StatementsParserProductions[action.Target]
			rhsNodes := make([]*asts.ASTNode, prod.rhsCount)
			for i := prod.rhsCount - 1; i >= 0; i-- {
				stateStack = stateStack[:len(stateStack)-1]
				rhsNodes[i] = nodeStack[len(nodeStack)-1]
				nodeStack = nodeStack[:len(nodeStack)-1]
			}
			if astMode == "noast" {
				nodeStack = append(nodeStack, StatementsParserNoASTSentinel)
			} else {
				if prod.rhsCount == 0 {
					rhsNodes = []*asts.ASTNode{}
				}
				node := asts.NewASTNode(nil, prod.lhs, rhsNodes)
				nodeStack = append(nodeStack, node)
			}
			state = stateStack[len(stateStack)-1]
			nextState, ok := StatementsParserGotos[state][prod.lhs]
			if !ok {
				return nil, fmt.Errorf("parse error: missing goto for %s", prod.lhs)
			}
			stateStack = append(stateStack, nextState)
			if parser.Trace != nil && parser.Trace.OnStack != nil {
				parser.Trace.OnStack(stateStack, nodeStack)
			}
		case StatementsParserActionAccept:
			if len(nodeStack) != 1 {
				return nil, fmt.Errorf("parse error: unexpected parse stack size %d", len(nodeStack))
			}
			if parser.Trace != nil && parser.Trace.OnStack != nil {
				parser.Trace.OnStack(stateStack, nodeStack)
			}
			if astMode == "noast" {
				return nil, nil
			}
			return asts.NewAST(nodeStack[0]), nil
		case StatementsParserActionAcceptAndYield:
			return nil, fmt.Errorf("parse error: multiple objects; use ParseOne for multi-object input")
		default:
			return nil, fmt.Errorf("parse error: no action")
		}
	}
}

// ParseOne parses one record from the lexer. It is for multi-object input: call in a loop until done.
// Returns (ast, true, nil) on EOF after a record, (ast, false, nil) when more input follows, or (nil, false, err) on error.
func (parser *StatementsParser) ParseOne(lexer liblexers.AbstractLexer, astMode string) (*asts.AST, bool, error) {
	if lexer == nil {
		return nil, false, fmt.Errorf("parser: nil lexer")
	}
	stateStack := []int{0}
	nodeStack := []*asts.ASTNode{}
	var lookahead *tokens.Token
	if parser.stashedLookahead != nil {
		lookahead = parser.stashedLookahead
		parser.stashedLookahead = nil
	} else {
		lookahead = lexer.Scan()
	}
	if parser.Trace != nil && parser.Trace.OnToken != nil {
		parser.Trace.OnToken(lookahead)
	}
	for {
		if lookahead == nil {
			return nil, false, fmt.Errorf("parser: lexer returned nil token")
		}
		if lookahead.Type == tokens.TokenTypeError {
			return nil, false, fmt.Errorf("lexer error: %s", string(lookahead.Lexeme))
		}
		state := stateStack[len(stateStack)-1]
		action, ok := StatementsParserActions[state][lookahead.Type]
		if !ok {
			return nil, false, fmt.Errorf("parse error: unexpected %s (%q)", lookahead.Type, string(lookahead.Lexeme))
		}
		if parser.Trace != nil && parser.Trace.OnAction != nil {
			parser.Trace.OnAction(state, action, lookahead)
		}
		switch action.Kind {
		case StatementsParserActionShift:
			if astMode == "noast" {
				nodeStack = append(nodeStack, StatementsParserNoASTSentinel)
			} else {
				nodeStack = append(nodeStack, asts.NewASTNodeTerminal(lookahead, asts.NodeType(lookahead.Type)))
			}
			stateStack = append(stateStack, action.Target)
			lookahead = lexer.Scan()
			if parser.Trace != nil && parser.Trace.OnToken != nil {
				parser.Trace.OnToken(lookahead)
			}
			if parser.Trace != nil && parser.Trace.OnStack != nil {
				parser.Trace.OnStack(stateStack, nodeStack)
			}
		case StatementsParserActionReduce:
			prod := StatementsParserProductions[action.Target]
			rhsNodes := make([]*asts.ASTNode, prod.rhsCount)
			for i := prod.rhsCount - 1; i >= 0; i-- {
				stateStack = stateStack[:len(stateStack)-1]
				rhsNodes[i] = nodeStack[len(nodeStack)-1]
				nodeStack = nodeStack[:len(nodeStack)-1]
			}
			if astMode == "noast" {
				nodeStack = append(nodeStack, StatementsParserNoASTSentinel)
			} else {
				if prod.rhsCount == 0 {
					rhsNodes = []*asts.ASTNode{}
				}
				node := asts.NewASTNode(nil, prod.lhs, rhsNodes)
				nodeStack = append(nodeStack, node)
			}
			state = stateStack[len(stateStack)-1]
			nextState, ok := StatementsParserGotos[state][prod.lhs]
			if !ok {
				return nil, false, fmt.Errorf("parse error: missing goto for %s", prod.lhs)
			}
			stateStack = append(stateStack, nextState)
			if parser.Trace != nil && parser.Trace.OnStack != nil {
				parser.Trace.OnStack(stateStack, nodeStack)
			}
		case StatementsParserActionAccept:
			if len(nodeStack) != 1 {
				return nil, false, fmt.Errorf("parse error: unexpected parse stack size %d", len(nodeStack))
			}
			if parser.Trace != nil && parser.Trace.OnStack != nil {
				parser.Trace.OnStack(stateStack, nodeStack)
			}
			if astMode == "noast" {
				return nil, true, nil
			}
			return asts.NewAST(nodeStack[0]), true, nil
		case StatementsParserActionAcceptAndYield:
			if len(nodeStack) != 1 {
				return nil, false, fmt.Errorf("parse error: unexpected parse stack size %d", len(nodeStack))
			}
			if parser.Trace != nil && parser.Trace.OnStack != nil {
				parser.Trace.OnStack(stateStack, nodeStack)
			}
			parser.stashedLookahead = lookahead
			if astMode == "noast" {
				return nil, false, nil
			}
			return asts.NewAST(nodeStack[0]), false, nil
		default:
			return nil, false, fmt.Errorf("parse error: no action")
		}
	}
}

// AttachCLITrace installs tracing hooks for CLI debugging.
func (parser *StatementsParser) AttachCLITrace(traceTokens bool, traceStates bool, traceStack bool) {
	if !traceTokens && !traceStates && !traceStack {
		return
	}
	parser.Trace = &StatementsParserTraceHooks{
		OnToken: func(tok *tokens.Token) {
			if !traceTokens {
				return
			}
			fmt.Fprintln(os.Stderr, formatStatementsParserToken(tok))
		},
		OnAction: func(state int, action StatementsParserAction, lookahead *tokens.Token) {
			if !traceStates {
				return
			}
			fmt.Fprintf(os.Stderr, "STATE %d %s on %s(%q)\n",
				state, formatStatementsParserAction(action), tokenTypeNameStatementsParser(lookahead), tokenLexemeStatementsParser(lookahead))
		},
		OnStack: func(stateStack []int, nodeStack []*asts.ASTNode) {
			if !traceStack {
				return
			}
			fmt.Fprintf(os.Stderr, "STACK states=%s nodes=%s\n",
				formatStatementsParserIntStack(stateStack), formatStatementsParserNodeStack(nodeStack))
		},
	}
}

type StatementsParserActionKind int

const (
	StatementsParserActionShift StatementsParserActionKind = iota
	StatementsParserActionReduce
	StatementsParserActionAccept
	StatementsParserActionAcceptAndYield
)

type StatementsParserAction struct {
	Kind   StatementsParserActionKind
	Target int
}

func formatStatementsParserToken(tok *tokens.Token) string {
	if tok == nil {
		return "TOK <nil>"
	}
	return fmt.Sprintf("TOK type=%s lexeme=%q line=%d col=%d",
		tok.Type, string(tok.Lexeme), tok.Location.LineNumber, tok.Location.ColumnNumber)
}

func tokenTypeNameStatementsParser(tok *tokens.Token) string {
	if tok == nil {
		return "<nil>"
	}
	return string(tok.Type)
}

func tokenLexemeStatementsParser(tok *tokens.Token) string {
	if tok == nil {
		return ""
	}
	return string(tok.Lexeme)
}

func formatStatementsParserIntStack(stack []int) string {
	parts := make([]string, len(stack))
	for i, v := range stack {
		parts[i] = fmt.Sprintf("%d", v)
	}
	return "[" + strings.Join(parts, " ") + "]"
}

func formatStatementsParserNodeStack(stack []*asts.ASTNode) string {
	parts := make([]string, len(stack))
	for i, node := range stack {
		if node == nil {
			parts[i] = "<nil>"
			continue
		}
		parts[i] = string(node.Type)
	}
	return "[" + strings.Join(parts, " ") + "]"
}

func formatStatementsParserAction(action StatementsParserAction) string {
	switch action.Kind {
	case StatementsParserActionShift:
		return fmt.Sprintf("shift(%d)", action.Target)
	case StatementsParserActionReduce:
		return fmt.Sprintf("reduce(%d)", action.Target)
	case StatementsParserActionAccept:
		return "accept"
	case StatementsParserActionAcceptAndYield:
		return "accept_and_yield"
	default:
		return "unknown"
	}
}

type StatementsParserProduction struct {
	lhs      asts.NodeType
	rhsCount int
}

var StatementsParserActions = map[int]map[tokens.TokenType]StatementsParserAction{
	0: {
		tokens.TokenTypeEOF:             {Kind: StatementsParserActionReduce, Target: 1},
		tokens.TokenType("id"):          {Kind: StatementsParserActionShift, Target: 7},
		tokens.TokenType("if"):          {Kind: StatementsParserActionShift, Target: 8},
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionShift, Target: 9},
		tokens.TokenType("print"):       {Kind: StatementsParserActionShift, Target: 10},
		tokens.TokenType("semicolon"):   {Kind: StatementsParserActionShift, Target: 11},
	},
	1: {
		tokens.TokenType("semicolon"): {Kind: StatementsParserActionShift, Target: 12},
	},
	2: {
		tokens.TokenTypeEOF:             {Kind: StatementsParserActionReduce, Target: 6},
		tokens.TokenType("id"):          {Kind: StatementsParserActionReduce, Target: 6},
		tokens.TokenType("if"):          {Kind: StatementsParserActionReduce, Target: 6},
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionReduce, Target: 6},
		tokens.TokenType("print"):       {Kind: StatementsParserActionReduce, Target: 6},
		tokens.TokenType("semicolon"):   {Kind: StatementsParserActionReduce, Target: 6},
	},
	3: {
		tokens.TokenTypeEOF:             {Kind: StatementsParserActionReduce, Target: 7},
		tokens.TokenType("id"):          {Kind: StatementsParserActionReduce, Target: 7},
		tokens.TokenType("if"):          {Kind: StatementsParserActionReduce, Target: 7},
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionReduce, Target: 7},
		tokens.TokenType("print"):       {Kind: StatementsParserActionReduce, Target: 7},
		tokens.TokenType("semicolon"):   {Kind: StatementsParserActionReduce, Target: 7},
	},
	4: {
		tokens.TokenTypeEOF:             {Kind: StatementsParserActionAccept},
		tokens.TokenType("equals"):      {Kind: StatementsParserActionAcceptAndYield},
		tokens.TokenType("id"):          {Kind: StatementsParserActionAcceptAndYield},
		tokens.TokenType("if"):          {Kind: StatementsParserActionAcceptAndYield},
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionAcceptAndYield},
		tokens.TokenType("lparen"):      {Kind: StatementsParserActionAcceptAndYield},
		tokens.TokenType("print"):       {Kind: StatementsParserActionAcceptAndYield},
		tokens.TokenType("rparen"):      {Kind: StatementsParserActionAcceptAndYield},
		tokens.TokenType("semicolon"):   {Kind: StatementsParserActionAcceptAndYield},
	},
	5: {
		tokens.TokenTypeEOF:             {Kind: StatementsParserActionReduce, Target: 1},
		tokens.TokenType("id"):          {Kind: StatementsParserActionShift, Target: 7},
		tokens.TokenType("if"):          {Kind: StatementsParserActionShift, Target: 8},
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionShift, Target: 9},
		tokens.TokenType("print"):       {Kind: StatementsParserActionShift, Target: 10},
		tokens.TokenType("semicolon"):   {Kind: StatementsParserActionShift, Target: 11},
	},
	6: {
		tokens.TokenTypeEOF: {Kind: StatementsParserActionReduce, Target: 3},
	},
	7: {
		tokens.TokenType("equals"): {Kind: StatementsParserActionShift, Target: 14},
	},
	8: {
		tokens.TokenType("lparen"): {Kind: StatementsParserActionShift, Target: 15},
	},
	9: {
		tokens.TokenType("semicolon"): {Kind: StatementsParserActionReduce, Target: 9},
	},
	10: {
		tokens.TokenType("lparen"): {Kind: StatementsParserActionShift, Target: 16},
	},
	11: {
		tokens.TokenTypeEOF:             {Kind: StatementsParserActionReduce, Target: 4},
		tokens.TokenType("id"):          {Kind: StatementsParserActionReduce, Target: 4},
		tokens.TokenType("if"):          {Kind: StatementsParserActionReduce, Target: 4},
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionReduce, Target: 4},
		tokens.TokenType("print"):       {Kind: StatementsParserActionReduce, Target: 4},
		tokens.TokenType("semicolon"):   {Kind: StatementsParserActionReduce, Target: 4},
	},
	12: {
		tokens.TokenTypeEOF:             {Kind: StatementsParserActionReduce, Target: 5},
		tokens.TokenType("id"):          {Kind: StatementsParserActionReduce, Target: 5},
		tokens.TokenType("if"):          {Kind: StatementsParserActionReduce, Target: 5},
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionReduce, Target: 5},
		tokens.TokenType("print"):       {Kind: StatementsParserActionReduce, Target: 5},
		tokens.TokenType("semicolon"):   {Kind: StatementsParserActionReduce, Target: 5},
	},
	13: {
		tokens.TokenTypeEOF: {Kind: StatementsParserActionReduce, Target: 2},
	},
	14: {
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionShift, Target: 17},
	},
	15: {
		tokens.TokenType("id"):          {Kind: StatementsParserActionShift, Target: 19},
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionShift, Target: 20},
	},
	16: {
		tokens.TokenType("id"):          {Kind: StatementsParserActionShift, Target: 19},
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionShift, Target: 20},
	},
	17: {
		tokens.TokenType("semicolon"): {Kind: StatementsParserActionReduce, Target: 8},
	},
	18: {
		tokens.TokenType("rparen"): {Kind: StatementsParserActionShift, Target: 22},
	},
	19: {
		tokens.TokenType("equals"): {Kind: StatementsParserActionShift, Target: 23},
	},
	20: {
		tokens.TokenType("rparen"): {Kind: StatementsParserActionReduce, Target: 9},
	},
	21: {
		tokens.TokenType("rparen"): {Kind: StatementsParserActionShift, Target: 24},
	},
	22: {
		tokens.TokenType("id"):          {Kind: StatementsParserActionShift, Target: 7},
		tokens.TokenType("if"):          {Kind: StatementsParserActionShift, Target: 8},
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionShift, Target: 9},
		tokens.TokenType("print"):       {Kind: StatementsParserActionShift, Target: 10},
		tokens.TokenType("semicolon"):   {Kind: StatementsParserActionShift, Target: 11},
	},
	23: {
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionShift, Target: 26},
	},
	24: {
		tokens.TokenType("semicolon"): {Kind: StatementsParserActionShift, Target: 27},
	},
	25: {
		tokens.TokenTypeEOF:             {Kind: StatementsParserActionReduce, Target: 10},
		tokens.TokenType("id"):          {Kind: StatementsParserActionReduce, Target: 10},
		tokens.TokenType("if"):          {Kind: StatementsParserActionReduce, Target: 10},
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionReduce, Target: 10},
		tokens.TokenType("print"):       {Kind: StatementsParserActionReduce, Target: 10},
		tokens.TokenType("semicolon"):   {Kind: StatementsParserActionReduce, Target: 10},
	},
	26: {
		tokens.TokenType("rparen"): {Kind: StatementsParserActionReduce, Target: 8},
	},
	27: {
		tokens.TokenTypeEOF:             {Kind: StatementsParserActionReduce, Target: 11},
		tokens.TokenType("id"):          {Kind: StatementsParserActionReduce, Target: 11},
		tokens.TokenType("if"):          {Kind: StatementsParserActionReduce, Target: 11},
		tokens.TokenType("int_literal"): {Kind: StatementsParserActionReduce, Target: 11},
		tokens.TokenType("print"):       {Kind: StatementsParserActionReduce, Target: 11},
		tokens.TokenType("semicolon"):   {Kind: StatementsParserActionReduce, Target: 11},
	},
}

var StatementsParserGotos = map[int]map[asts.NodeType]int{
	0: {
		asts.NodeType("Expression"):      1,
		asts.NodeType("IfStatement"):     2,
		asts.NodeType("PrintStatement"):  3,
		asts.NodeType("Program"):         4,
		asts.NodeType("Statement"):       5,
		asts.NodeType("__pgpg_repeat_1"): 6,
	},
	5: {
		asts.NodeType("Expression"):      1,
		asts.NodeType("IfStatement"):     2,
		asts.NodeType("PrintStatement"):  3,
		asts.NodeType("Statement"):       5,
		asts.NodeType("__pgpg_repeat_1"): 13,
	},
	15: {
		asts.NodeType("Expression"): 18,
	},
	16: {
		asts.NodeType("Expression"): 21,
	},
	22: {
		asts.NodeType("Expression"):     1,
		asts.NodeType("IfStatement"):    2,
		asts.NodeType("PrintStatement"): 3,
		asts.NodeType("Statement"):      25,
	},
}

var StatementsParserProductions = []StatementsParserProduction{
	{lhs: asts.NodeType("__pgpg_start_2"), rhsCount: 1},
	{lhs: asts.NodeType("__pgpg_repeat_1"), rhsCount: 0},
	{lhs: asts.NodeType("__pgpg_repeat_1"), rhsCount: 2},
	{lhs: asts.NodeType("Program"), rhsCount: 1},
	{lhs: asts.NodeType("Statement"), rhsCount: 1},
	{lhs: asts.NodeType("Statement"), rhsCount: 2},
	{lhs: asts.NodeType("Statement"), rhsCount: 1},
	{lhs: asts.NodeType("Statement"), rhsCount: 1},
	{lhs: asts.NodeType("Expression"), rhsCount: 3},
	{lhs: asts.NodeType("Expression"), rhsCount: 1},
	{lhs: asts.NodeType("IfStatement"), rhsCount: 5},
	{lhs: asts.NodeType("PrintStatement"), rhsCount: 5},
}
