'use strict';
/**
 * Versioned skill snapshot.
 *
 * Borrowed from OpenClaw's resolveCronSkillsSnapshot (skills-snapshot.ts):
 * each cron run resolves a snapshot keyed by `{ snapshotVersion, skillFilter }`.
 * If both match the existing snapshot, it's reused; otherwise rebuilt.
 *
 * Wall-E's adaptation: each agent.js outer tick takes a snapshot at the
 * top, threads it through runDueSkills/think/reflect. Within a tick, all
 * consumers see the SAME enable-state and metadata — no race when a
 * skill is enabled/disabled mid-tick.
 *
 * Version stamp:
 *   sha1(
 *     latest_mtime(BUNDLED_DIR + USER_DIR + REGISTRY_DIR)
 *     + skill-id-list-hash(DB)
 *     + skill-enabled-bitmap(DB)
 *   )
 *
 * Responsiveness option (per user request):
 *   WALLE_SKILL_SNAPSHOT_RESPONSIVE=1 → cache disabled, rebuild every call.
 *   Use when UI changes need immediate effect (e.g. demos). Default: cached
 *   per-tick — UI changes show up next outer tick.
 *
 * Item B of skill-dispatch deep-dive.
 */
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

const BUNDLED_DIR = path.join(__dirname, '_bundled');
const USER_DIR = path.join(process.env.HOME || '', '.wall-e', 'skills');
const REGISTRY_DIR = path.join(process.env.HOME || '', '.wall-e', 'registry-skills');

let _currentSnapshot = null;

function _isResponsive() {
  if (process.env.WALLE_SKILL_SNAPSHOT_RESPONSIVE === '1') return true;
  // Also check kv if brain is available — lets users toggle without restart
  try {
    const brain = require('../brain');
    const v = brain.getKv && brain.getKv('skill_snapshot_responsive');
    return v === '1' || v === 'true';
  } catch { return false; }
}

function _latestMtimeOf(dir) {
  if (!fs.existsSync(dir)) return 0;
  let max = 0;
  let stack = [dir];
  while (stack.length) {
    const cur = stack.pop();
    let stat;
    try { stat = fs.statSync(cur); } catch { continue; }
    if (stat.mtimeMs > max) max = stat.mtimeMs;
    if (stat.isDirectory()) {
      let entries;
      try { entries = fs.readdirSync(cur); } catch { continue; }
      for (const e of entries) {
        if (e.startsWith('.')) continue;
        stack.push(path.join(cur, e));
      }
    }
  }
  return max;
}

function _computeVersion(dbSkills) {
  const dirMtime = Math.max(
    _latestMtimeOf(BUNDLED_DIR),
    _latestMtimeOf(USER_DIR),
    _latestMtimeOf(REGISTRY_DIR),
  );
  // Encode DB skill set + their enabled flags + auto-disable state.
  // Deliberately excludes last_run / last_result / success_count — those
  // shift during normal dispatch and would invalidate the cache every
  // tick, defeating the freeze semantic. The snapshot's job is to
  // freeze "which skills exist + which are eligible" within a tick.
  const enabledBitmap = (dbSkills || [])
    .map(s => `${s.id}:${s.enabled || 0}:${s.auto_disabled_at || ''}`)
    .sort()
    .join('|');
  const hash = crypto.createHash('sha1');
  hash.update(String(dirMtime));
  hash.update('::');
  hash.update(enabledBitmap);
  return hash.digest('hex').slice(0, 16);
}

/**
 * Returns the current skill snapshot. Cached by version stamp; rebuilt
 * when mtime or DB state changes (or always when responsive mode is on).
 *
 * @param {Object} [opts]
 * @param {Object} [opts.brain] - brain module (defaults to require('../brain'))
 * @param {boolean} [opts.force] - skip cache, rebuild
 * @returns {{
 *   version: string,
 *   builtAt: string,
 *   skills: Array<Object>,
 *   fromCache: boolean,
 *   responsive: boolean,
 * }}
 */
function getCurrentSkillSnapshot(opts = {}) {
  const brain = opts.brain || require('../brain');
  const responsive = _isResponsive();
  const dbSkills = brain.listSkills({});
  const version = _computeVersion(dbSkills);

  if (!opts.force && !responsive && _currentSnapshot && _currentSnapshot.version === version) {
    return Object.assign({}, _currentSnapshot, { fromCache: true });
  }

  _currentSnapshot = {
    version,
    builtAt: new Date().toISOString(),
    skills: dbSkills,
    fromCache: false,
    responsive,
  };
  return _currentSnapshot;
}

function invalidateSkillSnapshot() {
  _currentSnapshot = null;
}

module.exports = {
  getCurrentSkillSnapshot,
  invalidateSkillSnapshot,
  _computeVersion, // for tests
};
