/**
 * Helper functions for MatterServer.registerAccessory()
 * Extracted from the monolithic 521-line function for better maintainability
 */
import { Logger } from '../logger.js';
import { HomebridgeRvcCleanModeServer, HomebridgeServiceAreaServer, HomebridgeWindowCoveringServer, } from './behaviors/index.js';
// Direct matter.js .with() API used instead of typeHelpers wrappers
import { clusters, devices, MatterDeviceError } from './types.js';
const log = Logger.withPrefix('Matter/Server');
/**
 * Cluster IDs from Matter specification
 * Using Matter.js Cluster references instead of magic numbers
 */
export const CLUSTER_IDS = {
    AIR_QUALITY: clusters.AirQuality.Cluster.id,
    CARBON_MONOXIDE_CONCENTRATION: clusters.CarbonMonoxideConcentrationMeasurement.Cluster.id,
    COLOR_CONTROL: clusters.ColorControl.Cluster.id,
    DOOR_LOCK: clusters.DoorLock.Cluster.id,
    LEVEL_CONTROL: clusters.LevelControl.Cluster.id,
    NITROGEN_DIOXIDE_CONCENTRATION: clusters.NitrogenDioxideConcentrationMeasurement.Cluster.id,
    ON_OFF: clusters.OnOff.Cluster.id,
    OZONE_CONCENTRATION: clusters.OzoneConcentrationMeasurement.Cluster.id,
    PM10_CONCENTRATION: clusters.Pm10ConcentrationMeasurement.Cluster.id,
    PM25_CONCENTRATION: clusters.Pm25ConcentrationMeasurement.Cluster.id,
    THERMOSTAT: clusters.Thermostat.Cluster.id,
    WINDOW_COVERING: clusters.WindowCovering.Cluster.id,
};
/**
 * Validates required fields on a Matter accessory
 * @throws MatterDeviceError if validation fails
 */
export function validateAccessoryRequiredFields(accessory) {
    if (!accessory.deviceType) {
        throw new MatterDeviceError(`Matter accessory "${accessory.displayName || 'unknown'}" is missing required field 'deviceType'. `
            + 'Example: deviceType: api.matter!.deviceTypes.OnOffLight\n'
            + 'Available device types: OnOffLight, DimmableLight, GenericSwitch, TemperatureSensor, etc.\n'
            + 'See the Matter types documentation for the full list.');
    }
    if (!accessory.UUID) {
        throw new MatterDeviceError('Matter accessory is missing required field \'UUID\'.\n'
            + 'Generate a unique UUID for your accessory:\n'
            + '  const UUID = api.hap.uuid.generate(\'my-unique-id\')');
    }
    if (!accessory.displayName) {
        throw new MatterDeviceError(`Matter accessory (${accessory.UUID}) is missing required field 'displayName'.\n`
            + 'Example: displayName: \'Living Room Light\'');
    }
    if (!accessory.serialNumber) {
        throw new MatterDeviceError(`Matter accessory "${accessory.displayName}" is missing required field 'serialNumber'.\n`
            + 'Example: serialNumber: \'ABC123\' or serialNumber: accessory.UUID');
    }
    if (!accessory.manufacturer) {
        throw new MatterDeviceError(`Matter accessory "${accessory.displayName}" is missing required field 'manufacturer'.\n`
            + 'Example: manufacturer: \'Homebridge\' or manufacturer: \'My Plugin Name\'');
    }
    if (!accessory.model) {
        throw new MatterDeviceError(`Matter accessory "${accessory.displayName}" is missing required field 'model'.\n`
            + 'Example: model: \'v1.0\' or model: \'Smart Light\'');
    }
    // Clusters are required unless parts are provided (for composed devices)
    if (!accessory.parts || accessory.parts.length === 0) {
        if (!accessory.clusters || typeof accessory.clusters !== 'object') {
            throw new MatterDeviceError(`Matter accessory "${accessory.displayName}" is missing or has invalid 'clusters' field.\n`
                + 'Clusters define the functionality of your device. Example:\n'
                + '  clusters: {\n'
                + '    onOff: { onOff: false },\n'
                + '    levelControl: { currentLevel: 0, minLevel: 0, maxLevel: 254 }\n'
                + '  }\n'
                + 'Alternatively, use "parts" array for composed devices with multiple endpoints.');
        }
    }
    // Validate parts if provided
    if (accessory.parts && accessory.parts.length > 0) {
        for (const part of accessory.parts) {
            if (!part.id) {
                throw new MatterDeviceError(`Matter accessory "${accessory.displayName}" has a part missing required field 'id'`);
            }
            if (!part.deviceType) {
                throw new MatterDeviceError(`Matter accessory "${accessory.displayName}" part "${part.id}" is missing required field 'deviceType'`);
            }
            if (!part.clusters || typeof part.clusters !== 'object') {
                throw new MatterDeviceError(`Matter accessory "${accessory.displayName}" part "${part.id}" is missing or has invalid 'clusters' field`);
            }
        }
    }
}
/**
 * Convert device type behaviors to array
 * Handles array, Set, object, or iterable formats
 */
