package ultidraw

import (
	"encoding/json"
	"strings"
	"time"
)

// UltiDrawDoc is the on-disk format for Excalidraw drawings in UltiDrive.
type UltiDrawDoc struct {
	Type       string          `json:"type"`
	Version    int             `json:"version"`
	Source     string          `json:"source,omitempty"`
	Elements   json.RawMessage `json:"elements"`
	AppState   json.RawMessage `json:"appState,omitempty"`
	Files      json.RawMessage `json:"files,omitempty"`
	YjsState   string          `json:"yjsState,omitempty"`
	UpdatedAt  string          `json:"updatedAt,omitempty"`
}

func ParseUltiDrawDoc(raw []byte) (UltiDrawDoc, error) {
	var doc UltiDrawDoc
	if err := json.Unmarshal(raw, &doc); err != nil {
		return UltiDrawDoc{}, err
	}
	if doc.Type == "" {
		doc.Type = "excalidraw"
	}
	if doc.Version == 0 {
		doc.Version = 2
	}
	return doc, nil
}

func NewUltiDrawDoc(elements, appState, files json.RawMessage) UltiDrawDoc {
	if len(elements) == 0 {
		elements = json.RawMessage("[]")
	}
	if len(appState) == 0 {
		appState = json.RawMessage(`{"gridSize":null,"viewBackgroundColor":"#ffffff"}`)
	}
	if len(files) == 0 {
		files = json.RawMessage("{}")
	}
	return UltiDrawDoc{
		Type:     "excalidraw",
		Version:  2,
		Source:   "https://ultidrive",
		Elements: elements,
		AppState: appState,
		Files:    files,
	}
}

func (d UltiDrawDoc) Marshal() ([]byte, error) {
	d.UpdatedAt = time.Now().UTC().Format(time.RFC3339)
	return json.Marshal(d)
}

func ApplyUltiDrawPatch(existing UltiDrawDoc, raw []byte) (UltiDrawDoc, error) {
	var patch map[string]json.RawMessage
	if err := json.Unmarshal(raw, &patch); err != nil {
		return UltiDrawDoc{}, err
	}
	doc := existing
	if doc.Type == "" {
		doc = NewUltiDrawDoc(nil, nil, nil)
	}
	if v, ok := patch["elements"]; ok {
		doc.Elements = v
	}
	if v, ok := patch["appState"]; ok {
		doc.AppState = v
	}
	if v, ok := patch["files"]; ok {
		doc.Files = v
	}
	if v, ok := patch["yjsState"]; ok {
		doc.YjsState = strings.Trim(string(v), `"`)
	}
	return doc, nil
}

func isEmptyElements(elements json.RawMessage) bool {
	if len(elements) == 0 {
		return true
	}
	s := strings.TrimSpace(string(elements))
	return s == "" || s == "[]" || s == "null"
}
