package theme

import (
	"encoding/json"
	"fmt"
	"os"
	"regexp"
	"sort"
	"strings"

	"github.com/charmbracelet/lipgloss"
	"github.com/muesli/termenv"

	"github.com/antonmedv/fx/internal/utils"
)

type Theme struct {
	Cursor    Color
	Syntax    Color
	Preview   Color
	StatusBar Color
	Search    Color
	Key       Color
	String    Color
	Null      Color
	Boolean   Color
	Number    Color
	Size      Color
}

type Color func(s []byte) []byte

func Value(b []byte, selected, chunk bool) Color {
	if selected {
		return CurrentTheme.Cursor
	} else if chunk {
		return CurrentTheme.String
	} else {
		switch b[0] {
		case '"':
			return CurrentTheme.String
		case 't', 'f':
			return CurrentTheme.Boolean
		case 'n':
			return CurrentTheme.Null
		case '{', '[', '}', ']':
			return CurrentTheme.Syntax
		default:
			if utils.IsDigit(b[0]) || b[0] == '-' {
				return CurrentTheme.Number
			}
			return noColor
		}
	}
}

var (
	TermOutput = termenv.NewOutput(os.Stderr)
)

func init() {
	themeNames = make([]string, 0, len(themes))
	for name := range themes {
		themeNames = append(themeNames, name)
	}
	sort.Strings(themeNames)

	themeId, ok := os.LookupEnv("FX_THEME")
	if !ok {
		themeId = "1"
	}

	showSizesValue, ok := os.LookupEnv("FX_SHOW_SIZE")
	if ok {
		showSizesValue := strings.ToLower(showSizesValue)
		ShowSizes = showSizesValue == "true" || showSizesValue == "yes" || showSizesValue == "on" || showSizesValue == "1"
	}

	CurrentTheme, ok = themes[themeId]
	if !ok {
		_, _ = fmt.Fprintf(os.Stderr, "fx: unknown theme %q, available themes: %v\n", themeId, themeNames)
		os.Exit(1)
	}

	if TermOutput.ColorProfile() == termenv.Ascii {
		CurrentTheme = themes["0"]
	}

	Colon = CurrentTheme.Syntax([]byte{':', ' '})
	ColonPreview = CurrentTheme.Preview([]byte{':'})
	Comma = CurrentTheme.Syntax([]byte{','})
	Empty = CurrentTheme.Preview([]byte{'~'})
	Dot3 = CurrentTheme.Preview([]byte("…"))
	CloseCurlyBracket = CurrentTheme.Syntax([]byte{'}'})
	CloseSquareBracket = CurrentTheme.Syntax([]byte{']'})
}

var (
	themeNames       []string
	CurrentTheme     Theme
	defaultCursor    = toColor(lipgloss.NewStyle().Reverse(true).Render)
	defaultPreview   = toColor(lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Render)
	defaultStatusBar = toColor(lipgloss.NewStyle().Background(lipgloss.Color("7")).Foreground(lipgloss.Color("0")).Render)
	defaultSearch    = toColor(lipgloss.NewStyle().Background(lipgloss.Color("11")).Foreground(lipgloss.Color("16")).Render)
	defaultNull      = fg("243")
	defaultSize      = toColor(lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Render)
	ShowSizes        = false
)

var (
	Colon              []byte
	ColonPreview       []byte
	Comma              []byte
	Empty              []byte
	Dot3               []byte
	CloseCurlyBracket  []byte
	CloseSquareBracket []byte
)