function convertBehaviorsToArray(behaviors) {
    if (Array.isArray(behaviors)) {
        return behaviors;
    }
    if (typeof behaviors === 'object' && behaviors !== null) {
        const values = Object.values(behaviors);
        if (values.length > 0) {
            return values;
        }
    }
    try {
        return [...behaviors];
    }
    catch {
        return [];
    }
}
/**
 * Find a specific behavior by cluster ID or name
 */
function findBehaviorByCluster(behaviors, clusterIdOrName) {
    return behaviors.find((behavior) => {
        if (typeof clusterIdOrName === 'number') {
            return behavior.cluster?.id === clusterIdOrName;
        }
        return behavior.id === clusterIdOrName;
    });
}
/**
 * Generic feature detection from device type behaviors
 * Extracts supported features from a device type's cluster definition
 *
 * @param deviceType - The Matter device type
 * @param clusterIdOrName - Cluster ID (number) or name (string)
 * @param featureExtractor - Function to extract feature names from supportedFeatures
 * @returns Array of detected features or null if cluster not found
 */
export function detectBehaviorFeatures(deviceType, clusterIdOrName, featureExtractor) {
    const deviceTypeDef = deviceType;
    const existingBehaviors = deviceTypeDef.behaviors;
    if (!existingBehaviors) {
        return null;
    }
    const behaviorsArray = convertBehaviorsToArray(existingBehaviors);
    const behavior = findBehaviorByCluster(behaviorsArray, clusterIdOrName);
    if (!behavior?.cluster?.supportedFeatures) {
        return null;
    }
    return featureExtractor(behavior.cluster.supportedFeatures);
}
/**
 * Extract ColorControl features from supportedFeatures
 */
export function extractColorControlFeatures(supportedFeatures) {
    const features = [];
    if (supportedFeatures.hueSaturation) {
        features.push('HueSaturation');
    }
    if (supportedFeatures.xy) {
        features.push('Xy');
    }
    if (supportedFeatures.colorTemperature) {
        features.push('ColorTemperature');
    }
    return features;
}
/**
 * Extract Thermostat features from supportedFeatures
 */
export function extractThermostatFeatures(supportedFeatures) {
    const features = [];
    if (supportedFeatures.heating) {
        features.push('Heating');
    }
    if (supportedFeatures.cooling) {
        features.push('Cooling');
    }
    if (supportedFeatures.occupancy) {
        features.push('Occupancy');
    }
    if (supportedFeatures.autoMode) {
        features.push('AutoMode');
    }
    return features;
}
/**
 * Extract LevelControl features from supportedFeatures.
 *
 * Used to read features off a device type's declared LevelControl requirement
 * (e.g. DimmableLightDevice's `LevelControlServer.with("Lighting","OnOff")`).
 * When the device type doesn't declare LevelControl at all (e.g. PumpDevice,
 * which has LevelControl only in its `optional` requirements and not in
 * `SupportedBehaviors`), the caller should apply an empty feature set via
 * `.with()` so the Lighting feature inherited from matter.js's internal
 * `LevelControlBase = LevelControlBehavior.with(OnOff, Lighting)` is stripped
 * — otherwise the Pump endpoint inherits the `[LT]` branch of the spec
 * (minLevel constraint 1-254, initializeLighting warnings) that only applies
 * to lighting devices.
 */
export function extractLevelControlFeatures(supportedFeatures) {
    const features = [];
    if (supportedFeatures.onOff) {
        features.push('OnOff');
    }
    if (supportedFeatures.lighting) {
        features.push('Lighting');
    }
    if (supportedFeatures.frequency) {
        features.push('Frequency');
    }
    return features;
}
/**
 * Determine ColorControl features based on handlers
 * Only includes features that have corresponding handler methods
 */
export function determineColorControlFeaturesFromHandlers(handlers) {
    const features = [];
    if ('moveToHueAndSaturationLogic' in handlers) {
        features.push('HueSaturation');
    }
    if ('moveToColorLogic' in handlers) {
        features.push('Xy');
    }
    if ('moveToColorTemperatureLogic' in handlers) {
        features.push('ColorTemperature');
    }
    return features;
}
/**
 * Detect WindowCovering features from accessory attributes
 * Auto-detects Lift and Tilt capabilities based on cluster attributes
 *
 * @param accessory - Matter accessory to inspect
 * @returns Array of detected feature names
 */
