package packs

import (
	"strings"

	"github.com/nexuscrm/shared/pkg/constants"
)

// L0 reserved metadata names — single source of truth for the names that
// no L1 pack may declare. Two consumers read this:
//
//  1. validate-pack rejects pack-declared object_spec components whose
//     object name or field name overlaps with these lists, before any
//     install attempt is made (see pack_source.go).
//  2. pack authoring tooling (e.g. the salesforce-crm-standard-v66
//     extractor) drops these names from generated specs.
//
// Putting the lists here means a name added to L0's platform-managed
// surface is reflected in validation without per-tool hardcoding.

// L0ReservedObjectNames returns object API names L0 owns as platform-universal
// admin/identity primitives (architecture §3, "L0 owns User/Profile/Group/
// UserRole when they are platform-universal metadata or identity primitives").
// L1 packs must not redeclare these.
func L0ReservedObjectNames() []string {
	return []string{
		constants.TableUser,
		constants.TableProfile,
		constants.TableGroup,
		constants.TableUserRole,
		constants.TablePermissionSet,
		constants.TablePermissionSetGroup,
	}
}

// L0ReservedFieldNames returns field API names L0 injects into every object
// via GetStandardSystemColumns. L1 packs must not redeclare these as object
// fields; redeclaration triggers the runtime relationship-registry conflict
// observed in the L0 completeness probe.
func L0ReservedFieldNames() []string {
	return []string{
		constants.FieldID,
		constants.FieldOwnerID,
		constants.FieldCreatedByID,
		constants.FieldLastModifiedByID,
		constants.FieldCreatedDate,
		constants.FieldLastModifiedDate,
		constants.FieldSystemModstamp,
		constants.FieldIsDeleted,
	}
}

// l0ReservedObjectNameSet returns a case-insensitive lookup set built from
// L0ReservedObjectNames.
func l0ReservedObjectNameSet() map[string]bool {
	return lowerSet(L0ReservedObjectNames())
}

// l0ReservedFieldNameSet returns a case-insensitive lookup set built from
// L0ReservedFieldNames.
func l0ReservedFieldNameSet() map[string]bool {
	return lowerSet(L0ReservedFieldNames())
}

func lowerSet(values []string) map[string]bool {
	out := make(map[string]bool, len(values))
	for _, v := range values {
		out[strings.ToLower(strings.TrimSpace(v))] = true
	}
	return out
}

// validateObjectSpecAgainstL0Reserved walks parsed object specs and emits a
// diagnostic for each object or field that overlaps with the L0 reserved-name
// lists. Called from pack_source.go inside the validate-pack flow so authors
// see the conflict before any install attempt.
func validateObjectSpecAgainstL0Reserved(specs []ObjectSpec, componentPath string) []PackDiagnostic {
	objectReserved := l0ReservedObjectNameSet()
	fieldReserved := l0ReservedFieldNameSet()

	var diagnostics []PackDiagnostic
	for _, spec := range specs {
		objKey := strings.ToLower(strings.TrimSpace(spec.APIName))
		if objectReserved[objKey] {
			diagnostics = append(diagnostics, diag(
				"component.payload.reserved_object_name",
				componentPath+".payload",
				"object "+spec.APIName+" is L0-managed (User/Profile/Group/UserRole/PermissionSet/PermissionSetGroup); L1 packs must not redeclare it",
			))
		}
		for _, field := range spec.Fields {
			fieldKey := strings.ToLower(strings.TrimSpace(field.APIName))
			if fieldReserved[fieldKey] {
				diagnostics = append(diagnostics, diag(
					"component.payload.reserved_field_name",
					componentPath+".payload",
					"object "+spec.APIName+" field "+field.APIName+" is an L0 system column injected automatically; L1 packs must not redeclare it",
				))
			}
		}
	}
	return diagnostics
}
