// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

package waveobj

import (
	"encoding/json"
	"fmt"
	"reflect"
)

type UpdatesRtnType = []WaveObjUpdate

type UIContext struct {
	WindowId    string `json:"windowid"`
	ActiveTabId string `json:"activetabid"`
}

const (
	UpdateType_Update = "update"
	UpdateType_Delete = "delete"
)

const (
	OType_Client      = "client"
	OType_Window      = "window"
	OType_Workspace   = "workspace"
	OType_Tab         = "tab"
	OType_LayoutState = "layout"
	OType_Block       = "block"
	OType_Temp        = "temp"
)

var ValidOTypes = map[string]bool{
	OType_Client:      true,
	OType_Window:      true,
	OType_Workspace:   true,
	OType_Tab:         true,
	OType_LayoutState: true,
	OType_Block:       true,
	OType_Temp:        true,
}

type WaveObjUpdate struct {
	UpdateType string  `json:"updatetype"`
	OType      string  `json:"otype"`
	OID        string  `json:"oid"`
	Obj        WaveObj `json:"obj,omitempty"`
}

func (update WaveObjUpdate) MarshalJSON() ([]byte, error) {
	rtn := make(map[string]any)
	rtn["updatetype"] = update.UpdateType
	rtn["otype"] = update.OType
	rtn["oid"] = update.OID
	if update.Obj != nil {
		var err error
		rtn["obj"], err = ToJsonMap(update.Obj)
		if err != nil {
			return nil, err
		}
	}
	return json.Marshal(rtn)
}

func MakeUpdate(obj WaveObj) WaveObjUpdate {
	return WaveObjUpdate{
		UpdateType: UpdateType_Update,
		OType:      obj.GetOType(),
		OID:        GetOID(obj),
		Obj:        obj,
	}
}

func MakeUpdates(objs []WaveObj) []WaveObjUpdate {
	rtn := make([]WaveObjUpdate, 0, len(objs))
	for _, obj := range objs {
		rtn = append(rtn, MakeUpdate(obj))
	}
	return rtn
}

func (update *WaveObjUpdate) UnmarshalJSON(data []byte) error {
	var objMap map[string]any
	err := json.Unmarshal(data, &objMap)
	if err != nil {
		return err
	}
	var ok1, ok2, ok3 bool
	if _, found := objMap["updatetype"]; !found {
		return fmt.Errorf("missing updatetype (in WaveObjUpdate)")
	}
	update.UpdateType, ok1 = objMap["updatetype"].(string)
	if !ok1 {
		return fmt.Errorf("in WaveObjUpdate bad updatetype type %T", objMap["updatetype"])
	}
	if _, found := objMap["otype"]; !found {
		return fmt.Errorf("missing otype (in WaveObjUpdate)")
	}
	update.OType, ok2 = objMap["otype"].(string)
	if !ok2 {
		return fmt.Errorf("in WaveObjUpdate bad otype type %T", objMap["otype"])
	}
	if _, found := objMap["oid"]; !found {
		return fmt.Errorf("missing oid (in WaveObjUpdate)")
	}
	update.OID, ok3 = objMap["oid"].(string)
	if !ok3 {
		return fmt.Errorf("in WaveObjUpdate bad oid type %T", objMap["oid"])
	}
	if _, found := objMap["obj"]; found {
		objMap, ok := objMap["obj"].(map[string]any)
		if !ok {
			return fmt.Errorf("in WaveObjUpdate bad obj type %T", objMap["obj"])
		}
		waveObj, err := FromJsonMap(objMap)
		if err != nil {
			return fmt.Errorf("in WaveObjUpdate error decoding obj: %w", err)
		}
		update.Obj = waveObj
	}
	return nil
}

type Client struct {
	OID           string      `json:"oid"`
	Version       int         `json:"version"`
	WindowIds     []string    `json:"windowids"`
	Meta          MetaMapType `json:"meta"`
	TosAgreed     int64       `json:"tosagreed,omitempty"`
	HasOldHistory bool        `json:"hasoldhistory,omitempty"`
	TempOID       string      `json:"tempoid,omitempty"`
}

func (*Client) GetOType() string {
	return OType_Client
}

// stores the ui-context of the window, points to a workspace containing the actual data being displayed in the window
type Window struct {
	OID         string      `json:"oid"`
	Version     int         `json:"version"`
	WorkspaceId string      `json:"workspaceid"`
	IsNew       bool        `json:"isnew,omitempty"` // set when a window is created on the backend so the FE can size it properly.  cleared on first resize
	Pos         Point       `json:"pos"`
	WinSize     WinSize     `json:"winsize"`
	LastFocusTs int64       `json:"lastfocusts"`
	Meta        MetaMapType `json:"meta"`
}

func (*Window) GetOType() string {
	return OType_Window
}

type WorkspaceListEntry struct {
	WorkspaceId string `json:"workspaceid"`
	WindowId    string `json:"windowid"`
}

