package service

import (
	"context"
	"encoding/json"
	"net/http"
	"strings"

	"cmr-backend/internal/apperr"
	"cmr-backend/internal/platform/security"
	"cmr-backend/internal/store/postgres"
)

type AdminResourceService struct {
	store *postgres.Store
}

type AdminMapSummary struct {
	ID               string                `json:"id"`
	Code             string                `json:"code"`
	Name             string                `json:"name"`
	Status           string                `json:"status"`
	Description      *string               `json:"description,omitempty"`
	CurrentVersionID *string               `json:"currentVersionId,omitempty"`
	CurrentVersion   *AdminMapVersionBrief `json:"currentVersion,omitempty"`
}

type AdminMapVersionBrief struct {
	ID          string `json:"id"`
	VersionCode string `json:"versionCode"`
	Status      string `json:"status"`
}

type AdminMapVersion struct {
	ID                 string         `json:"id"`
	VersionCode        string         `json:"versionCode"`
	Status             string         `json:"status"`
	MapmetaURL         string         `json:"mapmetaUrl"`
	TilesRootURL       string         `json:"tilesRootUrl"`
	PublishedAssetRoot *string        `json:"publishedAssetRoot,omitempty"`
	Bounds             map[string]any `json:"bounds,omitempty"`
	Metadata           map[string]any `json:"metadata,omitempty"`
}

type AdminMapDetail struct {
	Map      AdminMapSummary   `json:"map"`
	Versions []AdminMapVersion `json:"versions"`
}

type CreateAdminMapInput struct {
	Code        string  `json:"code"`
	Name        string  `json:"name"`
	Status      string  `json:"status"`
	Description *string `json:"description,omitempty"`
}

type CreateAdminMapVersionInput struct {
	VersionCode        string         `json:"versionCode"`
	Status             string         `json:"status"`
	MapmetaURL         string         `json:"mapmetaUrl"`
	TilesRootURL       string         `json:"tilesRootUrl"`
	PublishedAssetRoot *string        `json:"publishedAssetRoot,omitempty"`
	Bounds             map[string]any `json:"bounds,omitempty"`
	Metadata           map[string]any `json:"metadata,omitempty"`
	SetAsCurrent       bool           `json:"setAsCurrent"`
}

type AdminPlayfieldSummary struct {
	ID               string                      `json:"id"`
	Code             string                      `json:"code"`
	Name             string                      `json:"name"`
	Kind             string                      `json:"kind"`
	Status           string                      `json:"status"`
	Description      *string                     `json:"description,omitempty"`
	CurrentVersionID *string                     `json:"currentVersionId,omitempty"`
	CurrentVersion   *AdminPlayfieldVersionBrief `json:"currentVersion,omitempty"`
}

type AdminPlayfieldVersionBrief struct {
	ID          string `json:"id"`
	VersionCode string `json:"versionCode"`
	Status      string `json:"status"`
	SourceType  string `json:"sourceType"`
}

type AdminPlayfieldVersion struct {
	ID                 string         `json:"id"`
	VersionCode        string         `json:"versionCode"`
	Status             string         `json:"status"`
	SourceType         string         `json:"sourceType"`
	SourceURL          string         `json:"sourceUrl"`
	PublishedAssetRoot *string        `json:"publishedAssetRoot,omitempty"`
	ControlCount       *int           `json:"controlCount,omitempty"`
	Bounds             map[string]any `json:"bounds,omitempty"`
	Metadata           map[string]any `json:"metadata,omitempty"`
}

type AdminPlayfieldDetail struct {
	Playfield AdminPlayfieldSummary   `json:"playfield"`
	Versions  []AdminPlayfieldVersion `json:"versions"`
}

type CreateAdminPlayfieldInput struct {
	Code        string  `json:"code"`
	Name        string  `json:"name"`
	Kind        string  `json:"kind"`
	Status      string  `json:"status"`
	Description *string `json:"description,omitempty"`
}

type CreateAdminPlayfieldVersionInput struct {
	VersionCode        string         `json:"versionCode"`
	Status             string         `json:"status"`
	SourceType         string         `json:"sourceType"`
	SourceURL          string         `json:"sourceUrl"`
	PublishedAssetRoot *string        `json:"publishedAssetRoot,omitempty"`
	ControlCount       *int           `json:"controlCount,omitempty"`
	Bounds             map[string]any `json:"bounds,omitempty"`
	Metadata           map[string]any `json:"metadata,omitempty"`
	SetAsCurrent       bool           `json:"setAsCurrent"`
}

