package dialectsfcrm

import (
	"context"
	"fmt"
	"net/http"

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

// DescribeLayoutsNeutralHandler serves
// GET /sobjects/:sobject/describe/layouts.
//
// Salesforce parity: when the object has additional active record types
// (HasConcreteRecordTypes), it returns layouts:null + populated
// recordTypeMappings so clients fetch per-RT layouts from
// /describe/layouts/{recordTypeId}; otherwise it returns the populated layouts
// alongside the mappings. The raw layouts come through the neutral contract
// (Metadata.ResolveLayout, which folds the assignment scope behind the session);
// the v63 envelope is projected here.
func DescribeLayoutsNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	objectName := req.Params["sobject"]
	metadata, policy, ok := describeDeps(deps)
	if !ok {
		return internalResponse("Unable to resolve record type access"), nil
	}

	access, resp := effectiveAccessForDescribe(ctx, policy, req.User, objectName, "Insufficient access to describe layouts", "Unable to resolve record type access")
	if resp != nil {
		return resp, nil
	}

	schema, err := metadata.Describe(ctx, objectName)
	if err != nil || schema == nil {
		return notFoundResponse("The requested resource does not exist"), nil
	}

	if access.HasConcreteRecordTypes && access.AvailableConcreteCount == 0 {
		return accessDeniedResponse(fmt.Sprintf("No record types are available for object '%s'", objectName)), nil
	}

	layoutID := fmt.Sprintf("%s-default-layout", objectName)
	presentationRecordTypes := recordtypes.ProjectContractSelectionEntries(access)
	mappings := make([]map[string]any, 0, len(presentationRecordTypes))
	for _, rt := range presentationRecordTypes {
		isDefault := rt.Default
		mappings = append(mappings, map[string]any{
			"active":                   true,
			"available":                rt.Available,
			"defaultRecordTypeMapping": isDefault,
			"developerName":            rt.APIName,
			"layoutId":                 layoutID,
			"master":                   rt.Master,
			"name":                     rt.Name,
			"recordTypeId":             rt.ID,
			"urls":                     map[string]any{},
		})
	}

	// Build layouts array: resolve the actual layout structure for each RT. The
	// contract folds (profileID, role-ancestor chain) behind the neutral session.
	layouts := make([]map[string]any, 0, len(mappings))
	layoutsByID := make(map[string]struct{}, len(mappings))
	for i, m := range mappings {
		rtID, _ := m["recordTypeId"].(string)
		if available, ok := m["available"].(bool); ok && !available {
			continue
		}
		storedLayout, err := metadata.ResolveLayout(ctx, objectName, req.User, rtID)
		if err != nil {
			return internalResponse("Unable to resolve record type access"), nil
		}
		if storedLayout == nil {
			// No persisted layout — emit a minimal layout shell so the
			// response is never null (Salesforce always returns an array).
			if _, exists := layoutsByID[layoutID]; !exists {
				layouts = append(layouts, map[string]any{
					"id":                   layoutID,
					"name":                 layoutID,
					"detailLayoutSections": []map[string]any{},
					"editLayoutSections":   []map[string]any{},
					"relatedLists":         []map[string]any{},
					"sections":             []map[string]any{},
				})
				layoutsByID[layoutID] = struct{}{}
			}
			continue
		}
		// Align mapping layoutId with the actual resolved layout ID so clients
		// can dereference layouts[].id from recordTypeMappings[].layoutId.
		mappings[i]["layoutId"] = storedLayout.ID
		if _, exists := layoutsByID[storedLayout.ID]; exists {
			continue
		}
		relatedLists := storedLayoutRelatedListsToSFFormat(ctx, metadata, storedLayout.RelatedLists)
		layouts = append(layouts, convertStoredLayoutToSFFormat(storedLayout, schema, relatedLists))
		layoutsByID[storedLayout.ID] = struct{}{}
	}

	var layoutsResp any = layouts
	if access.HasConcreteRecordTypes {
		layoutsResp = nil
	}

	return &dialect.Response{
		Status: http.StatusOK,
		Body: map[string]any{
			"layouts":                    layoutsResp,
			"recordTypeMappings":         mappings,
			"recordTypeSelectorRequired": access.AvailableConcreteCount > 0,
		},
	}, nil
}

// DescribeLayoutByRecordTypeNeutralHandler serves
// GET /sobjects/:sobject/describe/layouts/:recordTypeId.
//
// Salesforce parity: returns the full layout object with sections and
// relatedLists for the requested record type.
func DescribeLayoutByRecordTypeNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	objectName := req.Params["sobject"]
	recordTypeID := req.Params["recordTypeId"]
	metadata, policy, ok := describeDeps(deps)
	if !ok {
		return internalResponse("Unable to resolve record type access"), nil
	}

	access, resp := effectiveAccessForDescribe(ctx, policy, req.User, objectName, "Insufficient access to describe layouts", "Unable to resolve record type access")
	if resp != nil {
		return resp, nil
	}

	schema, err := metadata.Describe(ctx, objectName)
	if err != nil || schema == nil {
		return notFoundResponse("The requested resource does not exist"), nil
	}

	if recordTypeID == constants.SyntheticMasterRecordTypeID && access.HasConcreteRecordTypes {
		return accessDeniedResponse(fmt.Sprintf("record type '%s' is not available for object '%s'", recordTypeID, objectName)), nil
	}
	if recordTypeUnavailableInEffectiveAccess(access, recordTypeID) {
		return accessDeniedResponse(fmt.Sprintf("record type '%s' is not available for object '%s'", recordTypeID, objectName)), nil
	}

	storedLayout, err := metadata.ResolveLayout(ctx, objectName, req.User, recordTypeID)
	if err != nil {
		return internalResponse("Unable to resolve record type access"), nil
	}
	if storedLayout == nil {
		return notFoundResponse(fmt.Sprintf("No layout could be resolved for object %s", objectName)), nil
	}

	// Use the layout's curated related lists (populated by ResolveLayout/augment),
	// not raw auto-discovery. This matches Salesforce behavior where Quick Links
	// and record pages show only layout-configured related lists.
	relatedLists := storedLayoutRelatedListsToSFFormat(ctx, metadata, storedLayout.RelatedLists)

	return &dialect.Response{
		Status: http.StatusOK,
		Body:   convertStoredLayoutToSFFormat(storedLayout, schema, relatedLists),
	}, nil
}

// DescribeSystemFileLayoutsNeutralHandler serves the dedicated static
// GET /sobjects/_System_File/describe/layouts route. It is a literal-path clone
// of the generic layouts handler with the sobject param injected, registered as
// its own Route so Gin's radix tree does not stop at the
// /sobjects/_System_File/:id param node and shadow the generic layouts route
// (the #2037 radix-shadow lesson). Mirrors DescribeSystemFileNeutralHandler.
func DescribeSystemFileLayoutsNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	if req == nil {
		req = &dialect.Request{}
	}
	cloned := *req
	cloned.Params = make(map[string]string, len(req.Params)+1)
	for key, value := range req.Params {
		cloned.Params[key] = value
	}
	cloned.Params["sobject"] = systemFileObjectName
	return DescribeLayoutsNeutralHandler(ctx, &cloned, deps)
}