type WorkspaceList []*WorkspaceListEntry

type ActiveTabUpdate struct {
	WorkspaceId    string `json:"workspaceid"`
	NewActiveTabId string `json:"newactivetabid"`
}

type Workspace struct {
	OID          string      `json:"oid"`
	Version      int         `json:"version"`
	Name         string      `json:"name,omitempty"`
	Icon         string      `json:"icon,omitempty"`
	Color        string      `json:"color,omitempty"`
	TabIds       []string    `json:"tabids"`
	PinnedTabIds []string    `json:"pinnedtabids"`
	ActiveTabId  string      `json:"activetabid"`
	Meta         MetaMapType `json:"meta"`
}

func (*Workspace) GetOType() string {
	return OType_Workspace
}

type Tab struct {
	OID         string      `json:"oid"`
	Version     int         `json:"version"`
	Name        string      `json:"name"`
	LayoutState string      `json:"layoutstate"`
	BlockIds    []string    `json:"blockids"`
	Meta        MetaMapType `json:"meta"`
}

func (*Tab) GetOType() string {
	return OType_Tab
}

func (t *Tab) GetBlockORefs() []ORef {
	rtn := make([]ORef, 0, len(t.BlockIds))
	for _, blockId := range t.BlockIds {
		rtn = append(rtn, ORef{OType: OType_Block, OID: blockId})
	}
	return rtn
}

type LayoutActionData struct {
	ActionType    string `json:"actiontype"`
	BlockId       string `json:"blockid"`
	NodeSize      *uint  `json:"nodesize,omitempty"`
	IndexArr      *[]int `json:"indexarr,omitempty"`
	Focused       bool   `json:"focused"`
	Magnified     bool   `json:"magnified"`
	Ephemeral     bool   `json:"ephemeral"`
	TargetBlockId string `json:"targetblockid,omitempty"`
	Position      string `json:"position,omitempty"`
}

type LeafOrderEntry struct {
	NodeId  string `json:"nodeid"`
	BlockId string `json:"blockid"`
}

type LayoutState struct {
	OID                   string              `json:"oid"`
	Version               int                 `json:"version"`
	RootNode              any                 `json:"rootnode,omitempty"`
	MagnifiedNodeId       string              `json:"magnifiednodeid,omitempty"`
	FocusedNodeId         string              `json:"focusednodeid,omitempty"`
	LeafOrder             *[]LeafOrderEntry   `json:"leaforder,omitempty"`
	PendingBackendActions *[]LayoutActionData `json:"pendingbackendactions,omitempty"`
	Meta                  MetaMapType         `json:"meta,omitempty"`
}

func (*LayoutState) GetOType() string {
	return OType_LayoutState
}

type FileDef struct {
	Content string         `json:"content,omitempty"`
	Meta    map[string]any `json:"meta,omitempty"`
}

type BlockDef struct {
	Files map[string]*FileDef `json:"files,omitempty"`
	Meta  MetaMapType         `json:"meta,omitempty"`
}

type StickerClickOptsType struct {
	SendInput   string    `json:"sendinput,omitempty"`
	CreateBlock *BlockDef `json:"createblock,omitempty"`
}

type StickerDisplayOptsType struct {
	Icon    string `json:"icon"`
	ImgSrc  string `json:"imgsrc"`
	SvgBlob string `json:"svgblob,omitempty"`
}

type StickerType struct {
	StickerType string                  `json:"stickertype"`
	Style       map[string]any          `json:"style"`
	ClickOpts   *StickerClickOptsType   `json:"clickopts,omitempty"`
	Display     *StickerDisplayOptsType `json:"display"`
}

type RuntimeOpts struct {
	TermSize TermSize `json:"termsize,omitempty"`
	WinSize  WinSize  `json:"winsize,omitempty"`
}

type Point struct {
	X int `json:"x"`
	Y int `json:"y"`
}

type WinSize struct {
	Width  int `json:"width"`
	Height int `json:"height"`
}

type Block struct {
	OID         string         `json:"oid"`
	ParentORef  string         `json:"parentoref,omitempty"`
	Version     int            `json:"version"`
	RuntimeOpts *RuntimeOpts   `json:"runtimeopts,omitempty"`
	Stickers    []*StickerType `json:"stickers,omitempty"`
	Meta        MetaMapType    `json:"meta"`
	SubBlockIds []string       `json:"subblockids,omitempty"`
}

func (*Block) GetOType() string {
	return OType_Block
}

func AllWaveObjTypes() []reflect.Type {
	return []reflect.Type{
		reflect.TypeOf(&Client{}),
		reflect.TypeOf(&Window{}),
		reflect.TypeOf(&Workspace{}),
		reflect.TypeOf(&Tab{}),
		reflect.TypeOf(&Block{}),
		reflect.TypeOf(&LayoutState{}),
	}
}

type TermSize struct {
	Rows int `json:"rows"`
	Cols int `json:"cols"`
}
