package oviewer

import (
	"context"
	"fmt"
	"strings"

	"github.com/gdamore/tcell/v3"
	"github.com/gdamore/tcell/v3/color"
)

// SidebarItem represents an item to display in the sidebar.
type SidebarItem struct {
	Label     string   // Always-visible label (e.g., index, description)
	Contents  contents // Contents to display
	IsCurrent bool
}

type SidebarMode int

// sidebarMode represents the mode of the sidebar.
const (
	// SidebarModeNone is no sidebar.
	SidebarModeNone SidebarMode = iota
	// SidebarModeHelp is the help sidebar.
	SidebarModeHelp
	// SidebarModeMarks is the mark list sidebar.
	SidebarModeMarks
	// SidebarModeDocList is the document list sidebar.
	SidebarModeDocList
	// SidebarModeSections is the section list sidebar.
	SidebarModeSections
	// SidebarModeViewMode is the view mode list sidebar.
	SidebarModeViewMode
	// SidebarModeStyles is the style list sidebar.
	SidebarModeStyles

	// SidebarModeEnd is the end of sidebar modes.
	SidebarModeEnd
)

// String returns the string representation of the SidebarMode.
func (s SidebarMode) String() string {
	switch s {
	case SidebarModeHelp:
		return "Help"
	case SidebarModeMarks:
		return "Marks"
	case SidebarModeDocList:
		return "Documents"
	case SidebarModeSections:
		return "Sections"
	case SidebarModeViewMode:
		return "View Modes"
	case SidebarModeStyles:
		return "Styles"
	default:
		return "none"
	}
}

const (
	minSidebarWidth     = 12
	maxSidebarWidth     = 100
	defaultSidebarWidth = "20%"
)

// sidebarScroll holds scroll positions for sidebar.
type sidebarScroll struct {
	x        int
	y        int
	currentY int // CurrentY is the current Y position.
}

// helpStyle is the style for help sidebar items.
var helpStyle = tcell.StyleDefault.Foreground(color.Green)

// prepareSidebarItems creates the sidebarItems slice for display.
func (root *Root) prepareSidebarItems() {
	if root.sidebarWidth <= 0 {
		return
	}
	var items []SidebarItem
	switch root.sidebarMode {
	case SidebarModeHelp:
		items = root.sidebarItemsForHelp()
	case SidebarModeMarks:
		items = root.sidebarItemsForMarks()
	case SidebarModeDocList:
		items = root.sidebarItemsForDocList()
	case SidebarModeSections:
		items = root.sidebarItemsForSections()
	case SidebarModeViewMode:
		items = root.sidebarItemsForViewMode()
	case SidebarModeStyles:
		items = root.sidebarItemsForStyles()
	}
	root.SidebarItems = items
}

// sidebarItemsForHelp creates SidebarItems for the help sidebar.
func (root *Root) sidebarItemsForHelp() []SidebarItem {
	var items []SidebarItem
	keyBinds := GetKeyBinds(root.Config)
	descriptions := keyBinds.GetKeyBindDescriptions(GroupAll)
	totalLines := len(descriptions) * 2
	root.adjustSidebarScroll(SidebarModeHelp, totalLines, 0)
	scroll := root.sidebarScrolls[SidebarModeHelp]
	start := scroll.y
	end := min(start+root.scr.vHeight, totalLines)
	for line := start; line < end; line++ {
		i := line / 2
		desc := descriptions[i]
		if line%2 == 0 {
			content := StrToContents("["+desc[1]+"]", 0)
			items = append(items, SidebarItem{Label: "", Contents: content, IsCurrent: false})
		} else {
			contentDesc := StrToContents("  "+desc[0], 0)
			items = append(items, SidebarItem{Label: "", Contents: contentDesc, IsCurrent: false})
		}
	}
	root.SidebarHelpItems = items
	return items
}

