package components

import (
	"cmp"
	"context"
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strings"

	"github.com/gdamore/tcell/v2"
	"github.com/google/uuid"
	"github.com/rivo/tview"

	"github.com/jorgerojas26/lazysql/app"
	"github.com/jorgerojas26/lazysql/commands"
	"github.com/jorgerojas26/lazysql/drivers"
	"github.com/jorgerojas26/lazysql/helpers"
	"github.com/jorgerojas26/lazysql/helpers/logger"
	"github.com/jorgerojas26/lazysql/internal/history"
	"github.com/jorgerojas26/lazysql/lib"
	"github.com/jorgerojas26/lazysql/models"
)

type ResultsTableState struct {
	listOfDBChanges       *[]models.DBDMLChange
	error                 string
	currentSort           string
	databaseName          string
	tableName             string
	primaryKeyColumnNames []string
	columns               [][]string
	constraints           [][]string
	foreignKeys           [][]string
	indexes               [][]string
	records               [][]string
	foreignKeyColumns     map[string]bool
	foreignKeyJumpTargets map[string]foreignKeyJumpTarget
	fkRawCellValues       map[string]string
	isEditing             bool
	isFiltering           bool
	isLoading             bool
	showSidebar           bool
	loadingCancel         context.CancelFunc
}

type foreignKeyJumpTarget struct {
	ReferencedTable  string
	ReferencedColumn string
}

type ResultsTable struct {
	*tview.Table
	state                *ResultsTableState
	Page                 *tview.Pages
	Wrapper              *tview.Flex
	Menu                 *ResultsTableMenu
	Filter               *ResultsTableFilter
	Error                *tview.Modal
	jsonViewer           *JSONViewer
	Pagination           *Pagination
	Editor               *SQLEditor
	EditorPages          *tview.Pages
	ResultsInfo          *tview.TextView
	Tree                 *Tree
	Sidebar              *Sidebar
	SidebarContainer     *tview.Flex
	DBDriver             drivers.Driver
	Home                 *Home
	connectionIdentifier string
	ConnectionURL        string
	ReadOnly             bool
}

func NewResultsTable(listOfDBChanges *[]models.DBDMLChange, tree *Tree, dbdriver drivers.Driver, home *Home, connectionIdentifier string, connectionURL string, readOnly bool) *ResultsTable {
	state := &ResultsTableState{
		records:               [][]string{},
		columns:               [][]string{},
		constraints:           [][]string{},
		foreignKeys:           [][]string{},
		indexes:               [][]string{},
		foreignKeyColumns:     map[string]bool{},
		foreignKeyJumpTargets: map[string]foreignKeyJumpTarget{},
		fkRawCellValues:       map[string]string{},
		isEditing:             false,
		isLoading:             false,
		listOfDBChanges:       listOfDBChanges,
		showSidebar:           false,
	}

	wrapper := tview.NewFlex()
	wrapper.SetDirection(tview.FlexColumnCSS)

	errorModal := tview.NewModal()
	errorModal.AddButtons([]string{"Ok"})
	errorModal.SetText("An error occurred")
	errorModal.SetBackgroundColor(tcell.ColorRed)
	errorModal.SetTextColor(app.Styles.PrimaryTextColor)
	errorModal.SetButtonStyle(tcell.StyleDefault.Foreground(app.Styles.PrimaryTextColor))
	errorModal.SetFocus(0)

	pages := tview.NewPages()
	pages.AddPage(pageNameTable, wrapper, true, true)
	pages.AddPage(pageNameTableError, errorModal, true, false)

	pagination := NewPagination()

	sidebar := NewSidebar(dbdriver.GetProvider(), readOnly)

	table := &ResultsTable{
		Table:      tview.NewTable(),
		state:      state,
		Page:       pages,
		Wrapper:    wrapper,
		Error:      errorModal,
		Pagination: pagination,
		Editor:     nil,
		Tree:       tree,
		DBDriver:   dbdriver,
		Home:       home,
		Sidebar:    sidebar,
		// SidebarContainer is only used when AppConfig.SidebarOverlay is false.
		SidebarContainer:     tview.NewFlex(),
		connectionIdentifier: connectionIdentifier,
		ConnectionURL:        connectionURL,
		ReadOnly:             readOnly,
	}

	table.jsonViewer = NewJSONViewer(pages)

	// When AppConfig.SidebarOverlay is true, the sidebar is added as a page to the table.Page.
	// When AppConfig.SidebarOverlay is false, the sidebar is added to the table.SidebarContainer.
	table.Page.AddPage(pageNameSidebar, table.Sidebar, false, false)

	table.SetSelectable(true, true)
	table.SetBorders(true)
	table.SetFixed(1, 0)
	table.SetInputCapture(table.tableInputCapture)
	table.SetSelectedStyle(tcell.StyleDefault.Background(app.Styles.SecondaryTextColor).Foreground(tview.Styles.ContrastSecondaryTextColor))

	table.SetSelectionChangedFunc(func(_, _ int) {
		if table.GetShowSidebar() {
			go table.UpdateSidebar()
		}
	})

	go table.subscribeToTreeChanges()
	go table.subscribeToSidebarChanges()

	return table
}

func (table *ResultsTable) WithFilter() *ResultsTable {
	menu := NewResultsTableMenu()
	filter := NewResultsFilter()

	table.Menu = menu
	table.Filter = filter

	if App.Config().SidebarOverlay {
		table.Wrapper.AddItem(menu, 3, 0, false)
		table.Wrapper.AddItem(filter, 3, 0, false)
		table.Wrapper.AddItem(table, 0, 1, true)
		table.Wrapper.AddItem(table.Pagination, 3, 0, false)
	} else {
		tableContainer := tview.NewFlex().SetDirection(tview.FlexColumnCSS)
		tableContainer.AddItem(menu, 3, 0, false)
		tableContainer.AddItem(filter, 3, 0, false)
		tableContainer.AddItem(table, 0, 1, true)
		tableContainer.AddItem(table.Pagination, 3, 0, false)
		tableContainer.SetBorder(true)

		if table.ReadOnly {
			tableContainer.SetTitle(" [READ-ONLY] ")
			tableContainer.SetTitleColor(tcell.ColorLightBlue)
		}

		table.SidebarContainer.AddItem(tableContainer, 0, 4, true)

		table.Wrapper.AddItem(table.SidebarContainer, 0, 1, true)
	}

	go table.subscribeToFilterChanges()

	return table
}

func (table *ResultsTable) WithEditor() *ResultsTable {
	editor := NewSQLEditor(table.ConnectionURL)
	editorPages := tview.NewPages()

	editor.SetFocusFunc(func() {
		table.SetIsEditing(true)
	})

	editor.SetBlurFunc(func() {
		table.SetIsEditing(false)
	})

	table.Editor = editor

	table.Wrapper.Clear()

	table.Wrapper.AddItem(editor, 12, 0, true)
	table.SetBorder(true)

	tableWrapper := tview.NewFlex().SetDirection(tview.FlexColumnCSS)
	tableWrapper.AddItem(table, 0, 1, false)
	tableWrapper.AddItem(table.Pagination, 3, 0, false)

	resultsInfoWrapper := tview.NewFlex().SetDirection(tview.FlexColumnCSS)
	resultsInfoText := tview.NewTextView()
	resultsInfoText.SetBorder(true)
	resultsInfoText.SetBorderColor(app.Styles.PrimaryTextColor)
	resultsInfoText.SetTextColor(app.Styles.PrimaryTextColor)
	resultsInfoWrapper.AddItem(resultsInfoText, 3, 0, false)

	editorPages.AddPage(pageNameTableEditorTable, tableWrapper, true, false)
	editorPages.AddPage(pageNameTableEditorResultsInfo, resultsInfoWrapper, true, true)

	table.EditorPages = editorPages
	table.ResultsInfo = resultsInfoText

	table.Wrapper.AddItem(editorPages, 0, 1, true)

	go table.subscribeToEditorChanges()

	return table
}

// loadEditorSchema queries the driver for tables and columns to populate
// autocomplete suggestions in the editor.
func (table *ResultsTable) loadEditorSchema() {
	dbName := table.GetDatabaseName()
	if dbName == "" || table.DBDriver == nil {
		return
	}

	tablesMap, err := table.DBDriver.GetTables(dbName)
	if err != nil {
		logger.Error("Failed to load tables for editor autocomplete", map[string]any{"error": err.Error()})
		return
	}
	if len(tablesMap) == 0 {
		return
	}

	// Collect all bare table names (for SetTables) and also build
	// schema-qualified names for the GetTableColumns driver call.
	// Postgres returns {schema: [tables]}, others return {dbName: [tables]}.
	type namedTable struct {
		bareName      string // stored in autocompleter for "table." lookup
		qualifiedName string // passed to GetTableColumns (schema.table for Postgres)
	}
	var allTables []string
	var tableList []namedTable

	for schema, tbls := range tablesMap {
		for _, tbl := range tbls {
			allTables = append(allTables, tbl)
			qn := tbl
			// Postgres-style drivers use schemas distinct from the database name.
			if schema != "" && schema != dbName {
				qn = schema + "." + tbl
			}
			tableList = append(tableList, namedTable{bareName: tbl, qualifiedName: qn})
		}
	}

	app.App.QueueUpdateDraw(func() {
		if table.Editor != nil {
			table.Editor.SetTables(allTables)
		}
	})

	// Load columns for each table, using the qualified name for the driver call
	// but storing under the bare table name for autocomplete lookup ("table.col").
	for _, nt := range tableList {
		cols, err := table.DBDriver.GetTableColumns(dbName, nt.qualifiedName)
		if err != nil {
			_ = err
			continue
		}
		if len(cols) < 2 {
			continue
		}
		// cols[0] = headers, cols[1:] = data rows, column name at index 0
		colNames := make([]string, 0, len(cols)-1)
		for i := 1; i < len(cols); i++ {
			if len(cols[i]) > 0 && cols[i][0] != "" {
				colNames = append(colNames, cols[i][0])
			}
		}
		if len(colNames) == 0 {
			continue
		}

		tblCopy := nt.bareName
		app.App.QueueUpdateDraw(func() {
			if table.Editor != nil {
				table.Editor.SetColumns(tblCopy, colNames)
			}
		})
	}
}

