package querycodec

import (
	"errors"
	"fmt"
)

var errMissingRecordID = errors.New("sfcrm query codec: record has no Id field")

func projectRecord(version, fallbackType string, record Record, aggregate, topLevel bool, childRelationships map[string]struct{}) (map[string]any, error) {
	row := make(map[string]any, len(record.Fields)+1)
	for name, value := range record.Fields {
		projected, childRecords, err := projectValue(version, value)
		if err != nil {
			return nil, err
		}
		_, declaredChildRelationship := childRelationships[name]
		if childRecords || declaredChildRelationship {
			records, ok := projected.([]any)
			if !ok {
				return nil, fmt.Errorf("sfcrm query codec: child relationship %q is not a record list", name)
			}
			projected = map[string]any{"totalSize": len(records), "done": true, "records": records}
		}
		row[name] = projected
	}
	if aggregate && topLevel {
		row["attributes"] = map[string]any{"type": "AggregateResult"}
		return row, nil
	}
	objectType := record.ObjectType
	if objectType == "" {
		objectType = fallbackType
	}
	if objectType == "" {
		return row, nil
	}
	if !topLevel {
		row["attributes"] = map[string]any{"type": objectType}
		return row, nil
	}
	id, ok := row["Id"].(string)
	if !ok || id == "" {
		return nil, errMissingRecordID
	}
	row["attributes"] = map[string]any{
		"type": objectType,
		"url":  fmt.Sprintf("/services/data/%s/sobjects/%s/%s", versionOrDefault(version), objectType, id),
	}
	return row, nil
}

// projectValue returns true when value is a list of records and therefore a
// Salesforce child-subquery envelope rather than an ordinary list scalar.
func projectValue(version string, value any) (projected any, childRecords bool, err error) {
	switch typed := value.(type) {
	case Record:
		row, err := projectRecord(version, "", typed, false, false, nil)
		return row, false, err
	case *Record:
		if typed == nil {
			return nil, false, nil
		}
		row, err := projectRecord(version, "", *typed, false, false, nil)
		return row, false, err
	case []Record:
		rows := make([]any, 0, len(typed))
		for _, item := range typed {
			row, err := projectRecord(version, "", item, false, false, nil)
			if err != nil {
				return nil, false, err
			}
			rows = append(rows, row)
		}
		return rows, true, nil
	case []any:
		items := make([]any, 0, len(typed))
		allRecords := len(typed) > 0
		for _, item := range typed {
			mapped, isRecordList, err := projectValue(version, item)
			if err != nil {
				return nil, false, err
			}
			if _, ok := item.(Record); !ok {
				if _, ok := item.(*Record); !ok {
					allRecords = false
				}
			}
			if isRecordList {
				allRecords = false
			}
			items = append(items, mapped)
		}
		return items, allRecords, nil
	default:
		return value, false, nil
	}
}
