package savepath

import (
	"bytes"
	"time"

	"github.com/diamondburned/cchat-gtk/internal/gts"
	"github.com/diamondburned/cchat-gtk/internal/log"
	"github.com/diamondburned/cchat-gtk/internal/ui/config"
	"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse"
	"github.com/pkg/errors"
)

// map of services to a list of list of IDs.
var paths = make(pathMap)

type pathMap map[string]pathMap

const configName = "savepaths.json"

func init() {
	config.RegisterConfig(configName, &paths)
}

// ActiveSetter is an interface for all widgets that allow setting the active
// state.
type ActiveSetter interface {
	SetActive(bool)
}

// Restore restores the expand state by calling SetActive. This is meant to be
// used on a ToggledButton.
func Restore(b traverse.Breadcrumber, asetter ActiveSetter) {
	if IsExpanded(b) {
		asetter.SetActive(true)
	}
}

// IsExpanded returns true if the current breadcrumb node is expanded.
func IsExpanded(b traverse.Breadcrumber) bool {
	var path = traverse.TryID(b)
	var node = paths

	// Descend and traverse.
	var nest = 0
	for ; nest < len(path); nest++ {
		ch, ok := node[path[nest]]
		if !ok {
			return false
		}

		node = ch
	}

	// Return true if there is available a path that at least matches with the
	// breadcrumb path.
	return nest == len(path)
}

// SaveDelay is the delay to wait before saving.
const SaveDelay = 5 * time.Second

var lastSaved int64

// Save saves the list of paths. This function is not thread-safe. It is also
// non-blocking.
func Save() {
	var now = time.Now().UnixNano()

	if (lastSaved + int64(SaveDelay)) > now {
		return
	}

	lastSaved = now

	gts.AfterFunc(SaveDelay, func() {
		var buf bytes.Buffer

		// Marshal in the same thread to avoid race conditions.
		if err := config.PrettyMarshal(&buf, paths); err != nil {
			log.Error(errors.Wrap(err, "Failed to marshal paths"))
			return
		}

		go func() {
			if err := config.SaveToFile(configName, buf.Bytes()); err != nil {
				log.Error(errors.Wrap(err, "Failed to save paths"))
			}
		}()
	})
}

func Update(b traverse.Breadcrumber, expanded bool) {
	var path = traverse.TryID(b)
	var node = paths

	// TODO: this doesn't actually account for paths that no longer exist, but
	// it's complex to check.

	if expanded {
		// Descend and initialize.
		for i := 0; i < len(path); i++ {
			ch, ok := node[path[i]]
			if !ok {
				ch = make(pathMap)
				node[path[i]] = ch
			}

			node = ch
		}
	} else {
		for i := 0; i < len(path); i++ {
			ch, ok := node[path[i]]
			if !ok {
				// We can't find anything.
				return
			}

			if i == len(path)-1 {
				// We're at the last node, so we can delete things now.
				delete(node, path[i])
			}

			node = ch
		}
	}

	Save()
}