func (table *ResultsTable) subscribeToTreeChanges() {
	ch := table.Tree.Subscribe()

	for stateChange := range ch {
		if stateChange.Key == eventTreeSelectedDatabase {
			table.SetDatabaseName(stateChange.Value.(string))
		}
	}
}

func (table *ResultsTable) subscribeToSidebarChanges() {
	ch := table.Sidebar.Subscribe()

	for stateChange := range ch {
		switch stateChange.Key {
		case eventSidebarEditing:
			editing := stateChange.Value.(bool)
			table.SetIsEditing(editing)
		case eventSidebarUnfocusing:
			App.QueueUpdateDraw(func() {
				App.SetFocus(table)
			})
		case eventSidebarToggling:
			App.QueueUpdateDraw(func() {
				table.ShowSidebar(false)
			})
		case eventSidebarCommitEditing:
			params := stateChange.Value.(models.SidebarEditingCommitParams)

			App.QueueUpdateDraw(func() {
				table.SetInputCapture(table.tableInputCapture)
				table.SetIsEditing(false)

				row, _ := table.GetSelection()
				changedColumnIndex := table.GetColumnIndexByName(params.ColumnName)
				tableCell := table.GetCell(row, changedColumnIndex)

				tableCell.SetText(params.NewValue)

				cellValue := models.CellValue{
					Type:             params.Type,
					Column:           params.ColumnName,
					Value:            params.NewValue,
					TableColumnIndex: changedColumnIndex,
					TableRowIndex:    row,
				}

				logger.Info("eventSidebarCommitEditing", map[string]any{"cellValue": cellValue, "params": params, "rowIndex": row, "changedColumnIndex": changedColumnIndex})
				err := table.AppendNewChange(models.DMLUpdateType, row, changedColumnIndex, cellValue)
				if err != nil {
					table.SetError(err.Error(), nil)
				}
			})
		case eventSidebarError:
			errorMessage := stateChange.Value.(string)
			App.QueueUpdateDraw(func() {
				table.SetError(errorMessage, nil)
			})
		}
	}
}

func (table *ResultsTable) AddRows(rows [][]string) {
	for i, row := range rows {
		for j, cell := range row {
			tableCell := tview.NewTableCell(cell)
			tableCell.SetTextColor(app.Styles.PrimaryTextColor)

			if cell == "EMPTY&" || cell == "NULL&" || cell == "DEFAULT&" {
				tableCell.SetText(strings.Replace(cell, "&", "", 1))
				tableCell.SetStyle(table.GetItalicStyle())
				tableCell.SetReference(cell)
			}

			tableCell.SetSelectable(i > 0)
			tableCell.SetExpansion(1)

			if i == 0 && table.shouldShowForeignKeyHeaderMarker(j) {
				tableCell.SetStyle(tcell.StyleDefault.Underline(true))
			}

			if i > 0 && table.shouldShowForeignKeyMarker(i, j, cell) {
				tableCell.SetStyle(tcell.StyleDefault.Underline(true))
			}

			table.SetCell(i, j, tableCell)
		}
	}
}

func (table *ResultsTable) AddInsertedRows() {
	inserts := make([]models.DBDMLChange, 0)

	for _, change := range *table.state.listOfDBChanges {
		if change.Type == models.DMLInsertType {
			inserts = append(inserts, change)
		}
	}

	rows := make([][]models.CellValue, len(inserts))

	if len(inserts) > 0 {
		for i, insert := range inserts {
			if insert.Table == table.GetTableName() {
				rows[i] = insert.Values
			}
		}
	}

	rowCount := table.GetRowCount()
	for i, row := range rows {
		rowIndex := rowCount + i

		for j, cell := range row {
			tableCell := tview.NewTableCell(cell.Value.(string))
			tableCell.SetExpansion(1)
			tableCell.SetReference(inserts[i].PrimaryKeyInfo[0].Value)

			tableCell.SetTextColor(app.Styles.PrimaryTextColor)
			tableCell.SetBackgroundColor(colorTableInsert)

			table.SetCell(rowIndex, j, tableCell)
		}
	}
}

func (table *ResultsTable) AppendNewRow(cells []models.CellValue, index int, UUID string) {
	for i, cell := range cells {
		tableCell := tview.NewTableCell(cell.Value.(string))
		tableCell.SetExpansion(1)
		// Appended rows have a reference to the row UUID so we can identify them later
		// Also, rows that have columns marked to be UPDATED will have a reference to the type of the new value (NULL, EMPTY, DEFAULT)
		// So, the cell reference will be used to determine if the row/column is an inserted row or if it's an UPDATED row
		// there might be a better way to do this, but it works for now
		tableCell.SetReference(UUID)
		tableCell.SetTextColor(app.Styles.PrimaryTextColor)
		tableCell.SetBackgroundColor(tcell.ColorDarkGreen)

		switch cell.Type {
		case models.Null, models.Empty, models.Default:
			tableCell.SetText(strings.Replace(cell.Value.(string), "&", "", 1))
			tableCell.SetStyle(table.GetItalicStyle())
			// tableCell.SetText("")

			tableCell.SetTextColor(app.Styles.InverseTextColor)
		}

		tableCell.SetBackgroundColor(colorTableInsert)
		table.SetCell(index, i, tableCell)
	}

	table.Select(index, 0)
	App.ForceDraw()
}

