package gojs

import (
	"context"
	"fmt"
	"time"

	"github.com/tetratelabs/wazero/api"
	"github.com/tetratelabs/wazero/internal/gojs/custom"
	"github.com/tetratelabs/wazero/internal/gojs/goarch"
	internalsys "github.com/tetratelabs/wazero/internal/sys"
	"github.com/tetratelabs/wazero/internal/wasm"
)

// Debug has unknown use, so stubbed.
//
// See https://github.com/golang/go/blob/go1.20/src/cmd/link/internal/wasm/asm.go#L131-L136
var Debug = goarch.StubFunction(custom.NameDebug)

// TODO: should this call runtime.Breakpoint()?

// WasmExit implements runtime.wasmExit which supports runtime.exit.
//
// See https://github.com/golang/go/blob/go1.20/src/runtime/sys_wasm.go#L24
var WasmExit = goarch.NewFunc(custom.NameRuntimeWasmExit, wasmExit)

func wasmExit(ctx context.Context, mod api.Module, stack goarch.Stack) {
	code := stack.ParamUint32(0)

	getState(ctx).close()
	_ = mod.CloseWithExitCode(ctx, code)
}

// WasmWrite implements runtime.wasmWrite which supports runtime.write and
// runtime.writeErr. This implements `println`.
//
// See https://github.com/golang/go/blob/go1.20/src/runtime/os_js.go#L30
var WasmWrite = goarch.NewFunc(custom.NameRuntimeWasmWrite, wasmWrite)

func wasmWrite(_ context.Context, mod api.Module, stack goarch.Stack) {
	fd := stack.ParamUint32(0)
	p := stack.ParamBytes(mod.Memory(), 1 /*, 2 */)

	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
	if writer := internalsys.WriterForFile(fsc, fd); writer == nil {
		panic(fmt.Errorf("fd %d invalid", fd))
	} else if _, err := writer.Write(p); err != nil {
		panic(fmt.Errorf("error writing p: %w", err))
	}
}

// ResetMemoryDataView signals wasm.OpcodeMemoryGrow happened, indicating any
// cached view of memory should be reset.
//
// See https://github.com/golang/go/blob/go1.20/src/runtime/mem_js.go#L82
var ResetMemoryDataView = goarch.NewFunc(custom.NameRuntimeResetMemoryDataView, resetMemoryDataView)

func resetMemoryDataView(context.Context, api.Module, goarch.Stack) {
	// context state does not cache a memory view, and all byte slices used
	// are safely copied. Also, user-defined functions are not supported.
	// Hence, there's currently no known reason to reset anything.
}

// Nanotime1 implements runtime.nanotime which supports time.Since.
//
// See https://github.com/golang/go/blob/go1.20/src/runtime/sys_wasm.s#L117
var Nanotime1 = goarch.NewFunc(custom.NameRuntimeNanotime1, nanotime1)

func nanotime1(_ context.Context, mod api.Module, stack goarch.Stack) {
	nsec := mod.(*wasm.ModuleInstance).Sys.Nanotime()

	stack.SetResultI64(0, nsec)
}

// Walltime implements runtime.walltime which supports time.Now.
//
// See https://github.com/golang/go/blob/go1.20/src/runtime/sys_wasm.s#L121
var Walltime = goarch.NewFunc(custom.NameRuntimeWalltime, walltime)

func walltime(_ context.Context, mod api.Module, stack goarch.Stack) {
	sec, nsec := mod.(*wasm.ModuleInstance).Sys.Walltime()

	stack.SetResultI64(0, sec)
	stack.SetResultI32(1, nsec)
}

// ScheduleTimeoutEvent implements runtime.scheduleTimeoutEvent which supports
// runtime.notetsleepg used by runtime.signal_recv.
//
// Unlike other most functions prefixed by "runtime.", this both launches a
// goroutine and invokes code compiled into wasm "resume".
//
// See https://github.com/golang/go/blob/go1.20/src/runtime/sys_wasm.s#L125
var ScheduleTimeoutEvent = goarch.NewFunc(custom.NameRuntimeScheduleTimeoutEvent, scheduleTimeoutEvent)

// Note: Signal handling is not implemented in GOOS=js.
func scheduleTimeoutEvent(ctx context.Context, mod api.Module, stack goarch.Stack) {
	ms := stack.Param(0)

	s := getState(ctx)
	id := s._nextCallbackTimeoutID
	stack.SetResultUint32(0, id)
	s._nextCallbackTimeoutID++

	cleared := make(chan bool)
	timeout := time.Millisecond * time.Duration(ms)
	s._scheduledTimeouts[id] = cleared

	// As wasm is currently not concurrent, a timeout on another goroutine may
	// not make sense. However, this implements what wasm_exec.js does anyway.
	go func() {
		select {
		case <-cleared: // do nothing
		case <-time.After(timeout):
			if _, err := mod.ExportedFunction("resume").Call(ctx); err != nil {
				println(err)
			}
		}
	}()
}

// ClearTimeoutEvent implements runtime.clearTimeoutEvent which supports
// runtime.notetsleepg used by runtime.signal_recv.
//
// See https://github.com/golang/go/blob/go1.20/src/runtime/sys_wasm.s#L129
var ClearTimeoutEvent = goarch.NewFunc(custom.NameRuntimeClearTimeoutEvent, clearTimeoutEvent)

// Note: Signal handling is not implemented in GOOS=js.
func clearTimeoutEvent(ctx context.Context, _ api.Module, stack goarch.Stack) {
	id := stack.ParamUint32(0)
	s := getState(ctx)
	if cancel, ok := s._scheduledTimeouts[id]; ok {
		delete(s._scheduledTimeouts, id)
		cancel <- true
	}
}

// GetRandomData implements runtime.getRandomData, which initializes the seed
// for runtime.fastrand.
//
// See https://github.com/golang/go/blob/go1.20/src/runtime/sys_wasm.s#L133
var GetRandomData = goarch.NewFunc(custom.NameRuntimeGetRandomData, getRandomData)

func getRandomData(_ context.Context, mod api.Module, stack goarch.Stack) {
	r := stack.ParamBytes(mod.Memory(), 0 /*, 1 */)

	randSource := mod.(*wasm.ModuleInstance).Sys.RandSource()

	bufLen := len(r)
	if n, err := randSource.Read(r); err != nil {
		panic(fmt.Errorf("RandSource.Read(r /* len=%d */) failed: %w", bufLen, err))
	} else if n != bufLen {
		panic(fmt.Errorf("RandSource.Read(r /* len=%d */) read %d bytes", bufLen, n))
	}
}
