// kbdb_upsert_block — 用 page_name 當 idempotency key 做 upsert
// 內部：GET /blocks?page_name=X → user_id filter → 找到 PATCH /blocks/:id 沒找到 POST /blocks
// 解 arcrun workflow 沒 IF/branch 能力的缺口（arcrun.md P1 #1）
// 對應 SDD：polaris/mira/.agents/specs/mira-app/design.md §3.5.12.4.1
//
//go:build tinygo

package main

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

//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 {
	KBDBUrl    string `json:"kbdb_url"`    // optional
	APIKey     string `json:"api_key"`     // 必填
	PageName   string `json:"page_name"`   // 必填，當 idempotency key
	Content    string `json:"content"`     // 必填
	Type       string `json:"type"`        // optional（建立時用，PATCH 時忽略）
	ParentID   string `json:"parent_id"`   // optional（建立時用，PATCH 時忽略）
	UserID     string `json:"user_id"`     // optional（建立時用 + lookup filter）
	Source     string `json:"source"`      // optional
	TagsJSON   string `json:"tags_json"`   // optional（完整覆寫）
	CreateOnly bool   `json:"create_only"` // 2026-05-17 加：若 true + 已存在 → 不 PATCH，回 action="exists"
	                                       // 用於 stub creation 場景（避免 stub 覆寫已存在的 full wiki）
}

var dummy [1]byte

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 writeError(msg string) {
	out, _ := json.Marshal(map[string]interface{}{"success": false, "error": msg})
	os.Stdout.Write(out)
}

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

// urlEncode：跟 kbdb_get 一致，避免引入 net/url
func urlEncode(s string) string {
	var out []byte
	for i := 0; i < len(s); i++ {
		c := s[i]
		if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ||
			c == '-' || c == '_' || c == '.' || c == '~' {
			out = append(out, c)
		} else {
			const hex = "0123456789ABCDEF"
			out = append(out, '%', hex[c>>4], hex[c&0x0f])
		}
	}
	return string(out)
}

