package layoutauthoring

import (
	"encoding/json"
	"errors"
	"fmt"
	"math"

	"github.com/nexuscrm/metadata/catalogsnapshot"
	"github.com/nexuscrm/shared/pkg/models"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv1"
)

func DocumentFromPageLayout(layout models.PageLayout) (semantic.LayoutDocument, error) {
	return catalogsnapshot.ProjectStoredLayoutDocument(layout)
}

// PageLayoutFromDocument converts the complete neutral authored fact into the
// existing storage model. Server-owned lifecycle/default/timestamps are set by
// the caller and never accepted from LayoutDocument.
func PageLayoutFromDocument(document semantic.LayoutDocument) (*models.PageLayout, error) {
	if err := semantic.ValidateLayoutDocument(document); err != nil {
		return nil, err
	}
	layout := &models.PageLayout{
		ID: document.LayoutID, ObjectAPIName: document.ObjectID,
		LayoutName: document.Name, Type: document.Kind, LayoutVersion: int(document.LayoutVersion),
		CompactLayout:         cloneSlice(document.CompactFields),
		Tabs:                  cloneSlice(document.Tabs),
		Sections:              make([]models.PageSection, 0, len(document.Sections)),
		RelatedLists:          make([]models.RelatedListConfig, 0, len(document.RelatedLists)),
		RecordPanels:          make([]models.RecordPanelConfig, 0, len(document.RecordPanels)),
		HeaderActionsAuthored: document.HeaderActions.Authored,
		QuickActionsAuthored:  document.QuickActions.Authored,
		RecordStates:          make([]models.RecordStateRule, 0, len(document.RecordStates)),
		PrimaryHeaderActions:  cloneSlice(document.PrimaryHeaderActions),
	}
	for _, section := range document.Sections {
		configuration, err := namedValues(section.ComponentConfiguration)
		if err != nil {
			return nil, fmt.Errorf("section %q component configuration: %w", section.SectionID, err)
		}
		labels := make(map[string]string, len(section.FieldLabels))
		for _, label := range section.FieldLabels {
			labels[label.FieldReference] = label.Label
		}
		behaviors := make(map[string]map[string]string, len(section.FieldBehaviors))
		for _, group := range section.FieldBehaviors {
			if group.Behaviors == nil {
				behaviors[group.FieldReference] = nil
				continue
			}
			entries := make(map[string]string, len(*group.Behaviors))
			for _, behavior := range *group.Behaviors {
				entries[behavior.Mode] = behavior.Behavior
			}
			behaviors[group.FieldReference] = entries
		}
		layout.Sections = append(layout.Sections, models.PageSection{
			ID: section.SectionID, Label: section.Label, Tab: cloneString(section.TabID),
			Type: cloneString(section.Kind), ComponentName: cloneString(section.ComponentName),
			ComponentConfig: configuration, Columns: int(section.Columns),
			Fields: cloneSlice(section.Fields), FieldLabels: labels,
			FieldBehaviors: behaviors, VisibilityCondition: cloneString(section.VisibilityCondition),
		})
	}
	for _, list := range document.RelatedLists {
		mappings := make(map[string]string, len(list.PrefillMappings))
		for _, mapping := range list.PrefillMappings {
			mappings[mapping.TargetFieldID] = mapping.SourceFieldID
		}
		layout.RelatedLists = append(layout.RelatedLists, models.RelatedListConfig{
			ID: list.RelatedListID, Label: list.Label, SecondaryLabel: list.SecondaryLabel,
			ObjectAPIName: list.ObjectID, LookupField: list.LookupFieldID,
			Fields: cloneSlice(list.FieldIDs), PrefillMappings: mappings,
		})
	}
	for _, panel := range document.RecordPanels {
		configuration, err := namedValues(panel.RendererConfiguration)
		if err != nil {
			return nil, fmt.Errorf("panel %q configuration: %w", panel.PanelID, err)
		}
		layout.RecordPanels = append(layout.RecordPanels, models.RecordPanelConfig{
			ID: panel.PanelID, RendererID: panel.RendererID, Position: panel.Position,
			TabID: panel.TabID, TabLabel: panel.TabLabel, Order: int(panel.Order),
			VisibilityCondition: panel.VisibilityCondition, Config: configuration,
		})
	}
	var err error
	layout.HeaderActions, err = actions(document.HeaderActions.Actions)
	if err != nil {
		return nil, fmt.Errorf("header actions: %w", err)
	}
	layout.QuickActions, err = actions(document.QuickActions.Actions)
	if err != nil {
		return nil, fmt.Errorf("quick actions: %w", err)
	}
	for _, rule := range document.RecordStates {
		converted := models.RecordStateRule{
			ID: rule.RuleID, When: rule.Condition, Priority: int(rule.Priority),
			Links:           make([]models.RecordStateLink, 0, len(rule.Links)),
			ActionOverrides: make([]models.RecordStateActionOverride, 0, len(rule.ActionOverrides)),
		}
		if rule.Banner != nil {
			converted.Banner = &models.RecordStateBanner{
				Severity: rule.Banner.Severity, Title: rule.Banner.Title, Body: rule.Banner.Body,
				LinkIDs: cloneSlice(rule.Banner.LinkIDs),
			}
		}
		for _, link := range rule.Links {
			converted.Links = append(converted.Links, models.RecordStateLink{
				ID: link.LinkID, Label: link.Label, ReferenceFieldAPIName: link.ReferenceFieldID,
				ObjectAPIName: link.ObjectID, Placement: link.Placement,
			})
		}
		for _, override := range rule.ActionOverrides {
			converted.ActionOverrides = append(converted.ActionOverrides, models.RecordStateActionOverride{
				ActionKey: override.ActionKey, Mode: override.Mode, Reason: override.Reason,
			})
		}
		if rule.ReadOnly != nil {
			converted.ReadOnly = &models.RecordStateReadOnly{Enabled: rule.ReadOnly.Enabled, Reason: rule.ReadOnly.Reason}
		}
		layout.RecordStates = append(layout.RecordStates, converted)
	}
	return layout, nil
}

