package ui

import (
	"fmt"
	"path/filepath"
	"strings"

	"github.com/charmbracelet/lipgloss"

	"vcom/internal/config"
	vfs "vcom/internal/fs"
	"vcom/internal/theme"
)

type PaneID string

const (
	PaneLeft  PaneID = "left"
	PaneRight PaneID = "right"
)

type BrowserPane struct {
	ID      PaneID
	Path    string
	Entries []vfs.Entry
	Cursor  int
	Offset  int
	Marked  map[string]struct{}
	Archive []ArchiveMount
}

type ArchiveMount struct {
	SourcePath string
	ParentPath string
	RootPath   string
	TempDir    string
}

func (p *BrowserPane) Selected() (vfs.Entry, bool) {
	if len(p.Entries) == 0 || p.Cursor < 0 || p.Cursor >= len(p.Entries) {
		return vfs.Entry{}, false
	}
	return p.Entries[p.Cursor], true
}

func (p *BrowserPane) SetEntries(entries []vfs.Entry, preserveKey string) {
	p.Entries = entries
	p.PruneMarks()
	if len(entries) == 0 {
		p.Cursor = 0
		p.Offset = 0
		return
	}
	if preserveKey != "" {
		p.Cursor = vfs.FindSelected(entries, preserveKey)
	}
	if p.Cursor >= len(entries) {
		p.Cursor = len(entries) - 1
	}
	if p.Cursor < 0 {
		p.Cursor = 0
	}
	if p.Offset > p.Cursor {
		p.Offset = p.Cursor
	}
}

func (p *BrowserPane) Move(delta int, pageSize int) {
	if len(p.Entries) == 0 {
		p.Cursor = 0
		return
	}
	p.Cursor += delta
	if p.Cursor < 0 {
		p.Cursor = 0
	}
	if p.Cursor >= len(p.Entries) {
		p.Cursor = len(p.Entries) - 1
	}

	if p.Cursor < p.Offset {
		p.Offset = p.Cursor
	}
	if pageSize > 0 && p.Cursor >= p.Offset+pageSize {
		p.Offset = p.Cursor - pageSize + 1
	}
	if p.Offset < 0 {
		p.Offset = 0
	}
}

func (p *BrowserPane) EnsureMarked(path string) {
	if strings.TrimSpace(path) == "" {
		return
	}
	if p.Marked == nil {
		p.Marked = map[string]struct{}{}
	}
	p.Marked[path] = struct{}{}
}

func (p *BrowserPane) ToggleMarked(path string) {
	if strings.TrimSpace(path) == "" {
		return
	}
	if p.Marked == nil {
		p.Marked = map[string]struct{}{}
	}
	if _, ok := p.Marked[path]; ok {
		delete(p.Marked, path)
		if len(p.Marked) == 0 {
			p.Marked = nil
		}
		return
	}
	p.Marked[path] = struct{}{}
}

func (p *BrowserPane) IsMarked(path string) bool {
	if p.Marked == nil {
		return false
	}
	_, ok := p.Marked[path]
	return ok
}

func (p *BrowserPane) ClearMarks() {
	p.Marked = nil
}

func (p *BrowserPane) PruneMarks() {
	if len(p.Marked) == 0 {
		return
	}
	valid := map[string]struct{}{}
	for _, entry := range p.Entries {
		if entry.IsParent {
			continue
		}
		valid[entry.Path] = struct{}{}
	}
	for path := range p.Marked {
		if _, ok := valid[path]; !ok {
			delete(p.Marked, path)
		}
	}
	if len(p.Marked) == 0 {
		p.Marked = nil
	}
}

func (p *BrowserPane) MarkedEntries() []vfs.Entry {
	if len(p.Marked) == 0 {
		return nil
	}
	result := make([]vfs.Entry, 0, len(p.Marked))
	for _, entry := range p.Entries {
		if entry.IsParent {
			continue
		}
		if p.IsMarked(entry.Path) {
			result = append(result, entry)
		}
	}
	return result
}

func (p *BrowserPane) EnsureVisible(pageSize int) {
	if pageSize <= 0 {
		return
	}
	if p.Cursor < p.Offset {
		p.Offset = p.Cursor
	}
	if p.Cursor >= p.Offset+pageSize {
		p.Offset = p.Cursor - pageSize + 1
	}
	if p.Offset < 0 {
		p.Offset = 0
	}
}

func (p *BrowserPane) InArchive() bool {
	return len(p.Archive) > 0
}

func (p *BrowserPane) PushArchive(mount ArchiveMount) {
	p.Archive = append(p.Archive, mount)
}

func (p *BrowserPane) PopArchive() (ArchiveMount, bool) {
	if len(p.Archive) == 0 {
		return ArchiveMount{}, false
	}
	last := p.Archive[len(p.Archive)-1]
	p.Archive = p.Archive[:len(p.Archive)-1]
	return last, true
}

