package mfilesclient

import (
	"fmt"
	"time"

	"google.golang.org/protobuf/types/known/timestamppb"

	"github.com/M-Files/mfiles-grpc-api/go/mfiles"
)

// Small constructors for messages every program needs. Go's generated code wraps each oneof
// member in a type of its own; these keep that out of the way.

// ObjID names an object: its object type (0 = document) and its ID.
func ObjID(objectType, id int32) *mfiles.ObjID {
	return &mfiles.ObjID{Type: objectType, ItemId: &mfiles.ItemID{InternalId: id}}
}

// ObjVer names one version of an object.
func ObjVer(objectType, id, version int32) *mfiles.ObjVer {
	return &mfiles.ObjVer{
		ObjId: ObjID(objectType, id),
		Version: &mfiles.ObjVerVersion{
			Type:            mfiles.ObjVerVersionType_OBJ_VER_VERSION_TYPE_SPECIFIC,
			InternalVersion: version,
		},
	}
}

// LatestObjVer names the latest version of an object. Reads accept it; writes to a checked-in
// object need the specific version (ObjVer).
func LatestObjVer(objectType, id int32) *mfiles.ObjVer {
	return &mfiles.ObjVer{
		ObjId: ObjID(objectType, id),
		Version: &mfiles.ObjVerVersion{
			Type:            mfiles.ObjVerVersionType_OBJ_VER_VERSION_TYPE_LATEST,
			InternalVersion: -1,
		},
	}
}

// [START typed-values]

// Text is a single-line text value.
func Text(value string) *mfiles.TypedValue {
	return &mfiles.TypedValue{
		Type: mfiles.Datatype_DATATYPE_TEXT,
		Data: &mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_Text{Text: value}},
	}
}

// Integer is an integer value.
func Integer(value int32) *mfiles.TypedValue {
	return &mfiles.TypedValue{
		Type: mfiles.Datatype_DATATYPE_INTEGER,
		Data: &mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_Integer{Integer: value}},
	}
}

// Boolean is a yes/no value.
func Boolean(value bool) *mfiles.TypedValue {
	return &mfiles.TypedValue{
		Type: mfiles.Datatype_DATATYPE_BOOLEAN,
		Data: &mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_Boolean{Boolean: value}},
	}
}

// Timestamp is a date and time value, such as "Last modified" (property 21).
func Timestamp(value time.Time) *mfiles.TypedValue {
	return &mfiles.TypedValue{
		Type: mfiles.Datatype_DATATYPE_TIMESTAMP,
		Data: &mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_Timestamp{
			Timestamp: timestamppb.New(value),
		}},
	}
}

// Lookup is a single-select lookup value: the item with the given ID in a value list (or an
// object of the object type with that ID).
func Lookup(valueList, item int32) *mfiles.TypedValue {
	return &mfiles.TypedValue{
		Type: mfiles.Datatype_DATATYPE_LOOKUP,
		Data: &mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_Lookup{
			Lookup: lookupItem(valueList, item),
		}},
	}
}

// MultiSelectLookup is a multi-select lookup value.
func MultiSelectLookup(valueList int32, items ...int32) *mfiles.TypedValue {
	lookups := make([]*mfiles.Lookup, 0, len(items))
	for _, item := range items {
		lookups = append(lookups, lookupItem(valueList, item))
	}
	return &mfiles.TypedValue{
		Type: mfiles.Datatype_DATATYPE_MULTI_SELECT_LOOKUP,
		Data: &mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_MultiSelectLookup{
			MultiSelectLookup: &mfiles.MultiSelectLookup{Values: lookups},
		}},
	}
}

func lookupItem(valueList, item int32) *mfiles.Lookup {
	return &mfiles.Lookup{
		ValueListItemInfo: &mfiles.ItemInfo{ObjId: ObjID(valueList, item)},
		Version: &mfiles.ObjVerVersion{
			Type:            mfiles.ObjVerVersionType_OBJ_VER_VERSION_TYPE_LATEST,
			InternalVersion: -1,
		},
	}
}

// [END typed-values]

// Date is a date value: midnight UTC of the given day.
func Date(year int, month time.Month, day int) *mfiles.TypedValue {
	return &mfiles.TypedValue{
		Type: mfiles.Datatype_DATATYPE_DATE,
		Data: &mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_Date{
			Date: timestamppb.New(time.Date(year, month, day, 0, 0, 0, 0, time.UTC)),
		}},
	}
}

// Empty is an empty value of the given data type; setting it clears a property.
func Empty(datatype mfiles.Datatype) *mfiles.TypedValue {
	return &mfiles.TypedValue{Type: datatype, IsNullValue: true}
}

// [START named-acl]

// NamedACLFullControlForAllInternalUsers is the ID of the built-in named ACL "Full control for
// all internal users".
const NamedACLFullControlForAllInternalUsers = -1

// NamedACL is an access control list that takes its permissions from a named ACL. Pass one when
// creating an object: an object created without an ACL has no permissions, so not even its
// creator can see it.
func NamedACL(namedACL int32) *mfiles.AccessControlList {
	return &mfiles.AccessControlList{
		CustomComponent: &mfiles.AccessControlListComponent{
			HasNamedAcl: true,
			NamedAclId:  namedACL,
		},
	}
}

// [END named-acl]

// PropertyValue pairs a property definition ID with a value.
func PropertyValue(propertyDef int32, value *mfiles.TypedValue) *mfiles.PropertyValue {
	return &mfiles.PropertyValue{PropertyDef: propertyDef, Value: value}
}

// VersionString decodes a version number from QueryVersion: four 16-bit parts, major first,
// e.g. 0x001A000B41050000 is "26.11.16645.0".
func VersionString(version uint64) string {
	return fmt.Sprintf("%d.%d.%d.%d",
		version>>48&0xFFFF, version>>32&0xFFFF, version>>16&0xFFFF, version&0xFFFF)
}
