"use strict";
/**
 * Module: team-config-updates
 *
 * Best-effort broadcast when `.minds/team.yaml` changes so multi-tab WebUIs can refresh
 * their cached team config without polling.
 *
 * Notes:
 * - File watching is inherently best-effort (editors may write via rename; some platforms miss events).
 * - We use a polling fallback for reliability, mirroring the MCP supervisor pattern.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.setTeamConfigBroadcaster = setTeamConfigBroadcaster;
exports.clearTeamConfigBroadcaster = clearTeamConfigBroadcaster;
exports.startTeamConfigWatcher = startTeamConfigWatcher;
exports.stopTeamConfigWatcher = stopTeamConfigWatcher;
exports.notifyTeamConfigUpdated = notifyTeamConfigUpdated;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const time_1 = require("@longrun-ai/kernel/utils/time");
const log_1 = require("./log");
const tool_availability_updates_1 = require("./tool-availability-updates");
const log = (0, log_1.createLogger)('team-config-updates');
const MINDS_DIR = '.minds';
const TEAM_YAML_BASENAME = 'team.yaml';
const TEAM_YAML_PATH = path_1.default.join(MINDS_DIR, TEAM_YAML_BASENAME);
function isFsErrWithCode(err) {
    if (typeof err !== 'object' || err === null)
        return false;
    const maybe = err.code;
    return typeof maybe === 'string';
}
async function readTeamYamlSig() {
    try {
        const st = await fs_1.default.promises.stat(TEAM_YAML_PATH);
        if (!st.isFile()) {
            return { sig: `not_file/${st.size}/${st.mtimeMs}`, exists: false };
        }
        return { sig: `${st.size}/${st.mtimeMs}`, exists: true };
    }
    catch (err) {
        if (isFsErrWithCode(err) && err.code === 'ENOENT') {
            return { sig: 'missing', exists: false };
        }
        log.warn('Failed to stat .minds/team.yaml', err);
        return { sig: 'error', exists: false };
    }
}
let broadcastToClients;
function setTeamConfigBroadcaster(fn) {
    broadcastToClients = fn;
}
function clearTeamConfigBroadcaster() {
    broadcastToClients = undefined;
}
function broadcast(msg) {
    const fn = broadcastToClients;
    if (!fn)
        return;
    try {
        fn(msg);
    }
    catch (err) {
        log.warn('Failed to broadcast team config update', err);
    }
}
let mindsDirWatcher;
let workspaceWatcher;
let pollTimer;
let debounceTimer;
let lastSeenSig;
let watcherStarted = false;
let checkChain = Promise.resolve();
function makeUpdateMessage(args) {
    return {
        type: 'team_config_updated',
        path: TEAM_YAML_PATH,
        exists: args.exists,
        timestamp: (0, time_1.formatUnifiedTimestamp)(new Date()),
        trigger: args.trigger,
    };
}
async function checkAndMaybeBroadcast(trigger) {
    const state = await readTeamYamlSig();
    if (lastSeenSig === undefined) {
        // Initialize without broadcasting; clients load team config on boot anyway.
        lastSeenSig = state.sig;
        return;
    }
    if (state.sig === lastSeenSig)
        return;
    lastSeenSig = state.sig;
    broadcast(makeUpdateMessage({ exists: state.exists, trigger }));
    (0, tool_availability_updates_1.notifyToolAvailabilityBindingChanged)(`team-config:${trigger}`);
}
function scheduleCheck(trigger) {
    if (debounceTimer) {
        clearTimeout(debounceTimer);
        debounceTimer = undefined;
    }
    debounceTimer = setTimeout(() => {
        checkChain = checkChain
            .then(async () => await checkAndMaybeBroadcast(trigger))
            .catch((err) => {
            log.warn('Team config check failed', err);
        });
    }, 150);
}
async function ensureMindsDirWatcher(reason) {
    if (mindsDirWatcher)
        return;
    try {
        mindsDirWatcher = fs_1.default.watch(MINDS_DIR, { persistent: false }, (_event, filename) => {
            const name = filename ? filename.toString() : '';
            if (name !== '' && name !== TEAM_YAML_BASENAME)
                return;
            scheduleCheck(`minds.watch:${reason}`);
        });
        mindsDirWatcher.on('error', () => {
            if (mindsDirWatcher) {
                mindsDirWatcher.close();
                mindsDirWatcher = undefined;
            }
        });
    }
    catch {
        // ignore; polling still works
    }
}
function startTeamConfigWatcher() {
    if (watcherStarted)
        return;
    watcherStarted = true;
    checkChain = checkChain
        .then(async () => {
        const initial = await readTeamYamlSig();
        lastSeenSig = initial.sig;
    })
        .catch((err) => {
        log.warn('Initial team config signature read failed', err);
    });
    // Best-effort fast watcher.
    void ensureMindsDirWatcher('startup');
    // Watch rtws root for `.minds/` create/delete.
    try {
        workspaceWatcher = fs_1.default.watch('.', { persistent: false }, (_event, filename) => {
            const name = filename ? filename.toString() : '';
            if (name !== '' && name !== MINDS_DIR)
                return;
            void ensureMindsDirWatcher('rtws.watch');
            scheduleCheck('rtws.watch');
        });
        workspaceWatcher.on('error', () => {
            if (workspaceWatcher) {
                workspaceWatcher.close();
                workspaceWatcher = undefined;
            }
        });
    }
    catch {
        // ignore; polling still works
    }
    // Polling fallback for reliability.
    pollTimer = setInterval(() => {
        void ensureMindsDirWatcher('poll');
        scheduleCheck('poll');
    }, 1500);
    pollTimer.unref();
}
function stopTeamConfigWatcher() {
    if (mindsDirWatcher) {
        mindsDirWatcher.close();
        mindsDirWatcher = undefined;
    }
    if (workspaceWatcher) {
        workspaceWatcher.close();
        workspaceWatcher = undefined;
    }
    if (pollTimer) {
        clearInterval(pollTimer);
        pollTimer = undefined;
    }
    if (debounceTimer) {
        clearTimeout(debounceTimer);
        debounceTimer = undefined;
    }
    watcherStarted = false;
    lastSeenSig = undefined;
}
/**
 * Explicit notification hook for code paths that are known to mutate `.minds/team.yaml`.
 *
 * This is used for the setup flow so the UI updates immediately even if file watch is flaky.
 */
function notifyTeamConfigUpdated(trigger) {
    checkChain = checkChain
        .then(async () => {
        const state = await readTeamYamlSig();
        lastSeenSig = state.sig;
        broadcast(makeUpdateMessage({ exists: state.exists, trigger }));
        (0, tool_availability_updates_1.notifyToolAvailabilityBindingChanged)(`team-config:${trigger}`);
    })
        .catch((err) => {
        log.warn('Failed to notify team config updated', err);
    });
}
