/**
 * Lightweight Matter Configuration Utilities
 *
 * This module provides config collection and validation without importing
 * heavy Matter.js libraries. This ensures fast startup for users without
 * Matter configured.
 */
import { Logger } from '../logger.js';
const log = Logger.withPrefix('Matter/Config');
/**
 * Whether a Matter config block represents an *enabled* Matter setup.
 * The block being present means Matter is configured; `enabled: false` means
 * it is configured but intentionally turned off (storage + port preserved so
 * it can be re-enabled without re-commissioning). Missing `enabled` = enabled,
 * which keeps every pre-existing config working unchanged.
 *
 * Note: this does NOT distinguish "the main matter server should start" from
 * "the matter API surface should be exposed to plugins". Use
 * `shouldStartMatterServer` for the former — it additionally accounts for
 * externalsOnly mode, where the API is exposed but the bridge server is not
 * started.
 */
export function isMatterConfigEnabled(matter) {
    return !!matter && matter.enabled !== false;
}
/**
 * Whether the main Matter server should start for this bridge — i.e. Matter
 * is configured, enabled, and NOT in externalsOnly mode. In externalsOnly
 * mode the API is still loaded (so plugins can publish externals via their
 * own per-accessory MatterServer instances) but the bridge aggregator does
 * not come up.
 */
export function shouldStartMatterServer(matter) {
    return isMatterConfigEnabled(matter) && !matter?.externalsOnly;
}
/**
 * Whether Matter is "active" for this bridge in any form — either fully on,
 * or in externalsOnly mode (api.matter loaded, manager listening for external
 * publish events, main bridge server NOT started). Returns false only when
 * matter is missing or fully disabled (`enabled: false` without `externalsOnly`).
 *
 * Use this for gates like "should api.matter be loaded?" and "should the
 * matter manager be constructed?". Use `shouldStartMatterServer` for the
 * tighter gate of "should the bridge aggregator come up?".
 */
export function isMatterActive(matter) {
    return !!matter && (matter.enabled !== false || matter.externalsOnly === true);
}
/**
 * Normalise the coherence of `matter.externalsOnly` for a single bridge block.
 *
 * `externalsOnly: true` is meant to be paired with `enabled: false` (the two
 * flags together confirm "bridge node off, externals still publish"). If a
 * config sets `externalsOnly: true` on its own we honour the unambiguous
 * intent rather than failing the whole process — we warn and set
 * `enabled: false` in place so the block matches the canonical externalsOnly
 * form every downstream check expects. This mirrors the log-and-continue
 * behaviour of the port validators rather than taking the whole instance down
 * over one stray flag.
 *
 * For accessory child bridges, callers should use
 * `stripMatterExternalsOnlyForAccessory` instead — externals are not
 * supported on accessory plugins, so the field is dropped with a warning
 * before this check runs.
 *
 * Mutates the passed matter block in place.
 */
export function validateMatterExternalsOnly(matter, bridgeLabel) {
    if (matter.externalsOnly === true && matter.enabled !== false) {
        log.warn(`${bridgeLabel}: 'matter.externalsOnly: true' was set without 'matter.enabled: false'. Proceeding in externalsOnly mode (the bridge node will not start). Set 'matter.enabled: false' to confirm intent and silence this warning.`);
        matter.enabled = false;
    }
}
/**
 * Strip `matter.externalsOnly` (if set) from an accessory child bridge's
 * matter block, logging a warning. Externals are not supported via the
 * accessory plugin API, so the flag is meaningless there — mirrors the
 * accessory-side behaviour of `hap.externalsOnly`.
 *
 * Mutates the passed matter block in place.
 */
export function stripMatterExternalsOnlyForAccessory(matter, bridgeLabel) {
    if (matter.externalsOnly === true) {
        log.warn(`${bridgeLabel}: 'matter.externalsOnly' is not supported on accessory child bridges. Ignoring.`);
        delete matter.externalsOnly;
    }
}
/**
 * Lightweight config collector that doesn't require Matter.js imports
 */