type AdminResourcePackSummary struct {
	ID               string                         `json:"id"`
	Code             string                         `json:"code"`
	Name             string                         `json:"name"`
	Status           string                         `json:"status"`
	Description      *string                        `json:"description,omitempty"`
	CurrentVersionID *string                        `json:"currentVersionId,omitempty"`
	CurrentVersion   *AdminResourcePackVersionBrief `json:"currentVersion,omitempty"`
}

type AdminResourcePackVersionBrief struct {
	ID          string `json:"id"`
	VersionCode string `json:"versionCode"`
	Status      string `json:"status"`
}

type AdminResourcePackVersion struct {
	ID                 string         `json:"id"`
	VersionCode        string         `json:"versionCode"`
	Status             string         `json:"status"`
	ContentEntryURL    *string        `json:"contentEntryUrl,omitempty"`
	AudioRootURL       *string        `json:"audioRootUrl,omitempty"`
	ThemeProfileCode   *string        `json:"themeProfileCode,omitempty"`
	PublishedAssetRoot *string        `json:"publishedAssetRoot,omitempty"`
	Metadata           map[string]any `json:"metadata,omitempty"`
}

type AdminResourcePackDetail struct {
	ResourcePack AdminResourcePackSummary   `json:"resourcePack"`
	Versions     []AdminResourcePackVersion `json:"versions"`
}

type CreateAdminResourcePackInput struct {
	Code        string  `json:"code"`
	Name        string  `json:"name"`
	Status      string  `json:"status"`
	Description *string `json:"description,omitempty"`
}

type CreateAdminResourcePackVersionInput struct {
	VersionCode        string         `json:"versionCode"`
	Status             string         `json:"status"`
	ContentEntryURL    *string        `json:"contentEntryUrl,omitempty"`
	AudioRootURL       *string        `json:"audioRootUrl,omitempty"`
	ThemeProfileCode   *string        `json:"themeProfileCode,omitempty"`
	PublishedAssetRoot *string        `json:"publishedAssetRoot,omitempty"`
	Metadata           map[string]any `json:"metadata,omitempty"`
	SetAsCurrent       bool           `json:"setAsCurrent"`
}

func NewAdminResourceService(store *postgres.Store) *AdminResourceService {
	return &AdminResourceService{store: store}
}

func (s *AdminResourceService) ListMaps(ctx context.Context, limit int) ([]AdminMapSummary, error) {
	items, err := s.store.ListResourceMaps(ctx, limit)
	if err != nil {
		return nil, err
	}
	results := make([]AdminMapSummary, 0, len(items))
	for _, item := range items {
		results = append(results, AdminMapSummary{
			ID:               item.PublicID,
			Code:             item.Code,
			Name:             item.Name,
			Status:           item.Status,
			Description:      item.Description,
			CurrentVersionID: item.CurrentVersionID,
		})
	}
	return results, nil
}

func (s *AdminResourceService) CreateMap(ctx context.Context, input CreateAdminMapInput) (*AdminMapSummary, error) {
	input.Code = strings.TrimSpace(input.Code)
	input.Name = strings.TrimSpace(input.Name)
	status := normalizeCatalogStatus(input.Status)
	if input.Code == "" || input.Name == "" {
		return nil, apperr.New(http.StatusBadRequest, "invalid_params", "code and name are required")
	}

	publicID, err := security.GeneratePublicID("map")
	if err != nil {
		return nil, err
	}
	tx, err := s.store.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	item, err := s.store.CreateResourceMap(ctx, tx, postgres.CreateResourceMapParams{
		PublicID:    publicID,
		Code:        input.Code,
		Name:        input.Name,
		Status:      status,
		Description: trimStringPtr(input.Description),
	})
	if err != nil {
		return nil, err
	}
	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}
	return &AdminMapSummary{
		ID:               item.PublicID,
		Code:             item.Code,
		Name:             item.Name,
		Status:           item.Status,
		Description:      item.Description,
		CurrentVersionID: item.CurrentVersionID,
	}, nil
}

