package blueprint

import (
	"fmt"
	"strings"
)

// MergeResult describes what was added/updated when merging incoming into base.
type MergeResult struct {
	AddedPages       []string
	AddedEntities    []string
	AddedResources   []string
	AddedFields      []string // entity.field
	UpdatedPages     []string
	UpdatedResources []string
}

// MergeInto merges incoming pages/entities/apis into base (existing published app).
// Existing entities keep their fields; new fields on known entities are appended.
// Same-path resources union operations (e.g. add import).
// Same page id：合并 actions，不再报错（支持「只改 API/按钮、不新建页」的编辑发布）。
// 仅 route 冲突且 id 不同仍拒绝。
func MergeInto(base, incoming *Blueprint) (*MergeResult, error) {
	if base == nil {
		return nil, fmt.Errorf("base blueprint is nil")
	}
	if incoming == nil {
		return nil, fmt.Errorf("incoming blueprint is nil")
	}
	res := &MergeResult{}

	entityByName := map[string]int{}
	for i, e := range base.Entities {
		entityByName[e.Name] = i
	}
	for _, e := range incoming.Entities {
		if idx, ok := entityByName[e.Name]; ok {
			before := len(base.Entities[idx].Fields)
			base.Entities[idx] = mergeEntity(base.Entities[idx], e)
			if len(base.Entities[idx].Fields) > before {
				for _, f := range base.Entities[idx].Fields[before:] {
					res.AddedFields = append(res.AddedFields, e.Name+"."+f.Name)
				}
			}
			continue
		}
		base.Entities = append(base.Entities, e)
		entityByName[e.Name] = len(base.Entities) - 1
		res.AddedEntities = append(res.AddedEntities, e.Name)
	}

	pathKey := func(p string) string {
		p = strings.TrimPrefix(strings.TrimSpace(p), "/")
		return p
	}
	resByPath := map[string]int{}
	for i, r := range base.Apis.Resources {
		resByPath[pathKey(r.Path)] = i
	}
	for _, r := range incoming.Apis.Resources {
		k := pathKey(r.Path)
		if idx, ok := resByPath[k]; ok {
			before := len(base.Apis.Resources[idx].Operations)
			base.Apis.Resources[idx] = mergeResource(base.Apis.Resources[idx], r)
			if len(base.Apis.Resources[idx].Operations) > before {
				res.UpdatedResources = append(res.UpdatedResources, k)
			}
			continue
		}
		base.Apis.Resources = append(base.Apis.Resources, r)
		resByPath[k] = len(base.Apis.Resources) - 1
		res.AddedResources = append(res.AddedResources, k)
	}

	pageByID := map[string]int{}
	routeBy := map[string]string{} // route -> page id
	for i, p := range base.Pages {
		pageByID[p.ID] = i
		routeBy[p.Route] = p.ID
	}
	for _, p := range incoming.Pages {
		if idx, ok := pageByID[p.ID]; ok {
			before := pageActionCount(base.Pages[idx])
			base.Pages[idx] = mergePage(base.Pages[idx], p)
			if pageActionCount(base.Pages[idx]) > before {
				res.UpdatedPages = append(res.UpdatedPages, p.ID)
			}
			continue
		}
		if owner, ok := routeBy[p.Route]; ok && owner != p.ID {
			return nil, fmt.Errorf("page route already exists: %s", p.Route)
		}
		base.Pages = append(base.Pages, p)
		pageByID[p.ID] = len(base.Pages) - 1
		routeBy[p.Route] = p.ID
		res.AddedPages = append(res.AddedPages, p.ID)
	}

	if incoming.Meta.Description != "" && base.Meta.Description == "" {
		base.Meta.Description = incoming.Meta.Description
	}
	if incoming.Meta.UIPreset != "" && base.Meta.UIPreset == "" {
		base.Meta.UIPreset = incoming.Meta.UIPreset
	}
	if len(incoming.Meta.UI) > 0 && len(base.Meta.UI) == 0 {
		base.Meta.UI = incoming.Meta.UI
	}
	if incoming.Seed != nil && base.Seed == nil {
		base.Seed = incoming.Seed
	}

	if len(res.AddedPages) == 0 &&
		len(res.AddedEntities) == 0 &&
		len(res.AddedResources) == 0 &&
		len(res.AddedFields) == 0 &&
		len(res.UpdatedPages) == 0 &&
		len(res.UpdatedResources) == 0 {
		return nil, fmt.Errorf("nothing new to publish: provide newly generated pages (and entities/apis if needed) for an existing app")
	}
	return res, nil
}

func mergeEntity(base, incoming Entity) Entity {
	fieldBy := map[string]struct{}{}
	for _, f := range base.Fields {
		fieldBy[f.Name] = struct{}{}
	}
	for _, f := range incoming.Fields {
		if _, ok := fieldBy[f.Name]; ok {
			continue
		}
		base.Fields = append(base.Fields, f)
		fieldBy[f.Name] = struct{}{}
	}
	idxBy := map[string]struct{}{}
	for _, ix := range base.Indexes {
		idxBy[ix.Name] = struct{}{}
	}
	for _, ix := range incoming.Indexes {
		if _, ok := idxBy[ix.Name]; ok {
			continue
		}
		base.Indexes = append(base.Indexes, ix)
	}
	if base.Label == "" && incoming.Label != "" {
		base.Label = incoming.Label
	}
	return base
}

func pageActionCount(p Page) int {
	if p.Layout == nil {
		return 0
	}
	return len(p.Layout.Actions)
}

func mergePage(base, incoming Page) Page {
	if incoming.Layout == nil || len(incoming.Layout.Actions) == 0 {
		return base
	}
	if base.Layout == nil {
		base.Layout = &PageLayout{}
	}
	actSet := map[string]struct{}{}
	for _, a := range base.Layout.Actions {
		actSet[a] = struct{}{}
	}
	for _, a := range incoming.Layout.Actions {
		if _, ok := actSet[a]; ok {
			continue
		}
		base.Layout.Actions = append(base.Layout.Actions, a)
		actSet[a] = struct{}{}
	}
	return base
}

func mergeResource(base, incoming APIResource) APIResource {
	opSet := map[string]struct{}{}
	for _, op := range base.Operations {
		opSet[op] = struct{}{}
	}
	for _, op := range incoming.Operations {
		if _, ok := opSet[op]; ok {
			continue
		}
		base.Operations = append(base.Operations, op)
		opSet[op] = struct{}{}
	}
	if base.List == nil && incoming.List != nil {
		base.List = incoming.List
	} else if base.List != nil && incoming.List != nil {
		filt := map[string]struct{}{}
		for _, f := range base.List.AllowedFilters {
			filt[f] = struct{}{}
		}
		for _, f := range incoming.List.AllowedFilters {
			if _, ok := filt[f]; !ok {
				base.List.AllowedFilters = append(base.List.AllowedFilters, f)
				filt[f] = struct{}{}
			}
		}
		sorts := map[string]struct{}{}
		for _, s := range base.List.AllowedSorts {
			sorts[s] = struct{}{}
		}
		for _, s := range incoming.List.AllowedSorts {
			if _, ok := sorts[s]; !ok {
				base.List.AllowedSorts = append(base.List.AllowedSorts, s)
				sorts[s] = struct{}{}
			}
		}
	}
	return base
}
