/**
 * Matter API Implementation
 *
 * Implements the Matter API facade with lazy loading to optimize performance.
 *
 * Architecture:
 * - Separates Matter-specific logic from core HomebridgeAPI class
 * - Uses dynamic imports to prevent loading Matter.js at module parse time
 * - Loads Matter types on first access to `api.matter` properties
 * - Child bridges that don't use Matter have zero Matter.js overhead
 *
 * Performance Impact:
 * - Before: Every child bridge loaded ~800ms of Matter.js code (8-16s on RPi)
 * - After: Only child bridges using Matter load it on first access
 * - Improvement: 75-90% reduction in startup time for multi-bridge setups
 */
import { Logger } from '../logger.js';
import { clusterNames, clusters, deviceTypes, MatterTypes } from './index.js';
import { SwitchAPIImpl } from './SwitchAPI.js';
const log = Logger.withPrefix('Matter/API');
// ============================================================================
// External Device Type Configuration
// ============================================================================
/**
 * Device types that require dedicated external bridges.
 *
 * Some Matter devices (like RoboticVacuumCleaner) are complex and must be
 * published on their own dedicated bridge, not added to the main/child bridge.
 */
const EXTERNAL_DEVICE_TYPES = [
    deviceTypes.RoboticVacuumCleaner,
];
/**
 * Check if a device type requires external bridge publishing.
 * Compares device type IDs for exact match against the external device types list.
 */
function requiresExternalBridge(deviceType) {
    return EXTERNAL_DEVICE_TYPES.some(externalType => externalType.deviceType === deviceType.deviceType);
}
// ============================================================================
/**
 * Validation error for Matter accessories
 */
class MatterAccessoryValidationError extends Error {
    accessory;
    constructor(message, accessory) {
        super(message);
        this.accessory = accessory;
        this.name = 'MatterAccessoryValidationError';
    }
}
/**
 * Implementation of the Matter API
 *
 * This facade provides Matter protocol support through the Homebridge API.
 * It uses lazy loading to prevent loading the heavy Matter.js library until
 * actually needed, improving startup performance for child bridges that don't
 * use Matter.
 *
 * Features:
 * - Lazy-loads Matter types on first access
 * - Validates accessories before registration
 * - Handles both bridge accessories and external standalone devices
 * - Provides detailed error messages for debugging
 * - Delegates to HomebridgeAPI for event emission and server access
 */