func (s *AdminResourceService) GetMapDetail(ctx context.Context, mapPublicID string) (*AdminMapDetail, error) {
	item, err := s.store.GetResourceMapByPublicID(ctx, strings.TrimSpace(mapPublicID))
	if err != nil {
		return nil, err
	}
	if item == nil {
		return nil, apperr.New(http.StatusNotFound, "map_not_found", "map not found")
	}
	versions, err := s.store.ListResourceMapVersions(ctx, item.ID)
	if err != nil {
		return nil, err
	}
	result := &AdminMapDetail{
		Map: AdminMapSummary{
			ID:               item.PublicID,
			Code:             item.Code,
			Name:             item.Name,
			Status:           item.Status,
			Description:      item.Description,
			CurrentVersionID: item.CurrentVersionID,
		},
		Versions: make([]AdminMapVersion, 0, len(versions)),
	}
	for _, version := range versions {
		view := AdminMapVersion{
			ID:                 version.PublicID,
			VersionCode:        version.VersionCode,
			Status:             version.Status,
			MapmetaURL:         version.MapmetaURL,
			TilesRootURL:       version.TilesRootURL,
			PublishedAssetRoot: version.PublishedAssetRoot,
			Bounds:             decodeJSONMap(version.BoundsJSON),
			Metadata:           decodeJSONMap(version.MetadataJSON),
		}
		result.Versions = append(result.Versions, view)
		if item.CurrentVersionID != nil && *item.CurrentVersionID == version.ID {
			result.Map.CurrentVersion = &AdminMapVersionBrief{
				ID:          version.PublicID,
				VersionCode: version.VersionCode,
				Status:      version.Status,
			}
			result.Map.CurrentVersionID = &view.ID
		}
	}
	return result, nil
}

func (s *AdminResourceService) CreateMapVersion(ctx context.Context, mapPublicID string, input CreateAdminMapVersionInput) (*AdminMapVersion, error) {
	mapItem, err := s.store.GetResourceMapByPublicID(ctx, strings.TrimSpace(mapPublicID))
	if err != nil {
		return nil, err
	}
	if mapItem == nil {
		return nil, apperr.New(http.StatusNotFound, "map_not_found", "map not found")
	}
	input.VersionCode = strings.TrimSpace(input.VersionCode)
	input.MapmetaURL = strings.TrimSpace(input.MapmetaURL)
	input.TilesRootURL = strings.TrimSpace(input.TilesRootURL)
	if input.VersionCode == "" || input.MapmetaURL == "" || input.TilesRootURL == "" {
		return nil, apperr.New(http.StatusBadRequest, "invalid_params", "versionCode, mapmetaUrl and tilesRootUrl are required")
	}

	publicID, err := security.GeneratePublicID("mapv")
	if err != nil {
		return nil, err
	}
	tx, err := s.store.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	version, err := s.store.CreateResourceMapVersion(ctx, tx, postgres.CreateResourceMapVersionParams{
		PublicID:           publicID,
		MapID:              mapItem.ID,
		VersionCode:        input.VersionCode,
		Status:             normalizeVersionStatus(input.Status),
		MapmetaURL:         input.MapmetaURL,
		TilesRootURL:       input.TilesRootURL,
		PublishedAssetRoot: trimStringPtr(input.PublishedAssetRoot),
		BoundsJSON:         input.Bounds,
		MetadataJSON:       input.Metadata,
	})
	if err != nil {
		return nil, err
	}
	if input.SetAsCurrent {
		if err := s.store.SetResourceMapCurrentVersion(ctx, tx, mapItem.ID, version.ID); err != nil {
			return nil, err
		}
	}
	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}
	return &AdminMapVersion{
		ID:                 version.PublicID,
		VersionCode:        version.VersionCode,
		Status:             version.Status,
		MapmetaURL:         version.MapmetaURL,
		TilesRootURL:       version.TilesRootURL,
		PublishedAssetRoot: version.PublishedAssetRoot,
		Bounds:             decodeJSONMap(version.BoundsJSON),
		Metadata:           decodeJSONMap(version.MetadataJSON),
	}, nil
}