func (p *BrowserPane) CurrentArchive() (ArchiveMount, bool) {
	if len(p.Archive) == 0 {
		return ArchiveMount{}, false
	}
	return p.Archive[len(p.Archive)-1], true
}

func (p *BrowserPane) ClearArchives() []ArchiveMount {
	if len(p.Archive) == 0 {
		return nil
	}
	out := make([]ArchiveMount, len(p.Archive))
	copy(out, p.Archive)
	p.Archive = nil
	return out
}

func (p *BrowserPane) DisplayPath() string {
	if len(p.Archive) == 0 {
		return p.Path
	}
	top := p.Archive[len(p.Archive)-1]
	rel, err := filepath.Rel(top.RootPath, p.Path)
	if err != nil {
		return p.Path
	}
	rel = filepath.ToSlash(rel)
	if rel == "." {
		rel = ""
	}
	if rel == "" {
		return top.SourcePath + "::"
	}
	return top.SourcePath + "::/" + rel
}

func renderPane(
	pane BrowserPane,
	cfg config.Config,
	palette theme.Palette,
	width int,
	height int,
	active bool,
	hoverIndex int,
	useNerdIcons bool,
) string {
	if width <= 0 || height <= 0 {
		return ""
	}

	borderColor := palette.Border
	headerBg := palette.PanelInactive
	bodyBg := palette.Panel
	if active {
		borderColor = palette.BorderActive
		headerBg = palette.Selection
	}

	innerWidth := max(width-2, 1)
	innerHeight := max(height-2, 1)

	box := lipgloss.NewStyle().
		Width(innerWidth).
		Height(innerHeight).
		Background(bodyBg).
		Foreground(palette.Text).
		BorderStyle(borderStyle(cfg.UI.Border)).
		BorderForeground(borderColor).
		BorderBackground(bodyBg)

	header := lipgloss.NewStyle().
		Render(renderPaneHeader(pane, cfg, palette, innerWidth, active, headerBg))

	rowsHeight := max(innerHeight-2, 1)
	headerRow := renderColumnsHeader(cfg, innerWidth, palette, bodyBg, useNerdIcons)
	rows := renderPaneRows(pane, cfg, palette, innerWidth, rowsHeight, active, hoverIndex, bodyBg, useNerdIcons)
	content := lipgloss.JoinVertical(lipgloss.Left, header, headerRow, rows)
	return box.Render(content)
}

func renderPaneHeader(pane BrowserPane, cfg config.Config, palette theme.Palette, width int, active bool, headerBg lipgloss.Color) string {
	pathWidth := max(width, 4)
	pathStyle := lipgloss.NewStyle().
		Width(pathWidth).
		Background(headerBg).
		Foreground(palette.Text).
		Bold(active)
	if active {
		pathStyle = pathStyle.Foreground(palette.ActivePath)
	}

	return lipgloss.NewStyle().
		Width(width).
		Background(headerBg).
		Render(pathStyle.Render(truncateMiddle(compactPath(pane.DisplayPath(), cfg.UI.PathDisplay), pathWidth)))
}

func renderColumnsHeader(cfg config.Config, width int, palette theme.Palette, background lipgloss.Color, useNerdIcons bool) string {
	columns := buildColumns(cfg, width, useNerdIcons)
	parts := make([]string, 0, len(columns))
	for idx, column := range columns {
		style := lipgloss.NewStyle().
			Width(column.Width).
			Foreground(palette.Muted).
			Background(background).
			Bold(true)
		if column.AlignRight {
			style = style.Align(lipgloss.Right)
		}
		parts = append(parts, style.Render(truncateRight(column.Title, column.Width)))
		if idx < len(columns)-1 {
			parts = append(parts, columnSeparator(column.Key, palette, background))
		}
	}
	return lipgloss.NewStyle().
		Width(width).
		Background(background).
		Render(strings.Join(parts, ""))
}

func renderPaneRows(pane BrowserPane, cfg config.Config, palette theme.Palette, width int, height int, active bool, hoverIndex int, background lipgloss.Color, useNerdIcons bool) string {
	if len(pane.Entries) == 0 {
		return lipgloss.NewStyle().
			Width(width).
			Height(height).
			Padding(1, 1).
			Background(background).
			Foreground(palette.Muted).
			Render("Empty directory")
	}

	visibleHeight := max(height, 1)
	pane.EnsureVisible(visibleHeight)
	end := min(len(pane.Entries), pane.Offset+visibleHeight)

	lines := make([]string, 0, visibleHeight)
	for idx := pane.Offset; idx < end; idx++ {
		entry := pane.Entries[idx]
		isSelected := idx == pane.Cursor && active
		marked := !entry.IsParent && pane.IsMarked(entry.Path)
		row := renderEntryRow(entry, cfg, width, isSelected, marked, idx == hoverIndex, active, palette, background, useNerdIcons)
		lines = append(lines, row)
	}
	for len(lines) < visibleHeight {
		lines = append(lines, lipgloss.NewStyle().Width(width).Background(background).Render(""))
	}

	return lipgloss.NewStyle().
		Width(width).
		Background(background).
		Render(strings.Join(lines, "\n"))
}

