package extension

import (
	
	
	

	
	gast 
	
	
	
	
	
	
)

var escapedPipeCellListKey = parser.NewContextKey()

type escapedPipeCell struct {
	Cell        *ast.TableCell
	Pos         []int
	Transformed bool
}

// TableCellAlignMethod indicates how are table cells aligned in HTML format.
type TableCellAlignMethod int

const (
	// TableCellAlignDefault renders alignments by default method.
	// With XHTML, alignments are rendered as an align attribute.
	// With HTML5, alignments are rendered as a style attribute.
	TableCellAlignDefault TableCellAlignMethod = iota

	// TableCellAlignAttribute renders alignments as an align attribute.
	TableCellAlignAttribute

	// TableCellAlignStyle renders alignments as a style attribute.
	TableCellAlignStyle

	// TableCellAlignNone does not care about alignments.
	// If you using classes or other styles, you can add these attributes
	// in an ASTTransformer.
	TableCellAlignNone
)

// TableConfig struct holds options for the extension.
type TableConfig struct {
	html.Config

	// TableCellAlignMethod indicates how are table celss aligned.
	TableCellAlignMethod TableCellAlignMethod
}

// TableOption interface is a functional option interface for the extension.
type TableOption interface {
	renderer.Option
	// SetTableOption sets given option to the extension.
	SetTableOption(*TableConfig)
}

// NewTableConfig returns a new Config with defaults.
func () TableConfig {
	return TableConfig{
		Config:               html.NewConfig(),
		TableCellAlignMethod: TableCellAlignDefault,
	}
}

// SetOption implements renderer.SetOptioner.
func ( *TableConfig) ( renderer.OptionName,  interface{}) {
	switch  {
	case optTableCellAlignMethod:
		.TableCellAlignMethod = .(TableCellAlignMethod)
	default:
		.Config.SetOption(, )
	}
}

type withTableHTMLOptions struct {
	value []html.Option
}

func ( *withTableHTMLOptions) ( *renderer.Config) {
	if .value != nil {
		for ,  := range .value {
			.(renderer.Option).SetConfig()
		}
	}
}

func ( *withTableHTMLOptions) ( *TableConfig) {
	if .value != nil {
		for ,  := range .value {
			.SetHTMLOption(&.Config)
		}
	}
}

// WithTableHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
func ( ...html.Option) TableOption {
	return &withTableHTMLOptions{}
}

const optTableCellAlignMethod renderer.OptionName = "TableTableCellAlignMethod"

type withTableCellAlignMethod struct {
	value TableCellAlignMethod
}

func ( *withTableCellAlignMethod) ( *renderer.Config) {
	.Options[optTableCellAlignMethod] = .value
}

func ( *withTableCellAlignMethod) ( *TableConfig) {
	.TableCellAlignMethod = .value
}

// WithTableCellAlignMethod is a functional option that indicates how are table cells aligned in HTML format.
func ( TableCellAlignMethod) TableOption {
	return &withTableCellAlignMethod{}
}

func isTableDelim( []byte) bool {
	if ,  := util.IndentWidth(, 0);  > 3 {
		return false
	}
	for ,  := range  {
		if !(util.IsSpace() ||  == '-' ||  == '|' ||  == ':') {
			return false
		}
	}
	return true
}

var tableDelimLeft = regexp.MustCompile(`^\s*\:\-+\s*$`)
var tableDelimRight = regexp.MustCompile(`^\s*\-+\:\s*$`)
var tableDelimCenter = regexp.MustCompile(`^\s*\:\-+\:\s*$`)
var tableDelimNone = regexp.MustCompile(`^\s*\-+\s*$`)

type tableParagraphTransformer struct {
}

var defaultTableParagraphTransformer = &tableParagraphTransformer{}

// NewTableParagraphTransformer returns  a new ParagraphTransformer
// that can transform paragraphs into tables.
func () parser.ParagraphTransformer {
	return defaultTableParagraphTransformer
}

func ( *tableParagraphTransformer) ( *gast.Paragraph,  text.Reader,  parser.Context) {
	 := .Lines()
	if .Len() < 2 {
		return
	}
	for  := 1;  < .Len(); ++ {
		 := .parseDelimiter(.At(), )
		if  == nil {
			continue
		}
		 := .parseRow(.At(-1), , true, , )
		if  == nil || len() != .ChildCount() {
			return
		}
		 := ast.NewTable()
		.Alignments = 
		.AppendChild(, ast.NewTableHeader())
		for  :=  + 1;  < .Len(); ++ {
			.AppendChild(, .parseRow(.At(), , false, , ))
		}
		.Lines().SetSliced(0, -1)
		.Parent().InsertAfter(.Parent(), , )
		if .Lines().Len() == 0 {
			.Parent().RemoveChild(.Parent(), )
		} else {
			 := .Lines().At( - 2)
			.Stop = .Stop - 1 // trim last newline(\n)
			.Lines().Set(-2, )
		}
	}
}