// sidebarItemsForMarks creates SidebarItems for the mark sidebar.
func (root *Root) sidebarItemsForMarks() []SidebarItem {
	var items []SidebarItem
	length := root.sidebarWidth - 4
	marks := root.Doc.marked
	current := root.Doc.markedPoint
	root.adjustSidebarScroll(SidebarModeMarks, len(marks), current)
	scroll := root.sidebarScrolls[SidebarModeMarks]
	start := scroll.y
	end := min(start+root.scr.vHeight, len(marks))
	for i := start; i < end; i++ {
		mark := marks[i]
		isCurrent := (i == current)
		contents := StrToContents(string(mark.line), 0)
		lc := contents.TrimLeft()
		numContents := StrToContents(fmt.Sprintf("%d ", mark.lineNum), 0)
		lc = append(numContents, lc...)
		if len(lc) < length {
			spaces := StrToContents(strings.Repeat(" ", length-len(lc)), 0)
			lc = append(lc, spaces...)
		}
		label := fmt.Sprintf("%2d ", i)
		items = append(items, SidebarItem{
			Label:     label,
			Contents:  lc,
			IsCurrent: isCurrent,
		})
	}
	return items
}

// sidebarItemsForDocList creates SidebarItems for the document list sidebar.
func (root *Root) sidebarItemsForDocList() []SidebarItem {
	var items []SidebarItem
	length := root.sidebarWidth - 5
	current := root.CurrentDoc
	root.adjustSidebarScroll(SidebarModeDocList, len(root.DocList), current)
	scroll := root.sidebarScrolls[SidebarModeDocList]
	start := scroll.y
	end := min(start+root.scr.vHeight, len(root.DocList))
	for i := start; i < end; i++ {
		doc := root.DocList[i]
		displayName := StrToContents(doc.FileName, 0)
		if len(displayName) < length {
			spaces := StrToContents(strings.Repeat(" ", length-len(displayName)), 0)
			displayName = append(displayName, spaces...)
		}
		isCurrent := (i == current)
		label := fmt.Sprintf("%2d ", i)
		items = append(items, SidebarItem{
			Label:     label,
			Contents:  displayName,
			IsCurrent: isCurrent,
		})
	}
	return items
}

// sidebarItemsForSections returns SidebarItems for sectionList.
func (root *Root) sidebarItemsForSections() []SidebarItem {
	var items []SidebarItem
	length := root.sidebarWidth - 4
	sections := root.Doc.sectionList
	lN := root.Doc.topLN + root.Doc.firstLine()
	current := -1
	for i, section := range sections {
		if section.lineNum >= lN {
			current = i
			break
		}
	}
	root.adjustSidebarScroll(SidebarModeSections, len(sections), current)
	scroll := root.sidebarScrolls[SidebarModeSections]
	start := scroll.y
	end := min(start+root.scr.vHeight, len(sections))
	for i := start; i < end; i++ {
		section := sections[i]
		contents := StrToContents(string(section.line), 0)
		lc := contents.TrimLeft()
		numContents := StrToContents(fmt.Sprintf("%d ", section.lineNum), 0)
		lc = append(numContents, lc...)
		if len(lc) < length {
			spaces := StrToContents(strings.Repeat(" ", length-len(lc)), 0)
			lc = append(lc, spaces...)
		}
		label := fmt.Sprintf("%2d ", i)
		items = append(items, SidebarItem{
			Label:     label,
			Contents:  lc,
			IsCurrent: (i == current),
		})
	}
	return items
}

// sidebarItemsForViewMode returns SidebarItems for view modes.
func (root *Root) sidebarItemsForViewMode() []SidebarItem {
	var items []SidebarItem
	length := root.sidebarWidth - 4
	names := ListViewMode(root.Config)
	root.adjustSidebarScroll(SidebarModeViewMode, len(names), 0)
	scroll := root.sidebarScrolls[SidebarModeViewMode]
	start := scroll.y
	end := min(start+root.scr.vHeight, len(names))
	for i := start; i < end; i++ {
		name := names[i]
		displayName := StrToContents(name, 0)
		if len(displayName) < length {
			spaces := StrToContents(strings.Repeat(" ", length-len(displayName)), 0)
			displayName = append(displayName, spaces...)
		}
		label := fmt.Sprintf("%2d ", i)
		items = append(items, SidebarItem{
			Label:     label,
			Contents:  displayName,
			IsCurrent: false,
		})
	}
	return items
}