func (s *AdminResourceService) ListPlayfields(ctx context.Context, limit int) ([]AdminPlayfieldSummary, error) {
	items, err := s.store.ListResourcePlayfields(ctx, limit)
	if err != nil {
		return nil, err
	}
	results := make([]AdminPlayfieldSummary, 0, len(items))
	for _, item := range items {
		results = append(results, AdminPlayfieldSummary{
			ID:               item.PublicID,
			Code:             item.Code,
			Name:             item.Name,
			Kind:             item.Kind,
			Status:           item.Status,
			Description:      item.Description,
			CurrentVersionID: item.CurrentVersionID,
		})
	}
	return results, nil
}

func (s *AdminResourceService) CreatePlayfield(ctx context.Context, input CreateAdminPlayfieldInput) (*AdminPlayfieldSummary, error) {
	input.Code = strings.TrimSpace(input.Code)
	input.Name = strings.TrimSpace(input.Name)
	kind := strings.TrimSpace(input.Kind)
	if kind == "" {
		kind = "course"
	}
	if input.Code == "" || input.Name == "" {
		return nil, apperr.New(http.StatusBadRequest, "invalid_params", "code and name are required")
	}
	publicID, err := security.GeneratePublicID("pf")
	if err != nil {
		return nil, err
	}
	tx, err := s.store.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)
	item, err := s.store.CreateResourcePlayfield(ctx, tx, postgres.CreateResourcePlayfieldParams{
		PublicID:    publicID,
		Code:        input.Code,
		Name:        input.Name,
		Kind:        kind,
		Status:      normalizeCatalogStatus(input.Status),
		Description: trimStringPtr(input.Description),
	})
	if err != nil {
		return nil, err
	}
	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}
	return &AdminPlayfieldSummary{
		ID:               item.PublicID,
		Code:             item.Code,
		Name:             item.Name,
		Kind:             item.Kind,
		Status:           item.Status,
		Description:      item.Description,
		CurrentVersionID: item.CurrentVersionID,
	}, nil
}

func (s *AdminResourceService) GetPlayfieldDetail(ctx context.Context, publicID string) (*AdminPlayfieldDetail, error) {
	item, err := s.store.GetResourcePlayfieldByPublicID(ctx, strings.TrimSpace(publicID))
	if err != nil {
		return nil, err
	}
	if item == nil {
		return nil, apperr.New(http.StatusNotFound, "playfield_not_found", "playfield not found")
	}
	versions, err := s.store.ListResourcePlayfieldVersions(ctx, item.ID)
	if err != nil {
		return nil, err
	}
	result := &AdminPlayfieldDetail{
		Playfield: AdminPlayfieldSummary{
			ID:               item.PublicID,
			Code:             item.Code,
			Name:             item.Name,
			Kind:             item.Kind,
			Status:           item.Status,
			Description:      item.Description,
			CurrentVersionID: item.CurrentVersionID,
		},
		Versions: make([]AdminPlayfieldVersion, 0, len(versions)),
	}
	for _, version := range versions {
		view := AdminPlayfieldVersion{
			ID:                 version.PublicID,
			VersionCode:        version.VersionCode,
			Status:             version.Status,
			SourceType:         version.SourceType,
			SourceURL:          version.SourceURL,
			PublishedAssetRoot: version.PublishedAssetRoot,
			ControlCount:       version.ControlCount,
			Bounds:             decodeJSONMap(version.BoundsJSON),
			Metadata:           decodeJSONMap(version.MetadataJSON),
		}
		result.Versions = append(result.Versions, view)
		if item.CurrentVersionID != nil && *item.CurrentVersionID == version.ID {
			result.Playfield.CurrentVersion = &AdminPlayfieldVersionBrief{
				ID:          version.PublicID,
				VersionCode: version.VersionCode,
				Status:      version.Status,
				SourceType:  version.SourceType,
			}
			result.Playfield.CurrentVersionID = &view.ID
		}
	}
	return result, nil
}

