'use strict';
/**
 * Per-loop directives — opt-in scoped operating rules.
 *
 * Borrowed from OpenClaw's scoped AGENTS.md pattern (src/agents/AGENTS.md,
 * test/helpers/AGENTS.md, etc.). Each loop / harness / chat surface can
 * have a markdown file at `wall-e/loops/<name>.directives.md` (or in the
 * user's `~/.wall-e/directives/<name>.md` for user-overrides). If
 * present, its content augments or replaces the inline system prompt.
 *
 * Default behavior: no directives file exists, callers use their inline
 * prompt — current behavior unchanged. Strictly additive.
 *
 * Template substitution: directives can use {{ ownerName }} and other
 * simple placeholders. We intentionally keep this stupid-simple — no
 * Liquid, no Handlebars, no embedded JS. The placeholder set is fixed:
 *   {{ ownerName }}        — brain.getOwnerName()
 *   {{ now }}              — new Date().toISOString()
 *   {{ skill }}            — the dispatching skill name (when relevant)
 *   {{ source }}            — dispatch source (when relevant, Item E)
 *
 * Loaders pass an `extra` object for custom placeholders.
 *
 * Item D of multi-agent deep-dive.
 */
const fs = require('fs');
const path = require('path');

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

let _cache = new Map(); // name -> { mtime, content }

/**
 * Resolve directives content for a given loop/handler name.
 * Search order: user > bundled. Returns null if no directives file exists.
 */
function _resolveDirectivesPath(name) {
  if (typeof name !== 'string' || !name) return null;
  // Reject path-traversal-shaped names defensively (matches the
  // hardening already applied to internal-skill-registry).
  if (name.includes('..') || path.isAbsolute(name) || name.includes('/') || name.includes('\\')) {
    return null;
  }
  const userPath = path.join(USER_DIR, `${name}.md`);
  if (fs.existsSync(userPath)) return userPath;
  const bundledPath = path.join(BUNDLED_DIR, `${name}.directives.md`);
  if (fs.existsSync(bundledPath)) return bundledPath;
  return null;
}

function _readDirectives(filePath) {
  try {
    const stat = fs.statSync(filePath);
    const cached = _cache.get(filePath);
    if (cached && cached.mtime === stat.mtimeMs) return cached.content;
    const content = fs.readFileSync(filePath, 'utf8');
    _cache.set(filePath, { mtime: stat.mtimeMs, content });
    return content;
  } catch { return null; }
}

function _substitute(template, substitutions) {
  if (!template || !substitutions) return template;
  return template.replace(/\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g, (match, key) => {
    if (Object.prototype.hasOwnProperty.call(substitutions, key)) {
      const v = substitutions[key];
      return v == null ? '' : String(v);
    }
    return match; // leave unknown placeholders intact
  });
}

/**
 * Resolve a directives string for a named loop/handler.
 *
 * @param {string} name - e.g. 'initiative', 'skill-executor', 'chat'
 * @param {Object} [extra] - extra placeholder values
 * @returns {string|null} directives content with placeholders substituted, or null if no directives file
 */
function getDirectives(name, extra = {}) {
  const filePath = _resolveDirectivesPath(name);
  if (!filePath) return null;
  const content = _readDirectives(filePath);
  if (content == null) return null;

  // Build substitution context lazily (only call brain.getOwnerName if
  // the template references it).
  const ctx = Object.assign({}, extra);
  if (ctx.ownerName == null) {
    try {
      const brain = require('../brain');
      ctx.ownerName = brain.getOwnerName ? brain.getOwnerName() : 'the user';
    } catch { ctx.ownerName = 'the user'; }
  }
  if (ctx.now == null) {
    ctx.now = new Date().toISOString();
  }

  return _substitute(content, ctx);
}

/**
 * Augment an inline system prompt with directives content if present.
 * Returns the inline prompt unchanged if no directives file.
 *
 * Default mode: append directives AFTER the inline prompt.
 * 'replace' mode: directives REPLACE the inline prompt entirely.
 */
function augmentSystemPrompt(name, inlinePrompt, opts = {}) {
  const directives = getDirectives(name, opts.extra || {});
  if (!directives) return inlinePrompt;
  if (opts.mode === 'replace') return directives;
  // default: append
  return `${inlinePrompt}\n\n--- workspace directives (${name}) ---\n${directives}`;
}

function clearDirectivesCache() {
  _cache.clear();
}

module.exports = {
  getDirectives,
  augmentSystemPrompt,
  clearDirectivesCache,
  _resolveDirectivesPath,
  _substitute,
};