// sidebarItemsForStyles returns SidebarItems for styles.
func (root *Root) sidebarItemsForStyles() []SidebarItem {
	var items []SidebarItem
	length := root.sidebarWidth - 4
	stylesLen := root.Doc.styles.Len()
	helpLines := []string{
		"o: disable all, then enable (e.g., o1)",
		"a: enable all (e.g., a1-2)",
		"i: invert all (e.g., i1,3)",
	}
	if stylesLen == 0 {
		helpLines = []string{
			"No ANSI escape sequences found.",
		}
	}
	totalLines := stylesLen + len(helpLines)
	root.adjustSidebarScroll(SidebarModeStyles, totalLines, 0)
	scroll := root.sidebarScrolls[SidebarModeStyles]
	start := scroll.y
	end := min(start+root.scr.vHeight, totalLines)
	helpEnd := min(end, len(helpLines))
	for i := start; i < helpEnd; i++ {
		items = append(items, root.sidebarItemForStyleHelp(helpLines[i], length))
	}

	styleStart := max(start-len(helpLines), 0)
	styleEnd := end - len(helpLines)
	for styleIndex := styleStart; styleIndex < styleEnd; styleIndex++ {
		item, ok := root.sidebarItemForStyle(styleIndex, length)
		if !ok {
			break
		}
		items = append(items, item)
	}
	return items
}

// sidebarItemForStyleHelp creates a SidebarItem for a help line in the styles sidebar.
func (root *Root) sidebarItemForStyleHelp(helpLine string, length int) SidebarItem {
	displayName := StrToContents(helpLine, 0)
	if len(displayName) < length {
		spaces := StrToContents(strings.Repeat(" ", length-len(displayName)), 0)
		displayName = append(displayName, spaces...)
	}
	for j := range displayName {
		displayName[j].style = helpStyle
	}
	return SidebarItem{
		Label:     "",
		Contents:  displayName,
		IsCurrent: false,
	}
}

// sidebarItemForStyle creates a SidebarItem for a style in the styles sidebar.
func (root *Root) sidebarItemForStyle(styleIndex, length int) (SidebarItem, bool) {
	style, enabled, ok := root.Doc.styles.Index(styleIndex)
	if !ok {
		return SidebarItem{}, false
	}
	repr := "[*] "
	if !enabled {
		repr = "[ ] "
	}
	displayName := StrToContents(repr+styleString(style), 0)
	for j := 4; j < len(displayName); j++ {
		displayName[j].style = style
	}
	if len(displayName) < length {
		spaces := StrToContents(strings.Repeat(" ", length-len(displayName)), 0)
		displayName = append(displayName, spaces...)
	}
	label := fmt.Sprintf("%2d ", styleIndex)
	return SidebarItem{
		Label:     label,
		Contents:  displayName,
		IsCurrent: false,
	}, true
}

// sidebarUp scrolls the sidebar up.
func (root *Root) sidebarUp(_ context.Context) {
	scroll := root.sidebarScrolls[root.sidebarMode]
	if scroll.y > 0 {
		scroll.y--
		root.sidebarScrolls[root.sidebarMode] = scroll
	}
}

// sidebarDown scrolls the sidebar down.
func (root *Root) sidebarDown(_ context.Context) {
	scroll := root.sidebarScrolls[root.sidebarMode]
	scroll.y++
	root.sidebarScrolls[root.sidebarMode] = scroll
}

// sidebarLeft scrolls the sidebar left.
func (root *Root) sidebarLeft(_ context.Context) {
	scroll := root.sidebarScrolls[root.sidebarMode]
	scroll.x--
	scroll.x = max(scroll.x, 0)
	root.sidebarScrolls[root.sidebarMode] = scroll
}

// sidebarRight scrolls the sidebar right.
func (root *Root) sidebarRight(_ context.Context) {
	scroll := root.sidebarScrolls[root.sidebarMode]
	scroll.x++
	root.sidebarScrolls[root.sidebarMode] = scroll
}