func (s *AdminResourceService) CreatePlayfieldVersion(ctx context.Context, publicID string, input CreateAdminPlayfieldVersionInput) (*AdminPlayfieldVersion, error) {
	item, err := s.store.GetResourcePlayfieldByPublicID(ctx, strings.TrimSpace(publicID))
	if err != nil {
		return nil, err
	}
	if item == nil {
		return nil, apperr.New(http.StatusNotFound, "playfield_not_found", "playfield not found")
	}
	input.VersionCode = strings.TrimSpace(input.VersionCode)
	input.SourceType = strings.TrimSpace(input.SourceType)
	input.SourceURL = strings.TrimSpace(input.SourceURL)
	if input.VersionCode == "" || input.SourceType == "" || input.SourceURL == "" {
		return nil, apperr.New(http.StatusBadRequest, "invalid_params", "versionCode, sourceType and sourceUrl are required")
	}
	publicVersionID, err := security.GeneratePublicID("pfv")
	if err != nil {
		return nil, err
	}
	tx, err := s.store.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)
	version, err := s.store.CreateResourcePlayfieldVersion(ctx, tx, postgres.CreateResourcePlayfieldVersionParams{
		PublicID:           publicVersionID,
		PlayfieldID:        item.ID,
		VersionCode:        input.VersionCode,
		Status:             normalizeVersionStatus(input.Status),
		SourceType:         input.SourceType,
		SourceURL:          input.SourceURL,
		PublishedAssetRoot: trimStringPtr(input.PublishedAssetRoot),
		ControlCount:       input.ControlCount,
		BoundsJSON:         input.Bounds,
		MetadataJSON:       input.Metadata,
	})
	if err != nil {
		return nil, err
	}
	if input.SetAsCurrent {
		if err := s.store.SetResourcePlayfieldCurrentVersion(ctx, tx, item.ID, version.ID); err != nil {
			return nil, err
		}
	}
	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}
	return &AdminPlayfieldVersion{
		ID:                 version.PublicID,
		VersionCode:        version.VersionCode,
		Status:             version.Status,
		SourceType:         version.SourceType,
		SourceURL:          version.SourceURL,
		PublishedAssetRoot: version.PublishedAssetRoot,
		ControlCount:       version.ControlCount,
		Bounds:             decodeJSONMap(version.BoundsJSON),
		Metadata:           decodeJSONMap(version.MetadataJSON),
	}, nil
}

func (s *AdminResourceService) ListResourcePacks(ctx context.Context, limit int) ([]AdminResourcePackSummary, error) {
	items, err := s.store.ListResourcePacks(ctx, limit)
	if err != nil {
		return nil, err
	}
	results := make([]AdminResourcePackSummary, 0, len(items))
	for _, item := range items {
		results = append(results, AdminResourcePackSummary{
			ID:               item.PublicID,
			Code:             item.Code,
			Name:             item.Name,
			Status:           item.Status,
			Description:      item.Description,
			CurrentVersionID: item.CurrentVersionID,
		})
	}
	return results, nil
}

func (s *AdminResourceService) CreateResourcePack(ctx context.Context, input CreateAdminResourcePackInput) (*AdminResourcePackSummary, error) {
	input.Code = strings.TrimSpace(input.Code)
	input.Name = strings.TrimSpace(input.Name)
	if input.Code == "" || input.Name == "" {
		return nil, apperr.New(http.StatusBadRequest, "invalid_params", "code and name are required")
	}
	publicID, err := security.GeneratePublicID("rp")
	if err != nil {
		return nil, err
	}
	tx, err := s.store.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)
	item, err := s.store.CreateResourcePack(ctx, tx, postgres.CreateResourcePackParams{
		PublicID:    publicID,
		Code:        input.Code,
		Name:        input.Name,
		Status:      normalizeCatalogStatus(input.Status),
		Description: trimStringPtr(input.Description),
	})
	if err != nil {
		return nil, err
	}
	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}
	return &AdminResourcePackSummary{
		ID:               item.PublicID,
		Code:             item.Code,
		Name:             item.Name,
		Status:           item.Status,
		Description:      item.Description,
		CurrentVersionID: item.CurrentVersionID,
	}, nil
}