func (table *ResultsTable) tableInputCapture(event *tcell.EventKey) *tcell.EventKey {
	selectedRowIndex, selectedColumnIndex := table.GetSelection()
	colCount := table.GetColumnCount()
	rowCount := table.GetRowCount()

	eventKey := event.Rune()

	command := app.Keymaps.Group(app.TableGroup).Resolve(event)

	menuCommands := []commands.Command{commands.RecordsMenu, commands.ColumnsMenu, commands.ConstraintsMenu, commands.ForeignKeysMenu, commands.IndexesMenu, commands.Refresh}

	if helpers.ContainsCommand(menuCommands, command) {
		table.Select(1, 0)
	}

	if table.Menu != nil {
		switch command {
		case commands.RecordsMenu:
			table.Menu.SetSelectedOption(1)
			table.UpdateRows(table.GetRecords())
			table.colorChangedCells()
			table.AddInsertedRows()
			table.UpdateRowsColor(app.Styles.PrimaryTextColor, tview.Styles.PrimaryTextColor)
		case commands.ColumnsMenu:
			table.Menu.SetSelectedOption(2)
			table.UpdateRows(table.GetColumns())
		case commands.ConstraintsMenu:
			table.Menu.SetSelectedOption(3)
			table.UpdateRows(table.GetConstraints())
		case commands.ForeignKeysMenu:
			table.Menu.SetSelectedOption(4)
			table.UpdateRows(table.GetForeignKeys())
		case commands.IndexesMenu:
			table.Menu.SetSelectedOption(5)
			table.UpdateRows(table.GetIndexes())
		case commands.Refresh:
			table.Menu.SetSelectedOption(1)
			table.FetchRecords(nil, nil)
		}
	}

	switch command {
	case commands.AppendNewRow:
		if table.ReadOnly {
			table.SetError("Cannot modify data: Connection is in read-only mode", nil)
			return nil
		}
		if table.Menu.GetSelectedOption() == 1 {
			table.appendNewRow()
		}
	case commands.DuplicateRow:
		if table.ReadOnly {
			table.SetError("Cannot modify data: Connection is in read-only mode", nil)
			return nil
		}
		if table.Menu.GetSelectedOption() == 1 {
			table.duplicateRow()
		}
	case commands.Search:
		table.search()
	}

	if rowCount == 1 || colCount == 0 {
		return nil
	}

	if command == commands.Edit {
		if table.ReadOnly {
			table.SetError("Cannot modify data: Connection is in read-only mode", nil)
			return nil
		}
		if table.Editor == nil {
			table.StartEditingCell(selectedRowIndex, selectedColumnIndex, func(_ string, _, _ int) {
				if table.GetShowSidebar() {
					table.UpdateSidebar()
				}
			})
		}
	} else if command == commands.GotoNext {
		if selectedColumnIndex+1 < colCount {
			table.Select(selectedRowIndex, selectedColumnIndex+1)
		}
	} else if command == commands.GotoPrev {
		if selectedColumnIndex > 0 {
			table.Select(selectedRowIndex, selectedColumnIndex-1)
		}
	} else if command == commands.GotoEnd {
		table.Select(selectedRowIndex, colCount-1)
	} else if command == commands.GotoStart {
		table.Select(selectedRowIndex, 0)
	} else if command == commands.GotoBottom {
		go table.Select(1, selectedColumnIndex)
	} else if command == commands.GotoTop {
		go table.Select(rowCount-1, selectedColumnIndex)
	} else if eventKey == 4 { // Ctrl + D
		if selectedRowIndex+7 > rowCount-1 {
			go table.Select(rowCount-1, selectedColumnIndex)
		} else {
			go table.Select(selectedRowIndex+7, selectedColumnIndex)
		}
	} else if eventKey == 21 { // Ctrl + U
		if selectedRowIndex-7 < 1 {
			go table.Select(1, selectedColumnIndex)
		} else {
			go table.Select(selectedRowIndex-7, selectedColumnIndex)
		}
	} else if command == commands.Delete {
		if table.ReadOnly {
			table.SetError("Cannot modify data: Connection is in read-only mode", nil)
			return nil
		}
		if table.Menu != nil && table.Menu.GetSelectedOption() == 1 && table.Editor == nil {

			isAnInsertedRow, indexOfInsertedRow := table.isAnInsertedRow(selectedRowIndex)

			if isAnInsertedRow {
				*table.state.listOfDBChanges = append((*table.state.listOfDBChanges)[:indexOfInsertedRow], (*table.state.listOfDBChanges)[indexOfInsertedRow+1:]...)
				table.RemoveRow(selectedRowIndex)
				if selectedRowIndex-1 != 0 {
					table.Select(selectedRowIndex-1, 0)
				} else {
					if selectedRowIndex+1 < rowCount {
						table.Select(selectedRowIndex+1, 0)
					}
				}
			} else {
				err := table.AppendNewChange(models.DMLDeleteType, selectedRowIndex, -1, models.CellValue{TableColumnIndex: -1, TableRowIndex: selectedRowIndex, Column: table.GetColumnNameByIndex(selectedColumnIndex)})
				if err != nil {
					table.SetError(err.Error(), nil)
				}
			}

		}
	} else if command == commands.SetValue {
		if table.ReadOnly {
			table.SetError("Cannot modify data: Connection is in read-only mode", nil)
			return nil
		}
		if table.Editor == nil {
			table.SetIsEditing(true)
			table.SetInputCapture(nil)

			cell := table.GetCell(selectedRowIndex, selectedColumnIndex)
			x, y, _ := cell.GetLastPosition()

			list := NewSetValueList(table.DBDriver.GetProvider())

			list.OnFinish(func(selection models.CellValueType, value string) {
				table.FinishSettingValue()

				if selection >= 0 {
					err := table.AppendNewChange(models.DMLUpdateType, selectedRowIndex, selectedColumnIndex, models.CellValue{Type: selection, Value: value, Column: table.GetColumnNameByIndex(selectedColumnIndex)})
					if err != nil {
						table.SetError(err.Error(), nil)
					}
				}
			})

			list.Show(x, y, 30)
		}
	} else if command == commands.ToggleSidebar {
		if table.Editor == nil {
			table.ShowSidebar(!table.GetShowSidebar())
		}
	} else if command == commands.FocusSidebar {
		if table.GetShowSidebar() {
			App.SetFocus(table.Sidebar)
		}
	} else if command == commands.ShowRowJSONViewer {
		table.handleShowJSONViewer(commands.ShowRowJSONViewer)
		return nil
	} else if command == commands.ShowCellJSONViewer {
		table.handleShowJSONViewer(commands.ShowCellJSONViewer)
		return nil
	} else if event.Key() == tcell.KeyEnter {
		if table.handleForeignKeyEnter(selectedRowIndex, selectedColumnIndex) {
			return nil
		}
		if app.App.Config().EnterOpensJSONViewer {
			table.handleShowJSONViewer(commands.ShowCellJSONViewer)
			return nil
		}
	} else if command == commands.Copy {
		selectedCell := table.GetCell(selectedRowIndex, selectedColumnIndex)
		if selectedCell != nil {
			clipboard := lib.NewClipboard()
			err := clipboard.Write(selectedCell.Text)
			if err != nil {
				table.SetError(err.Error(), nil)
			}
		}
	} else if command == commands.OpenCellInExternalEditor {
		if table.ReadOnly {
			table.SetError("Cannot modify data: Connection is in read-only mode", nil)
			return nil
		}
		if table.Editor == nil && (runtime.GOOS == "linux" || runtime.GOOS == "darwin") {
			selectedCell := table.GetCell(selectedRowIndex, selectedColumnIndex)
			if selectedCell != nil {
				originalText := selectedCell.Text
				var newText string
				app.App.Suspend(func() {
					newText = openCellInExternalEditor(originalText)
				})
				// Strip trailing newline that editors typically add
				newText = strings.TrimSuffix(newText, "\n")
				if newText != originalText {
					selectedCell.SetText(newText)
					columnName := table.GetColumnNameByIndex(selectedColumnIndex)
					err := table.AppendNewChange(models.DMLUpdateType, selectedRowIndex, selectedColumnIndex, models.CellValue{
						Type:             models.String,
						Value:            newText,
						Column:           columnName,
						TableColumnIndex: selectedColumnIndex,
						TableRowIndex:    selectedRowIndex,
					})
					if err != nil {
						table.SetError(err.Error(), nil)
					}
					if table.GetShowSidebar() {
						table.UpdateSidebar()
					}
				}
			}
		}
		return nil
	} else if command == commands.ExportCSV {
		table.showCSVExportModal()
		return nil
	}

	if len(table.GetRecords()) > 0 {
		switch command {
		case commands.SortDesc:
			currentColumnName := table.GetColumnNameByIndex(selectedColumnIndex)
			table.Pagination.SetOffset(0)
			table.SetSortedBy(currentColumnName, "DESC")
		case commands.SortAsc:
			currentColumnName := table.GetColumnNameByIndex(selectedColumnIndex)
			table.Pagination.SetOffset(0)
			table.SetSortedBy(currentColumnName, "ASC")
		}
	}

	return event
}

func (table *ResultsTable) UpdateRows(rows [][]string) {
	table.state.fkRawCellValues = map[string]string{}
	table.Clear()
	table.AddRows(rows)
	App.ForceDraw()
	table.Select(1, 0)
}

func (table *ResultsTable) UpdateRowsColor(headerColor tcell.Color, rowColor tcell.Color) {
	for i := 0; i < table.GetRowCount(); i++ {
		for j := 0; j < table.GetColumnCount(); j++ {
			cell := table.GetCell(i, j)
			if i == 0 && headerColor != 0 {
				if table.shouldShowForeignKeyHeaderMarker(j) {
					cell.SetStyle(tcell.StyleDefault.Underline(true))
					cell.SetTextColor(headerColor)
				} else {
					cell.SetStyle(tcell.StyleDefault)
					cell.SetTextColor(headerColor)
				}
			} else {
				cellReference := cell.GetReference()

				if cellReference != nil && (cellReference == "EMPTY&" || cellReference == "NULL&" || cellReference == "DEFAULT&") && (cell.BackgroundColor != colorTableDelete && cell.BackgroundColor != colorTableChange && cell.BackgroundColor != colorTableInsert) {
					cell.SetStyle(table.GetItalicStyle())
				} else if table.shouldShowForeignKeyMarker(i, j, cell.Text) {
					cell.SetStyle(tcell.StyleDefault.Underline(true))
					cell.SetTextColor(rowColor)
				} else {
					cell.SetStyle(tcell.StyleDefault)
					cell.SetTextColor(rowColor)
				}
			}
		}
	}
}

func (table *ResultsTable) shouldShowForeignKeyHeaderMarker(columnIndex int) bool {
	if table.Menu != nil && table.Menu.GetSelectedOption() != 1 {
		return false
	}

	if !table.IsForeignKeyJumpSupportedProvider() {
		return false
	}

	columnName := table.GetColumnNameByIndex(columnIndex)
	if columnName == "" || !table.isForeignKeyColumn(columnName) {
		return false
	}

	_, ok := table.getForeignKeyJumpTarget(columnName)
	return ok
}

func (table *ResultsTable) RemoveHighlightTable() {
	table.SetBorderColor(app.Styles.InverseTextColor)
	table.SetBordersColor(app.Styles.InverseTextColor)
	table.SetTitleColor(app.Styles.InverseTextColor)
	table.UpdateRowsColor(app.Styles.InverseTextColor, tview.Styles.InverseTextColor)
}

func (table *ResultsTable) RemoveHighlightAll() {
	table.RemoveHighlightTable()
	if table.Menu != nil {
		table.Menu.SetBlur()
	}
	if table.Filter != nil {
		table.Filter.RemoveHighlight()
	}
}

func (table *ResultsTable) HighlightTable() {
	table.SetBorderColor(app.Styles.PrimaryTextColor)
	table.SetBordersColor(app.Styles.PrimaryTextColor)
	table.SetTitleColor(app.Styles.PrimaryTextColor)
	table.UpdateRowsColor(app.Styles.PrimaryTextColor, tview.Styles.PrimaryTextColor)
}

func (table *ResultsTable) HighlightAll() {
	table.HighlightTable()
	if table.Menu != nil {
		table.Menu.SetFocus()
	}
	if table.Filter != nil {
		table.Filter.Highlight()
	}
}

func (table *ResultsTable) subscribeToFilterChanges() {
	ch := table.Filter.Subscribe()

	for stateChange := range ch {
		switch stateChange.Key {
		case eventResultsTableFiltering:
			if stateChange.Value != "" {
				table.FetchRecords(nil, func() {
					records := table.GetRecords()
					if len(records) > 0 {
						table.Menu.SetSelectedOption(1)
						table.SetIsFiltering(false)
						App.SetFocus(table)
						table.HighlightTable()
						table.Filter.HighlightLocal()
						table.SetInputCapture(table.tableInputCapture)
					}
				})
			} else {
				App.QueueUpdateDraw(func() {
					table.SetIsFiltering(false)
					table.SetInputCapture(table.tableInputCapture)
					App.SetFocus(table)
					table.HighlightTable()
					table.Filter.HighlightLocal()
				})
			}
		}
	}
}