func httpCall(method, url string, headers map[string]string, body []byte) ([]byte, uint32) {
	headersBytes, _ := json.Marshal(headers)
	urlBytes := []byte(url)
	methodBytes := []byte(method)

	outBuf := make([]byte, 1<<20) // 1MB
	var outLen uint32

	urlPtr, urlLen := safePtr(urlBytes)
	methodPtr, methodLen := safePtr(methodBytes)
	headersPtr, headersLenU := safePtr(headersBytes)
	bodyPtr, bodyLenU := safePtr(body)

	result := hostHttpRequest(
		urlPtr, urlLen,
		methodPtr, methodLen,
		headersPtr, headersLenU,
		bodyPtr, bodyLenU,
		uintptr(unsafe.Pointer(&outBuf[0])), uintptr(unsafe.Pointer(&outLen)),
	)
	return outBuf[:outLen], result
}

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.APIKey == "" {
		writeError("api_key 必填")
		return
	}
	if input.PageName == "" {
		writeError("page_name 必填（upsert 的 idempotency key）")
		return
	}
	if input.Content == "" {
		writeError("content 必填")
		return
	}

	kbdbURL := input.KBDBUrl
	if kbdbURL == "" {
		kbdbURL = "https://kbdb.finally.click"
	}

	headers := map[string]string{
		"Authorization": "Bearer " + input.APIKey,
	}

	// ── Step 1：lookup by page_name ────────────────────────────────────
	lookupURL := kbdbURL + "/blocks?page_name=" + urlEncode(input.PageName) +
		"&limit=" + strconv.Itoa(10)
	lookupResp, callResult := httpCall("GET", lookupURL, headers, nil)
	if callResult != 0 {
		writeError("KBDB lookup failed (host_http_request returned non-zero)")
		return
	}

	var lookupParsed struct {
		Blocks []map[string]interface{} `json:"blocks"`
		Count  int                      `json:"count"`
		Error  interface{}              `json:"error"`
	}
	if err := json.Unmarshal(lookupResp, &lookupParsed); err != nil {
		writeError("KBDB lookup returned non-JSON: " + string(lookupResp))
		return
	}
	if lookupParsed.Error != nil {
		errBytes, _ := json.Marshal(map[string]interface{}{
			"success": false,
			"error":   lookupParsed.Error,
			"phase":   "lookup",
		})
		os.Stdout.Write(errBytes)
		return
	}

	// ── Step 2：找符合 user_id 的第一筆 ──────────────────────────────
	var existing map[string]interface{}
	for _, b := range lookupParsed.Blocks {
		if input.UserID == "" {
			existing = b
			break
		}
		if uid, ok := b["user_id"].(string); ok && uid == input.UserID {
			existing = b
			break
		}
	}

	// ── Step 3：分支寫入 ───────────────────────────────────────────────
	postHeaders := map[string]string{
		"Content-Type":  "application/json",
		"Authorization": "Bearer " + input.APIKey,
	}

	if existing != nil {
		// CreateOnly 模式：已存在 → 不動，回 action="exists"（給 stub creation 用，
		// 避免後續 raw 提到同 entity 時把完整 wiki 覆寫成 stub）
		if input.CreateOnly {
			writeResult("exists", existing)
			return
		}

		// PATCH 路徑
		existingID, _ := existing["id"].(string)
		if existingID == "" {
			writeError("lookup 找到 block 但 id 為空")
			return
		}

		patchBody := make(map[string]interface{})
		patchBody["content"] = input.Content
		if input.Source != "" {
			patchBody["source"] = input.Source
		}
		if input.TagsJSON != "" {
			// PATCH endpoint 用 tags array 不是 tags_json string
			var tagsArr []string
			if err := json.Unmarshal([]byte(input.TagsJSON), &tagsArr); err == nil {
				patchBody["tags"] = tagsArr
			}
		}
		patchBodyBytes, _ := json.Marshal(patchBody)

		patchURL := kbdbURL + "/blocks/" + existingID
		patchResp, callResult := httpCall("PATCH", patchURL, postHeaders, patchBodyBytes)
		if callResult != 0 {
			writeError("KBDB PATCH failed (host_http_request returned non-zero)")
			return
		}
		var patchParsed map[string]interface{}
		if err := json.Unmarshal(patchResp, &patchParsed); err != nil {
			writeError("KBDB PATCH returned non-JSON: " + string(patchResp))
			return
		}
		if _, hasErr := patchParsed["error"]; hasErr {
			errBytes, _ := json.Marshal(map[string]interface{}{
				"success": false,
				"error":   patchParsed["error"],
				"phase":   "patch",
			})
			os.Stdout.Write(errBytes)
			return
		}
		writeResult("patched", patchParsed)
		return
	}

	// CREATE 路徑
	postBody := make(map[string]interface{})
	postBody["content"] = input.Content
	postBody["page_name"] = input.PageName
	if input.Type != "" {
		postBody["type"] = input.Type
	}
	if input.ParentID != "" {
		postBody["parent_id"] = input.ParentID
	}
	if input.UserID != "" {
		postBody["user_id"] = input.UserID
	}
	if input.Source != "" {
		postBody["source"] = input.Source
	}
	if input.TagsJSON != "" {
		postBody["tags_json"] = input.TagsJSON
	}
	postBodyBytes, _ := json.Marshal(postBody)

	postURL := kbdbURL + "/blocks"
	postResp, callResult := httpCall("POST", postURL, postHeaders, postBodyBytes)
	if callResult != 0 {
		writeError("KBDB POST failed (host_http_request returned non-zero)")
		return
	}
	var postParsed map[string]interface{}
	if err := json.Unmarshal(postResp, &postParsed); err != nil {
		writeError("KBDB POST returned non-JSON: " + string(postResp))
		return
	}
	if _, hasErr := postParsed["error"]; hasErr {
		errBytes, _ := json.Marshal(map[string]interface{}{
			"success": false,
			"error":   postParsed["error"],
			"phase":   "create",
		})
		os.Stdout.Write(errBytes)
		return
	}
	writeResult("created", postParsed)
}