// AppliedDocumentValidationError identifies only the final semantic validation
// of the combined post-change document. Validation failures for the stored
// current document or the standalone change set remain unwrapped so callers
// cannot misclassify corrupt authority as a caller-owned command rejection.
type AppliedDocumentValidationError struct {
	cause error
}

func (failure *AppliedDocumentValidationError) Error() string {
	return "Layout changes do not produce a valid authored document"
}

func (failure *AppliedDocumentValidationError) Unwrap() error {
	if failure == nil {
		return nil
	}
	return failure.cause
}

func ApplyDocumentChanges(current semantic.LayoutDocument, changes semantic.LayoutDocumentChanges) (semantic.LayoutDocument, error) {
	if err := semantic.ValidateLayoutDocument(current); err != nil {
		return semantic.LayoutDocument{}, err
	}
	if err := semantic.ValidateLayoutDocumentChanges(changes); err != nil {
		return semantic.LayoutDocument{}, err
	}
	result := current
	applyScalar(&result.Name, changes.Name)
	applyScalar(&result.Kind, changes.Kind)
	applyScalar(&result.LayoutVersion, changes.LayoutVersion)
	applySlice(&result.CompactFields, changes.CompactFields)
	applySlice(&result.Tabs, changes.Tabs)
	applySlice(&result.Sections, changes.Sections)
	applySlice(&result.RelatedLists, changes.RelatedLists)
	applySlice(&result.RecordPanels, changes.RecordPanels)
	applyScalar(&result.HeaderActions, changes.HeaderActions)
	applyScalar(&result.QuickActions, changes.QuickActions)
	applySlice(&result.RecordStates, changes.RecordStates)
	applySlice(&result.PrimaryHeaderActions, changes.PrimaryHeaderActions)
	if err := semantic.ValidateLayoutDocument(result); err != nil {
		return semantic.LayoutDocument{}, &AppliedDocumentValidationError{cause: err}
	}
	return result, nil
}