func (table *ResultsTable) subscribeToEditorChanges() {
	ch := table.Editor.Subscribe()

	for stateChange := range ch {
		switch stateChange.Key {
		case eventSQLEditorQuery:
			query := stateChange.Value.(string)
			if query != "" {
				queryLower := strings.ToLower(query)
				queryTrimmed := strings.TrimSpace(queryLower)

				isSelect := strings.HasPrefix(queryTrimmed, "select") ||
					strings.HasPrefix(queryTrimmed, "with") ||
					strings.HasPrefix(queryTrimmed, "explain") ||
					strings.HasPrefix(queryTrimmed, "show") ||
					strings.HasPrefix(queryTrimmed, "describe") ||
					strings.HasPrefix(queryTrimmed, "desc")

				// Clear existing records immediately for SQL editor queries and
				// start a cancellable loading cycle on the UI goroutine.
				var ctx context.Context
				App.QueueUpdateDraw(func() {
					table.SetRecords([][]string{})
					ctx = table.StartLoad()
				})

				if isSelect {
					go func() {
						if ctx.Err() != nil {
							return
						}

						rows, records, err := table.DBDriver.ExecuteQuery(query)

						if ctx.Err() != nil {
							return
						}

						App.QueueUpdateDraw(func() {
							if ctx.Err() != nil {
								return
							}

							if err != nil {
								table.SetLoading(false)
								table.SetError(err.Error(), nil)
								return
							}

							table.Pagination.SetTotalRecords(records)
							table.Pagination.SetLimit(records)
							table.SetRecords(rows)
							table.SetLoading(false)
							closeQuitConfirmation()
							table.SetIsFiltering(false)
							table.HighlightTable()
							table.Editor.SetBlur()
							table.SetInputCapture(table.tableInputCapture)
							table.EditorPages.SwitchToPage(pageNameTableEditorTable)
							App.SetFocus(table)

							if err := history.AddQueryToHistory(table.connectionIdentifier, query); err != nil {
								logger.Error("Failed to add SELECT query to history", map[string]any{"error": err, "query": query, "connection": table.connectionIdentifier})
							}
						})
					}()
				} else {
					if table.ReadOnly {
						if err := drivers.ValidateQueryForReadOnly(query); err != nil {
							App.QueueUpdateDraw(func() {
								table.SetError("Cannot execute mutation query: Connection is in read-only mode", nil)
								table.SetLoading(false)
							})
							continue
						}
					}

					go func() {
						if ctx.Err() != nil {
							return
						}

						result, err := table.DBDriver.ExecuteDMLStatement(query)

						if ctx.Err() != nil {
							return
						}

						App.QueueUpdateDraw(func() {
							if ctx.Err() != nil {
								return
							}

							if err != nil {
								table.SetLoading(false)
								table.SetError(err.Error(), nil)
								return
							}

							table.SetResultsInfo(result)
							table.SetLoading(false)
							closeQuitConfirmation()
							table.EditorPages.SwitchToPage(pageNameTableEditorResultsInfo)
							App.SetFocus(table.Editor)

							if err := history.AddQueryToHistory(table.connectionIdentifier, query); err != nil {
								logger.Error("Failed to add DML query to history", map[string]any{"error": err, "query": query, "connection": table.connectionIdentifier})
							}
						})
					}()
				}
			}
		case eventSQLEditorEscape:
			App.QueueUpdateDraw(func() {
				table.SetIsFiltering(false)
				App.SetFocus(table)
				table.HighlightTable()
				table.Editor.SetBlur()
				table.SetInputCapture(table.tableInputCapture)
			})
		}
	}
}

// Getters

func (table *ResultsTable) GetRecords() [][]string {
	return table.state.records
}

func (table *ResultsTable) GetIndexes() [][]string {
	return table.state.indexes
}

func (table *ResultsTable) GetColumns() [][]string {
	return table.state.columns
}

func (table *ResultsTable) GetConstraints() [][]string {
	return table.state.constraints
}

func (table *ResultsTable) GetForeignKeys() [][]string {
	return table.state.foreignKeys
}

func (table *ResultsTable) GetTableName() string {
	return table.state.tableName
}

func (table *ResultsTable) GetDatabaseName() string {
	return table.state.databaseName
}

func (table *ResultsTable) GetDatabaseAndTableName() string {
	return fmt.Sprintf("%s.%s", table.GetDatabaseName(), table.GetTableName())
}

func (table *ResultsTable) GetIsEditing() bool {
	return table.state.isEditing
}

func (table *ResultsTable) GetCurrentSort() string {
	return table.state.currentSort
}

func (table *ResultsTable) GetColumnNameByIndex(index int) string {
	columns := table.GetColumns()

	for i, col := range columns {
		if i > 0 && i == index+1 {
			return col[0]
		}
	}

	return ""
}

func (table *ResultsTable) GetColumnIndexByName(columnName string) int {
	cols := table.GetColumns()
	index := -1

	for i, col := range cols {
		if i > 0 && col[0] == columnName {
			index = i - 1 // Because the first column is the column names
			break
		}
	}

	return index
}

func (table *ResultsTable) GetIsLoading() bool {
	return table.state.isLoading
}

func (table *ResultsTable) GetIsFiltering() bool {
	return table.state.isFiltering
}

func (table *ResultsTable) GetShowSidebar() bool {
	return table.state.showSidebar
}

func (table *ResultsTable) GetPrimaryKeyColumnNames() []string {
	return table.state.primaryKeyColumnNames
}

// GetPrimaryKeySort returns an ORDER BY clause using primary key columns.
// Returns empty string if no primary key columns are available.
func (table *ResultsTable) GetPrimaryKeySort() string {
	pkColumnNames := table.GetPrimaryKeyColumnNames()
	if len(pkColumnNames) == 0 {
		return ""
	}
	// Use first primary key column for sorting
	return fmt.Sprintf("%s ASC", pkColumnNames[0])
}

// Setters

func (table *ResultsTable) SetRecords(rows [][]string) {
	table.state.records = rows
	table.UpdateRows(rows)
	table.colorChangedCells()
}

func (table *ResultsTable) SetColumns(columns [][]string) {
	table.state.columns = columns
}

func (table *ResultsTable) SetConstraints(constraints [][]string) {
	table.state.constraints = constraints
}

func (table *ResultsTable) SetForeignKeys(foreignKeys [][]string) {
	table.state.foreignKeys = foreignKeys
	table.rebuildForeignKeyJumpMetadata()
}

func (table *ResultsTable) SetIndexes(indexes [][]string) {
	table.state.indexes = indexes
}

func (table *ResultsTable) SetDatabaseName(databaseName string) {
	table.state.databaseName = databaseName
}

func (table *ResultsTable) SetTableName(tableName string) {
	table.state.tableName = tableName
}

func (table *ResultsTable) SetError(err string, done func()) {
	table.state.error = err

	table.Error.SetText(err)
	table.Error.SetDoneFunc(func(_ int, _ string) {
		table.state.error = ""
		table.Page.HidePage(pageNameTableError)
		closeQuitConfirmation()
		if table.GetIsFiltering() {
			if table.Editor != nil {
				App.SetFocus(table.Editor)
			} else {
				App.SetFocus(table.Filter.Input)
			}
		} else {
			App.SetFocus(table)
		}
		if done != nil {
			done()
		}
	})
	table.Page.ShowPage(pageNameTableError)
	App.SetFocus(table.Error)
	App.ForceDraw()
}

func (table *ResultsTable) SetResultsInfo(text string) {
	table.ResultsInfo.SetText(text)
}

func (table *ResultsTable) SetLoading(show bool) {
	table.state.isLoading = show
	table.Pagination.SetLoading(show)
}

func (table *ResultsTable) CancelLoading() {
	if table.state.loadingCancel != nil {
		table.state.loadingCancel()
		table.state.loadingCancel = nil
	}
}

func (table *ResultsTable) StartLoad() context.Context {
	table.CancelLoading()
	ctx, cancel := context.WithCancel(app.App.Context())
	table.state.loadingCancel = cancel
	table.SetLoading(true)
	return ctx
}

func (table *ResultsTable) SetIsEditing(editing bool) {
	table.state.isEditing = editing
}

func (table *ResultsTable) SetIsFiltering(filtering bool) {
	table.state.isFiltering = filtering
}

func (table *ResultsTable) SetCurrentSort(sort string) {
	table.state.currentSort = sort
}

