package model

import (
	"fmt"

	"github.com/mattermost/focalboard/server/utils"

	"github.com/mattermost/mattermost-server/v6/shared/mlog"
)

// GenerateBlockIDs generates new IDs for all the blocks of the list,
// keeping consistent any references that other blocks would made to
// the original IDs, so a tree of blocks can get new IDs and maintain
// its shape.
func GenerateBlockIDs(blocks []Block, logger *mlog.Logger) []Block {
	blockIDs := map[string]BlockType{}
	referenceIDs := map[string]bool{}
	for _, block := range blocks {
		if _, ok := blockIDs[block.ID]; !ok {
			blockIDs[block.ID] = block.Type
		}

		if _, ok := referenceIDs[block.BoardID]; !ok {
			referenceIDs[block.BoardID] = true
		}
		if _, ok := referenceIDs[block.ParentID]; !ok {
			referenceIDs[block.ParentID] = true
		}

		if _, ok := block.Fields["contentOrder"]; ok {
			contentOrder, typeOk := block.Fields["contentOrder"].([]interface{})
			if !typeOk {
				logger.Warn(
					"type assertion failed for content order when saving reference block IDs",
					mlog.String("blockID", block.ID),
					mlog.String("actionType", fmt.Sprintf("%T", block.Fields["contentOrder"])),
					mlog.String("expectedType", "[]interface{}"),
					mlog.String("contentOrder", fmt.Sprintf("%v", block.Fields["contentOrder"])),
				)
				continue
			}

			for _, blockID := range contentOrder {
				switch v := blockID.(type) {
				case []interface{}:
					for _, columnBlockID := range v {
						referenceIDs[columnBlockID.(string)] = true
					}
				case string:
					referenceIDs[v] = true
				default:
				}
			}
		}
	}

	newIDs := map[string]string{}
	for id, blockType := range blockIDs {
		for referenceID := range referenceIDs {
			if id == referenceID {
				newIDs[id] = utils.NewID(BlockType2IDType(blockType))
				continue
			}
		}
	}

	getExistingOrOldID := func(id string) string {
		if existingID, ok := newIDs[id]; ok {
			return existingID
		}
		return id
	}

	getExistingOrNewID := func(id string) string {
		if existingID, ok := newIDs[id]; ok {
			return existingID
		}
		return utils.NewID(BlockType2IDType(blockIDs[id]))
	}

	newBlocks := make([]Block, len(blocks))
	for i, block := range blocks {
		block.ID = getExistingOrNewID(block.ID)
		block.BoardID = getExistingOrOldID(block.BoardID)
		block.ParentID = getExistingOrOldID(block.ParentID)

		blockMod := block
		if _, ok := blockMod.Fields["contentOrder"]; ok {
			fixFieldIDs(&blockMod, "contentOrder", getExistingOrOldID, logger)
		}

		if _, ok := blockMod.Fields["cardOrder"]; ok {
			fixFieldIDs(&blockMod, "cardOrder", getExistingOrOldID, logger)
		}

		newBlocks[i] = blockMod
	}

	return newBlocks
}

func fixFieldIDs(block *Block, fieldName string, getExistingOrOldID func(string) string, logger *mlog.Logger) {
	field, typeOk := block.Fields[fieldName].([]interface{})
	if !typeOk {
		logger.Warn(
			"type assertion failed for JSON field when setting new block IDs",
			mlog.String("blockID", block.ID),
			mlog.String("fieldName", fieldName),
			mlog.String("actionType", fmt.Sprintf("%T", block.Fields[fieldName])),
			mlog.String("expectedType", "[]interface{}"),
			mlog.String("value", fmt.Sprintf("%v", block.Fields[fieldName])),
		)
	} else {
		for j := range field {
			switch v := field[j].(type) {
			case string:
				field[j] = getExistingOrOldID(v)
			case []interface{}:
				subOrder := field[j].([]interface{})
				for k := range v {
					subOrder[k] = getExistingOrOldID(v[k].(string))
				}
			}
		}
	}
}
