// http_request — 發送任意 HTTP 請求，回傳 status + body
// 透過 host function 發出 HTTP，.wasm 本身不含網路 syscall
//
//go:build tinygo

package main

import (
	"encoding/json"
	"io"
	"os"
	"unsafe"
)

// host function 宣告（由 WASI shim 注入）
//
//go:wasmimport u6u http_request
func hostHttpRequest(
	urlPtr uintptr, urlLen uint32,
	methodPtr uintptr, methodLen uint32,
	headersPtr uintptr, headersLen uint32,
	bodyPtr uintptr, bodyLen uint32,
	outPtr uintptr, outLenPtr uintptr,
) uint32

type Input struct {
	URL      string                 `json:"url"`
	Method   string                 `json:"method"`
	Headers  map[string]string      `json:"headers"`
	Body     string                 `json:"body"`      // 模式 A：直接 string body
	BodyJSON map[string]interface{} `json:"body_json"` // 模式 B：物件，內部 stringify（避免 yaml 端要自己組 JSON 字串）
}

// dummy byte for safe zero-length unsafe.Pointer operations
var dummy [1]byte

// safePtr returns a valid pointer for an empty-or-nonempty byte slice.
// TinyGo panics with "index out of range" when taking &b[0] on empty b.
func safePtr(b []byte) (uintptr, uint32) {
	if len(b) == 0 {
		return uintptr(unsafe.Pointer(&dummy[0])), 0
	}
	return uintptr(unsafe.Pointer(&b[0])), uint32(len(b))
}

func main() {
	raw, err := io.ReadAll(os.Stdin)
	if err != nil {
		writeError("failed to read stdin: " + err.Error())
		return
	}

	var input Input
	if err := json.Unmarshal(raw, &input); err != nil {
		writeError("invalid input JSON: " + err.Error())
		return
	}

	if input.URL == "" {
		writeError("url 必填")
		return
	}

	method := input.Method
	if method == "" {
		method = "GET"
	}

	headersJSON := "{}"
	if len(input.Headers) > 0 {
		b, _ := json.Marshal(input.Headers)
		headersJSON = string(b)
	}

	// body 來源優先順序：body_json（物件 → JSON 字串）> body（直接 string）
	bodyStr := input.Body
	if input.BodyJSON != nil {
		b, err := json.Marshal(input.BodyJSON)
		if err == nil {
			bodyStr = string(b)
		}
	}

	urlBytes := []byte(input.URL)
	methodBytes := []byte(method)
	headersBytes := []byte(headersJSON)
	bodyBytes := []byte(bodyStr)
	outBuf := make([]byte, 65536) // 64KB output buffer
	var outLen uint32

	urlPtr, urlLen := safePtr(urlBytes)
	methodPtr, methodLen := safePtr(methodBytes)
	headersPtr, headersLen := safePtr(headersBytes)
	bodyPtr, bodyLen := safePtr(bodyBytes)

	result := hostHttpRequest(
		urlPtr, urlLen,
		methodPtr, methodLen,
		headersPtr, headersLen,
		bodyPtr, bodyLen,
		uintptr(unsafe.Pointer(&outBuf[0])), uintptr(unsafe.Pointer(&outLen)),
	)

	if result != 0 {
		writeError("HTTP request failed")
		return
	}

	responseStr := string(outBuf[:outLen])

	// 偵測 JSON `{"error":"..."}` 模式視為失敗。
	// 2026-06-09 修架構債：host function（.component-builds/http_request/src/index.ts）現在對非 2xx
	// 回 envelope `{"error":"HTTP <status>","status":<code>,"body":<原文>}`——故此處 parsed["error"]
	// 能正確 catch 所有 4xx/5xx（含 Notion 401 那種 body 用 {"object":"error"} 不帶 error key 的）。
	// 之前 host fn 只回 body 原文丟掉 status → 401 被判 success（系統假綠根因，已修）。
	// 註：claude_api/kbdb_upsert_block/km_writer 已同樣修（非 2xx 回 error envelope）。
	//     auth_service_account 不套此 envelope——它 main.go 自己解析 OAuth token 回應的
	//     {access_token,error,error_description}，access_token 空即視為失敗，已有自己的判定，
	//     套 envelope 反而會丟掉 error_description 破壞 token exchange 錯誤處理。
	//     待辦：4 份 inline host fn 最好抽成共用 helper（dedup，目前複製貼上）。
	var parsed map[string]interface{}
	if err := json.Unmarshal([]byte(responseStr), &parsed); err == nil {
		if errVal, ok := parsed["error"]; ok && errVal != nil {
			out, _ := json.Marshal(map[string]interface{}{
				"success": false,
				"error":   errVal,
				"data":    map[string]interface{}{"body": responseStr},
			})
			os.Stdout.Write(out)
			return
		}
	}

	out, _ := json.Marshal(map[string]interface{}{
		"success": true,
		"data":    map[string]interface{}{"body": responseStr},
	})
	os.Stdout.Write(out)
}

func writeError(msg string) {
	out, _ := json.Marshal(map[string]interface{}{"success": false, "error": msg})
	os.Stdout.Write(out)
}