func (table *ResultsTable) SetSortedBy(column string, direction string) {
	sort := fmt.Sprintf("%s %s", column, direction)

	if table.GetCurrentSort() != sort {
		ctx := table.StartLoad()

		go func() {
			if ctx.Err() != nil {
				return
			}

			where := ""
			if table.Filter != nil {
				where = table.Filter.GetCurrentFilter()
			}

			records, _, _, err := table.DBDriver.GetRecords(table.GetDatabaseName(), table.GetTableName(), where, sort, table.Pagination.GetOffset(), table.Pagination.GetLimit())

			if ctx.Err() != nil {
				return
			}

			App.QueueUpdateDraw(func() {
				if ctx.Err() != nil {
					return
				}

				if err != nil {
					table.SetLoading(false)
					table.SetError(err.Error(), nil)
					return
				}

				previousRow, previousColumn := table.GetSelection()
				table.SetRecords(records)
				table.Select(previousRow, previousColumn)
				table.SetCurrentSort(sort)

				columns := table.GetColumns()
				iconDirection := "▲"

				if direction == "DESC" {
					iconDirection = "▼"
				}

				for i, col := range columns {
					if i > 0 {
						tableCell := tview.NewTableCell(col[0])
						tableCell.SetSelectable(false)
						tableCell.SetExpansion(1)
						tableCell.SetTextColor(app.Styles.PrimaryTextColor)

						if col[0] == column {
							tableCell.SetText(fmt.Sprintf("%s %s", col[0], iconDirection))
							table.SetCell(0, i-1, tableCell)
						} else {
							table.SetCell(0, i-1, tableCell)
						}
					}
				}

				table.SetLoading(false)
				App.ForceDraw()
			})
		}()
	}
}

func (table *ResultsTable) SetPrimaryKeyColumnNames(primaryKeyColumnNames []string) {
	table.state.primaryKeyColumnNames = primaryKeyColumnNames
}

func (table *ResultsTable) FetchRecords(onError func(), onSuccess func()) {
	databaseName := table.GetDatabaseName()
	tableName := table.GetTableName()

	ctx := table.StartLoad()

	go func() {
		if ctx.Err() != nil {
			return
		}

		where := ""
		if table.Filter != nil {
			where = table.Filter.GetCurrentFilter()
		}
		sort := table.GetCurrentSort()

		records, totalRecords, executedQuery, err := table.DBDriver.GetRecords(databaseName, tableName, where, sort, table.Pagination.GetOffset(), table.Pagination.GetLimit())

		if ctx.Err() != nil {
			return
		}

		if err == nil {
			var columns, constraints, foreignKeys, indexes [][]string
			var primaryKeyColumnNames []string

			columns, _ = table.DBDriver.GetTableColumns(databaseName, tableName)
			constraints, _ = table.DBDriver.GetConstraints(databaseName, tableName)
			foreignKeys, _ = table.DBDriver.GetForeignKeys(databaseName, tableName)
			indexes, _ = table.DBDriver.GetIndexes(databaseName, tableName)
			primaryKeyColumnNames, _ = table.DBDriver.GetPrimaryKeyColumnNames(databaseName, tableName)

			if ctx.Err() != nil {
				return
			}

			App.QueueUpdateDraw(func() {
				if ctx.Err() != nil {
					return
				}

				if where != "" && executedQuery != "" {
					if err := history.AddQueryToHistory(table.connectionIdentifier, executedQuery); err != nil {
						logger.Error("Failed to add filter query to history", map[string]any{"error": err, "query": executedQuery, "connection": table.connectionIdentifier})
					}
				}

				table.SetColumns(columns)
				table.SetConstraints(constraints)
				table.SetForeignKeys(foreignKeys)
				table.SetIndexes(indexes)
				table.SetPrimaryKeyColumnNames(primaryKeyColumnNames)

				if len(records) > 0 {
					table.SetRecords(records)
				}
				table.Select(1, 0)
				table.Pagination.SetTotalRecords(totalRecords)
				table.SetLoading(false)

				if len(primaryKeyColumnNames) == 0 {
					currentText := table.Pagination.textView.GetText(false)
					table.Pagination.textView.SetText(currentText + " ⚠ No Primary Key")
				}

				if onSuccess != nil {
					onSuccess()
				}
			})
		} else {
			App.QueueUpdateDraw(func() {
				if ctx.Err() != nil {
					return
				}
				table.SetError(err.Error(), onError)
				table.SetLoading(false)
			})
		}
	}()
}

func (table *ResultsTable) StartEditingCell(row int, col int, callback func(newValue string, row, col int)) {
	table.SetIsEditing(true)
	table.SetInputCapture(nil)

	cell := table.GetCell(row, col)
	inputField := tview.NewInputField()
	inputField.SetText(cell.Text)
	inputField.SetFieldBackgroundColor(app.Styles.PrimaryTextColor)
	inputField.SetFieldTextColor(app.Styles.PrimitiveBackgroundColor)
	inputField.SetBorder(true)

	initialText := cell.Text

	inputField.SetDoneFunc(func(key tcell.Key) {
		table.SetIsEditing(false)
		currentValue := cell.Text
		newValue := inputField.GetText()
		columnName := table.GetCell(0, col).Text

		// Remove the sorting icon from the column name
		columnName = strings.ReplaceAll(columnName, " ▼", "")
		columnName = strings.ReplaceAll(columnName, " ▲", "")

		var appendErr error

		if key != tcell.KeyEscape {
			cell.SetText(newValue)

			if currentValue != newValue {
				appendErr = table.AppendNewChange(models.DMLUpdateType, row, col, models.CellValue{Type: models.String, Value: newValue, Column: columnName, TableColumnIndex: col, TableRowIndex: row})
			}

			switch key {
			case tcell.KeyTab:
				nextEditableColumnIndex := col + 1

				if nextEditableColumnIndex <= table.GetColumnCount()-1 {
					table.Select(row, nextEditableColumnIndex)

					table.StartEditingCell(row, nextEditableColumnIndex, callback)

				}
			case tcell.KeyBacktab:
				nextEditableColumnIndex := col - 1

				if nextEditableColumnIndex >= 0 {
					table.Select(row, nextEditableColumnIndex)

					table.StartEditingCell(row, nextEditableColumnIndex, callback)

				}
			}

		}

		if key == tcell.KeyEnter || key == tcell.KeyEscape {
			if appendErr != nil {
				cell.Text = initialText
			}

			table.SetInputCapture(table.tableInputCapture)
			table.Page.RemovePage(pageNameTableEditCell)

			if appendErr != nil {
				table.SetError(appendErr.Error(), nil)
			} else {
				App.SetFocus(table)
			}
		}

		if callback != nil {
			callback(newValue, row, col)
		}
	})

	x, y, width := cell.GetLastPosition()
	inputField.SetRect(x-1, y-1, width+3, 3)
	table.Page.AddPage(pageNameTableEditCell, inputField, false, true)
	App.SetFocus(inputField)
}

func (table *ResultsTable) handleShowJSONViewer(command commands.Command) {
	selectedRow, selectedCol := table.GetSelection()
	if selectedRow == 0 { // It's the header
		return
	}

	// is an inserted row, do not show json viewer
	isAnInsertedRow, _ := table.isAnInsertedRow(selectedRow)
	if isAnInsertedRow {
		return
	}

	rowData := make(map[string]string)

	if command == commands.ShowRowJSONViewer {
		for i := 0; i < table.GetColumnCount(); i++ {
			columnName := table.GetColumnNameByIndex(i)
			cellValue := table.GetCell(selectedRow, i).Text
			rowData[columnName] = cellValue
		}
	} else if command == commands.ShowCellJSONViewer {
		columnName := table.GetColumnNameByIndex(selectedCol)
		cellValue := table.GetCell(selectedRow, selectedCol).Text
		rowData[columnName] = cellValue
	}

	table.jsonViewer.Show(rowData, table)
}

func (table *ResultsTable) CheckIfRowIsInserted(rowID string) bool {
	for _, dmlChange := range *table.state.listOfDBChanges {
		if dmlChange.Type == models.DMLInsertType && dmlChange.PrimaryKeyInfo[0].Value == rowID {
			return true
		}
	}

	return false
}

func (table *ResultsTable) MutateInsertedRowCell(rowID string, newValue models.CellValue) {
	for i, dmlChange := range *table.state.listOfDBChanges {
		if dmlChange.PrimaryKeyInfo[0].Value == rowID && dmlChange.Type == models.DMLInsertType {
			for j, v := range dmlChange.Values {
				if v.Column == newValue.Column {
					(*table.state.listOfDBChanges)[i].Values[j] = newValue
					break
				}
			}
		}
	}
}