func ( *tableParagraphTransformer) ( text.Segment,
	 []ast.Alignment,  bool,  text.Reader,  parser.Context) *ast.TableRow {
	 := .Source()
	 := .Value()
	 := 0
	 += util.TrimLeftSpaceLength()
	 := len()
	 -= util.TrimRightSpaceLength()
	 := ast.NewTableRow()
	if len() > 0 && [] == '|' {
		++
	}
	if len() > 0 && [-1] == '|' {
		--
	}
	 := 0
	for ;  < ; ++ {
		 := ast.AlignNone
		if  >= len() {
			if ! {
				return 
			}
		} else {
			 = []
		}

		var  *escapedPipeCell
		 := ast.NewTableCell()
		.Alignment = 
		 := false
		 := 
		for ;  < ; ++ {
			if [] == '`' {
				 = true
			}
			if [] == '|' {
				if  == 0 || [-1] != '\\' {
					break
				} else if  {
					if  == nil {
						 = &escapedPipeCell{, []int{}, false}
						 := .ComputeIfAbsent(escapedPipeCellListKey,
							func() interface{} {
								return []*escapedPipeCell{}
							}).([]*escapedPipeCell)
						 = append(, )
						.Set(escapedPipeCellListKey, )
					}
					.Pos = append(.Pos, .Start+-1)
				}
			}
		}
		 := text.NewSegment(.Start+, .Start+)
		 = .TrimLeftSpace()
		 = .TrimRightSpace()
		.Lines().Append()
		.AppendChild(, )
		 =  + 1
	}
	for ;  < len(); ++ {
		.AppendChild(, ast.NewTableCell())
	}
	return 
}

func ( *tableParagraphTransformer) ( text.Segment,  text.Reader) []ast.Alignment {

	 := .Value(.Source())
	if !isTableDelim() {
		return nil
	}
	 := bytes.Split(, []byte{'|'})
	if util.IsBlank([0]) {
		 = [1:]
	}
	if len() > 0 && util.IsBlank([len()-1]) {
		 = [:len()-1]
	}

	var  []ast.Alignment
	for ,  := range  {
		if tableDelimLeft.Match() {
			 = append(, ast.AlignLeft)
		} else if tableDelimRight.Match() {
			 = append(, ast.AlignRight)
		} else if tableDelimCenter.Match() {
			 = append(, ast.AlignCenter)
		} else if tableDelimNone.Match() {
			 = append(, ast.AlignNone)
		} else {
			return nil
		}
	}
	return 
}

type tableASTTransformer struct {
}

var defaultTableASTTransformer = &tableASTTransformer{}

// NewTableASTTransformer returns a parser.ASTTransformer for tables.
func () parser.ASTTransformer {
	return defaultTableASTTransformer
}

func ( *tableASTTransformer) ( *gast.Document,  text.Reader,  parser.Context) {
	 := .Get(escapedPipeCellListKey)
	if  == nil {
		return
	}
	.Set(escapedPipeCellListKey, nil)
	for ,  := range .([]*escapedPipeCell) {
		if .Transformed {
			continue
		}
		_ = gast.Walk(.Cell, func( gast.Node,  bool) (gast.WalkStatus, error) {
			if ! || .Kind() != gast.KindCodeSpan {
				return gast.WalkContinue, nil
			}

			for  := .FirstChild();  != nil; {
				 := .NextSibling()
				if .Kind() != gast.KindText {
					 = 
					continue
				}
				 := .Parent()
				 := &.(*gast.Text).Segment
				 := 
				for ,  := range .([]*escapedPipeCell) {
					for ,  := range .Pos {
						if .Start <=  &&  < .Stop {
							 := .(*gast.Text).Segment
							 := gast.NewRawTextSegment(.WithStop())
							 := gast.NewRawTextSegment(.WithStart( + 1))
							.InsertAfter(, , )
							.InsertAfter(, , )
							.RemoveChild(, )
							 = 
							.Transformed = true
						}
					}
				}
				 = 
			}
			return gast.WalkContinue, nil
		})
	}
}

