package render

import (
	"context"
	"encoding/json"
	"fmt"
	"io"

	"github.com/logrusorgru/aurora"
	"github.com/morikuni/aec"
	"github.com/olekukonko/tablewriter"
	"github.com/olekukonko/tablewriter/tw"
	"github.com/superfly/flyctl/iostreams"
)

func JSON(w io.Writer, v any) error {
	enc := json.NewEncoder(w)
	enc.SetIndent("", "    ")

	return enc.Encode(v)
}

func TitledJSON(w io.Writer, title string, v any) error {
	return JSON(w, map[string]any{
		title: v,
	})
}

// NewTable creates and configures a new tablewriter.Table with our default
// settings. The caller can make other configuration changes before calling
// table.Render() on the returned Table.
func NewTable(w io.Writer, title string, rows [][]string, cols ...string) *tablewriter.Table {
	if title != "" {
		fmt.Fprintln(w, aurora.Bold(title))
	}

	table := tablewriter.NewTable(w,
		tablewriter.WithHeaderAlignment(tw.AlignLeft),
		tablewriter.WithRowAlignment(tw.AlignLeft),
		tablewriter.WithRendition(tw.Rendition{
			Borders: tw.Border{Left: tw.Off, Right: tw.Off, Top: tw.Off, Bottom: tw.Off},
			Settings: tw.Settings{
				Lines: tw.Lines{ShowHeaderLine: tw.Off},
			},
		}),
	)

	if len(cols) > 0 {
		table.Options(tablewriter.WithHeader(cols))
	}

	table.Configure(func(cfg *tablewriter.Config) {
		cfg.Header.Formatting.AutoFormat = tw.On
		cfg.Row.Formatting.AutoWrap = tw.WrapNone
	})

	table.Bulk(rows) //nolint:errcheck

	return table
}

// Table renders the table defined by the given properties into w. Both title &
// cols are optional.
func Table(w io.Writer, title string, rows [][]string, cols ...string) error {
	table := NewTable(w, title, rows, cols...)

	table.Render() //nolint:errcheck

	fmt.Fprintln(w)

	return nil
}

func VerticalTable(w io.Writer, title string, objects [][]string, cols ...string) error {
	if title != "" {
		fmt.Fprintln(w, aurora.Bold(title))
	}

	table := tablewriter.NewTable(w,
		tablewriter.WithRowAlignment(tw.AlignLeft),
		tablewriter.WithHeaderAlignment(tw.AlignLeft),
		tablewriter.WithRendition(tw.Rendition{
			Borders: tw.Border{Left: tw.Off, Right: tw.Off, Top: tw.Off, Bottom: tw.Off},
		}),
	)

	for _, obj := range objects {
		for i, col := range cols {
			table.Append(col, obj[i]) //nolint:errcheck
		}

		table.Render() //nolint:errcheck

		fmt.Fprintln(w)
	}

	return nil
}

func ReusableTable(w io.Writer, title string, rows [][]string, cols ...string) (err error) {
	if title != "" {
		fmt.Fprintln(w, aurora.Bold(title))
	}

	table := tablewriter.NewTable(w,
		tablewriter.WithHeaderAlignment(tw.AlignLeft),
		tablewriter.WithRowAlignment(tw.AlignLeft),
		tablewriter.WithRendition(tw.Rendition{
			Borders: tw.Border{Left: tw.Off, Right: tw.Off, Top: tw.Off, Bottom: tw.Off},
			Settings: tw.Settings{
				Lines: tw.Lines{ShowHeaderLine: tw.Off},
			},
		}),
	)

	if len(cols) > 0 {
		table.Options(tablewriter.WithHeader(cols))
	}

	table.Configure(func(cfg *tablewriter.Config) {
		cfg.Header.Formatting.AutoFormat = tw.On
		cfg.Row.Formatting.AutoWrap = tw.WrapNone
	})

	table.Bulk(rows) //nolint:errcheck

	table.Render() //nolint:errcheck

	fmt.Fprintln(w)

	return
}

func NewTextBlock(ctx context.Context, v ...any) (tb *TextBlock) {
	io := iostreams.FromContext(ctx)
	colorize := io.ColorScheme()

	tb = &TextBlock{
		out:      iostreams.FromContext(ctx).ErrOut,
		colorize: colorize,
	}

	if len(v) > 0 {
		tb.Println(colorize.Green("==> " + fmt.Sprint(v...)))
	}

	return
}

type TextBlock struct {
	out      io.Writer
	colorize *iostreams.ColorScheme
}

func (tb *TextBlock) Print(v ...any) {
	fmt.Fprint(tb.out, v...)
}

func (tb *TextBlock) Println(v ...any) {
	fmt.Fprintln(tb.out, v...)
}

func (tb *TextBlock) Printf(format string, v ...any) {
	fmt.Fprintf(tb.out, format, v...)
}

// Detail prints to the output ctx carries. It behaves similarly to log.Print.
func (tb *TextBlock) Detail(v ...any) {
	tb.Println("> ", fmt.Sprint(v...))
}

// Detailf prints to the output ctx carries. It behaves similarly to log.Printf.
func (tb *TextBlock) Detailf(format string, v ...any) {
	tb.Detail(fmt.Sprintf(format, v...))
}

func (tb *TextBlock) Overwrite() {
	tb.Print(aec.Up(1), aec.EraseLine(aec.EraseModes.All))
}

func (tb *TextBlock) Done(v ...any) {
	tb.Println(tb.colorize.Green("--> " + fmt.Sprint(v...)))
}

func (tb *TextBlock) Donef(format string, v ...any) {
	tb.Done(fmt.Sprintf(format, v...))
}