func (table *ResultsTable) AppendNewChange(changeType models.DMLType, rowIndex int, colIndex int, value models.CellValue) error {
	// case models.Empty:
	// placeholders = append(placeholders, "")
	databaseName := table.GetDatabaseName()
	tableName := table.GetTableName()

	dmlChangeAlreadyExists := false

	// If the column has a reference, it means it's an inserted rowIndex
	// There is maybe a better way to detect it is an inserted row
	tableCell := table.GetCell(rowIndex, colIndex)
	tableCellReference := tableCell.GetReference()

	isAnInsertedRow, _ := table.isAnInsertedRow(rowIndex)

	if isAnInsertedRow {
		if changeType == models.DMLUpdateType {
			switch value.Type {
			case models.Null, models.Empty, models.Default:
				tableCell.SetText(value.Value.(string))
				tableCell.SetStyle(tcell.StyleDefault.Italic(true))
			}
		}
		table.MutateInsertedRowCell(tableCellReference.(string), value)
		return nil
	}

	rowPrimaryKeyInfo := table.GetPrimaryKeyValue(rowIndex)

	if len(rowPrimaryKeyInfo) == 0 {
		return fmt.Errorf("Primary key not found for row %d", rowIndex)
	}

	if changeType == models.DMLUpdateType {
		switch value.Type {
		case models.Null, models.Empty, models.Default:
			tableCell.SetText(value.Value.(string))
			tableCell.SetStyle(tcell.StyleDefault.Italic(true))
			tableCell.SetReference(value.Value.(string) + "&")
		}
	}

	for i, dmlChange := range *table.state.listOfDBChanges {
		changeExistOnSameCell := false

		for _, value := range dmlChange.Values {
			if value.TableRowIndex == rowIndex && value.TableColumnIndex == colIndex {
				changeExistOnSameCell = true
				break
			}
		}

		if dmlChange.Table == tableName && dmlChange.Type == changeType && changeExistOnSameCell {
			dmlChangeAlreadyExists = true

			changeForColExists := false
			valueIndex := -1

			for j, v := range dmlChange.Values {
				if v.Column == value.Column {
					changeForColExists = true
					valueIndex = j
					break
				}
			}

			switch changeType {
			case models.DMLUpdateType:
				originalValue := table.GetRecords()[rowIndex][colIndex]

				if changeForColExists {
					if originalValue == value.Value {
						if len((*table.state.listOfDBChanges)[i].Values) == 1 {
							*table.state.listOfDBChanges = append((*table.state.listOfDBChanges)[:i], (*table.state.listOfDBChanges)[i+1:]...)
						} else {
							(*table.state.listOfDBChanges)[i].Values = append((*table.state.listOfDBChanges)[i].Values[:valueIndex], (*table.state.listOfDBChanges)[i].Values[valueIndex+1:]...)
						}
						table.SetCellColor(rowIndex, colIndex, app.Styles.PrimitiveBackgroundColor)
					} else {
						(*table.state.listOfDBChanges)[i].Values[valueIndex] = value
					}
				} else {
					(*table.state.listOfDBChanges)[i].Values = append((*table.state.listOfDBChanges)[i].Values, value)
					table.SetCellColor(rowIndex, colIndex, colorTableChange)
				}

			case models.DMLDeleteType:
				*table.state.listOfDBChanges = append((*table.state.listOfDBChanges)[:i], (*table.state.listOfDBChanges)[i+1:]...)
				table.SetRowColor(rowIndex, app.Styles.PrimitiveBackgroundColor)
			}
		}
	}

	if !dmlChangeAlreadyExists {
		switch changeType {
		case models.DMLDeleteType:
			table.SetRowColor(rowIndex, colorTableDelete)
		case models.DMLUpdateType:
			tableCell.SetStyle(tcell.StyleDefault.Background(colorTableChange))
			table.SetCellColor(rowIndex, colIndex, colorTableChange)
		}

		newDMLChange := models.DBDMLChange{
			Type:           changeType,
			Database:       databaseName,
			Table:          tableName,
			Values:         []models.CellValue{value},
			PrimaryKeyInfo: rowPrimaryKeyInfo,
		}

		*table.state.listOfDBChanges = append(*table.state.listOfDBChanges, newDMLChange)
	}

	return nil
}

func (table *ResultsTable) GetPrimaryKeyValue(rowIndex int) []models.PrimaryKeyInfo {
	primaryKeyColumnNames := table.GetPrimaryKeyColumnNames()

	info := []models.PrimaryKeyInfo{}

	if len(primaryKeyColumnNames) == 0 {
		allRecords := table.GetRecords()
		// Unsaved-new-row deletions shrink the record slice while the
		// caller still passes the original rowIndex; without these
		// guards lazysql panicked with 'index out of range' instead of
		// no-op'ing the second delete.
		if len(allRecords) == 0 || rowIndex < 0 || rowIndex >= len(allRecords) {
			return info
		}
		columns := allRecords[0]
		row := allRecords[rowIndex]
		for i, colName := range columns {
			if i >= len(row) {
				break
			}
			info = append(info, models.PrimaryKeyInfo{Name: colName, Value: row[i]})
		}
		return info
	}

	for _, primaryKeyColumnName := range primaryKeyColumnNames {
		columnIndex := table.GetColumnIndexByName(primaryKeyColumnName)
		records := table.GetRecords()
		if rowIndex < 0 || rowIndex >= len(records) {
			continue
		}
		row := records[rowIndex]
		if columnIndex < 0 || columnIndex >= len(row) {
			continue
		}
		primaryKeyValue := row[columnIndex]

		info = append(info, models.PrimaryKeyInfo{Name: primaryKeyColumnName, Value: primaryKeyValue})
	}

	return info
}

func (table *ResultsTable) SetRowColor(rowIndex int, color tcell.Color) {
	for i := 0; i < table.GetColumnCount(); i++ {
		table.GetCell(rowIndex, i).SetBackgroundColor(color)
	}
}

func (table *ResultsTable) SetCellColor(rowIndex int, colIndex int, color tcell.Color) {
	table.GetCell(rowIndex, colIndex).SetBackgroundColor(color)
}

func (table *ResultsTable) appendNewRow() {
	dbColumns := table.GetColumns()
	newRowTableIndex := table.GetRowCount()
	newRowUUID := uuid.New().String()
	newRow := make([]models.CellValue, len(dbColumns)-1)

	for i, column := range dbColumns {
		if i != 0 { // Skip the first row because they are the column names (e.x "Field", "Type", "Null", "Key", "Default", "Extra")
			newRow[i-1] = models.CellValue{Type: models.Default, Column: column[0], Value: "DEFAULT", TableRowIndex: newRowTableIndex, TableColumnIndex: i}
		}
	}

	newInsert := models.DBDMLChange{
		Type:           models.DMLInsertType,
		Database:       table.GetDatabaseName(),
		Table:          table.GetTableName(),
		Values:         newRow,
		PrimaryKeyInfo: []models.PrimaryKeyInfo{{Name: "", Value: newRowUUID}},
	}

	*table.state.listOfDBChanges = append(*table.state.listOfDBChanges, newInsert)

	table.AppendNewRow(newRow, newRowTableIndex, newRowUUID)

	table.StartEditingCell(newRowTableIndex, 0, nil)
}

func (table *ResultsTable) duplicateRow() {
	row, _ := table.GetSelection()
	if row <= 0 { // ignorer l'en-tête
		return
	}

	dbColumns := table.GetColumns()
	newRowTableIndex := row + 1 // table.GetRowCount()
	newRowUUID := uuid.New().String()
	newRow := make([]models.CellValue, len(dbColumns)-1)

	for i, column := range dbColumns {
		if i != 0 { // Skip the first row because they are the column names (e.x "Field", "Type", "Null", "Key", "Default", "Extra")
			origCell := table.GetCell(row, i-1)
			newRow[i-1] = models.CellValue{Type: models.String, Column: column[0], Value: origCell.Text, TableRowIndex: newRowTableIndex, TableColumnIndex: i}
		}
	}

	newInsert := models.DBDMLChange{
		Type:           models.DMLInsertType,
		Database:       table.GetDatabaseName(),
		Table:          table.GetTableName(),
		Values:         newRow,
		PrimaryKeyInfo: []models.PrimaryKeyInfo{{Name: "", Value: newRowUUID}},
	}

	*table.state.listOfDBChanges = append(*table.state.listOfDBChanges, newInsert)

	table.InsertRow(newRowTableIndex)

	table.AppendNewRow(newRow, newRowTableIndex, newRowUUID)

	table.StartEditingCell(newRowTableIndex, 0, nil)
}

func (table *ResultsTable) search() {
	if table.Editor != nil {
		App.SetFocus(table.Editor)
		table.Editor.Highlight()
		table.RemoveHighlightTable()
		table.SetIsFiltering(true)
		return
	}

	App.SetFocus(table.Filter.Input)
	table.RemoveHighlightTable()
	table.Filter.HighlightLocal()
	table.SetIsFiltering(true)

	if table.Filter.Input.GetText() == "/" {
		go table.Filter.Input.SetText("")
	}

	table.Filter.Input.SetAutocompleteFunc(func(currentText string) []string {
		split := strings.Split(currentText, " ")
		comparators := []string{
			"=", "!=",
			">", "<",
			">=", "<=",
			"between", "not between",
			"ilike", "not ilike",
			"in", "not in",
			"is", "is not",
			"like", "not like",
			"regexp", "not regexp",
		}

		matches := []string{}

		switch len(split) {
		case 1:
			cols := table.GetColumns()
			if len(cols) > 1 {
				for _, col := range cols[1:] {
					matches = append(matches, col[0])
				}
			}

		case 2:
			for _, comparator := range comparators {
				if strings.HasPrefix(comparator, split[1]) {
					matches = append(matches, fmt.Sprintf("%s %s", split[0], comparator))
				}
			}

		case 3:
			switch split[1] {
			case "not":
				comparators = []string{"between", "ilike", "in", "like", "regexp"}
			case "is":
				comparators = []string{"not", "null"}
			default:
				return matches
			}
			for _, comparator := range comparators {
				if strings.HasPrefix(comparator, split[2]) {
					matches = append(matches, fmt.Sprintf("%s %s %s", split[0], split[1], comparator))
				}
			}

		case 4:
			switch split[2] {
			case "not":
				comparators = []string{"null"}
			case "is":
				comparators = []string{"not", "null"}
			default:
				return matches
			}
			for _, comparator := range comparators {
				if strings.HasPrefix(comparator, split[3]) {
					matches = append(matches,
						fmt.Sprintf("%s %s %s %s", split[0], split[1], split[2], comparator),
					)
				}
			}
		}

		return matches
	})

	table.Filter.Input.SetAutocompletedFunc(func(text string, _ int, source int) bool {
		if source != tview.AutocompletedNavigate {
			inputText := strings.Split(table.Filter.Input.GetText(), " ")

			if len(inputText) == 1 {
				table.Filter.Input.SetText(fmt.Sprintf("%s =", text))
			} else if len(inputText) == 2 {
				table.Filter.Input.SetText(fmt.Sprintf("%s %s", inputText[0], text))
			}

			table.Filter.Input.SetText(text)
		}
		return source == tview.AutocompletedEnter || source == tview.AutocompletedClick
	})

	table.SetInputCapture(nil)
}