// adjustSidebarScroll adjusts scrollY so that currentIndex is visible, only if currentIndex has changed.
func (root *Root) adjustSidebarScroll(mode SidebarMode, itemsLen, currentIndex int) {
	if root.sidebarScrolls == nil {
		return
	}
	scroll := root.sidebarScrolls[mode]
	height := root.scr.vHeight - 4
	scroll.y = max(scroll.y, 0)
	scroll.y = min(scroll.y, max(itemsLen-height, 0))
	if scroll.currentY == currentIndex {
		root.sidebarScrolls[mode] = scroll
		return
	}

	if currentIndex < scroll.y {
		scroll.y = currentIndex
	} else if currentIndex >= scroll.y+height {
		scroll.y = currentIndex - height + 1
	}
	scroll.y = max(scroll.y, 0)
	maxY := max(itemsLen-height, 0)
	scroll.y = min(scroll.y, maxY)
	scroll.currentY = currentIndex
	root.sidebarScrolls[mode] = scroll
}

// toggleSidebar toggles the sidebar visibility.
func (root *Root) toggleSidebar(ctx context.Context, mode SidebarMode) {
	defer func() {
		root.previousSidebarMode = root.sidebarMode
	}()
	if mode == SidebarModeNone {
		root.closeSidebar(ctx)
		return
	}
	if root.sidebarVisible && root.sidebarMode == mode {
		root.closeSidebar(ctx)
		return
	}
	root.openSidebar(ctx, mode)
}

// setSidebar sets the sidebar visibility and mode.
func (root *Root) setSidebar(ctx context.Context, mode SidebarMode) {
	if mode == SidebarModeNone {
		root.closeSidebar(ctx)
		return
	}
	root.openSidebar(ctx, mode)
}

// prepareSidebarByMode prepares mode-specific sidebar data before first render.
func (root *Root) prepareSidebarByMode(mode SidebarMode) {
	switch mode {
	case SidebarModeStyles:
		root.Doc.EnsureStylesLoaded()
	case SidebarModeSections:
		root.generateSectionList()
	}
}

// openSidebar opens the sidebar with the specified mode.
func (root *Root) openSidebar(ctx context.Context, mode SidebarMode) {
	width, err := calcSideWidth(root.Config.SidebarWidth, root.scr.vWidth)
	if err != nil {
		root.setMessagef("Invalid sidebar width '%s': %s. Using default %s.", root.Config.SidebarWidth, err.Error(), defaultSidebarWidth)
		width, _ = calcSideWidth(defaultSidebarWidth, root.scr.vWidth)
	}
	root.sidebarMode = mode
	root.sidebarVisible = true
	root.sidebarWidth = width
	root.prepareSidebarByMode(mode)
	root.ViewSync(ctx)
	root.setMessagef("Sidebar (%s) shown", mode.String())
}

// calcSideWidth calculates the sidebar width based on the configuration string.
func calcSideWidth(sideWidth string, width int) (int, error) {
	w, err := calcPosition(sideWidth, width)
	if err != nil {
		return 0, err
	}
	w = min(max(minSidebarWidth, w), maxSidebarWidth)
	return int(w), nil
}

func (root *Root) closeSidebar(ctx context.Context) {
	root.sidebarVisible = false
	root.sidebarMode = SidebarModeNone
	root.sidebarWidth = 0
	root.ViewSync(ctx)
	root.setMessage("Sidebar hidden")
}

// toggleSidebarHelp toggles the help sidebar visibility.
func (root *Root) toggleSidebarHelp(ctx context.Context) {
	root.toggleSidebar(ctx, SidebarModeHelp)
}

// toggleSidebarMarks toggles the mark list sidebar visibility.
func (root *Root) toggleSidebarMarks(ctx context.Context) {
	root.toggleSidebar(ctx, SidebarModeMarks)
}

// toggleSidebarDocList toggles the document list sidebar visibility.
func (root *Root) toggleSidebarDocList(ctx context.Context) {
	root.toggleSidebar(ctx, SidebarModeDocList)
}

// toggleSidebarSections toggles the section list sidebar visibility.
func (root *Root) toggleSidebarSections(ctx context.Context) {
	root.toggleSidebar(ctx, SidebarModeSections)
}

func (root *Root) toggleSidebarStyles(ctx context.Context) {
	root.toggleSidebar(ctx, SidebarModeStyles)
}
