import { DeviceWireResultType, LutronCasetaLeap } from './Platform.HAP.js';
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
const MATTER_NODE_LABEL_MAX_LENGTH = 32;
function normalizeMatterDisplayName(fullName, serialNumber) {
    if (fullName.length <= MATTER_NODE_LABEL_MAX_LENGTH) {
        return fullName;
    }
    // Keep labels deterministic and human-readable while preserving uniqueness.
    const compactName = fullName.replace(/\s+/g, ' ').trim();
    if (compactName.length <= MATTER_NODE_LABEL_MAX_LENGTH) {
        return compactName;
    }
    const serialSuffix = ` ${serialNumber.slice(-4)}`;
    const available = Math.max(1, MATTER_NODE_LABEL_MAX_LENGTH - serialSuffix.length);
    return `${compactName.slice(0, available).trimEnd()}${serialSuffix}`;
}
/**
 * Homebridge Matter platform for the Lutron Caseta LEAP plugin.
 *
 * Extends the standard HAP platform ({@link LutronCasetaLeap}) and overrides
 * accessory registration to use the Homebridge Matter API when it is
 * available.  If the Matter API is absent at runtime the class falls back
 * transparently to the standard HAP behaviour inherited from
 * {@link LutronCasetaLeap}.
 *
 * Note: `configureAccessory` is intentionally **not** overridden here so that
 * cached accessories restored by Homebridge continue to be stored in
 * `this.accessories` via the base-class implementation.
 */