export function detectWindowCoveringFeatures(accessory) {
    const features = [];
    const wcCluster = accessory.clusters?.windowCovering;
    if (!wcCluster) {
        return features;
    }
    // Detect lift capability
    const hasLiftAttrs = 'targetPositionLiftPercent100ths' in wcCluster
        || 'currentPositionLiftPercent100ths' in wcCluster;
    const configStatus = wcCluster.configStatus;
    const hasConfigLift = configStatus?.liftPositionAware === true;
    // Detect tilt capability
    const hasTiltAttrs = 'targetPositionTiltPercent100ths' in wcCluster
        || 'currentPositionTiltPercent100ths' in wcCluster;
    const hasConfigTilt = configStatus?.tiltPositionAware === true;
    log.debug(`[${accessory.displayName}] WindowCovering detection: `
        + `hasLiftAttrs=${hasLiftAttrs}, hasConfigLift=${hasConfigLift}, `
        + `hasTiltAttrs=${hasTiltAttrs}, hasConfigTilt=${hasConfigTilt}`);
    if (hasLiftAttrs) {
        features.push('Lift');
        if (hasConfigLift) {
            features.push('PositionAwareLift');
        }
    }
    if (hasTiltAttrs) {
        features.push('Tilt');
        if (hasConfigTilt) {
            features.push('PositionAwareTilt');
        }
    }
    return features;
}
/**
 * Detect ServiceArea features from cluster attributes
 */
export function detectServiceAreaFeatures(serviceAreaCluster) {
    const features = [];
    if (!serviceAreaCluster) {
        return features;
    }
    if ('supportedMaps' in serviceAreaCluster) {
        features.push('Maps');
    }
    if ('progress' in serviceAreaCluster) {
        features.push('ProgressReporting');
    }
    return features;
}
/**
 * Apply WindowCovering features to device type
 */
export function applyWindowCoveringFeatures(deviceType, accessory, features) {
    if (features.length === 0) {
        log.warn(`⚠️  No WindowCovering features detected for ${accessory.displayName}!`);
        return deviceType;
    }
    log.info(`Auto-detected WindowCovering features for ${accessory.displayName}: ${features.join(', ')}`);
    // Add WindowCoveringServer with features to the device type
    const windowCoveringWithFeatures = HomebridgeWindowCoveringServer.with(...features);
    const modifiedDeviceType = deviceType.with(windowCoveringWithFeatures);
    const hasTiltFeatures = features.includes('Tilt');
    if (hasTiltFeatures && accessory.clusters) {
        const wcCluster = accessory.clusters.windowCovering;
        wcCluster.type = 8; // TiltBlindLift
        log.debug('Set WindowCovering type to 8 (TiltBlindLift) for tilt-capable device');
    }
    if (!accessory.context) {
        accessory.context = {};
    }
    accessory.context._skipWindowCoveringBehavior = true;
    return modifiedDeviceType;
}
/**
 * Build custom behaviors for RoboticVacuumCleaner devices
 */
export function buildRvcCustomBehaviors(accessory, serviceAreaFeatures) {
    const customBehaviors = [];
    const { RvcCleanModeServer, ServiceAreaServer } = devices.RoboticVacuumCleanerRequirements;
    if (accessory.clusters?.rvcCleanMode) {
        if (accessory.handlers?.rvcCleanMode) {
            customBehaviors.push(HomebridgeRvcCleanModeServer);
            log.info('Adding custom RvcCleanMode behavior with handlers');
        }
        else {
            customBehaviors.push(RvcCleanModeServer);
            log.info('Adding base RvcCleanMode server');
        }
    }
    if (accessory.clusters?.serviceArea) {
        let behaviorClass = accessory.handlers?.serviceArea
            ? HomebridgeServiceAreaServer
            : ServiceAreaServer;
        if (serviceAreaFeatures && serviceAreaFeatures.length > 0) {
            behaviorClass = behaviorClass.with(...serviceAreaFeatures);
            log.info(`ServiceArea ${accessory.handlers?.serviceArea ? 'custom behavior' : 'base server'} will have features: ${serviceAreaFeatures.join(', ')}`);
        }
        customBehaviors.push(behaviorClass);
    }
    return customBehaviors;
}
/**
 * Apply detected features to a behavior class
 */
export function applyFeaturesToBehavior(behaviorClass, features, clusterName) {
    if (!features || features.length === 0) {
        return behaviorClass;
    }
    const modifiedBehavior = behaviorClass.with(...features);
    log.info(`${clusterName} custom behavior will preserve features: ${features.join(', ')}`);
    return modifiedBehavior;
}
//# sourceMappingURL=serverHelpers.js.map