package parser

import (
	"errors"

	"github.com/usememos/memos/plugin/gomark/ast"
	"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)

type TableParser struct{}

func NewTableParser() *TableParser {
	return &TableParser{}
}

func (*TableParser) Match(tokens []*tokenizer.Token) (int, bool) {
	headerTokens := []*tokenizer.Token{}
	for _, token := range tokens {
		if token.Type == tokenizer.Newline {
			break
		}
		headerTokens = append(headerTokens, token)
	}
	if len(headerTokens) < 5 || len(tokens) < len(headerTokens)+3 {
		return 0, false
	}

	delimiterTokens := []*tokenizer.Token{}
	for _, token := range tokens[len(headerTokens)+1:] {
		if token.Type == tokenizer.Newline {
			break
		}
		delimiterTokens = append(delimiterTokens, token)
	}
	if len(delimiterTokens) < 5 || len(tokens) < len(headerTokens)+len(delimiterTokens)+3 {
		return 0, false
	}

	rowTokens := []*tokenizer.Token{}
	for index, token := range tokens[len(headerTokens)+len(delimiterTokens)+2:] {
		temp := len(headerTokens) + len(delimiterTokens) + 2 + index
		if token.Type == tokenizer.Newline {
			if (temp == len(tokens)-1) || (temp+1 == len(tokens)-1 && tokens[temp+1].Type == tokenizer.Newline) {
				break
			}
		}
		rowTokens = append(rowTokens, token)
	}
	if len(rowTokens) < 5 {
		return 0, false
	}

	// Check header.
	if len(headerTokens) < 5 {
		return 0, false
	}
	headerCells, ok := matchTableCellTokens(headerTokens)
	if headerCells == 0 || !ok {
		return 0, false
	}

	// Check delimiter.
	if len(delimiterTokens) < 5 {
		return 0, false
	}
	delimiterCells, ok := matchTableCellTokens(delimiterTokens)
	if delimiterCells != headerCells || !ok {
		return 0, false
	}

	for index, t := range tokenizer.Split(delimiterTokens, tokenizer.Pipe) {
		if index == 0 || index == headerCells {
			if len(t) != 0 {
				return 0, false
			}
			continue
		}
		if len(t) < 5 {
			return 0, false
		}

		delimiterTokens := t[1 : len(t)-1]
		if len(delimiterTokens) < 3 {
			return 0, false
		}
		if (delimiterTokens[0].Type != tokenizer.Colon && delimiterTokens[0].Type != tokenizer.Hyphen) || (delimiterTokens[len(delimiterTokens)-1].Type != tokenizer.Colon && delimiterTokens[len(delimiterTokens)-1].Type != tokenizer.Hyphen) {
			return 0, false
		}
		for _, token := range delimiterTokens[1 : len(delimiterTokens)-1] {
			if token.Type != tokenizer.Hyphen {
				return 0, false
			}
		}
	}

	// Check rows.
	if len(rowTokens) < 5 {
		return 0, false
	}
	rows := tokenizer.Split(rowTokens, tokenizer.Newline)
	if len(rows) == 0 {
		return 0, false
	}
	for _, row := range rows {
		cells, ok := matchTableCellTokens(row)
		if cells != headerCells || !ok {
			return 0, false
		}
	}

	return len(headerTokens) + len(delimiterTokens) + len(rowTokens) + 2, true
}

func (p *TableParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
	size, ok := p.Match(tokens)
	if size == 0 || !ok {
		return nil, errors.New("not matched")
	}

	rawRows := tokenizer.Split(tokens[:size-1], tokenizer.Newline)
	headerTokens := rawRows[0]
	dilimiterTokens := rawRows[1]
	rowTokens := rawRows[2:]
	header := make([]string, 0)
	delimiter := make([]string, 0)
	rows := make([][]string, 0)

	cols := len(tokenizer.Split(headerTokens, tokenizer.Pipe)) - 2
	for _, t := range tokenizer.Split(headerTokens, tokenizer.Pipe)[1 : cols+1] {
		header = append(header, tokenizer.Stringify(t[1:len(t)-1]))
	}
	for _, t := range tokenizer.Split(dilimiterTokens, tokenizer.Pipe)[1 : cols+1] {
		delimiter = append(delimiter, tokenizer.Stringify(t[1:len(t)-1]))
	}
	for _, row := range rowTokens {
		cells := make([]string, 0)
		for _, t := range tokenizer.Split(row, tokenizer.Pipe)[1 : cols+1] {
			cells = append(cells, tokenizer.Stringify(t[1:len(t)-1]))
		}
		rows = append(rows, cells)
	}

	return &ast.Table{
		Header:    header,
		Delimiter: delimiter,
		Rows:      rows,
	}, nil
}

func matchTableCellTokens(tokens []*tokenizer.Token) (int, bool) {
	if len(tokens) == 0 {
		return 0, false
	}

	pipes := 0
	for _, token := range tokens {
		if token.Type == tokenizer.Pipe {
			pipes++
		}
	}
	cells := tokenizer.Split(tokens, tokenizer.Pipe)
	if len(cells) != pipes+1 {
		return 0, false
	}
	if len(cells[0]) != 0 || len(cells[len(cells)-1]) != 0 {
		return 0, false
	}
	for _, cellTokens := range cells[1 : len(cells)-1] {
		if len(cellTokens) == 0 {
			return 0, false
		}
		if cellTokens[0].Type != tokenizer.Space {
			return 0, false
		}
		if cellTokens[len(cellTokens)-1].Type != tokenizer.Space {
			return 0, false
		}
	}

	return len(cells) - 1, true
}