export class LutronCasetaLeapMatterPlatform extends LutronCasetaLeap {
    constructor(log, config, api) {
        super(log, config, api);
        // Use this.log (wrapped by the base class with the user's logLevel) so
        // the Matter banner respects the same verbosity setting as everything else.
        this.log.info('LutronCasetaLeapMatterPlatform: Matter mode active');
    }
    /**
     * Returns the Homebridge Matter sub-API if both `registerPlatformAccessories`
     * and `unregisterPlatformAccessories` are present, or `undefined` otherwise.
     */
    get matterApi() {
        const anyApi = this.api;
        if (anyApi?.matter
            && typeof anyApi.matter.registerPlatformAccessories === 'function'
            && typeof anyApi.matter.unregisterPlatformAccessories === 'function') {
            return anyApi.matter;
        }
        return undefined;
    }
    /**
     * Overrides {@link LutronCasetaLeap.processDevice} to register newly
     * discovered accessories with the Matter API when available, falling back
     * to the standard HAP path when Matter is not present.
     */
    async processDevice(bridge, d) {
        const mApi = this.matterApi;
        if (!mApi) {
            // Matter API not present – delegate entirely to the HAP implementation.
            return super.processDevice(bridge, d);
        }
        const fullName = d.FullyQualifiedName.join(' ');
        const serialNumber = d.SerialNumber?.toString() || '';
        const matterDisplayName = normalizeMatterDisplayName(fullName, serialNumber);
        const uuid = this.api.hap.uuid.generate(d.SerialNumber.toString());
        let accessory = this.accessories.get(uuid);
        let isFromCache = true;
        if (accessory === undefined) {
            isFromCache = false;
            // Use the PlatformAccessory constructor from the API
            const PlatformAccessoryCtor = this.api.platformAccessory;
            accessory = new PlatformAccessoryCtor(fullName, uuid);
            this.log.debug(`Device ${fullName} not found in accessory cache (Matter mode)`);
        }
        const result = await this.wireAccessory(accessory, bridge, d);
        if (result.kind === DeviceWireResultType.Skipped
            && result.reason.startsWith('Device type excluded by config: ')) {
            if (isFromCache) {
                // In Matter mode, accessories can exist in both HAP and Matter registries.
                // Remove from both so previously cached accessories are fully purged.
                this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
                mApi.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
                this.accessories.delete(accessory.UUID);
                this.log.info(`Unregistered cached Matter accessory for excluded device type ${d.DeviceType}: ${fullName}`);
                return Promise.resolve(`Removed cached Matter accessory for excluded device type: ${fullName}`);
            }
            return Promise.resolve(`Skipped Matter registration for excluded device type: ${fullName}`);
        }
        accessory.displayName = matterDisplayName;
        if (matterDisplayName !== fullName) {
            this.log.debug(`Truncated Matter node label for ${fullName} -> ${matterDisplayName}`);
        }
        // Set all required Matter fields before registration
        // Generalized Matter support for all device types
        let matterFields;
        let clusters;
        let handlers;
        let shouldRefreshCachedMatterAccessory = false;
        switch (d.DeviceType) {
            case 'SmartBridge':
                // Suppress SmartBridge from logging or registration
                return Promise.resolve(`Skipped Matter registration for SmartBridge`);
            case 'Pico2Button':
            case 'Pico2ButtonRaiseLower':
            case 'Pico3Button':
            case 'Pico3ButtonRaiseLower':
            case 'Pico4Button':
            case 'Pico4Button2Group':
            case 'Pico4ButtonScene':
            case 'Pico4ButtonZone':
            case 'PaddleSwitchPico': {
                matterFields = {
                    deviceType: mApi.deviceTypes.BridgedNode,
                    manufacturer: 'Lutron Electronics',
                    model: (d.ModelNumber || d.DeviceType || 'Pico Remote').toString(),
                    serialNumber: serialNumber || uuid,
                };
                const PicoRemote = (await import('./PicoRemote.js')).PicoRemote;
                const pico = new PicoRemote(this, accessory, bridge, this.optionsFromConfig(this.config), mApi);
                clusters = pico.getMatterClusters();
                this.log.debug(`[Matter] Pico remote ${fullName} (${d.DeviceType}): getMatterClusters returned ${Object.keys(clusters).length} top-level keys: ${Object.keys(clusters).join(', ')}`);
                if (clusters.parts && Array.isArray(clusters.parts)) {
                    this.log.info(`[Matter] Pico remote ${fullName} will register with ${clusters.parts.length} composed endpoints`);
                }
                break;
            }
            case 'SerenaTiltOnlyWoodBlind': {
                matterFields = {
                    deviceType: mApi.deviceTypes.WindowCovering,
                    manufacturer: 'Lutron Electronics',
                    model: (d.ModelNumber || d.DeviceType || 'Serena Blind').toString(),
                    serialNumber: serialNumber || uuid,
                };
                const { SerenaTiltOnlyWoodBlinds } = await import('./SerenaTiltOnlyWoodBlinds.js');
                clusters = SerenaTiltOnlyWoodBlinds.getMatterClusters();
                handlers = {
                    windowCovering: {
                        goToTiltPercentage: async (request) => {
                            const zoneTilt100ths = Number(request?.tiltPercent100thsValue
                                ?? request?.targetPositionTiltPercent100ths
                                ?? 0);
                            const clamped100ths = Math.max(0, Math.min(10000, Math.round(zoneTilt100ths)));
                            const lutronTilt = clamped100ths / 200;
                            await bridge.setBlindsTilt(d, lutronTilt);
                        },
                    },
                };
                break;
            }
            case 'RPSOccupancySensor': {
                matterFields = {
                    deviceType: mApi.deviceTypes.OccupancySensor,
                    manufacturer: 'Lutron Electronics',
                    model: (d.ModelNumber || d.DeviceType || 'Occupancy Sensor').toString(),
                    serialNumber: serialNumber || uuid,
                };
                const { OccupancySensor } = await import('./OccupancySensor.js');
                clusters = OccupancySensor.getMatterClusters();
                break;
            }
            case 'WallDimmer': {
                matterFields = {
                    deviceType: mApi.deviceTypes.DimmableLight,
                    manufacturer: 'Lutron Electronics',
                    model: (d.ModelNumber || d.DeviceType || 'Wall Dimmer').toString(),
                    serialNumber: serialNumber || uuid,
                };
                const { WallDimmer } = await import('./WallDimmer.js');
                clusters = WallDimmer.getMatterClusters();
                const zoneHref = d.LocalZones?.[0]?.href;
                if (!zoneHref) {
                    return Promise.resolve(`Skipped Matter registration for device missing LocalZones: ${fullName}`);
                }
                const clampPercent = (value) => Math.max(0, Math.min(100, Math.round(value)));
                const percentFromMatterLevel = (level) => clampPercent((Math.max(0, Math.min(254, level)) / 254) * 100);
                const sendLevel = async (levelPercent) => {
                    await bridge.client.request('CreateRequest', `${zoneHref}/commandprocessor`, {
                        Command: {
                            CommandType: 'GoToLevel',
                            Parameter: [{ Type: 'Level', Value: clampPercent(levelPercent) }],
                        },
                    });
                };
                handlers = {
                    onOff: {
                        on: async () => {
                            const levelState = await mApi.getAccessoryState(uuid, 'levelControl');
                            const knownLevel = Number(levelState?.currentLevel);
                            const targetPercent = Number.isFinite(knownLevel) && knownLevel > 0
                                ? percentFromMatterLevel(knownLevel)
                                : 100;
                            this.log.debug(`[Matter] WallDimmer ${fullName}: received onOff.on, restoring to ${targetPercent}`);
                            await sendLevel(targetPercent);
                        },
                        off: async () => sendLevel(0),
                        toggle: async () => {
                            const resp = await bridge.client.request('ReadRequest', `${zoneHref}/status`);
                            const status = resp.Body?.ZoneStatus;
                            const isOn = typeof status?.Level === 'number' && status.Level > 0;
                            await sendLevel(isOn ? 0 : 100);
                        },
                    },
                    levelControl: {
                        moveToLevel: async (request) => sendLevel(percentFromMatterLevel(Number(request?.level ?? 0))),
                        moveToLevelWithOnOff: async (request) => sendLevel(percentFromMatterLevel(Number(request?.level ?? 0))),
                    },
                };
                break;
            }
            case 'WallSwitch': {
                matterFields = {
                    deviceType: mApi.deviceTypes.OnOffLight,
                    manufacturer: 'Lutron Electronics',
                    model: (d.ModelNumber || d.DeviceType || 'Wall Switch').toString(),
                    serialNumber: serialNumber || uuid,
                };
                const { WallSwitch } = await import('./WallSwitch.js');
                clusters = WallSwitch.getMatterClusters();
                const zoneHref = d.LocalZones?.[0]?.href;
                if (!zoneHref) {
                    return Promise.resolve(`Skipped Matter registration for device missing LocalZones: ${fullName}`);
                }
                const sendLevel = async (on) => {
                    this.log.debug(`[Matter->LEAP] WallSwitch ${fullName}: sending GoToLevel ${on ? 100 : 0} to ${zoneHref}`);
                    await bridge.client.request('CreateRequest', `${zoneHref}/commandprocessor`, {
                        Command: {
                            CommandType: 'GoToLevel',
                            Parameter: [{ Type: 'Level', Value: on ? 100 : 0 }],
                        },
                    });
                };
                handlers = {
                    onOff: {
                        on: async () => {
                            this.log.debug(`[Matter] WallSwitch ${fullName}: received onOff.on`);
                            await sendLevel(true);
                        },
                        off: async () => {
                            this.log.debug(`[Matter] WallSwitch ${fullName}: received onOff.off`);
                            await sendLevel(false);
                        },
                        toggle: async () => {
                            this.log.debug(`[Matter] WallSwitch ${fullName}: received onOff.toggle`);
                            const resp = await bridge.client.request('ReadRequest', `${zoneHref}/status`);
                            const status = resp.Body?.ZoneStatus;
                            const isOn = typeof status?.Level === 'number' && status.Level > 0;
                            this.log.debug(`[Matter] WallSwitch ${fullName}: current level=${status?.Level ?? 'unknown'}, toggling to ${isOn ? 0 : 100}`);
                            await sendLevel(!isOn);
                        },
                    },
                };
                break;
            }
            default:
                // Not supported for Matter, skip registration (no log for SmartBridge)
                this.log.debug(`Device type ${d.DeviceType} is not supported for Matter, skipping Matter registration for ${fullName}`);
                return Promise.resolve(`Skipped Matter registration for unsupported device: ${fullName}`);
        }
        if (matterFields && clusters) {
            const previousParts = Array.isArray(accessory.parts)
                ? accessory.parts
                : [];
            // Matter validator requires these fields on the accessory object itself.
            Object.assign(accessory, matterFields);
            // Keep metadata in context as well so cached accessory restores keep parity.
            Object.assign(accessory.context, matterFields);
            if ('parts' in clusters && Array.isArray(clusters.parts) && clusters.parts.length > 0) {
                const nextParts = clusters.parts;
                accessory.parts = nextParts;
                delete accessory.clusters;
                delete accessory.handlers;
                this.log.debug(`[Matter] Registering composed device '${fullName}' with ${nextParts.length} endpoint(s)`);
                this.log.debug(`[Matter Debug] Part structure for '${fullName}':`, JSON.stringify(nextParts, null, 2));
                // Cached accessories can retain stale endpoint metadata from earlier
                // registrations. If part IDs/count changed (or no prior parts existed),
                // force a re-register so Home receives the updated composed structure.
                if (isFromCache) {
                    const previousIds = previousParts.map(part => String(part.id)).sort();
                    const nextIds = nextParts.map(part => String(part.id)).sort();
                    shouldRefreshCachedMatterAccessory = previousIds.length !== nextIds.length
                        || previousIds.some((id, index) => id !== nextIds[index]);
                }
            }
            else {
                // Single-endpoint device — strip any 'behaviors' and 'parts' keys and set clusters directly
                const pureClusters = Object.fromEntries(Object.entries(clusters).filter(([k]) => k !== 'behaviors' && k !== 'parts'));
                if (Object.keys(pureClusters).length === 0) {
                    this.log.warn(`Device ${fullName} did not provide valid Matter clusters, skipping Matter registration.`);
                    return Promise.resolve(`Skipped Matter registration for device with invalid clusters: ${fullName}`);
                }
                ;
                accessory.clusters = pureClusters;
                if (handlers && Object.keys(handlers).length > 0) {
                    ;
                    accessory.handlers = handlers;
                }
                else {
                    delete accessory.handlers;
                }
                delete accessory.parts;
                this.log.debug(`[Matter Debug] Registering '${fullName}' with clusters:`, JSON.stringify(pureClusters, null, 2));
            }
        }
        else {
            this.log.warn(`Device ${fullName} did not provide valid Matter clusters, skipping Matter registration.`);
            return Promise.resolve(`Skipped Matter registration for device with invalid clusters: ${fullName}`);
        }
        switch (result.kind) {
            case DeviceWireResultType.Error: {
                if (isFromCache) {
                    mApi.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
                    this.log.debug(`un-registered cached device ${fullName} (Matter) due to an error: ${result.reason}`);
                }
                return Promise.reject(new Error(`Failed to wire device ${fullName}: ${result.reason}`));
            }
            case DeviceWireResultType.Skipped: {
                if (isFromCache) {
                    this.log.debug(`un-registered cached device ${fullName} (Matter) because it was skipped`);
                    mApi.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
                }
                return Promise.resolve(`Skipped setting up device: ${result.reason}`);
            }
            case DeviceWireResultType.Success: {
                if (!isFromCache) {
                    this.accessories.set(accessory.UUID, accessory);
                    mApi.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
                    this.log.debug(`registered new device ${fullName} (Matter) because it was new`);
                }
                else if (shouldRefreshCachedMatterAccessory) {
                    await mApi.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
                    await mApi.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
                    this.log.info(`Refreshed cached Matter accessory for composed endpoint changes: ${fullName}`);
                }
                return Promise.resolve(isFromCache
                    ? `Restoring existing accessory from cache (Matter): ${fullName}`
                    : `Adding new accessory (Matter): ${fullName}`);
            }
        }
    }
}
//# sourceMappingURL=Platform.Matter.js.map