import { isComposerUuid, normalizeAgentTitle } from './chat-sync.js';
import { assessAgentCompletionReadiness, } from './agent-completion-readiness.js';
import { messagesFingerprint } from './lenta-seq-journal.js';
export const AGENT_COMPLETION_PUSH_BODY = 'Агент завершил работу';
let suppressUntil = 0;
const suppressByAgentUntil = new Map();
export function suppressAgentCompletionPush(agentIdOrMs = 6000, ms = 6000) {
    if (typeof agentIdOrMs === 'number') {
        suppressUntil = Date.now() + agentIdOrMs;
        return;
    }
    const agentId = agentIdOrMs.trim();
    if (!agentId)
        return;
    suppressByAgentUntil.set(agentId, Date.now() + ms);
}
function isSuppressed() {
    return Date.now() < suppressUntil;
}
function isSuppressedForAgent(agentId) {
    const until = suppressByAgentUntil.get(agentId) ?? 0;
    if (until <= Date.now()) {
        if (until)
            suppressByAgentUntil.delete(agentId);
        return false;
    }
    return true;
}
function suppressAgentForMs(agentId, ms) {
    const id = agentId.trim();
    if (!id)
        return;
    suppressByAgentUntil.set(id, Date.now() + ms);
}
function normalizeTitle(title) {
    return title.trim().replace(/\s+/g, ' ');
}
function isStableComposerId(id) {
    if (!id?.trim())
        return false;
    const t = id.trim();
    if (t.startsWith('tab-'))
        return false;
    if (t.startsWith('sidebar-'))
        return false;
    return t.length >= 8;
}
function lookupComposerIdByTitle(title, map) {
    if (!title?.trim() || !map)
        return undefined;
    return map[normalizeAgentTitle(title)];
}
function resolveAgentIdFromIndex(title, agentsIndex) {
    const norm = normalizeAgentTitle(title);
    if (!norm || !agentsIndex)
        return undefined;
    for (const repo of agentsIndex.repos) {
        for (const agent of repo.agents) {
            if (normalizeAgentTitle(agent.title) !== norm)
                continue;
            if (isStableComposerId(agent.id) || isComposerUuid(agent.id))
                return agent.id.trim();
        }
    }
    return undefined;
}
function resolveSidebarAgentId(rawId, title, state, agentsIndex) {
    if (isStableComposerId(rawId))
        return rawId.trim();
    if (isComposerUuid(rawId))
        return rawId.trim();
    const activeId = state.activeComposerId?.trim();
    const activeTab = state.tabs.find((t) => t.active);
    if (activeTab?.isWorking && activeId && isStableComposerId(activeId)) {
        const raw = String(rawId).trim();
        const tabComposer = activeTab.composerId ? String(activeTab.composerId).trim() : '';
        const tabTitle = normalizeAgentTitle(activeTab.title ?? '');
        const titleNorm = normalizeAgentTitle(title);
        const sameRow = raw === activeTab.id ||
            (tabComposer && raw === tabComposer) ||
            !titleNorm ||
            tabTitle === titleNorm;
        if (sameRow && (!titleNorm || tabTitle === titleNorm || !tabTitle)) {
            return activeId;
        }
    }
    const fromTitle = lookupComposerIdByTitle(title, state.composerIdByTitle);
    if (fromTitle && isStableComposerId(fromTitle))
        return fromTitle;
    const fromIndex = resolveAgentIdFromIndex(title, agentsIndex);
    if (fromIndex)
        return fromIndex;
    return undefined;
}
function titleForComposerId(agentId, state) {
    const activeTitle = state.activeChatTitle?.trim();
    if (state.activeComposerId === agentId && activeTitle)
        return activeTitle;
    for (const tab of state.tabs) {
        const id = tab.composerId ?? tab.id;
        if (id === agentId)
            return tab.title?.trim() || undefined;
        const resolved = resolveSidebarAgentId(tab.composerId ?? tab.id, tab.title ?? '', state);
        if (resolved === agentId)
            return tab.title?.trim() || undefined;
    }
    for (const repo of state.sidebarRepos ?? []) {
        for (const agent of repo.agents) {
            if (agent.id === agentId)
                return agent.title?.trim() || undefined;
            const resolved = resolveSidebarAgentId(agent.id, agent.title, state);
            if (resolved === agentId)
                return agent.title?.trim() || undefined;
        }
    }
    return undefined;
}
function resolveAgentChatTitle(agentId, state, agentsIndex) {
    const fromState = titleForComposerId(agentId, state);
    if (fromState)
        return fromState;
    for (const repo of agentsIndex?.repos ?? []) {
        for (const agent of repo.agents) {
            if (agent.id === agentId)
                return agent.title?.trim() || 'Чат';
        }
    }
    return 'Чат';
}
/** Sidebar loading-indicator on list rows (`tabs` + `sidebarRepos`). */
function snapshotSidebarWorkingAgents(state, agentsIndex) {
    const map = new Map();
    const add = (rawId, title) => {
        const id = resolveSidebarAgentId(rawId, title, state, agentsIndex);
        if (!id)
            return;
        if (agentPausedForInput(state, id))
            return;
        map.set(id, title.trim() || resolveAgentChatTitle(id, state, agentsIndex));
    };
    const activeId = state.activeComposerId?.trim();
    const activeTab = state.tabs.find((t) => t.active);
    if (activeTab?.isWorking && activeId && isStableComposerId(activeId)) {
        add(activeId, activeTab.title ?? state.activeChatTitle ?? '');
    }
    for (const tab of state.tabs) {
        if (!tab.isWorking)
            continue;
        add(tab.composerId ?? tab.id, tab.title ?? '');
    }
    for (const repo of state.sidebarRepos ?? []) {
        for (const agent of repo.agents) {
            if (!agent.isWorking)
                continue;
            add(agent.id, agent.title);
        }
    }
    return map;
}
function agentPausedForInput(state, agentId) {
    const isActive = state.activeComposerId === agentId;
    if (!isActive)
        return false;
    return ((state.pendingApprovals?.length ?? 0) > 0 ||
        !!state.pendingQuestionnaire ||
        state.agentStatus === 'waiting_approval' ||
        state.agentStatus === 'waiting_questionnaire' ||
        (state.agentStatus === 'background_shell' && state.agentWorking !== true));
}
function isStillWorkingWithTitle(title, now) {
    const norm = normalizeTitle(title);
    if (!norm)
        return false;
    for (const t of now.values()) {
        if (normalizeTitle(t) === norm)
            return true;
    }
    return false;
}
/**
 * Push when sidebar loader off + archive/lenta ready for open-from-push.
 */
