package gui

import (
	"github.com/fatih/color"
	"github.com/jesseduffield/gocui"
	"github.com/jesseduffield/lazydocker/pkg/utils"
)

// getFocusLayout returns a manager function for when view gain and lose focus
func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
	var previousView *gocui.View
	return func(g *gocui.Gui) error {
		newView := gui.g.CurrentView()
		if err := gui.onFocusChange(); err != nil {
			return err
		}
		// for now we don't consider losing focus to a popup panel as actually losing focus
		if newView != previousView && !gui.isPopupPanel(newView.Name()) {
			gui.onFocusLost(previousView, newView)
			gui.onFocus(newView)
			previousView = newView
		}
		return nil
	}
}

func (gui *Gui) onFocusChange() error {
	currentView := gui.g.CurrentView()
	for _, view := range gui.g.Views() {
		view.Highlight = view == currentView && view.Name() != "main"
	}
	return nil
}

func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) {
	if v == nil {
		return
	}

	if !gui.isPopupPanel(newView.Name()) {
		v.ParentView = nil
	}

	// refocusing because in responsive mode (when the window is very short) we want to ensure that after the view size changes we can still see the last selected item
	gui.focusPointInView(v)

	gui.Log.Info(v.Name() + " focus lost")
}

func (gui *Gui) onFocus(v *gocui.View) {
	if v == nil {
		return
	}

	gui.focusPointInView(v)

	gui.Log.Info(v.Name() + " focus gained")
}

