package ui

import (
	"sort"
	"strings"
	"time"

	"gitea.mixdep.ru/mix/gosentry/src/domain"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
)

func newEvent(jobID int, jobName string, state string, detail string) event {
	// Use the same timestamp shape as command run records so the History tab is
	// visually consistent across startup, UI actions, manual runs, and schedules.
	return event{
		Time:    time.Now().Format("2006-01-02 15:04:05"),
		JobID:   jobID,
		JobName: jobName,
		Trigger: "UI",
		State:   state,
		Detail:  detail,
	}
}

func collectActivity(jobs []job, runtimes map[int]*domain.JobRuntime) []event {
	var events []event
	for _, current := range jobs {
		// At startup this is usually empty because jobs.json does not persist
		// runtime logs. The function still centralizes the merge for future
		// history loading from log metadata.
		if rt := runtimes[current.ID]; rt != nil {
			events = append(events, rt.Logs...)
		}
	}
	sort.SliceStable(events, func(left int, right int) bool {
		return events[left].Time < events[right].Time
	})
	return events
}

// logColumnMinWidth/logColumnMaxWidth bound the dynamically sized Log column.
// The minimum keeps the column readable when names are short or absent; the
// maximum stops a single very long file name from dominating the table (the
// table still scrolls horizontally past it).
const (
	logColumnMinWidth = 240
	logColumnMaxWidth = 520
	logColumnPadding  = 24
)

// logColumnWidth measures the widest Log cell value so the column can be sized
// to fit its content. Fyne tables do not auto-size columns, so without this the
// fixed width clips file names like "20260601-100000_SomeJobName.log".
func logColumnWidth(events []event) float32 {
	width := float32(logColumnMinWidth)
	for _, current := range events {
		text := logFileName(current.LogFile)
		if text == "" {
			continue
		}
		w := fyne.MeasureText(text, theme.TextSize(), fyne.TextStyle{}).Width + logColumnPadding
		if w > width {
			width = w
		}
	}
	if width > logColumnMaxWidth {
		width = logColumnMaxWidth
	}
	return width
}

// historyHeader is a bold tappable label used in the History table header row.
// In Fyne 2.7+ OnSelected is not fired for header cells (Row < 0), so the sort
// toggle is wired through the Tappable interface instead.
type historyHeader struct {
	widget.BaseWidget
	label    *widget.Label
	OnTapped func()
}

func newHistoryHeader() *historyHeader {
	h := &historyHeader{label: widget.NewLabel("")}
	h.label.TextStyle = fyne.TextStyle{Bold: true}
	h.label.Wrapping = fyne.TextTruncate
	h.ExtendBaseWidget(h)
	return h
}

func (h *historyHeader) CreateRenderer() fyne.WidgetRenderer {
	return widget.NewSimpleRenderer(h.label)
}

func (h *historyHeader) Tapped(*fyne.PointEvent) {
	if h.OnTapped != nil {
		h.OnTapped()
	}
}

func (h *historyHeader) SetText(text string) {
	h.label.SetText(text)
}

func newHistoryView(events *[]event) (*fyne.Container, func()) {
	descending := false
	headerText := func(id widget.TableCellID) string {
		headers := []string{"Time", "Trigger", "Job", "State", "Detail", "Log"}
		if id.Row < 0 && id.Col == 0 {
			if descending {
				return "Time ▼"
			}
			return "Time ▲"
		}
		if id.Row < 0 && id.Col >= 0 && id.Col < len(headers) {
			return headers[id.Col]
		}
		return ""
	}
	sortedEvents := func() []event {
		result := append([]event(nil), (*events)...)
		sort.SliceStable(result, func(left int, right int) bool {
			if descending {
				return result[left].Time > result[right].Time
			}
			return result[left].Time < result[right].Time
		})
		return result
	}

	table := widget.NewTable(
		func() (int, int) {
			return len(*events), 6
		},
		func() fyne.CanvasObject {
			label := widget.NewLabel("")
			label.Wrapping = fyne.TextTruncate
			return label
		},
		func(id widget.TableCellID, item fyne.CanvasObject) {
			label := item.(*widget.Label)
			label.SetText(historyCellText(id, sortedEvents()))
			label.TextStyle = fyne.TextStyle{}
			label.Refresh()
		},
	)
	table.ShowHeaderRow = true
	table.CreateHeader = func() fyne.CanvasObject {
		return newHistoryHeader()
	}
	table.UpdateHeader = func(id widget.TableCellID, item fyne.CanvasObject) {
		h := item.(*historyHeader)
		h.SetText(headerText(id))
		if id.Row < 0 && id.Col == 0 {
			h.OnTapped = func() {
				descending = !descending
				table.Refresh()
			}
		} else {
			h.OnTapped = nil
		}
		h.Refresh()
	}
	table.OnSelected = func(id widget.TableCellID) {
		table.Unselect(id)
	}
	table.SetColumnWidth(0, 150)
	table.SetColumnWidth(1, 90)
	table.SetColumnWidth(2, 170)
	table.SetColumnWidth(3, 90)
	table.SetColumnWidth(4, 260)
	table.SetColumnWidth(5, logColumnWidth(*events))

	// refresh recomputes the content-fit Log column width before redrawing, so
	// newly recorded events with longer file names widen the column instead of
	// being truncated.
	refresh := func() {
		table.SetColumnWidth(5, logColumnWidth(*events))
		table.Refresh()
	}
	return container.NewPadded(table), refresh
}

func historyCellText(id widget.TableCellID, events []event) string {
	if id.Row < 0 || id.Row >= len(events) {
		return ""
	}
	current := events[id.Row]
	trigger := current.Trigger
	if trigger == "" {
		trigger = "Unknown"
	}
	switch id.Col {
	case 0:
		return current.Time
	case 1:
		return trigger
	case 2:
		return current.JobName
	case 3:
		return current.State
	case 4:
		return current.Detail
	case 5:
		return logFileName(current.LogFile)
	default:
		return ""
	}
}

func logFileName(path string) string {
	path = strings.TrimSpace(path)
	if path == "" {
		return ""
	}
	path = strings.ReplaceAll(path, "\\", "/")
	if slash := strings.LastIndex(path, "/"); slash >= 0 {
		return path[slash+1:]
	}
	return path
}
