// Rclone as a wasm library
//
// This library exports the core rc functionality

// +build js

package main

import (
	"context"
	"encoding/json"
	"log"
	"net/http"
	"runtime"
	"syscall/js"

	"github.com/pkg/errors"
	"github.com/rclone/rclone/fs"
	"github.com/rclone/rclone/fs/rc"

	// Core functionality we need
	_ "github.com/rclone/rclone/fs/operations"
	_ "github.com/rclone/rclone/fs/sync"

	//	_ "github.com/rclone/rclone/backend/all" // import all backends

	// Backends
	_ "github.com/rclone/rclone/backend/memory"
)

var (
	document js.Value
	jsJSON   js.Value
)

func getElementById(name string) js.Value {
	node := document.Call("getElementById", name)
	if node.IsUndefined() {
		log.Fatalf("Couldn't find element %q", name)
	}
	return node
}

func time() int {
	return js.Global().Get("Date").New().Call("getTime").Int()
}

func paramToValue(in rc.Params) (out js.Value) {
	return js.Value{}
}

// errorValue turns an error into a js.Value
func errorValue(method string, in js.Value, err error) js.Value {
	fs.Errorf(nil, "rc: %q: error: %v", method, err)
	// Adjust the error return for some well known errors
	errOrig := errors.Cause(err)
	status := http.StatusInternalServerError
	switch {
	case errOrig == fs.ErrorDirNotFound || errOrig == fs.ErrorObjectNotFound:
		status = http.StatusNotFound
	case rc.IsErrParamInvalid(err) || rc.IsErrParamNotFound(err):
		status = http.StatusBadRequest
	}
	return js.ValueOf(map[string]interface{}{
		"status": status,
		"error":  err.Error(),
		"input":  in,
		"path":   method,
	})
}

// rcCallback is a callback for javascript to access the api
//
// FIXME should this should return a promise so we can return errors properly?
func rcCallback(this js.Value, args []js.Value) interface{} {
	ctx := context.Background() // FIXME
	log.Printf("rcCallback: this=%v args=%v", this, args)

	if len(args) != 2 {
		return errorValue("", js.Undefined(), errors.New("need two parameters to rc call"))
	}
	method := args[0].String()
	inRaw := args[1]
	var in = rc.Params{}
	switch inRaw.Type() {
	case js.TypeNull:
	case js.TypeObject:
		inJSON := jsJSON.Call("stringify", inRaw).String()
		err := json.Unmarshal([]byte(inJSON), &in)
		if err != nil {
			return errorValue(method, inRaw, errors.Wrap(err, "couldn't unmarshal input"))
		}
	default:
		return errorValue(method, inRaw, errors.New("in parameter must be null or object"))
	}

	call := rc.Calls.Get(method)
	if call == nil {
		return errorValue(method, inRaw, errors.Errorf("method %q not found", method))
	}

	out, err := call.Fn(ctx, in)
	if err != nil {
		return errorValue(method, inRaw, errors.Wrap(err, "method call failed"))
	}
	if out == nil {
		return nil
	}
	var out2 map[string]interface{}
	err = rc.Reshape(&out2, out)
	if err != nil {
		return errorValue(method, inRaw, errors.Wrap(err, "result reshape failed"))
	}

	return js.ValueOf(out2)
}

func main() {
	log.Printf("Running on goos/goarch = %s/%s", runtime.GOOS, runtime.GOARCH)
	if js.Global().IsUndefined() {
		log.Fatalf("Didn't find Global - not running in browser")
	}
	document = js.Global().Get("document")
	if document.IsUndefined() {
		log.Fatalf("Didn't find document - not running in browser")
	}

	jsJSON = js.Global().Get("JSON")
	if jsJSON.IsUndefined() {
		log.Fatalf("can't find JSON")
	}

	// Set rc
	js.Global().Set("rc", js.FuncOf(rcCallback))

	// Signal that it is valid
	rcValidResolve := js.Global().Get("rcValidResolve")
	if rcValidResolve.IsUndefined() {
		log.Fatalf("Didn't find rcValidResolve")
	}
	rcValidResolve.Invoke()

	// Wait forever
	select {}
}
