/**
 * @file src/utilities/planInjection.js
 * @description Build the "AGENT WORKING PLAN (from memory)" block
 *   that gets appended to the system prompt every turn.
 *
 * Design points (decided alongside the user):
 *   - Per-agent visibility (Plan A). A plan written by agent X is
 *     injected ONLY into agent X's system prompt.
 *     TODO (future Plan C): introduce a second prefix
 *     `team-plan/<slug>` whose injection is scoped to the agent's
 *     TEAM. Implementation lives at the scheduler layer (cross-
 *     agent join) rather than here.
 *   - Appended at the END of the (already-extended) system prompt
 *     so the immutable prefix stays cache-friendly.
 *   - Size-guarded so a plan dump can't blow up the prompt: per-plan
 *     soft cap, total hard cap, truncation warning the agent sees
 *     and can act on.
 */

/**
 * Rough token estimator. 4 chars/token is the canonical fudge for
 * English+code mix; it's wrong for any specific text but consistent
 * enough to draw a usable line.
 */
const APPROX_CHARS_PER_TOKEN = 4;
function approxTokens(s) {
  return Math.ceil((s || '').length / APPROX_CHARS_PER_TOKEN);
}

// Per-plan soft cap — a plan-of-plans bigger than this gets
// truncated with a clear truncation marker + a hint to summarize.
const PER_PLAN_TOKEN_CAP = 2000;
// Cross-plan hard cap across all plan/* entries on a single turn.
const TOTAL_PLAN_TOKEN_CAP = 8000;

const HEADER_OPEN  = '\n\n═══ AGENT WORKING PLAN (from memory) ═══';
const HEADER_CLOSE = '═══ END WORKING PLAN ═══';
const TRUNCATION_NOTE = '\n[…content truncated by injection size guard; consider summarizing this plan]';

/**
 * Build the plan block from a list of plan/* memories.
 *
 * @param {Array} planMemories  Output of memoryService.getPlanMemories(agentId).
 *   Each item: { title, content, updatedAt, createdAt, ... }
 * @returns {{ block: string, truncated: Array<{title: string, reason: string}> }}
 *   `block` is the formatted markdown header — empty string when no
 *   plan memories exist. `truncated` lists any plans that got cut so
 *   the caller can surface a warning.
 */
export function buildPlanBlock(planMemories) {
  if (!Array.isArray(planMemories) || planMemories.length === 0) {
    return { block: '', truncated: [] };
  }

  const truncated = [];
  const lines = [HEADER_OPEN];
  let totalTokens = 0;

  for (const memo of planMemories) {
    const title = memo.title || 'plan/(untitled)';
    const updatedAt = (memo.updatedAt || memo.createdAt || '').slice(0, 19).replace('T', ' ');
    let content = String(memo.content || '');

    // Per-plan soft cap.
    if (approxTokens(content) > PER_PLAN_TOKEN_CAP) {
      const charCap = PER_PLAN_TOKEN_CAP * APPROX_CHARS_PER_TOKEN;
      content = content.slice(0, charCap) + TRUNCATION_NOTE;
      truncated.push({ title, reason: `exceeds ${PER_PLAN_TOKEN_CAP}-token per-plan cap` });
    }

    // Cross-plan hard cap.
    const planText = `\n\n▸ ${title}\n   Last updated: ${updatedAt}\n   ───\n${content}`;
    const planTokens = approxTokens(planText);
    if (totalTokens + planTokens > TOTAL_PLAN_TOKEN_CAP) {
      truncated.push({
        title,
        reason: `dropped — running total would exceed ${TOTAL_PLAN_TOKEN_CAP}-token cross-plan cap`,
      });
      continue;
    }
    totalTokens += planTokens;
    lines.push(planText);
  }

  // If everything got truncated below the header, we emitted only
  // the open marker — emit a "nothing fit" message so the agent
  // knows what happened.
  if (lines.length === 1) {
    lines.push('\n\n(All plan/* memories exceeded the injection size guard. Summarize one.)');
  }

  lines.push('\n\n' + HEADER_CLOSE);
  return { block: lines.join(''), truncated };
}

// Exposed for tests + for the scheduler's truncation-warning surface.
export const _PLAN_INJECTION_INTERNALS = {
  PER_PLAN_TOKEN_CAP,
  TOTAL_PLAN_TOKEN_CAP,
  approxTokens,
  HEADER_OPEN,
  HEADER_CLOSE,
};
