package itemrenderer

import (
	"fmt"
	"io"
	"text/tabwriter"

	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models/itemrender"
)

type Service struct {
	annotation Annotation
	styles     styleRenderer
}

func NewService(
	fileTypeStyle StyleRenderer,
	metaInfoStyle StyleRenderer,
) *Service {
	if fileTypeStyle == nil {
		fileTypeStyle = plainTextStyleRenderer{}
	}
	if metaInfoStyle == nil {
		metaInfoStyle = plainTextStyleRenderer{}
	}
	return &Service{
		annotation: nil,
		styles: styleRenderer{
			fileTypeRenderer: fileTypeStyle,
			metaInfoRenderer: metaInfoStyle,
		},
	}
}

func (s *Service) SetAnnotation(a Annotation) {
	s.annotation = a
}

func (s *Service) RenderItem(w io.Writer, item models.Item, resultSet *models.ResultSet, plainText bool) {
	styles := s.styles
	if plainText {
		styles = styleRenderer{plainTextStyleRenderer{}, plainTextStyleRenderer{}}
	}

	tabWriter := tabwriter.NewWriter(w, 0, 1, 1, ' ', 0)

	seenColumns := make(map[string]struct{})
	for _, colName := range resultSet.Columns() {
		seenColumns[colName] = struct{}{}
		if r := itemrender.ToRenderer(item[colName]); r != nil {
			p := models.AttrPathNode{Key: colName}
			s.renderItem(tabWriter, resultSet, item, p, "", r, styles)
		}
	}
	for k, _ := range item {
		if _, seen := seenColumns[k]; !seen {
			if r := itemrender.ToRenderer(item[k]); r != nil {
				p := models.AttrPathNode{Key: k}
				s.renderItem(tabWriter, resultSet, item, p, "", r, styles)
			}
		}
	}
	tabWriter.Flush()
}

func (m *Service) renderItem(
	w io.Writer,
	resultSet *models.ResultSet,
	item models.Item,
	path models.AttrPathNode,
	prefix string,
	r itemrender.Renderer,
	sr styleRenderer,
) {
	fmt.Fprint(w, prefix)
	fmt.Fprint(w, path.Key)
	fmt.Fprint(w, "\t")
	fmt.Fprint(w, sr.fileTypeRenderer.Render(r.TypeName()))
	fmt.Fprint(w, "\t")
	fmt.Fprint(w, r.StringValue())
	fmt.Fprint(w, sr.metaInfoRenderer.Render(r.MetaInfo()))
	if m.annotation != nil {
		fmt.Fprint(w, "  ")
		fmt.Fprint(w, sr.metaInfoRenderer.Render(m.annotation.AnnotateAttribute(resultSet, item, path)))
	}
	fmt.Fprint(w, "\n")

	if subitems := r.SubItems(); len(subitems) > 0 {
		for _, si := range subitems {
			p := models.AttrPathNode{Key: si.Key, Parent: &path}
			m.renderItem(w, resultSet, item, p, prefix+"  ", si.Value, sr)
		}
	}
}

type styleRenderer struct {
	fileTypeRenderer StyleRenderer
	metaInfoRenderer StyleRenderer
}

type Annotation interface {
	AnnotateAttribute(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string
}

type AnnotationFunc func(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string

func (af AnnotationFunc) AnnotateAttribute(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string {
	return af(rs, item, path)
}