export class MatterAPIImpl {
    api;
    switch;
    constructor(api) {
        this.api = api;
        this.switch = new SwitchAPIImpl(this);
    }
    /**
     * Validate a Matter accessory has required fields
     * @throws MatterAccessoryValidationError if validation fails
     */
    validateAccessory(accessory, context) {
        if (!accessory.UUID) {
            throw new MatterAccessoryValidationError(`${context}: Matter accessory missing required 'UUID' field`, accessory);
        }
        if (!accessory.displayName) {
            throw new MatterAccessoryValidationError(`${context}: Matter accessory '${accessory.UUID}' missing required 'displayName' field`, accessory);
        }
        if (!accessory.deviceType) {
            throw new MatterAccessoryValidationError(`${context}: Matter accessory '${accessory.displayName}' (${accessory.UUID}) missing required 'deviceType' field`, accessory);
        }
        if (!accessory.manufacturer) {
            throw new MatterAccessoryValidationError(`${context}: Matter accessory '${accessory.displayName}' (${accessory.UUID}) missing required 'manufacturer' field`, accessory);
        }
        if (!accessory.model) {
            throw new MatterAccessoryValidationError(`${context}: Matter accessory '${accessory.displayName}' (${accessory.UUID}) missing required 'model' field`, accessory);
        }
        if (!accessory.serialNumber) {
            throw new MatterAccessoryValidationError(`${context}: Matter accessory '${accessory.displayName}' (${accessory.UUID}) missing required 'serialNumber' field`, accessory);
        }
    }
    /**
     * Validate an array of accessories, logging errors for invalid ones
     * @returns Array of valid accessories only
     */
    validateAccessories(accessories, context) {
        const validAccessories = [];
        for (const accessory of accessories) {
            try {
                this.validateAccessory(accessory, context);
                validAccessories.push(accessory);
            }
            catch (error) {
                if (error instanceof MatterAccessoryValidationError) {
                    log.error(error.message);
                    log.error('This accessory will not be registered. Please fix the issue in your plugin.');
                }
                else {
                    log.error(`${context}: Unexpected error validating accessory:`, error);
                }
            }
        }
        return validAccessories;
    }
    /**
     * Validate cluster name is valid
     *
     * @param clusterName - Cluster name to validate
     * @param context - Context string for error messages
     */
    validateClusterName(clusterName, context) {
        // Check if cluster name is in the known cluster names
        const validClusterNames = Object.values(clusterNames);
        if (!validClusterNames.includes(clusterName)) {
            log.warn(`${context}: Unknown cluster name '${clusterName}'. This might cause issues. `
                + `Valid clusters: ${validClusterNames.join(', ')}`);
        }
    }
    /**
     * UUID generator (alias of api.hap.uuid for convenience)
     */
    get uuid() {
        return this.api.hap.uuid;
    }
    /**
     * Matter device types for creating accessories
     */
    get deviceTypes() {
        return deviceTypes;
    }
    /**
     * Matter clusters - Direct access to Matter.js cluster definitions
     */
    get clusters() {
        return clusters;
    }
    /**
     * Matter cluster names for type safety and autocomplete
     */
    get clusterNames() {
        return clusterNames;
    }
    /**
     * Matter types - Access to Matter.js cluster type definitions and enums
     */
    get types() {
        return MatterTypes;
    }
    /**
     * Register Matter platform accessories
     * Automatically handles external accessories (e.g., RoboticVacuumCleaner) that need dedicated bridges
     * Validates accessories before registration
     * Returns a promise that resolves when all accessories are fully registered
     */
    async registerPlatformAccessories(pluginIdentifier, platformName, accessories) {
        if (accessories.length === 0) {
            log.warn(`${pluginIdentifier}: Attempted to register 0 Matter accessories`);
            return;
        }
        // Validate all accessories before registration
        const validAccessories = this.validateAccessories(accessories, `registerPlatformAccessories (${pluginIdentifier}/${platformName})`);
        if (validAccessories.length === 0) {
            log.error(`${pluginIdentifier}: All ${accessories.length} Matter accessories failed validation`);
            return;
        }
        if (validAccessories.length < accessories.length) {
            log.warn(`${pluginIdentifier}: ${accessories.length - validAccessories.length} of ${accessories.length} Matter accessories failed validation`);
        }
        // Split accessories into normal (bridge) and external (standalone) based on device type
        const normalAccessories = [];
        const externalAccessories = [];
        for (const accessory of validAccessories) {
            if (requiresExternalBridge(accessory.deviceType)) {
                externalAccessories.push(accessory);
            }
            else {
                normalAccessories.push(accessory);
            }
        }
        // Handle normal accessories (added to bridge)
        if (normalAccessories.length > 0) {
            // Add plugin/platform association
            normalAccessories.forEach((accessory) => {
                const internal = accessory;
                internal._associatedPlugin = pluginIdentifier;
                internal._associatedPlatform = platformName;
            });
            log.debug(`${pluginIdentifier}: Registering ${normalAccessories.length} Matter accessor${normalAccessories.length === 1 ? 'y' : 'ies'} for platform '${platformName}'`);
            this.api.emit("registerMatterPlatformAccessories" /* InternalAPIEvent.REGISTER_MATTER_PLATFORM_ACCESSORIES */, pluginIdentifier, platformName, normalAccessories);
        }
        // Handle external accessories (standalone bridges)
        if (externalAccessories.length > 0) {
            // Add plugin association (no platform for external)
            externalAccessories.forEach((accessory) => {
                const internal = accessory;
                internal._associatedPlugin = pluginIdentifier;
            });
            log.debug(`${pluginIdentifier}: Publishing ${externalAccessories.length} external Matter accessor${externalAccessories.length === 1 ? 'y' : 'ies'} (${externalAccessories.map(a => a.displayName).join(', ')})`);
            // Create a promise to track when external publishing completes
            const registrationId = `${pluginIdentifier}-${Date.now()}-${Math.random()}`;
            const registrationPromise = new Promise((resolve) => {
                // Store the resolve function so it can be called when publishing completes
                // Access internal properties through type assertion
                const internalApi = this.api;
                if (!internalApi._pendingExternalRegistrations) {
                    internalApi._pendingExternalRegistrations = new Map();
                }
                internalApi._pendingExternalRegistrations.set(registrationId, resolve);
            });
            // Emit event with registration ID
            this.api.emit("publishExternalMatterAccessories" /* InternalAPIEvent.PUBLISH_EXTERNAL_MATTER_ACCESSORIES */, externalAccessories, registrationId);
            // Wait for external publishing to complete
            await registrationPromise;
        }
    }
    /**
     * Update Matter platform accessories in the cache
     * Similar to api.updatePlatformAccessories() for HAP accessories
     */
    async updatePlatformAccessories(accessories) {
        if (accessories.length === 0) {
            log.warn('Attempted to update 0 Matter platform accessories');
            return;
        }
        log.debug(`Updating ${accessories.length} Matter platform accessor${accessories.length === 1 ? 'y' : 'ies'} in cache`);
        // Emit event for Server/ChildBridgeFork to handle
        this.api.emit("updateMatterPlatformAccessories" /* InternalAPIEvent.UPDATE_MATTER_PLATFORM_ACCESSORIES */, accessories);
    }
    /**
     * Unregister Matter platform accessories
     * Automatically handles external accessories (e.g., RoboticVacuumCleaner) that have dedicated bridges
     */
    async unregisterPlatformAccessories(pluginIdentifier, platformName, accessories) {
        if (accessories.length === 0) {
            log.warn(`${pluginIdentifier}: Attempted to unregister 0 Matter accessories`);
            return;
        }
        // Split accessories into normal (bridge) and external (standalone) based on device type
        const normalAccessories = [];
        const externalAccessories = [];
        for (const accessory of accessories) {
            if (requiresExternalBridge(accessory.deviceType)) {
                externalAccessories.push(accessory);
            }
            else {
                normalAccessories.push(accessory);
            }
        }
        // Handle normal accessories (on bridge)
        if (normalAccessories.length > 0) {
            log.debug(`${pluginIdentifier}: Unregistering ${normalAccessories.length} Matter accessor${normalAccessories.length === 1 ? 'y' : 'ies'} from platform '${platformName}'`);
            this.api.emit("unregisterMatterPlatformAccessories" /* InternalAPIEvent.UNREGISTER_MATTER_PLATFORM_ACCESSORIES */, pluginIdentifier, platformName, normalAccessories);
        }
        // Handle external accessories (standalone bridges)
        if (externalAccessories.length > 0) {
            log.debug(`${pluginIdentifier}: Unregistering ${externalAccessories.length} external Matter accessor${externalAccessories.length === 1 ? 'y' : 'ies'} (${externalAccessories.map(a => a.displayName).join(', ')})`);
            this.api.emit("unregisterExternalMatterAccessories" /* InternalAPIEvent.UNREGISTER_EXTERNAL_MATTER_ACCESSORIES */, externalAccessories);
        }
    }
    /**
     * Update a Matter accessory's cluster state
     * Validates inputs before updating
     */
    async updateAccessoryState(uuid, cluster, attributes, partId) {
        // Validate inputs
        if (!uuid) {
            log.error('updateAccessoryState: uuid parameter is required');
            return;
        }
        if (!cluster) {
            log.error(`updateAccessoryState: cluster parameter is required for accessory ${uuid}`);
            return;
        }
        if (!attributes || Object.keys(attributes).length === 0) {
            log.warn(`updateAccessoryState: No attributes provided for accessory ${uuid}, cluster ${cluster}`);
            return;
        }
        // Validate cluster name (warning only, don't block)
        this.validateClusterName(cluster, `updateAccessoryState (${uuid})`);
        log.debug(`Updating Matter accessory state: uuid=${uuid}, cluster=${cluster}, attributes=${Object.keys(attributes).join(', ')}${partId ? `, partId=${partId}` : ''}`);
        // Emit the event (listeners will be called synchronously by EventEmitter)
        this.api.emit("updateMatterAccessoryState" /* InternalAPIEvent.UPDATE_MATTER_ACCESSORY_STATE */, uuid, cluster, attributes, partId);
    }
    /**
     * Get a Matter accessory's current cluster state
     * Checks both external servers and main bridge server
     * Validates inputs before retrieving state
     */
    async getAccessoryState(uuid, cluster, partId) {
        // Validate inputs
        if (!uuid) {
            log.error('getAccessoryState: uuid parameter is required');
            return undefined;
        }
        if (!cluster) {
            log.error(`getAccessoryState: cluster parameter is required for accessory ${uuid}`);
            return undefined;
        }
        // Validate cluster name (warning only, don't block)
        this.validateClusterName(cluster, `getAccessoryState (${uuid})`);
        log.debug(`Getting Matter accessory state: uuid=${uuid}, cluster=${cluster}${partId ? `, partId=${partId}` : ''}`);
        // Check external servers first (for accessories like robot vacuums)
        const internalApi = this.api;
        const matterManager = internalApi._matterManager;
        if (matterManager) {
            const externalServer = matterManager.getExternalServer(uuid);
            if (externalServer) {
                return externalServer.getAccessoryState(uuid, cluster, partId);
            }
        }
        // Otherwise, try the main bridge server
        const matterServer = internalApi._matterServer;
        if (!matterServer) {
            log.debug(`getAccessoryState: Matter server not available for accessory ${uuid}`);
            return undefined;
        }
        return matterServer.getAccessoryState(uuid, cluster, partId);
    }
}
//# sourceMappingURL=MatterAPIImpl.js.map