func renderEntryRow(entry vfs.Entry, cfg config.Config, width int, selected bool, marked bool, hovered bool, active bool, palette theme.Palette, baseBackground lipgloss.Color, useNerdIcons bool) string {
	columns := buildColumns(cfg, width, useNerdIcons)
	rowBackground := baseBackground
	switch {
	case marked:
		rowBackground = palette.Marked
	case selected:
		rowBackground = palette.Selection
	case hovered:
		rowBackground = palette.Hover
	}

	parts := make([]string, 0, len(columns))
	for idx, column := range columns {
		value := column.Value(entry, cfg.Browser.HumanReadableSize)
		foreground := entryColor(entry, palette)
		if marked {
			foreground = palette.Background
		}
		style := lipgloss.NewStyle().
			Width(column.Width).
			Foreground(foreground).
			Background(rowBackground)

		if entry.IsHidden && !marked {
			style = style.Foreground(palette.Muted)
		}
		if column.AlignRight {
			style = style.Align(lipgloss.Right)
		}
		parts = append(parts, style.Render(truncateForColumn(value, column.Width, column.AlignRight)))
		if idx < len(columns)-1 {
			parts = append(parts, columnSeparator(column.Key, palette, rowBackground))
		}
	}

	rowStyle := lipgloss.NewStyle().Width(width).Background(rowBackground)
	if selected && active {
		rowStyle = rowStyle.Bold(true)
	}

	return rowStyle.Render(strings.Join(parts, ""))
}

type columnSpec struct {
	Key        string
	Title      string
	Width      int
	MinWidth   int
	AlignRight bool
	Value      func(entry vfs.Entry, human bool) string
}

func buildColumns(cfg config.Config, totalWidth int, useNerdIcons bool) []columnSpec {
	fixed := []columnSpec{}

	if cfg.Browser.Columns.Permissions {
		fixed = append(fixed, columnSpec{
			Key:      "permissions",
			Title:    "Perms",
			Width:    10,
			MinWidth: 9,
			Value: func(entry vfs.Entry, _ bool) string {
				return vfs.Permissions(entry.Mode)
			},
		})
	}
	if cfg.Browser.Columns.Extension {
		fixed = append(fixed, columnSpec{
			Key:      "extension",
			Title:    "Ext",
			Width:    6,
			MinWidth: 4,
			Value: func(entry vfs.Entry, _ bool) string {
				if entry.IsDir {
					return ""
				}
				return entry.Extension
			},
		})
	}
	if cfg.Browser.Columns.Size {
		fixed = append(fixed, columnSpec{
			Key:        "size",
			Title:      "Size",
			Width:      9,
			MinWidth:   6,
			AlignRight: true,
			Value: func(entry vfs.Entry, human bool) string {
				if entry.IsDir {
					if !entry.DirSizeKnown {
						return ""
					}
					if human {
						return vfs.HumanSize(entry.Size)
					}
					return fmt.Sprintf("%d", entry.Size)
				}
				if human {
					return vfs.HumanSize(entry.Size)
				}
				return fmt.Sprintf("%d", entry.Size)
			},
		})
	}
	if cfg.Browser.Columns.Created {
		fixed = append(fixed, columnSpec{
			Key:      "created",
			Title:    "Created",
			Width:    11,
			MinWidth: 8,
			Value: func(entry vfs.Entry, _ bool) string {
				if !entry.CreatedKnown {
					return "n/a"
				}
				return vfs.CompactTime(entry.CreatedAt)
			},
		})
	}
	if cfg.Browser.Columns.Modified {
		fixed = append(fixed, columnSpec{
			Key:      "modified",
			Title:    "Modified",
			Width:    11,
			MinWidth: 8,
			Value: func(entry vfs.Entry, _ bool) string {
				return vfs.CompactTime(entry.ModifiedAt)
			},
		})
	}

	minNameWidth := 4
	gaps := 0
	for _, column := range fixed {
		gaps += separatorWidth(column.Key)
	}
	availableForColumns := totalWidth - gaps
	if availableForColumns < minNameWidth {
		availableForColumns = minNameWidth
	}

	fixedWidth := 0
	for _, column := range fixed {
		fixedWidth += column.Width
	}
	for fixedWidth+minNameWidth > availableForColumns {
		changed := false
		for idx := len(fixed) - 1; idx >= 0 && fixedWidth+minNameWidth > availableForColumns; idx-- {
			if fixed[idx].Width > fixed[idx].MinWidth {
				fixed[idx].Width--
				fixedWidth--
				changed = true
			}
		}
		if !changed {
			break
		}
	}

	nameWidth := max(availableForColumns-fixedWidth, minNameWidth)
	name := columnSpec{
		Key:      "name",
		Title:    "Name",
		Width:    nameWidth,
		MinWidth: minNameWidth,
		Value: func(entry vfs.Entry, _ bool) string {
			return entryIcon(entry, useNerdIcons) + " " + entry.DisplayName()
		},
	}

	return append([]columnSpec{name}, fixed...)
}

