/**
 * state.js — Singleton application state + lightweight event bus.
 * All views read from S; only loaders write to S.
 * Subscribers call bus.on(key, fn) and are notified via bus.emit(key, value).
 */

export const API_VERSION = '4';
export const MAX_EVENTS  = 200;
export const TICKER_MAX  = 50;
export const CACHE_TTL   = 3_000;

export const PLAN_CAPS = {
    free:       { maxProjects: 10,       maxMemoryEntries: 5_000,   maxOperatives: 1,        skillProfile: 'foundation',   darwin: false },
    pro:        { maxProjects: 10,       maxMemoryEntries: 10_000,  maxOperatives: 5,        skillProfile: 'all-builtin',  darwin: true  },
    team:       { maxProjects: Infinity, maxMemoryEntries: 100_000, maxOperatives: Infinity, skillProfile: 'all-custom',   darwin: true  },
    enterprise: { maxProjects: Infinity, maxMemoryEntries: Infinity,maxOperatives: Infinity, skillProfile: 'all-registry', darwin: true  },
};

/** Singleton state bag. Mutable by loaders; never mutate from views. */
export const S = {
    // Navigation
    tab:       'board',
    connected: false,
    startTime: Date.now(),

    // Context scope
    scope:     { projectId: null, agentId: null, runtimeId: null },
    projects:  [],
    workspace: null,

    // Board / cockpit
    tokensSummary:  null,
    tokensLifetime: null,
    operateSurface: null,
    synapseHealth:  [],
    synapseHealthRaw: null,
    events:         [],
    ticker:         [],
    spark:          null,

    // Tasks
    worklist:    null,
    workItems:   [],
    escalations: [],
    taskView:    'board',
    taskFilter:  'all',
    taskSearch:  '',

    // Workforce / agents
    synapseTeams:  null,
    archDispatch:  null,
    approvals:     [],
    missions:      [],
    agentSubTab:   'manage',
    hireSubTab:    'specialists',
    assetsSurface: null,

    // Memory
    memories:      [],
    memHealth:     null,
    ragCols:       [],
    gNodes:        [],
    gLinks:        [],
    temporalCursor: null,
    memQuery:      '',
    graphSim:      null,
    memLane:       'all',
    selectedMemoryIds: [],
    memorySurface: null,
    topology:      null,

    // Knowledge
    ragCollections: [],
    ragCreating:    false,
    ragIngesting:   null,

    // Runs
    runs:       [],
    ledger:     null,
    workerPlan: null,

    // Orchestration pipeline (live decomposition + completion from SSE)
    lastDecomposition: null,
    lastCompletion:    null,
    lastDecisionSpine: null,
    contextLogRuns: [],
    contextLogSelectedRunId: null,
    contextLogSpine: null,
    contextLogLearning: null,
    learningSurface: null,
    learningGraph: null,
    learningSelectedPacketId: null,

    // Neural Stream HUD ring buffer (latest SSE events, restored from v3.8.0)
    neuralStream: [],

    // Assets sub-tabs
    assetsSubTab: 'skills',

    // Trust
    worktreeHealth: null,
    trustSurface:   null,
    auditEvents:    [],

    // First-run hire flow
    curatedSpecialists: null,

    // License / billing
    license:    null,
    capWarnings:[],

    // Config / health
    healthData: null,
    runtimes:   [],

    // System errors: persistent banner entries (replaces one-shot toast)
    systemErrors: /** @type {Array<{code:string,message:string,hint?:string,at:number}>} */ ([]),

    // Byzantine consensus: live vote sessions keyed by proposalId
    byzantineVotes: /** @type {Map<string,{proposalId:string,agentCount:number,quorum:number,votesFor:number,votesAgainst:number,votesPending:number,accepted:boolean|null,agentVotes:Array<{agentId:string,approved:boolean,reliability:number}>,startedAt:number}>} */ (new Map()),
};

/* ── Lightweight event bus ── */
const _listeners = new Map();

export const bus = {
    /** Subscribe: fn is called with the new value whenever emit(key) fires. */
    on(key, fn) {
        if (!_listeners.has(key)) _listeners.set(key, new Set());
        _listeners.get(key).add(fn);
        return () => _listeners.get(key)?.delete(fn);
    },
    /** Emit: notify all subscribers for this key. */
    emit(key, value) {
        _listeners.get(key)?.forEach(fn => fn(value));
    },
};