export class AgentCompletionPush {
    emit;
    agentsIndex;
    getState;
    deps;
    prevWorking = null;
    workingPolls = new Map();
    confirmedWorking = new Set();
    idlePolls = new Map();
    lastEmitAt = new Map();
    emittedThisSession = new Set();
    pendingContent = new Set();
    contentTimers = new Map();
    lastSkipLogAt = new Map();
    workStartSig = new Map();
    appInitiated = new Map();
    static WORKING_POLLS_REQUIRED = 2;
    static IDLE_POLLS_REQUIRED = 1;
    static MIN_EMIT_GAP_MS = 8_000;
    static CONTENT_WAIT_MAX_MS = 12_000;
    static SKIP_LOG_GAP_MS = 5_000;
    static STOPPED_IN_CURSOR_SUPPRESS_MS = 60_000;
    /** Mark that work for this agent was initiated from the mobile app. */
    markAppInitiated(agentId, commandId) {
        const id = agentId.trim();
        if (!id)
            return;
        const cid = commandId.trim();
        if (!cid)
            return;
        this.appInitiated.set(id, { commandId: cid, at: Date.now() });
    }
    clearAppInitiated(agentId) {
        this.appInitiated.delete(agentId.trim());
    }
    constructor(emit, agentsIndex, getState, deps) {
        this.emit = emit;
        this.agentsIndex = agentsIndex;
        this.getState = getState;
        this.deps = deps;
    }
    observe(state) {
        if (isSuppressed()) {
            this.syncPrevWorking(state);
            return;
        }
        const index = this.agentsIndex();
        const now = snapshotSidebarWorkingAgents(state, index);
        this.trackWorkingSessions(now, state);
        this.detectIdleCompletions(now, state);
        this.retryPendingContent(state);
    }
    retryPendingContent(state) {
        for (const agentId of [...this.pendingContent]) {
            this.tryEmit(agentId, state, { sidebarIdle: true });
        }
    }
    logSkip(agentId, reason) {
        const last = this.lastSkipLogAt.get(agentId) ?? 0;
        if (Date.now() - last < AgentCompletionPush.SKIP_LOG_GAP_MS)
            return;
        this.lastSkipLogAt.set(agentId, Date.now());
        console.log(`[agent-completion-push] skip agentId=${agentId.slice(0, 12)} reason=${reason}`);
    }
    trackWorkingSessions(now, state) {
        const activeId = state.activeComposerId?.trim();
        if (activeId && isStableComposerId(activeId) && now.has(activeId)) {
            this.confirmedWorking.add(activeId);
            this.emittedThisSession.delete(activeId);
        }
        for (const id of now.keys()) {
            const polls = (this.workingPolls.get(id) ?? 0) + 1;
            this.workingPolls.set(id, polls);
            if (polls >= AgentCompletionPush.WORKING_POLLS_REQUIRED) {
                // New real work session clears "stopped by user" suppression.
                suppressByAgentUntil.delete(id);
                this.confirmedWorking.add(id);
                this.emittedThisSession.delete(id);
                this.idlePolls.delete(id);
                this.pendingContent.delete(id);
                this.clearContentTimer(id);
                if (!this.workStartSig.has(id)) {
                    this.workStartSig.set(id, messagesFingerprint(this.deps.readiness.getDisplayMessages(id)));
                }
            }
        }
        for (const id of this.workingPolls.keys()) {
            if (!now.has(id))
                this.workingPolls.delete(id);
        }
    }
    detectIdleCompletions(now, state) {
        const prev = this.prevWorking;
        if (prev === null) {
            this.prevWorking = new Map(now);
            return;
        }
        for (const [agentId] of prev) {
            if (now.has(agentId)) {
                this.idlePolls.delete(agentId);
                continue;
            }
            if (!this.confirmedWorking.has(agentId)) {
                this.logSkip(agentId, 'no-confirmed-working');
                continue;
            }
            const polls = (this.idlePolls.get(agentId) ?? 0) + 1;
            this.idlePolls.set(agentId, polls);
            if (polls < AgentCompletionPush.IDLE_POLLS_REQUIRED)
                continue;
            console.log(`[agent-completion-push] sidebar idle agentId=${agentId.slice(0, 12)} title=${(prev.get(agentId) ?? '').slice(0, 40)}`);
            this.tryEmit(agentId, state, { sidebarIdle: true });
        }
        for (const id of this.idlePolls.keys()) {
            if (now.has(id))
                this.idlePolls.delete(id);
        }
        this.prevWorking = new Map(now);
    }
    syncPrevWorking(state) {
        this.prevWorking = snapshotSidebarWorkingAgents(state, this.agentsIndex());
    }
    tryEmit(agentId, state, opts) {
        if (agentPausedForInput(state, agentId)) {
            this.logSkip(agentId, 'paused-for-input');
            return;
        }
        if (this.emittedThisSession.has(agentId)) {
            this.logSkip(agentId, 'already-emitted');
            return;
        }
        if (!this.confirmedWorking.has(agentId)) {
            this.logSkip(agentId, 'no-confirmed-working');
            return;
        }
        if (isSuppressedForAgent(agentId)) {
            this.logSkip(agentId, 'stopped-by-user');
            return;
        }
        const index = this.agentsIndex();
        const now = snapshotSidebarWorkingAgents(state, index);
        if (now.has(agentId)) {
            this.pendingContent.delete(agentId);
            this.clearContentTimer(agentId);
            this.logSkip(agentId, 'sidebar-still-working');
            return;
        }
        const title = normalizeTitle(resolveAgentChatTitle(agentId, state, index)) || 'Чат';
        if (isStillWorkingWithTitle(title, now)) {
            this.logSkip(agentId, 'same-title-still-working');
            return;
        }
        const sidebarIdle = opts?.sidebarIdle === true;
        let contentDetail = 'no-check';
        if (!opts?.forceContent) {
            if (sidebarIdle) {
                const startSig = this.workStartSig.get(agentId);
                const nowSig = messagesFingerprint(this.deps.readiness.getDisplayMessages(agentId));
                if (startSig && nowSig === startSig) {
                    this.logSkip(agentId, 'stopped-in-cursor');
                    suppressAgentForMs(agentId, AgentCompletionPush.STOPPED_IN_CURSOR_SUPPRESS_MS);
                    this.confirmedWorking.delete(agentId);
                    this.workStartSig.delete(agentId);
                    this.pendingContent.delete(agentId);
                    this.clearContentTimer(agentId);
                    return;
                }
            }
            const readiness = assessAgentCompletionReadiness(agentId, state, this.deps.readiness, { sidebarIdle });
            contentDetail = readiness.detail;
            if (!readiness.ready) {
                if (!this.pendingContent.has(agentId)) {
                    this.pendingContent.add(agentId);
                    console.log(`[agent-completion-push] wait content agentId=${agentId.slice(0, 12)} detail=${readiness.detail}`);
                    this.deps.requestContentSync(agentId, state);
                    this.scheduleContentTimeout(agentId);
                }
                return;
            }
        }
        else {
            contentDetail = 'content-timeout';
        }
        const last = this.lastEmitAt.get(agentId) ?? 0;
        if (Date.now() - last < AgentCompletionPush.MIN_EMIT_GAP_MS) {
            this.logSkip(agentId, 'emit-gap');
            return;
        }
        this.emit({
            agentId,
            chatTitle: title,
            body: AGENT_COMPLETION_PUSH_BODY,
            origin: this.appInitiated.has(agentId) ? 'app' : 'cursor',
            commandId: this.appInitiated.get(agentId)?.commandId,
        });
        this.lastEmitAt.set(agentId, Date.now());
        this.emittedThisSession.add(agentId);
        this.confirmedWorking.delete(agentId);
        this.idlePolls.delete(agentId);
        this.workingPolls.delete(agentId);
        this.pendingContent.delete(agentId);
        this.workStartSig.delete(agentId);
        this.appInitiated.delete(agentId);
        this.clearContentTimer(agentId);
        console.log(`[agent-completion-push] → relay reason=sidebar-idle+${contentDetail} agentId=${agentId.slice(0, 12)} title=${title.slice(0, 48)}`);
    }
    scheduleContentTimeout(agentId) {
        this.clearContentTimer(agentId);
        const timer = setTimeout(() => {
            this.contentTimers.delete(agentId);
            if (!this.pendingContent.has(agentId))
                return;
            console.log(`[agent-completion-push] content wait timeout agentId=${agentId.slice(0, 12)} — push anyway`);
            this.tryEmit(agentId, this.getState(), { forceContent: true, sidebarIdle: true });
        }, AgentCompletionPush.CONTENT_WAIT_MAX_MS);
        this.contentTimers.set(agentId, timer);
    }
    clearContentTimer(agentId) {
        const t = this.contentTimers.get(agentId);
        if (t)
            clearTimeout(t);
        this.contentTimers.delete(agentId);
    }
}