export class MatterConfigCollector {
    /**
     * Check if any Matter configuration exists in the config
     */
    static hasMatterConfig(config) {
        // Use isMatterActive so externalsOnly bridges are considered configured —
        // they still need api.matter loaded and the manager set up to attach
        // their external-publish listeners.
        return (isMatterActive(config.bridge.matter)
            || config.platforms.some((p) => isMatterActive(p._bridge?.matter))
            || config.accessories.some((a) => isMatterActive(a._bridge?.matter)));
    }
    /**
     * Collect all configured Matter ports from config to avoid conflicts.
     *
     * Ports are collected from every bridge block that declares one, regardless
     * of `enabled`/`externalsOnly` state. This is deliberate: a disabled-in-place
     * bridge (`enabled: false`) keeps its configured port reserved so that
     * re-enabling it later reuses the same port (no re-commissioning) and the
     * allocator never hands that port to an automatic Matter/external allocation
     * in the meantime. The trade-off is that a disabled bridge's port stays
     * unavailable for auto-allocation even though no server currently binds it —
     * that is the cost of the "port preserved" contract on `enabled`.
     */
    static collectConfiguredMatterPorts(config) {
        const configuredMatterPorts = [];
        if (config.bridge.matter?.port) {
            configuredMatterPorts.push(config.bridge.matter.port);
        }
        for (const platform of config.platforms) {
            if (platform._bridge?.matter?.port) {
                configuredMatterPorts.push(platform._bridge.matter.port);
            }
        }
        for (const accessory of config.accessories) {
            if (accessory._bridge?.matter?.port) {
                configuredMatterPorts.push(accessory._bridge.matter.port);
            }
        }
        return configuredMatterPorts;
    }
    /**
     * Validate the matterPorts pool configuration
     * Ensures start and end are defined and start <= end
     *
     * @param config - The Homebridge configuration
     */
    static validateMatterPortsPool(config) {
        if (config.matterPorts !== undefined) {
            if (config.matterPorts.start && config.matterPorts.end) {
                if (config.matterPorts.start > config.matterPorts.end) {
                    log.error('Invalid Matter port pool configuration. End should be greater than or equal to start.');
                    config.matterPorts = undefined;
                }
            }
            else {
                log.error('Invalid configuration for \'matterPorts\'. Missing \'start\' and \'end\' properties! Ignoring it!');
                config.matterPorts = undefined;
            }
        }
    }
    /**
     * Validate Matter configuration (lazy-loads validator only when needed)
     * This function dynamically imports the full validator to avoid loading it
     * when Matter is not configured.
     */
    static async validateMatterConfig(config) {
        // Only validate if Matter config exists
        if (!this.hasMatterConfig(config)) {
            return;
        }
        // externalsOnly coherence checks run first so misconfigurations throw with
        // a clear message before downstream validators get the chance to silently
        // strip the matter block on unrelated errors. Accessory child bridges have
        // externalsOnly stripped with a warning here too (mirrors hap.externalsOnly).
        if (config.bridge.matter) {
            validateMatterExternalsOnly(config.bridge.matter, 'main bridge');
        }
        for (const platform of config.platforms) {
            if (platform._bridge?.matter) {
                validateMatterExternalsOnly(platform._bridge.matter, `platform "${platform.platform}" child bridge`);
            }
        }
        for (const accessory of config.accessories) {
            if (accessory._bridge?.matter) {
                stripMatterExternalsOnlyForAccessory(accessory._bridge.matter, `accessory "${accessory.accessory}" child bridge`);
            }
        }
        // Lazy-load the full validator (which has heavier dependencies)
        const { MatterConfigValidator } = await import('./configValidator.js');
        // Validate the main bridge Matter config only when it will actually start a
        // server. A disabled (`enabled: false`) or externalsOnly main bridge is
        // preserved as-is (disabled-in-place) — mirroring the child validator —
        // since stripping it would lose config/storage the user expects to survive
        // so it can be re-enabled without re-commissioning.
        if (config.bridge.matter && shouldStartMatterServer(config.bridge.matter)) {
            const validation = MatterConfigValidator.validate(config.bridge.matter);
            if (!validation.isValid) {
                log.error('Main bridge Matter configuration is invalid. Matter will not be enabled for the main bridge.');
                delete config.bridge.matter;
            }
        }
        // Reserve the main bridge's Matter port so the child validator catches
        // child↔main port collisions in the same pass — but only when the main
        // Matter server will actually bind it. A disabled or externalsOnly main
        // bridge never starts its bridge server, so its configured port must not
        // block a child bridge from legitimately using the same number.
        const reserved = new Set();
        if (shouldStartMatterServer(config.bridge.matter) && config.bridge.matter?.port) {
            reserved.add(config.bridge.matter.port);
        }
        // Validate all child bridge Matter configs and check for port conflicts
        const childMatterValidation = MatterConfigValidator.validateAllChildMatterConfigs(config.platforms, config.accessories, reserved);
        if (!childMatterValidation.isValid) {
            log.error('Some child bridge Matter configurations were invalid and have been disabled. The remaining configuration will start as normal.');
            // Surface the specific per-child errors (which platform/accessory and
            // which port) so the user knows what to fix. Previously these details
            // were collected into the result but never logged, leaving only the
            // generic line above — the user couldn't tell what had been disabled.
            for (const error of childMatterValidation.errors) {
                log.error(error);
            }
        }
        // Surface any non-fatal child Matter warnings too — also collected by the
        // validator but not previously logged by this caller.
        for (const warning of childMatterValidation.warnings) {
            log.warn(warning);
        }
        // Check for conflicts between main bridge Matter port and child bridge
        // ports — again only when the main server will actually bind its port,
        // so a disabled/externalsOnly main doesn't raise a spurious conflict.
        if (shouldStartMatterServer(config.bridge.matter) && config.bridge.matter?.port) {
            this.checkPortConflicts(config);
        }
    }
    /**
     * Check for port conflicts between main bridge and child bridges
     *
     * @param config - The Homebridge configuration
     */
    static checkPortConflicts(config) {
        const mainMatterPort = config.bridge.matter?.port;
        if (!mainMatterPort) {
            return;
        }
        // Collect all child bridge Matter ports
        const childMatterPorts = [];
        for (const platform of config.platforms) {
            if (platform._bridge?.matter?.port) {
                childMatterPorts.push(platform._bridge.matter.port);
            }
        }
        for (const accessory of config.accessories) {
            if (accessory._bridge?.matter?.port) {
                childMatterPorts.push(accessory._bridge.matter.port);
            }
        }
        // Check for conflicts with child bridge Matter ports
        if (childMatterPorts.includes(mainMatterPort)) {
            log.error(`Main bridge Matter port ${mainMatterPort} conflicts with a child bridge Matter port. Please use unique ports.`);
        }
        // Check for conflict with main bridge HAP port
        if (config.bridge.port && Math.abs(config.bridge.port - mainMatterPort) < 10) {
            log.warn(`Main bridge HAP port ${config.bridge.port} and Matter port ${mainMatterPort} are very close. Consider spacing them further apart.`);
        }
    }
}
//# sourceMappingURL=config.js.map