import type { TContext } from "@digital-alchemy/core"; import type { ALL_DOMAINS, PICK_ENTITY, PICK_FROM_PLATFORM, ResponseOptional, ServiceListField, ServiceListFieldDescription, ServiceListSelector, ServiceListServiceTarget, TAreaId, TDeviceId, TLabelId, TPlatformId } from "@digital-alchemy/hass"; import type { ServiceField } from "./field.mts"; import type { inferSymbol } from "./utils.mts"; import type { ExtractDomainUnion } from "./utils.mts"; export type UnknownObject = Record; /** * Processes nested fields within an object selector. * Object selectors have a different structure where fields contain selectors directly. */ type ProcessObjectFields]?: never; }; }[keyof ServiceListSelector]; required?: boolean; label?: string; }>> = { [K in keyof FIELDS]: FIELDS[K] extends { selector: infer FIELD_SELECTOR; } ? FIELD_SELECTOR extends ServiceListFieldDescription ? ExtractBrandedType : unknown : unknown; }; /** * Extracts the branded type from a field description using the inferSymbol. * The branded type contains 100% of the field's type information. */ type ExtractBrandedType = FIELD extends Record ? BRANDED : unknown; /** * Processes a single field value by extracting the branded type. * Handles object selectors with nested fields recursively. */ type ProcessFieldValue = FIELD["selector"] extends { object: { fields?: infer FIELDS; }; } ? FIELDS extends Record]?: never; }; }[keyof ServiceListSelector]; required?: boolean; label?: string; }> ? ProcessObjectFields : ExtractBrandedType : ExtractBrandedType; /** * Transforms a FieldList schema into a typed data object for service callbacks. * Each field in the schema is processed to determine its value type based on its selector. */ export type BuildServiceData = { [K in keyof FIELDS]: ProcessFieldValue; }; /** * Extracts the entity type from a single EntityFilterSelector. * If integration and domain are provided, uses PICK_FROM_PLATFORM. * If only domain is provided, uses PICK_ENTITY with domain. * Otherwise, defaults to PICK_ENTITY. */ type ExtractEntityTypeFromFilter = FILTER extends { integration: infer INTEGRATION; domain: infer DOMAIN; } ? INTEGRATION extends TPlatformId ? DOMAIN extends ALL_DOMAINS | ALL_DOMAINS[] ? PICK_FROM_PLATFORM> : PICK_ENTITY : PICK_ENTITY : FILTER extends { domain: infer DOMAIN; } ? DOMAIN extends ALL_DOMAINS | ALL_DOMAINS[] ? PICK_ENTITY> : PICK_ENTITY : PICK_ENTITY; /** * Extracts the entity type from target.entity based on integration and domain. * Handles both single EntityFilterSelector and array cases. */ type ExtractEntityTypeFromTarget = TARGET extends { entity: infer ENTITY_FILTER; } ? ENTITY_FILTER extends readonly (infer FILTER)[] ? ExtractEntityTypeFromFilter : ExtractEntityTypeFromFilter : PICK_ENTITY; /** * Service data type that includes entity_id when target is defined. * Merges field data with entity_id array, narrowed based on target.entity. */ export type BuildServiceDataWithTarget = BuildServiceData & { entity_id?: ExtractEntityTypeFromTarget | ExtractEntityTypeFromTarget[]; device_id?: TDeviceId | TDeviceId[]; label_id?: TLabelId | TLabelId[]; area_id?: TAreaId | TAreaId[]; }; export type SynapseServiceReturn = { create: SynapseServiceCreate; fields: typeof ServiceField; }; type HasTargetOverrideKey = Extract extends never ? false : true; /** * Extracts the fields type from options. */ type ExtractFieldsFromOptionsType = OPTIONS extends { fields: infer FIELDS; } ? FIELDS extends FieldList ? FIELDS : FieldList : FieldList; export type ValidateServiceOptions> = OPTIONS extends { target: ServiceListServiceTarget; } ? HasTargetOverrideKey> extends true ? never : OPTIONS : OPTIONS; /** * Extracts the actual fields type from validated options. * When target is present, fields are FieldsWithoutTargetOverrides. */ type ExtractFieldsFromOptions>> = OPTIONS extends { fields: infer ACTUAL_FIELDS; } ? ACTUAL_FIELDS extends FieldList ? ACTUAL_FIELDS : FIELDS : FIELDS; export type SynapseServiceCreate = = SynapseServiceCreateOptions>(options: ValidateServiceOptions, callback: SynapseServiceCreateCallback>, TARGET> : BuildServiceData>> : BuildServiceData>>>) => void; type TargetOverrideKeys = "entity_id" | "label_id" | "device_id" | "area_id"; export type FieldList = Record; type BaseSynapseServiceCreateOptions = { context: TContext; name: string; description?: string; domain?: string; unique_id?: string; response?: ResponseOptional; }; type FieldsWithoutTargetOverrides = { [K in keyof FIELDS as K extends TargetOverrideKeys ? never : K]: FIELDS[K]; }; export type SynapseServiceCreateOptions = (BaseSynapseServiceCreateOptions & { fields?: FIELDS; target?: never; }) | (BaseSynapseServiceCreateOptions & { fields?: FieldsWithoutTargetOverrides; target: ServiceListServiceTarget; }); export type BuildSynapseServiceRequestData = { [NAME in keyof FIELDS]: unknown; }; export type SynapseServiceCreateCallback = (data: DATA) => void | Promise; export type SynapseServiceListField = Omit; export type ServiceCallData = { id: `service_call_${number}`; service_data: DATA; service_name: string; service_unique_id: string; type: "synapse/service_call"; }; export {};