func (table *ResultsTable) FinishSettingValue() {
	table.SetIsEditing(false)
	table.SetInputCapture(table.tableInputCapture)
	App.SetFocus(table)
}

func (table *ResultsTable) GetItalicStyle() tcell.Style {
	return tcell.StyleDefault.Foreground(tview.Styles.InverseTextColor).Italic(true)
}

func (table *ResultsTable) ShowSidebar(show bool) {
	table.state.showSidebar = show

	if show {
		table.UpdateSidebar()

		if App.Config().SidebarOverlay {
			table.Page.SendToFront(pageNameSidebar)
			table.Page.ShowPage(pageNameSidebar)
		} else {
			table.SidebarContainer.AddItem(table.Sidebar, 0, 1, true)
		}
	} else {
		if App.Config().SidebarOverlay {
			table.Page.HidePage(pageNameSidebar)
		} else {
			table.SidebarContainer.RemoveItem(table.Sidebar)
		}
		App.SetFocus(table)
	}
}

func (table *ResultsTable) handleForeignKeyEnter(selectedRowIndex, selectedColumnIndex int) bool {
	if selectedRowIndex <= 0 {
		return false
	}

	if table.Menu != nil && table.Menu.GetSelectedOption() != 1 {
		return false
	}

	if !table.IsForeignKeyJumpSupportedProvider() {
		return false
	}

	columnName := table.GetColumnNameByIndex(selectedColumnIndex)
	if columnName == "" {
		return false
	}

	if !table.isForeignKeyColumn(columnName) {
		return false
	}

	target, ok := table.getForeignKeyJumpTarget(columnName)
	if !ok {
		return true
	}

	rawValue := table.getRawCellValue(selectedRowIndex, selectedColumnIndex)
	if !isNavigableForeignKeyValue(rawValue) {
		return true
	}

	if table.Home == nil || table.Filter == nil {
		return true
	}

	where := fmt.Sprintf("WHERE %s = '%s'", table.DBDriver.FormatReference(target.ReferencedColumn), escapeSingleQuotes(rawValue))
	table.Home.ShowTableWithFilter(table.GetDatabaseName(), target.ReferencedTable, where)

	return true
}

func (table *ResultsTable) foreignKeyCellMapKey(rowIndex, columnIndex int) string {
	return fmt.Sprintf("%d:%d", rowIndex, columnIndex)
}

func (table *ResultsTable) shouldShowForeignKeyMarker(rowIndex, columnIndex int, rawValue string) bool {
	if table.Menu != nil && table.Menu.GetSelectedOption() != 1 {
		return false
	}

	if !table.IsForeignKeyJumpSupportedProvider() {
		return false
	}

	columnName := table.GetColumnNameByIndex(columnIndex)
	if columnName == "" || !table.isForeignKeyColumn(columnName) {
		return false
	}

	if _, ok := table.getForeignKeyJumpTarget(columnName); !ok {
		return false
	}

	if !isNavigableForeignKeyValue(rawValue) {
		return false
	}

	if isInsertedRow, _ := table.isAnInsertedRow(rowIndex); isInsertedRow {
		return false
	}

	cell := table.GetCell(rowIndex, columnIndex)
	if cell != nil {
		switch cell.BackgroundColor {
		case colorTableDelete, colorTableChange, colorTableInsert:
			return false
		}
	}

	return true
}

func (table *ResultsTable) getRawCellValue(rowIndex, columnIndex int) string {
	key := table.foreignKeyCellMapKey(rowIndex, columnIndex)
	if value, ok := table.state.fkRawCellValues[key]; ok {
		return value
	}

	cell := table.GetCell(rowIndex, columnIndex)
	if cell == nil {
		return ""
	}

	return cell.Text
}

func (table *ResultsTable) isForeignKeyColumn(columnName string) bool {
	return table.state.foreignKeyColumns[columnName]
}

func (table *ResultsTable) getForeignKeyJumpTarget(columnName string) (foreignKeyJumpTarget, bool) {
	target, ok := table.state.foreignKeyJumpTargets[columnName]
	return target, ok
}

func (table *ResultsTable) rebuildForeignKeyJumpMetadata() {
	table.state.foreignKeyColumns = map[string]bool{}
	table.state.foreignKeyJumpTargets = map[string]foreignKeyJumpTarget{}

	if !table.IsForeignKeyJumpSupportedProvider() {
		return
	}

	if len(table.GetForeignKeys()) <= 1 {
		return
	}

	provider := table.DBDriver.GetProvider()
	headers := table.GetForeignKeys()[0]
	columnNameIndex, okColumn := foreignKeyHeaderIndex(headers, provider, "column_name", "from")
	foreignTableSchemaIndex, _ := foreignKeyHeaderIndex(headers, provider, "foreign_table_schema")
	referencedTableIndex, okTable := foreignKeyHeaderIndex(headers, provider, "foreign_table_name", "table", "referenced_table")
	referencedColumnIndex, okReferencedColumn := foreignKeyHeaderIndex(headers, provider, "foreign_column_name", "to", "referenced_column")
	constraintIndex, okConstraint := foreignKeyHeaderIndex(headers, provider, "constraint_name", "id")
	if !(okColumn && okTable && okReferencedColumn && okConstraint) {
		return
	}

	constraintCounts := map[string]int{}
	for _, row := range table.GetForeignKeys()[1:] {
		if constraintIndex >= len(row) {
			continue
		}
		constraintName := strings.TrimSpace(row[constraintIndex])
		if constraintName == "" {
			continue
		}
		constraintCounts[constraintName]++
	}

	for _, row := range table.GetForeignKeys()[1:] {
		if columnNameIndex >= len(row) || referencedTableIndex >= len(row) || referencedColumnIndex >= len(row) || constraintIndex >= len(row) {
			continue
		}

		constraintName := strings.TrimSpace(row[constraintIndex])
		if constraintName == "" || constraintCounts[constraintName] != 1 {
			continue
		}

		columnName := strings.TrimSpace(row[columnNameIndex])
		foreignTableSchema := ""
		if foreignTableSchemaIndex >= 0 && foreignTableSchemaIndex < len(row) {
			foreignTableSchema = strings.TrimSpace(row[foreignTableSchemaIndex])
		}
		referencedTable := strings.TrimSpace(row[referencedTableIndex])
		referencedColumn := strings.TrimSpace(row[referencedColumnIndex])

		if columnName == "" || referencedTable == "" || referencedColumn == "" {
			continue
		}

		table.state.foreignKeyColumns[columnName] = true
		table.state.foreignKeyJumpTargets[columnName] = foreignKeyJumpTarget{
			ReferencedTable:  table.normalizeForeignKeyReferencedTable(referencedTable, foreignTableSchema),
			ReferencedColumn: referencedColumn,
		}
	}
}

func (table *ResultsTable) normalizeForeignKeyReferencedTable(referencedTable string, foreignTableSchema string) string {
	provider := table.DBDriver.GetProvider()
	if provider == drivers.DriverPostgres {
		if strings.Contains(referencedTable, ".") {
			return referencedTable
		}

		if foreignTableSchema != "" {
			return foreignTableSchema + "." + referencedTable
		}

		currentTable := table.GetTableName()
		if strings.Contains(currentTable, ".") {
			schema := strings.SplitN(currentTable, ".", 2)[0]
			if schema != "" {
				return schema + "." + referencedTable
			}
		}
	}

	return referencedTable
}

func (table *ResultsTable) IsForeignKeyJumpSupportedProvider() bool {
	provider := table.DBDriver.GetProvider()
	return provider == drivers.DriverPostgres || provider == drivers.DriverSqlite || provider == drivers.DriverMSSQL
}

func foreignKeyHeaderIndex(headers []string, provider string, keys ...string) (int, bool) {
	for i, header := range headers {
		normalized := strings.ToLower(strings.TrimSpace(header))
		for _, key := range keys {
			if normalized == key {
				return i, true
			}
		}
	}

	if provider == drivers.DriverPostgres {
		for i, header := range headers {
			normalized := strings.ToLower(strings.TrimSpace(header))
			for _, key := range keys {
				if strings.Contains(normalized, key) {
					return i, true
				}
			}
		}
	}

	return -1, false
}

func isNavigableForeignKeyValue(value string) bool {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return false
	}

	switch strings.ToUpper(trimmed) {
	case "NULL", "EMPTY", "DEFAULT", "NULL&", "EMPTY&", "DEFAULT&":
		return false
	default:
		return true
	}
}

func escapeSingleQuotes(value string) string {
	return strings.ReplaceAll(value, "'", "''")
}

func (table *ResultsTable) UpdateSidebar() {
	columns := table.GetColumns()
	selectedRow, _ := table.GetSelection()

	if selectedRow > 0 {

		if App.Config().SidebarOverlay {
			table.recomputeSidebarPosition()
		}

		table.Sidebar.Clear()

		for i := 1; i < len(columns); i++ {
			name := columns[i][0]
			colType := columns[i][1]

			sidebarWidth := table.getSidebarWidth()

			text := table.GetCell(selectedRow, i-1).Text
			title := name

			repeatCount := sidebarWidth - len(name) - len(colType) - 4 // idk why 4 is needed, but it works.

			if repeatCount <= 0 {
				repeatCount = 1
			}

			title += fmt.Sprintf("[%s]", app.Styles.SidebarTitleBorderColor) + strings.Repeat("-", repeatCount)
			title += colType

			pendingEditExist := false

			for _, dmlChange := range *table.state.listOfDBChanges {
				if dmlChange.Type == models.DMLUpdateType {
					for _, v := range dmlChange.Values {
						if v.Column == name && v.TableRowIndex == selectedRow && v.TableColumnIndex == i-1 {
							pendingEditExist = true
							break
						}
					}
				}
			}

			table.Sidebar.AddField(title, text, sidebarWidth, pendingEditExist)
		}

	}
}