// TableHTMLRenderer is a renderer.NodeRenderer implementation that
// renders Table nodes.
type TableHTMLRenderer struct {
	TableConfig
}

// NewTableHTMLRenderer returns a new TableHTMLRenderer.
func ( ...TableOption) renderer.NodeRenderer {
	 := &TableHTMLRenderer{
		TableConfig: NewTableConfig(),
	}
	for ,  := range  {
		.SetTableOption(&.TableConfig)
	}
	return 
}

// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
func ( *TableHTMLRenderer) ( renderer.NodeRendererFuncRegisterer) {
	.Register(ast.KindTable, .renderTable)
	.Register(ast.KindTableHeader, .renderTableHeader)
	.Register(ast.KindTableRow, .renderTableRow)
	.Register(ast.KindTableCell, .renderTableCell)
}

// TableAttributeFilter defines attribute names which table elements can have.
var TableAttributeFilter = html.GlobalAttributeFilter.Extend(
	[]byte("align"),       // [Deprecated]
	[]byte("bgcolor"),     // [Deprecated]
	[]byte("border"),      // [Deprecated]
	[]byte("cellpadding"), // [Deprecated]
	[]byte("cellspacing"), // [Deprecated]
	[]byte("frame"),       // [Deprecated]
	[]byte("rules"),       // [Deprecated]
	[]byte("summary"),     // [Deprecated]
	[]byte("width"),       // [Deprecated]
)

func ( *TableHTMLRenderer) (
	 util.BufWriter,  []byte,  gast.Node,  bool) (gast.WalkStatus, error) {
	if  {
		_, _ = .WriteString("<table")
		if .Attributes() != nil {
			html.RenderAttributes(, , TableAttributeFilter)
		}
		_, _ = .WriteString(">\n")
	} else {
		_, _ = .WriteString("</table>\n")
	}
	return gast.WalkContinue, nil
}

// TableHeaderAttributeFilter defines attribute names which <thead> elements can have.
var TableHeaderAttributeFilter = html.GlobalAttributeFilter.Extend(
	[]byte("align"),   // [Deprecated since HTML4] [Obsolete since HTML5]
	[]byte("bgcolor"), // [Not Standardized]
	[]byte("char"),    // [Deprecated since HTML4] [Obsolete since HTML5]
	[]byte("charoff"), // [Deprecated since HTML4] [Obsolete since HTML5]
	[]byte("valign"),  // [Deprecated since HTML4] [Obsolete since HTML5]
)

func ( *TableHTMLRenderer) (
	 util.BufWriter,  []byte,  gast.Node,  bool) (gast.WalkStatus, error) {
	if  {
		_, _ = .WriteString("<thead")
		if .Attributes() != nil {
			html.RenderAttributes(, , TableHeaderAttributeFilter)
		}
		_, _ = .WriteString(">\n")
		_, _ = .WriteString("<tr>\n") // Header <tr> has no separate handle
	} else {
		_, _ = .WriteString("</tr>\n")
		_, _ = .WriteString("</thead>\n")
		if .NextSibling() != nil {
			_, _ = .WriteString("<tbody>\n")
		}
	}
	return gast.WalkContinue, nil
}

// TableRowAttributeFilter defines attribute names which <tr> elements can have.
var TableRowAttributeFilter = html.GlobalAttributeFilter.Extend(
	[]byte("align"),   // [Obsolete since HTML5]
	[]byte("bgcolor"), // [Obsolete since HTML5]
	[]byte("char"),    // [Obsolete since HTML5]
	[]byte("charoff"), // [Obsolete since HTML5]
	[]byte("valign"),  // [Obsolete since HTML5]
)

func ( *TableHTMLRenderer) (
	 util.BufWriter,  []byte,  gast.Node,  bool) (gast.WalkStatus, error) {
	if  {
		_, _ = .WriteString("<tr")
		if .Attributes() != nil {
			html.RenderAttributes(, , TableRowAttributeFilter)
		}
		_, _ = .WriteString(">\n")
	} else {
		_, _ = .WriteString("</tr>\n")
		if .Parent().LastChild() ==  {
			_, _ = .WriteString("</tbody>\n")
		}
	}
	return gast.WalkContinue, nil
}