var themes = map[string]Theme{
	"0": {
		Cursor:    defaultCursor,
		Syntax:    noColor,
		Preview:   noColor,
		StatusBar: noColor,
		Search:    defaultSearch,
		Key:       noColor,
		String:    noColor,
		Null:      noColor,
		Boolean:   noColor,
		Number:    noColor,
		Size:      noColor,
	},
	"1": {
		Cursor:    defaultCursor,
		Syntax:    noColor,
		Preview:   defaultPreview,
		StatusBar: defaultStatusBar,
		Search:    defaultSearch,
		Key:       boldFg("4"),
		String:    fg("2"),
		Null:      defaultNull,
		Boolean:   fg("5"),
		Number:    fg("6"),
		Size:      defaultSize,
	},
	"2": {
		Cursor:    defaultCursor,
		Syntax:    noColor,
		Preview:   defaultPreview,
		StatusBar: defaultStatusBar,
		Search:    defaultSearch,
		Key:       fg("2"),
		String:    fg("4"),
		Null:      defaultNull,
		Boolean:   fg("5"),
		Number:    fg("6"),
		Size:      defaultSize,
	},
	"3": {
		Cursor:    defaultCursor,
		Syntax:    noColor,
		Preview:   defaultPreview,
		StatusBar: defaultStatusBar,
		Search:    defaultSearch,
		Key:       fg("13"),
		String:    fg("11"),
		Null:      defaultNull,
		Boolean:   fg("1"),
		Number:    fg("14"),
		Size:      defaultSize,
	},
	"4": {
		Cursor:    defaultCursor,
		Syntax:    noColor,
		Preview:   defaultPreview,
		StatusBar: defaultStatusBar,
		Search:    defaultSearch,
		Key:       fg("#00F5D4"),
		String:    fg("#00BBF9"),
		Null:      defaultNull,
		Boolean:   fg("#F15BB5"),
		Number:    fg("#9B5DE5"),
		Size:      defaultSize,
	},
	"5": {
		Cursor:    defaultCursor,
		Syntax:    noColor,
		Preview:   defaultPreview,
		StatusBar: defaultStatusBar,
		Search:    defaultSearch,
		Key:       fg("#faf0ca"),
		String:    fg("#f4d35e"),
		Null:      defaultNull,
		Boolean:   fg("#ee964b"),
		Number:    fg("#ee964b"),
		Size:      defaultSize,
	},
	"6": {
		Cursor:    defaultCursor,
		Syntax:    noColor,
		Preview:   defaultPreview,
		StatusBar: defaultStatusBar,
		Search:    defaultSearch,
		Key:       fg("#4D96FF"),
		String:    fg("#6BCB77"),
		Null:      defaultNull,
		Boolean:   fg("#FF6B6B"),
		Number:    fg("#FFD93D"),
		Size:      defaultSize,
	},
	"7": {
		Cursor:    defaultCursor,
		Syntax:    noColor,
		Preview:   defaultPreview,
		StatusBar: defaultStatusBar,
		Search:    defaultSearch,
		Key:       boldFg("42"),
		String:    boldFg("213"),
		Null:      defaultNull,
		Boolean:   boldFg("201"),
		Number:    boldFg("201"),
		Size:      defaultSize,
	},
	"8": {
		Cursor:    defaultCursor,
		Syntax:    noColor,
		Preview:   defaultPreview,
		StatusBar: defaultStatusBar,
		Search:    defaultSearch,
		Key:       boldFg("51"),
		String:    fg("195"),
		Null:      defaultNull,
		Boolean:   fg("50"),
		Number:    fg("123"),
		Size:      defaultSize,
	},
	"🔵": {
		Cursor: toColor(lipgloss.NewStyle().
			Foreground(lipgloss.Color("15")).
			Background(lipgloss.Color("33")).
			Render),
		Syntax:    boldFg("33"),
		Preview:   defaultPreview,
		StatusBar: defaultStatusBar,
		Search:    defaultSearch,
		Key:       fg("33"),
		String:    noColor,
		Null:      noColor,
		Boolean:   noColor,
		Number:    noColor,
		Size:      defaultSize,
	},
	"🥝": {
		Cursor:    defaultCursor,
		Syntax:    fg("179"),
		Preview:   defaultPreview,
		StatusBar: defaultStatusBar,
		Search:    defaultSearch,
		Key:       boldFg("154"),
		String:    fg("82"),
		Null:      fg("230"),
		Boolean:   fg("226"),
		Number:    fg("226"),
		Size:      defaultSize,
	},
}

func noColor(s []byte) []byte {
	return s
}

func toColor(f func(s ...string) string) Color {
	return func(s []byte) []byte {
		return []byte(f(string(s)))
	}
}

func fg(color string) Color {
	return toColor(lipgloss.NewStyle().Foreground(lipgloss.Color(color)).Render)
}

func boldFg(color string) Color {
	return toColor(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(color)).Render)
}

func ThemeTester() {
	title := lipgloss.NewStyle().Bold(true)
	for _, name := range themeNames {
		t := themes[name]
		comma := string(t.Syntax([]byte{','}))
		colon := string(t.Syntax([]byte{':'}))

		fmt.Println(title.Render(fmt.Sprintf("Theme %q", name)))
		fmt.Println(string(t.Syntax([]byte("{"))))

		fmt.Printf("  %v%v %v%v\n",
			string(t.Key([]byte("\"string\""))),
			colon,
			string(t.String([]byte("\"Fox jumps over the lazy dog\""))),
			comma)

		fmt.Printf("  %v%v %v%v\n",
			string(t.Key([]byte("\"number\""))),
			colon,
			string(t.Number([]byte("1234567890"))),
			comma)

		fmt.Printf("  %v%v %v%v\n",
			string(t.Key([]byte("\"boolean\""))),
			colon,
			string(t.Boolean([]byte("true"))),
			comma)
		fmt.Printf("  %v%v %v%v\n",
			string(t.Key([]byte("\"null\""))),
			colon,
			string(t.Null([]byte("null"))),
			comma)
		fmt.Println(string(t.Syntax([]byte("}"))))
		println()
	}
}

func ExportThemes() {
	lipgloss.SetColorProfile(termenv.ANSI256) // Export in Terminal.app compatible colors
	placeholder := []byte{'_'}
	extract := func(b []byte) string {
		matches := regexp.
			MustCompile(`^\x1b\[(.+)m_`).
			FindStringSubmatch(string(b))
		if len(matches) == 0 {
			return ""
		} else {
			return matches[1]
		}
	}
	var export = map[string][]string{}
	for _, name := range themeNames {
		t := themes[name]
		export[name] = append(export[name], extract(t.Syntax(placeholder)))
		export[name] = append(export[name], extract(t.Key(placeholder)))
		export[name] = append(export[name], extract(t.String(placeholder)))
		export[name] = append(export[name], extract(t.Number(placeholder)))
		export[name] = append(export[name], extract(t.Boolean(placeholder)))
		export[name] = append(export[name], extract(t.Null(placeholder)))
	}
	data, err := json.Marshal(export)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(data))
}