// layout is called for every screen re-render e.g. when the screen is resized
func (gui *Gui) layout(g *gocui.Gui) error {
	g.Highlight = true
	width, height := g.Size()

	information := gui.Config.Version
	if gui.g.Mouse {
		donate := color.New(color.FgMagenta, color.Underline).Sprint(gui.Tr.Donate)
		information = donate + " " + information
	}

	minimumHeight := 9
	minimumWidth := 10
	if height < minimumHeight || width < minimumWidth {
		v, err := g.SetView("limit", 0, 0, width-1, height-1, 0)
		if err != nil {
			if err.Error() != "unknown view" {
				return err
			}
			v.Title = gui.Tr.NotEnoughSpace
			v.Wrap = true
			_, _ = g.SetViewOnTop("limit")
		}
		return nil
	}

	currView := gui.g.CurrentView()
	currentCyclebleView := gui.peekPreviousView()
	if currView != nil {
		viewName := currView.Name()
		usePreviouseView := true
		for _, view := range gui.CyclableViews {
			if view == viewName {
				currentCyclebleView = viewName
				usePreviouseView = false
				break
			}
		}
		if usePreviouseView {
			currentCyclebleView = gui.peekPreviousView()
		}
	}

	usableSpace := height - 4

	tallPanels := 3
	var vHeights map[string]int
	if gui.DockerCommand.InDockerComposeProject {
		tallPanels++
		vHeights = map[string]int{
			"project":    3,
			"services":   usableSpace/tallPanels + usableSpace%tallPanels,
			"containers": usableSpace / tallPanels,
			"images":     usableSpace / tallPanels,
			"volumes":    usableSpace / tallPanels,
			"options":    1,
		}
	} else {
		vHeights = map[string]int{
			"project":    3,
			"containers": usableSpace/tallPanels + usableSpace%tallPanels,
			"images":     usableSpace / tallPanels,
			"volumes":    usableSpace / tallPanels,
			"options":    1,
		}
	}

	if height < 28 {
		defaultHeight := 3
		if height < 21 {
			defaultHeight = 1
		}
		vHeights = map[string]int{
			"project":    defaultHeight,
			"containers": defaultHeight,
			"images":     defaultHeight,
			"volumes":    defaultHeight,
			"options":    defaultHeight,
		}
		if gui.DockerCommand.InDockerComposeProject {
			vHeights["services"] = defaultHeight
		}
		vHeights[currentCyclebleView] = height - defaultHeight*tallPanels - 1
	}

	optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1)
	leftSideWidth := width / 3

	appStatus := gui.statusManager.getStatusString()
	appStatusOptionsBoundary := 0
	if appStatus != "" {
		appStatusOptionsBoundary = len(appStatus) + 2
	}

	_, _ = g.SetViewOnBottom("limit")
	_ = g.DeleteView("limit")

	v, err := g.SetView("main", leftSideWidth+1, 0, width-1, height-2, gocui.LEFT)
	if err != nil {
		if err.Error() != "unknown view" {
			return err
		}
		v.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
		v.FgColor = gocui.ColorDefault

		// when you run a docker container with the -it flags (interactive mode) it adds carriage returns for some reason. This is not docker's fault, it's an os-level default.
		v.IgnoreCarriageReturns = true
	}

	if v, err := g.SetView("project", 0, 0, leftSideWidth, vHeights["project"]-1, gocui.BOTTOM|gocui.RIGHT); err != nil {
		if err.Error() != "unknown view" {
			return err
		}
		v.Title = gui.Tr.ProjectTitle
		v.FgColor = gocui.ColorDefault
	}

	var servicesView *gocui.View
	aboveContainersView := "project"
	if gui.DockerCommand.InDockerComposeProject {
		aboveContainersView = "services"
		servicesView, err = g.SetViewBeneath("services", "project", vHeights["services"])
		if err != nil {
			if err.Error() != "unknown view" {
				return err
			}
			servicesView.Highlight = true
			servicesView.Title = gui.Tr.ServicesTitle
			servicesView.FgColor = gocui.ColorDefault
		}
	}

	containersView, err := g.SetViewBeneath("containers", aboveContainersView, vHeights["containers"])
	if err != nil {
		if err.Error() != "unknown view" {
			return err
		}
		containersView.Highlight = true
		if gui.Config.UserConfig.Gui.ShowAllContainers || !gui.DockerCommand.InDockerComposeProject {
			containersView.Title = gui.Tr.ContainersTitle
		} else {
			containersView.Title = gui.Tr.StandaloneContainersTitle
		}
		containersView.FgColor = gocui.ColorDefault
	}

	imagesView, err := g.SetViewBeneath("images", "containers", vHeights["images"])
	if err != nil {
		if err.Error() != "unknown view" {
			return err
		}
		imagesView.Highlight = true
		imagesView.Title = gui.Tr.ImagesTitle
		imagesView.FgColor = gocui.ColorDefault
	}

	volumesView, err := g.SetViewBeneath("volumes", "images", vHeights["volumes"])
	if err != nil {
		if err.Error() != "unknown view" {
			return err
		}
		volumesView.Highlight = true
		volumesView.Title = gui.Tr.VolumesTitle
		volumesView.FgColor = gocui.ColorDefault
	}

	if v, err := g.SetView("options", appStatusOptionsBoundary-1, height-2, optionsVersionBoundary-1, height, 0); err != nil {
		if err.Error() != "unknown view" {
			return err
		}
		v.Frame = false
		if v.FgColor, err = gui.GetOptionsPanelTextColor(); err != nil {
			return err
		}
	}

	if appStatusView, err := g.SetView("appStatus", -1, height-2, width, height, 0); err != nil {
		if err.Error() != "unknown view" {
			return err
		}
		appStatusView.BgColor = gocui.ColorDefault
		appStatusView.FgColor = gocui.ColorCyan
		appStatusView.Frame = false
		if _, err := g.SetViewOnBottom("appStatus"); err != nil {
			return err
		}
	}

	if v, err := g.SetView("information", optionsVersionBoundary-1, height-2, width, height, 0); err != nil {
		if err.Error() != "unknown view" {
			return err
		}
		v.BgColor = gocui.ColorDefault
		v.FgColor = gocui.ColorGreen
		v.Frame = false
		if err := gui.renderString(g, "information", information); err != nil {
			return err
		}

		// doing this here because it'll only happen once
		if err := gui.loadNewDirectory(); err != nil {
			return err
		}
	}

	if gui.g.CurrentView() == nil {
		v, err := gui.g.View(gui.peekPreviousView())
		if err != nil {
			viewName := gui.initiallyFocusedViewName()
			v, err = gui.g.View(viewName)
			if err != nil {
				return err
			}
		}

		if err := gui.switchFocus(gui.g, nil, v, false); err != nil {
			return err
		}
	}

	// here is a good place log some stuff
	// if you download humanlog and do tail -f development.log | humanlog
	// this will let you see these branches as prettified json
	// gui.Log.Info(utils.AsJson(gui.State.Branches[0:4]))
	return gui.resizeCurrentPopupPanel(g)
}

type listViewState struct {
	selectedLine int
	lineCount    int
}

func (gui *Gui) focusPointInView(view *gocui.View) {
	if view == nil {
		return
	}

	listViews := map[string]listViewState{
		"containers": {selectedLine: gui.State.Panels.Containers.SelectedLine, lineCount: len(gui.DockerCommand.DisplayContainers)},
		"images":     {selectedLine: gui.State.Panels.Images.SelectedLine, lineCount: len(gui.DockerCommand.Images)},
		"volumes":    {selectedLine: gui.State.Panels.Volumes.SelectedLine, lineCount: len(gui.DockerCommand.Volumes)},
		"services":   {selectedLine: gui.State.Panels.Services.SelectedLine, lineCount: len(gui.DockerCommand.Services)},
		"menu":       {selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount},
	}

	if state, ok := listViews[view.Name()]; ok {
		gui.focusPoint(0, state.selectedLine, state.lineCount, view)
	}
}