func columnSeparator(columnKey string, palette theme.Palette, background lipgloss.Color) string {
	width := separatorWidth(columnKey)
	style := lipgloss.NewStyle().
		Width(width).
		Foreground(palette.Border)
	if background != lipgloss.Color("") {
		style = style.Background(background)
	}
	return style.Render(strings.Repeat(" ", width))
}

func separatorWidth(columnKey string) int {
	if columnKey == "size" {
		return 2
	}
	return 1
}

func borderStyle(value string) lipgloss.Border {
	switch strings.ToLower(value) {
	case "double":
		return lipgloss.DoubleBorder()
	case "thick":
		return lipgloss.ThickBorder()
	default:
		return lipgloss.RoundedBorder()
	}
}

func compactPath(path string, mode string) string {
	switch strings.ToLower(mode) {
	case "full":
		return path
	case "smart":
		return smartPath(path, 42)
	default:
		return vfs.SafeBase(path)
	}
}

func smartPath(path string, maxWidth int) string {
	if lipgloss.Width(path) <= maxWidth {
		return path
	}
	return truncateMiddle(path, maxWidth)
}

func truncateMiddle(value string, maxWidth int) string {
	if maxWidth <= 0 || lipgloss.Width(value) <= maxWidth {
		return value
	}
	if maxWidth <= 3 {
		return trimToWidthRight(value, maxWidth)
	}
	left := maxWidth/2 - 1
	right := maxWidth - left - 1
	if left < 1 {
		left = 1
	}
	if right < 1 {
		right = 1
	}
	return trimToWidthRight(value, left) + "…" + trimToWidthLeft(value, right)
}

func truncateRight(value string, maxWidth int) string {
	if maxWidth <= 0 || lipgloss.Width(value) <= maxWidth {
		return value
	}
	if maxWidth == 1 {
		return trimToWidthRight(value, 1)
	}
	return trimToWidthRight(value, maxWidth-1) + "…"
}

func truncateForColumn(value string, maxWidth int, alignRight bool) string {
	if lipgloss.Width(value) <= maxWidth {
		return value
	}
	if alignRight {
		if maxWidth <= 1 {
			return trimToWidthLeft(value, 1)
		}
		return "…" + trimToWidthLeft(value, maxWidth-1)
	}
	return truncateRight(value, maxWidth)
}

func trimToWidthRight(value string, maxWidth int) string {
	if maxWidth <= 0 {
		return ""
	}
	width := 0
	var builder strings.Builder
	for _, r := range value {
		rw := lipgloss.Width(string(r))
		if width+rw > maxWidth {
			break
		}
		builder.WriteRune(r)
		width += rw
	}
	return builder.String()
}

func trimToWidthLeft(value string, maxWidth int) string {
	if maxWidth <= 0 {
		return ""
	}
	runes := []rune(value)
	width := 0
	start := len(runes)
	for i := len(runes) - 1; i >= 0; i-- {
		rw := lipgloss.Width(string(runes[i]))
		if width+rw > maxWidth {
			break
		}
		width += rw
		start = i
	}
	return string(runes[start:])
}

func entryIcon(entry vfs.Entry, useNerdIcons bool) string {
	if !useNerdIcons {
		switch entry.Category() {
		case "parent":
			return "<-"
		case "directory":
			return "[D]"
		case "config":
			return "[C]"
		case "text":
			return "[T]"
		case "image":
			return "[I]"
		case "executable":
			return "[X]"
		case "archive":
			return "[A]"
		default:
			return "[F]"
		}
	}
	switch entry.Category() {
	case "parent":
		return "↩"
	case "directory":
		return ""
	case "config":
		return ""
	case "text":
		return "󰈙"
	case "image":
		return "󰋩"
	case "executable":
		return "󰆍"
	case "archive":
		return ""
	default:
		return "󰈔"
	}
}

func entryColor(entry vfs.Entry, palette theme.Palette) lipgloss.Color {
	switch entry.Category() {
	case "directory", "parent":
		return palette.Folder
	case "config":
		return palette.ConfigFile
	case "text":
		return palette.TextFile
	case "image":
		return palette.ImageFile
	case "executable":
		return palette.ExecFile
	default:
		return palette.BinaryFile
	}
}
