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

package wstore

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

	"github.com/wavetermdev/thenextwave/pkg/shellexec"
	"github.com/wavetermdev/thenextwave/pkg/waveobj"
)

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_LayoutNode = "layout"
	OType_Block      = "block"
)

type WaveObjUpdate struct {
	UpdateType string          `json:"updatetype"`
	OType      string          `json:"otype"`
	OID        string          `json:"oid"`
	Obj        waveobj.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 = waveobj.ToJsonMap(update.Obj)
		if err != nil {
			return nil, err
		}
	}
	return json.Marshal(rtn)
}

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

func MakeUpdates(objs []waveobj.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 := waveobj.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"`
	MainWindowId string         `json:"mainwindowid"` // deprecated
	WindowIds    []string       `json:"windowids"`
	Meta         map[string]any `json:"meta"`
}

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

// stores the ui-context of the window
// workspaceid, active tab, active block within each tab, window size, etc.
type Window struct {
	OID            string            `json:"oid"`
	Version        int               `json:"version"`
	WorkspaceId    string            `json:"workspaceid"`
	ActiveTabId    string            `json:"activetabid"`
	ActiveBlockId  string            `json:"activeblockid,omitempty"`
	ActiveBlockMap map[string]string `json:"activeblockmap"` // map from tabid to blockid
	Pos            Point             `json:"pos"`
	WinSize        WinSize           `json:"winsize"`
	LastFocusTs    int64             `json:"lastfocusts"`
	Meta           map[string]any    `json:"meta"`
}

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

type Workspace struct {
	OID     string         `json:"oid"`
	Version int            `json:"version"`
	Name    string         `json:"name"`
	TabIds  []string       `json:"tabids"`
	Meta    map[string]any `json:"meta"`
}

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

type Tab struct {
	OID        string         `json:"oid"`
	Version    int            `json:"version"`
	Name       string         `json:"name"`
	LayoutNode string         `json:"layoutNode"`
	BlockIds   []string       `json:"blockids"`
	Meta       map[string]any `json:"meta"`
}

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

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

type LayoutNode struct {
	OID     string         `json:"oid"`
	Version int            `json:"version"`
	Node    any            `json:"node,omitempty"`
	Meta    map[string]any `json:"meta,omitempty"`
}

func (*LayoutNode) GetOType() string {
	return OType_LayoutNode
}

type FileDef struct {
	FileType string         `json:"filetype,omitempty"`
	Path     string         `json:"path,omitempty"`
	Url      string         `json:"url,omitempty"`
	Content  string         `json:"content,omitempty"`
	Meta     map[string]any `json:"meta,omitempty"`
}

type BlockDef struct {
	Controller string              `json:"controller,omitempty"`
	View       string              `json:"view,omitempty"`
	Files      map[string]*FileDef `json:"files,omitempty"`
	Meta       map[string]any      `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 shellexec.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"`
	Version     int            `json:"version"`
	BlockDef    *BlockDef      `json:"blockdef"`
	Controller  string         `json:"controller"`
	View        string         `json:"view"`
	RuntimeOpts *RuntimeOpts   `json:"runtimeopts,omitempty"`
	Stickers    []*StickerType `json:"stickers,omitempty"`
	Meta        map[string]any `json:"meta"`
}

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(&LayoutNode{}),
	}
}