func ApplyListFields(current []string, change semantic.FieldIDListChange) ([]string, error) {
	if !change.Mode.Valid() ||
		(change.Mode == semantic.ChangeSet) != (change.Value != nil) ||
		(change.Mode != semantic.ChangeSet && change.Value != nil) {
		return nil, errors.New("layout authoring list fields change is invalid")
	}
	switch change.Mode {
	case semantic.ChangePreserve:
		return cloneSlice(current), nil
	case semantic.ChangeClear:
		return []string{}, nil
	case semantic.ChangeSet:
		return cloneSlice(*change.Value), nil
	default:
		return nil, errors.New("layout authoring list fields change mode is invalid")
	}
}

func applyScalar[T any](target *T, change semantic.Change[T]) {
	switch change.Mode {
	case semantic.ChangeSet:
		*target = *change.Value
	case semantic.ChangeClear:
		var zero T
		*target = zero
	}
}

func applySlice[T any](target *[]T, change semantic.Change[[]T]) {
	switch change.Mode {
	case semantic.ChangeSet:
		*target = cloneSlice(*change.Value)
	case semantic.ChangeClear:
		*target = []T{}
	}
}

func cloneSlice[T any](values []T) []T {
	if values == nil {
		return nil
	}
	result := make([]T, len(values))
	copy(result, values)
	return result
}

func actions(values []semantic.LayoutAction) ([]models.ActionConfig, error) {
	result := make([]models.ActionConfig, 0, len(values))
	for _, value := range values {
		configuration, err := namedValues(value.Configuration)
		if err != nil {
			return nil, fmt.Errorf("action %q configuration: %w", value.Name, err)
		}
		extensions, err := namedValues(value.Extensions)
		if err != nil {
			return nil, fmt.Errorf("action %q extensions: %w", value.Name, err)
		}
		result = append(result, models.ActionConfig{
			Name: value.Name, Label: value.Label, Type: value.Kind,
			Icon: cloneString(value.Icon), TargetObject: cloneString(value.TargetObjectID),
			Component: cloneString(value.Component), VisibilityCondition: cloneString(value.VisibilityCondition),
			Config: configuration, Extensions: extensions,
		})
	}
	return result, nil
}

func namedValues(values []semantic.NamedValue) (map[string]interface{}, error) {
	result := make(map[string]interface{}, len(values))
	for _, entry := range values {
		value, err := storedValue(entry.Value)
		if err != nil {
			return nil, fmt.Errorf("%q: %w", entry.Name, err)
		}
		result[entry.Name] = value
	}
	return result, nil
}

func storedValue(value semantic.Value) (interface{}, error) {
	switch value.Kind {
	case semantic.ValueNull:
		return nil, nil
	case semantic.ValueBoolean:
		if value.Boolean == nil {
			return nil, errors.New("boolean value is absent")
		}
		return *value.Boolean, nil
	case semantic.ValueString:
		if value.String == nil {
			return nil, errors.New("string value is absent")
		}
		return *value.String, nil
	case semantic.ValueDecimal:
		if value.Decimal == nil {
			return nil, errors.New("decimal value is absent")
		}
		var number json.Number = json.Number(*value.Decimal)
		if _, err := number.Float64(); err != nil {
			return nil, errors.New("decimal value is invalid")
		}
		if parsed, err := number.Float64(); err != nil || math.IsInf(parsed, 0) || math.IsNaN(parsed) {
			return nil, errors.New("decimal value is invalid")
		}
		return number, nil
	case semantic.ValueList:
		if value.List == nil {
			return nil, errors.New("list value is absent")
		}
		result := make([]interface{}, 0, len(*value.List))
		for _, item := range *value.List {
			stored, err := storedValue(item)
			if err != nil {
				return nil, err
			}
			result = append(result, stored)
		}
		return result, nil
	case semantic.ValueObject:
		if value.Object == nil {
			return nil, errors.New("object value is absent")
		}
		return namedValues(*value.Object)
	default:
		return nil, errors.New("value kind is invalid")
	}
}

func cloneString(value *string) *string {
	if value == nil {
		return nil
	}
	copy := *value
	return &copy
}
