/**
 * engine/bridge.js — Constellation-bridge config + marker accessors.
 *
 * Pure helpers for the `minions bridge ...` subcommand and any future
 * code that wants to inspect or mutate the read-only Constellation bridge
 * surface. The bridge polling logic itself lives in the Constellation
 * repo (P-wi1-bridge-readonly) — this file owns ONLY the on/off flag, the
 * marker-file contract, and atomic config writes for the toggle.
 *
 * Strict semantics: only the literal boolean `true` enables the bridge.
 * Mirror this check on the Constellation reader to avoid truthy coercion
 * silently flipping bridge state on a typo'd `"enabled": "false"`.
 */

const path = require('path');
const shared = require('./shared');

const {
  MINIONS_DIR,
  CONSTELLATION_BRIDGE_MARKER_PATH,
  CONSTELLATION_BRIDGE_MARKER_SCHEMA_VERSION,
  safeJson,
  mutateJsonFileLocked,
} = shared;

const CONFIG_PATH = path.join(MINIONS_DIR, 'config.json');

/**
 * Strict check: `true` ⇔ bridge enabled. Any other shape (missing field,
 * non-object, string "true", etc.) returns false.
 */
function isBridgeEnabled(config) {
  return config?.engine?.constellationBridge?.enabled === true;
}

/**
 * Read the cross-repo marker written by the Constellation agent. Returns
 * `null` when the file is missing, unreadable, or schema-mismatched.
 *
 * Marker shape (see ENGINE_DEFAULTS.constellationBridge docstring):
 *   { schemaVersion: 1, lastSeenAt: ISO8601,
 *     agentVersion?: string, source?: 'constellation-agent' }
 */
function readBridgeMarker(markerPath = CONSTELLATION_BRIDGE_MARKER_PATH) {
  const raw = safeJson(markerPath);
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
  if (raw.schemaVersion !== CONSTELLATION_BRIDGE_MARKER_SCHEMA_VERSION) return null;
  if (typeof raw.lastSeenAt !== 'string') return null;
  return {
    schemaVersion: raw.schemaVersion,
    lastSeenAt: raw.lastSeenAt,
    agentVersion: typeof raw.agentVersion === 'string' ? raw.agentVersion : null,
    source: typeof raw.source === 'string' ? raw.source : null,
  };
}

/**
 * Flip `config.engine.constellationBridge.enabled` atomically via
 * mutateJsonFileLocked. Returns `{ previous: bool, current: bool }`.
 * `configPath` override exists for unit tests.
 */
function setBridgeEnabled(enabled, configPath = CONFIG_PATH) {
  const next = enabled === true;
  let previous = false;
  mutateJsonFileLocked(configPath, (cfg) => {
    if (!cfg || typeof cfg !== 'object' || Array.isArray(cfg)) return cfg;
    cfg.engine = cfg.engine || {};
    cfg.engine.constellationBridge = cfg.engine.constellationBridge || {};
    previous = cfg.engine.constellationBridge.enabled === true;
    cfg.engine.constellationBridge.enabled = next;
    return cfg;
  });
  return { previous, current: next };
}

/**
 * Human-readable relative age string (e.g. "12s ago", "3m ago", "2h ago").
 * Caps at "1d+ ago" — anything older than the bridge polling cadence is
 * already actionable as "stale".
 */
function formatRelativeAge(isoTimestamp, nowMs = Date.now()) {
  const t = Date.parse(isoTimestamp);
  if (!Number.isFinite(t)) return '(unknown)';
  const deltaSec = Math.max(0, Math.round((nowMs - t) / 1000));
  if (deltaSec < 60) return `${deltaSec}s ago`;
  if (deltaSec < 3600) return `${Math.round(deltaSec / 60)}m ago`;
  if (deltaSec < 86400) return `${Math.round(deltaSec / 3600)}h ago`;
  return '1d+ ago';
}

/**
 * Compose the small stable projection the Constellation bridge consumes
 * from `/api/status`. Kept narrow on purpose: full /api/status is large,
 * unstable, and may surface unrelated local state. New fields go behind
 * a deliberate schema version bump.
 */
function projectStatusForBridge(statusJson) {
  if (!statusJson || typeof statusJson !== 'object') return null;
  const dispatch = statusJson.dispatch || {};
  const queueCount = (arr) => (Array.isArray(arr) ? arr.length : 0);
  return {
    engineState: statusJson.control?.state ?? null,
    enginePid: statusJson.control?.pid ?? null,
    minionsVersion: statusJson.version ?? null,
    agentCount: Array.isArray(statusJson.agents) ? statusJson.agents.length : null,
    activeAgentCount: Array.isArray(statusJson.agents)
      ? statusJson.agents.filter(a => a && a.status && a.status !== 'idle').length
      : null,
    dispatchPending: queueCount(dispatch.pending),
    dispatchActive: queueCount(dispatch.active),
    dispatchCompleted: queueCount(dispatch.completed),
    projectCount: Array.isArray(statusJson.projects) ? statusJson.projects.length : null,
  };
}

module.exports = {
  isBridgeEnabled,
  readBridgeMarker,
  setBridgeEnabled,
  formatRelativeAge,
  projectStatusForBridge,
  CONSTELLATION_BRIDGE_MARKER_PATH,
  CONSTELLATION_BRIDGE_MARKER_SCHEMA_VERSION,
};