func (table *ResultsTable) getSidebarWidth() int {
	if App.Config().SidebarOverlay {
		_, _, tableInnerWidth, _ := table.GetInnerRect()
		return (tableInnerWidth / 4)
	}

	_, _, width, _ := table.SidebarContainer.GetInnerRect()

	return width
}

// Only used when AppConfig.SidebarOverlay is true.
func (table *ResultsTable) recomputeSidebarPosition() {
	tableX, _, _, tableHeight := table.GetRect()
	_, _, tableInnerWidth, _ := table.GetInnerRect()
	_, tableMenuY, _, tableMenuHeight := table.Menu.GetRect()
	_, _, _, tableFilterHeight := table.Filter.GetRect()
	_, _, _, tablePaginationHeight := table.Pagination.GetRect()

	sidebarWidth := (tableInnerWidth / 4)
	sidebarHeight := tableHeight + tableMenuHeight + tableFilterHeight + tablePaginationHeight + 1

	table.Sidebar.SetRect(tableX+tableInnerWidth-sidebarWidth, tableMenuY, sidebarWidth, sidebarHeight)
}

func (table *ResultsTable) isAnInsertedRow(rowIndex int) (isAnInsertedRow bool, DBChangeIndex int) {
	for i, dmlChange := range *table.state.listOfDBChanges {
		for _, value := range dmlChange.Values {
			if value.TableRowIndex != rowIndex {
				continue
			}
			cellReference := table.GetCell(rowIndex, 0).GetReference()
			if cellReference == nil {
				break
			}
			switch cellReference.(string) {
			case "NULL&", "EMPTY&", "DEFAULT&":
			default:
				return true, i
			}
			break
		}
	}
	return false, -1
}

func (table *ResultsTable) colorChangedCells() {
	tableName := table.GetTableName()
	databaseName := table.GetDatabaseName()

	for _, dmlChange := range *table.state.listOfDBChanges {
		if dmlChange.Table != tableName || dmlChange.Database != databaseName {
			continue
		}

		switch dmlChange.Type {
		case models.DMLDeleteType:
			table.SetRowColor(dmlChange.Values[0].TableRowIndex, colorTableDelete)
		case models.DMLUpdateType:
			for _, value := range dmlChange.Values {
				table.SetCellColor(value.TableRowIndex, value.TableColumnIndex, colorTableChange)
			}
		}
	}
}

func (table *ResultsTable) GetPrimitive() tview.Primitive {
	return table.Page
}

func (table *ResultsTable) showCSVExportModal() {
	databaseName := table.GetDatabaseName()
	tableName := table.GetTableName()

	// Pagination exists only when we have both database and table context (table view)
	// Query results don't have pagination - all records are already in memory
	hasPagination := databaseName != "" && tableName != ""

	rowCount := max(len(table.GetRecords())-1, 0)

	opts := CSVExportOptions{
		DatabaseName:  cmp.Or(databaseName, "database"),
		TableName:     cmp.Or(tableName, "query_result"),
		HasPagination: hasPagination,
		RowCount:      rowCount,
	}

	modal := NewCSVExportModal(opts, func(filePath string, scope CSVExportScope, batchSize int) {
		App.ForceDraw()

		ctx := table.StartLoad()

		go func() {
			if ctx.Err() != nil {
				return
			}

			var exportedRowCount int
			var exportErr error

			if !hasPagination || scope == ExportCurrentPage {
				exportedRowCount, exportErr = table.exportCurrentPage(filePath)
			} else {
				where := ""
				if table.Filter != nil {
					where = table.Filter.GetCurrentFilter()
				}
				sort := cmp.Or(table.GetCurrentSort(), table.GetPrimaryKeySort())
				if sort == "" {
					if records := table.GetRecords(); len(records) > 0 && len(records[0]) > 0 {
						sort = records[0][0] + " ASC"
					}
				}
				exportedRowCount, exportErr = table.exportAllRecordsInBatches(
					filePath, databaseName, tableName, where, sort, batchSize,
				)
			}

			if ctx.Err() != nil {
				return
			}

			App.QueueUpdateDraw(func() {
				if ctx.Err() != nil {
					return
				}

				if exportErr != nil {
					table.SetError("Failed to export CSV: "+exportErr.Error(), nil)
					App.ForceDraw()
					return
				}

				table.showExportSuccessModal(filePath, exportedRowCount)
				App.ForceDraw()
			})
		}()
	})

	mainPages.AddPage(pageNameCSVExport, modal, true, true)
}

// exportCurrentPage exports the current page records (already in memory) to CSV.
// Returns the number of rows written (excluding header) and any error.
func (table *ResultsTable) exportCurrentPage(filePath string) (int, error) {
	records := table.GetRecords()
	writer, err := helpers.NewCSVWriter(filePath)
	if err != nil {
		return 0, err
	}
	defer writer.Abort()

	if err := writer.WriteRecords(records, true); err != nil {
		return 0, err
	}
	if err := writer.Commit(); err != nil {
		return writer.RowCount(), err
	}
	return writer.RowCount(), nil
}

// exportAllRecordsInBatches exports all records using batch fetching to avoid timeouts.
// Returns the number of rows written (excluding header) and any error.
func (table *ResultsTable) exportAllRecordsInBatches(
	filePath, databaseName, tableName, where, sort string,
	batchSize int,
) (int, error) {
	writer, err := helpers.NewCSVWriter(filePath)
	if err != nil {
		return 0, err
	}
	defer writer.Abort()

	for offset := 0; ; offset += batchSize {
		records, _, _, err := table.DBDriver.GetRecords(
			databaseName, tableName, where, sort, offset, batchSize,
		)
		if err != nil {
			return writer.RowCount(), err
		}

		// Header row only = no more data
		if len(records) <= 1 && offset > 0 {
			break
		}

		includeHeader := (offset == 0)
		if err := writer.WriteRecords(records, includeHeader); err != nil {
			return writer.RowCount(), err
		}
	}

	if err := writer.Commit(); err != nil {
		return writer.RowCount(), err
	}
	return writer.RowCount(), nil
}

func (table *ResultsTable) showExportSuccessModal(filePath string, rowCount int) {
	modal := tview.NewModal()
	modal.SetText(fmt.Sprintf("Successfully exported %d rows to:\n%s", rowCount, filePath))
	modal.AddButtons([]string{"OK"})
	modal.SetBackgroundColor(app.Styles.PrimitiveBackgroundColor)
	modal.SetBorderStyle(tcell.StyleDefault.Background(app.Styles.PrimitiveBackgroundColor))
	modal.SetTextColor(app.Styles.PrimaryTextColor)
	modal.SetButtonActivatedStyle(
		tcell.StyleDefault.
			Background(app.Styles.InverseTextColor).
			Foreground(app.Styles.ContrastSecondaryTextColor),
	)
	modal.SetDoneFunc(func(_ int, _ string) {
		mainPages.RemovePage(pageNameCSVExportSuccess)
	})

	mainPages.AddPage(pageNameCSVExportSuccess, modal, true, true)
	App.SetFocus(modal)
}

// openCellInExternalEditor opens the user's preferred editor to edit a cell value.
// It should be called within app.Suspend() to ensure the TUI is properly restored.
func openCellInExternalEditor(currentText string) string {
	tmpFile, err := os.CreateTemp("", "lazysql-cell-*.txt")
	if err != nil {
		logger.Error("Failed to create temporary file", map[string]any{"error": err.Error()})
		return currentText
	}
	defer os.Remove(tmpFile.Name())

	if _, err := tmpFile.WriteString(currentText); err != nil {
		logger.Error("Failed to write to temporary file", map[string]any{"error": err.Error()})
		if closeErr := tmpFile.Close(); closeErr != nil {
			logger.Error("Failed to close temporary file", map[string]any{"error": closeErr.Error()})
		}
		return currentText
	}

	if err := tmpFile.Close(); err != nil {
		logger.Error("Failed to close temporary file", map[string]any{"error": err.Error()})
		return currentText
	}

	editorParts := getCellEditorParts()
	editorArgs := append(editorParts[1:], tmpFile.Name())

	cmd := exec.Command(editorParts[0], editorArgs...) //nolint:gosec // editor comes from trusted $EDITOR env var
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		logger.Error("Error executing editor", map[string]any{"error": err.Error(), "command": cmd.String()})
		return currentText
	}

	updatedContent, err := os.ReadFile(tmpFile.Name())
	if err != nil {
		logger.Error("Failed to read from temporary file", map[string]any{"error": err.Error()})
		return currentText
	}

	return string(updatedContent)
}

// getCellEditorParts returns the editor command and its arguments for cell editing.
// Supports editor strings with flags like "code --wait" or "vim -u NONE".
func getCellEditorParts() []string {
	editor := os.Getenv("EDITOR")
	if editor == "" {
		editor = os.Getenv("VISUAL")
	}
	if editor == "" {
		editor = "vi"
	}
	return strings.Fields(editor)
}