func (s *AdminResourceService) GetResourcePackDetail(ctx context.Context, publicID string) (*AdminResourcePackDetail, error) {
	item, err := s.store.GetResourcePackByPublicID(ctx, strings.TrimSpace(publicID))
	if err != nil {
		return nil, err
	}
	if item == nil {
		return nil, apperr.New(http.StatusNotFound, "resource_pack_not_found", "resource pack not found")
	}
	versions, err := s.store.ListResourcePackVersions(ctx, item.ID)
	if err != nil {
		return nil, err
	}
	result := &AdminResourcePackDetail{
		ResourcePack: AdminResourcePackSummary{
			ID:               item.PublicID,
			Code:             item.Code,
			Name:             item.Name,
			Status:           item.Status,
			Description:      item.Description,
			CurrentVersionID: item.CurrentVersionID,
		},
		Versions: make([]AdminResourcePackVersion, 0, len(versions)),
	}
	for _, version := range versions {
		view := AdminResourcePackVersion{
			ID:                 version.PublicID,
			VersionCode:        version.VersionCode,
			Status:             version.Status,
			ContentEntryURL:    version.ContentEntryURL,
			AudioRootURL:       version.AudioRootURL,
			ThemeProfileCode:   version.ThemeProfileCode,
			PublishedAssetRoot: version.PublishedAssetRoot,
			Metadata:           decodeJSONMap(version.MetadataJSON),
		}
		result.Versions = append(result.Versions, view)
		if item.CurrentVersionID != nil && *item.CurrentVersionID == version.ID {
			result.ResourcePack.CurrentVersion = &AdminResourcePackVersionBrief{
				ID:          version.PublicID,
				VersionCode: version.VersionCode,
				Status:      version.Status,
			}
			result.ResourcePack.CurrentVersionID = &view.ID
		}
	}
	return result, nil
}

func (s *AdminResourceService) CreateResourcePackVersion(ctx context.Context, publicID string, input CreateAdminResourcePackVersionInput) (*AdminResourcePackVersion, error) {
	item, err := s.store.GetResourcePackByPublicID(ctx, strings.TrimSpace(publicID))
	if err != nil {
		return nil, err
	}
	if item == nil {
		return nil, apperr.New(http.StatusNotFound, "resource_pack_not_found", "resource pack not found")
	}
	input.VersionCode = strings.TrimSpace(input.VersionCode)
	if input.VersionCode == "" {
		return nil, apperr.New(http.StatusBadRequest, "invalid_params", "versionCode is required")
	}
	publicVersionID, err := security.GeneratePublicID("rpv")
	if err != nil {
		return nil, err
	}
	tx, err := s.store.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)
	version, err := s.store.CreateResourcePackVersion(ctx, tx, postgres.CreateResourcePackVersionParams{
		PublicID:           publicVersionID,
		ResourcePackID:     item.ID,
		VersionCode:        input.VersionCode,
		Status:             normalizeVersionStatus(input.Status),
		ContentEntryURL:    trimStringPtr(input.ContentEntryURL),
		AudioRootURL:       trimStringPtr(input.AudioRootURL),
		ThemeProfileCode:   trimStringPtr(input.ThemeProfileCode),
		PublishedAssetRoot: trimStringPtr(input.PublishedAssetRoot),
		MetadataJSON:       input.Metadata,
	})
	if err != nil {
		return nil, err
	}
	if input.SetAsCurrent {
		if err := s.store.SetResourcePackCurrentVersion(ctx, tx, item.ID, version.ID); err != nil {
			return nil, err
		}
	}
	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}
	return &AdminResourcePackVersion{
		ID:                 version.PublicID,
		VersionCode:        version.VersionCode,
		Status:             version.Status,
		ContentEntryURL:    version.ContentEntryURL,
		AudioRootURL:       version.AudioRootURL,
		ThemeProfileCode:   version.ThemeProfileCode,
		PublishedAssetRoot: version.PublishedAssetRoot,
		Metadata:           decodeJSONMap(version.MetadataJSON),
	}, nil
}

func normalizeCatalogStatus(value string) string {
	switch strings.TrimSpace(value) {
	case "active":
		return "active"
	case "disabled":
		return "disabled"
	case "archived":
		return "archived"
	default:
		return "draft"
	}
}

func normalizeVersionStatus(value string) string {
	switch strings.TrimSpace(value) {
	case "active":
		return "active"
	case "archived":
		return "archived"
	default:
		return "draft"
	}
}

func trimStringPtr(value *string) *string {
	if value == nil {
		return nil
	}
	trimmed := strings.TrimSpace(*value)
	if trimmed == "" {
		return nil
	}
	return &trimmed
}

func decodeJSONMap(raw json.RawMessage) map[string]any {
	if len(raw) == 0 {
		return nil
	}
	result := map[string]any{}
	if err := json.Unmarshal(raw, &result); err != nil || len(result) == 0 {
		return nil
	}
	return result
}