// TableThCellAttributeFilter defines attribute names which table <th> cells can have.
var TableThCellAttributeFilter = html.GlobalAttributeFilter.Extend(
	[]byte("abbr"), // [OK] Contains a short abbreviated description of the cell's content [NOT OK in <td>]

	[]byte("align"),   // [Obsolete since HTML5]
	[]byte("axis"),    // [Obsolete since HTML5]
	[]byte("bgcolor"), // [Not Standardized]
	[]byte("char"),    // [Obsolete since HTML5]
	[]byte("charoff"), // [Obsolete since HTML5]

	[]byte("colspan"), // [OK] Number of columns that the cell is to span
	[]byte("headers"), // [OK] This attribute contains a list of space-separated
	// strings, each corresponding to the id attribute of the <th> elements that apply to this element

	[]byte("height"), // [Deprecated since HTML4] [Obsolete since HTML5]

	[]byte("rowspan"), // [OK] Number of rows that the cell is to span
	[]byte("scope"),   // [OK] This enumerated attribute defines the cells that
	// the header (defined in the <th>) element relates to [NOT OK in <td>]

	[]byte("valign"), // [Obsolete since HTML5]
	[]byte("width"),  // [Deprecated since HTML4] [Obsolete since HTML5]
)

// TableTdCellAttributeFilter defines attribute names which table <td> cells can have.
var TableTdCellAttributeFilter = html.GlobalAttributeFilter.Extend(
	[]byte("abbr"),    // [Obsolete since HTML5] [OK in <th>]
	[]byte("align"),   // [Obsolete since HTML5]
	[]byte("axis"),    // [Obsolete since HTML5]
	[]byte("bgcolor"), // [Not Standardized]
	[]byte("char"),    // [Obsolete since HTML5]
	[]byte("charoff"), // [Obsolete since HTML5]

	[]byte("colspan"), // [OK] Number of columns that the cell is to span
	[]byte("headers"), // [OK] This attribute contains a list of space-separated
	// strings, each corresponding to the id attribute of the <th> elements that apply to this element

	[]byte("height"), // [Deprecated since HTML4] [Obsolete since HTML5]

	[]byte("rowspan"), // [OK] Number of rows that the cell is to span

	[]byte("scope"),  // [Obsolete since HTML5] [OK in <th>]
	[]byte("valign"), // [Obsolete since HTML5]
	[]byte("width"),  // [Deprecated since HTML4] [Obsolete since HTML5]
)

func ( *TableHTMLRenderer) (
	 util.BufWriter,  []byte,  gast.Node,  bool) (gast.WalkStatus, error) {
	 := .(*ast.TableCell)
	 := "td"
	if .Parent().Kind() == ast.KindTableHeader {
		 = "th"
	}
	if  {
		_, _ = fmt.Fprintf(, "<%s", )
		if .Alignment != ast.AlignNone {
			 := .TableConfig.TableCellAlignMethod
			if  == TableCellAlignDefault {
				if .Config.XHTML {
					 = TableCellAlignAttribute
				} else {
					 = TableCellAlignStyle
				}
			}
			switch  {
			case TableCellAlignAttribute:
				if ,  := .AttributeString("align"); ! { // Skip align render if overridden
					_, _ = fmt.Fprintf(, ` align="%s"`, .Alignment.String())
				}
			case TableCellAlignStyle:
				,  := .AttributeString("style")
				var  util.CopyOnWriteBuffer
				if  {
					 = util.NewCopyOnWriteBuffer(.([]byte))
					.AppendByte(';')
				}
				 := fmt.Sprintf("text-align:%s", .Alignment.String())
				.AppendString()
				.SetAttributeString("style", .Bytes())
			}
		}
		if .Attributes() != nil {
			if  == "td" {
				html.RenderAttributes(, , TableTdCellAttributeFilter) // <td>
			} else {
				html.RenderAttributes(, , TableThCellAttributeFilter) // <th>
			}
		}
		_ = .WriteByte('>')
	} else {
		_, _ = fmt.Fprintf(, "</%s>\n", )
	}
	return gast.WalkContinue, nil
}

type table struct {
	options []TableOption
}

// Table is an extension that allow you to use GFM tables .
var Table = &table{
	options: []TableOption{},
}

// NewTable returns a new extension with given options.
func ( ...TableOption) goldmark.Extender {
	return &table{
		options: ,
	}
}

func ( *table) ( goldmark.Markdown) {
	.Parser().AddOptions(
		parser.WithParagraphTransformers(
			util.Prioritized(NewTableParagraphTransformer(), 200),
		),
		parser.WithASTTransformers(
			util.Prioritized(defaultTableASTTransformer, 0),
		),
	)
	.Renderer().AddOptions(renderer.WithNodeRenderers(
		util.Prioritized(NewTableHTMLRenderer(.options...), 500),
	))
}