package qichacha

import "encoding/json"

// shareholderRecord is the upstream shareholder shape.
type shareholderRecord struct {
	KeyNo                 *string           `json:"KeyNo"`
	StockName             *string           `json:"StockName"`
	StockType             *string           `json:"StockType"`
	StockPercent          *string           `json:"StockPercent"`
	ShouldCapi            *string           `json:"ShouldCapi"`
	SubscribedCapital     *string           `json:"SubscribedCapital"`
	SubscribedCapitalUnit *string           `json:"SubscribedCapitalUnit"`
	SubscribedCapitalCCY  *string           `json:"SubscribedCapitalCCY"`
	ShoudDate             *string           `json:"ShoudDate"`
	StakeDate             *string           `json:"StakeDate"`
	CreditCode            *string           `json:"CreditCode"`
	Area                  *string           `json:"Area"`
	SubscribedList        []json.RawMessage `json:"SubscribedList"`
}

// normalizeShareholder maps one upstream shareholder record onto the catalog
// shape: nullable strings and the nested subscribed-contribution list.
func normalizeShareholder(record json.RawMessage) map[string]any {
	var source shareholderRecord
	if err := json.Unmarshal(record, &source); err != nil {
		source = shareholderRecord{}
	}
	subscriptions := make([]map[string]any, 0, len(source.SubscribedList))
	for _, item := range source.SubscribedList {
		var entry map[string]any
		if err := json.Unmarshal(item, &entry); err != nil || entry == nil {
			continue
		}
		subscriptions = append(subscriptions, map[string]any{
			"contributionType": anyStringOrNull(entry["ContributionType"]),
			"capital":          anyStringOrNull(entry["Capital"]),
			"date":             anyStringOrNull(entry["Date"]),
		})
	}
	return map[string]any{
		"keyNo":                     stringOrNull(source.KeyNo),
		"name":                      stringOrNull(source.StockName),
		"type":                      stringOrNull(source.StockType),
		"stockPercent":              stringOrNull(source.StockPercent),
		"subscribedAmount":          stringOrNull(source.ShouldCapi),
		"subscribedCapital":         stringOrNull(source.SubscribedCapital),
		"subscribedCapitalUnit":     stringOrNull(source.SubscribedCapitalUnit),
		"subscribedCapitalCurrency": stringOrNull(source.SubscribedCapitalCCY),
		"subscribedDate":            stringOrNull(source.ShoudDate),
		"stakeDate":                 stringOrNull(source.StakeDate),
		"creditCode":                stringOrNull(source.CreditCode),
		"area":                      stringOrNull(source.Area),
		"subscriptions":             subscriptions,
	}
}

// historicalInvestmentRecord is the upstream historical investment shape.
type historicalInvestmentRecord struct {
	KeyNo       *string `json:"KeyNo"`
	CompanyName *string `json:"CompanyName"`
	OperInfo    *struct {
		KeyNo *string `json:"KeyNo"`
		Name  *string `json:"Name"`
	} `json:"OperInfo"`
	RegistCapi            *string `json:"RegistCapi"`
	RegisteredCapital     *string `json:"RegisteredCapital"`
	RegisteredCapitalUnit *string `json:"RegisteredCapitalUnit"`
	RegisteredCapitalCCY  *string `json:"RegisteredCapitalCCY"`
	FundedRatio           *string `json:"FundedRatio"`
	Status                *string `json:"Status"`
	StartDate             *string `json:"StartDate"`
	ShouldCapi            *string `json:"ShouldCapi"`
	SubscribedCapital     *string `json:"SubscribedCapital"`
	SubscribedCapitalUnit *string `json:"SubscribedCapitalUnit"`
	SubscribedCapitalCCY  *string `json:"SubscribedCapitalCCY"`
	ExitDate              *string `json:"ExitDate"`
}

// normalizeHistoricalInvestment maps one upstream historical investment
// record onto the catalog shape, including the nested legal representative.
func normalizeHistoricalInvestment(record json.RawMessage) map[string]any {
	var source historicalInvestmentRecord
	if err := json.Unmarshal(record, &source); err != nil {
		source = historicalInvestmentRecord{}
	}
	var legalRepresentative any
	if source.OperInfo != nil {
		legalRepresentative = map[string]any{
			"keyNo": stringOrNull(source.OperInfo.KeyNo),
			"name":  stringOrNull(source.OperInfo.Name),
		}
	}
	return map[string]any{
		"keyNo":                     stringOrNull(source.KeyNo),
		"companyName":               stringOrNull(source.CompanyName),
		"legalRepresentative":       legalRepresentative,
		"registeredCapital":         stringOrNull(source.RegistCapi),
		"registeredCapitalValue":    stringOrNull(source.RegisteredCapital),
		"registeredCapitalUnit":     stringOrNull(source.RegisteredCapitalUnit),
		"registeredCapitalCurrency": stringOrNull(source.RegisteredCapitalCCY),
		"fundedRatio":               stringOrNull(source.FundedRatio),
		"status":                    stringOrNull(source.Status),
		"startDate":                 stringOrNull(source.StartDate),
		"subscribedAmount":          stringOrNull(source.ShouldCapi),
		"subscribedCapital":         stringOrNull(source.SubscribedCapital),
		"subscribedCapitalUnit":     stringOrNull(source.SubscribedCapitalUnit),
		"subscribedCapitalCurrency": stringOrNull(source.SubscribedCapitalCCY),
		"exitDate":                  stringOrNull(source.ExitDate),
	}
}

// stringOrNull forwards a decoded nullable string: absent and null map to
// null, a present value (empty included) forwards verbatim.
func stringOrNull(value *string) any {
	if value == nil {
		return nil
	}
	return *value
}

// anyStringOrNull is the loose-object variant used for nested list entries.
func anyStringOrNull(value any) any {
	if text, ok := value.(string); ok {
		return text
	}
	return nil
}

// isObjectRecord reports whether one raw record is a JSON object, so
// non-object entries can be dropped exactly like the upstream filter.
func isObjectRecord(record json.RawMessage) bool {
	var probe map[string]json.RawMessage
	return json.Unmarshal(record, &probe) == nil && probe != nil
}
