// Voice context builder.
//
// Produces the system-style instruction block we hand to the Realtime model
// (or to the Anthropic fallback stream) when a voice session opens. The block
// is intentionally short and structured: Realtime models behave best with
// concise, focused prompts.
//
// Inputs are read from durable sources — never from the network — so this is
// safe to call on every session mint. Memory respects engine-aware redaction:
// for cloud Realtime we pass only non-local-only memory; for the (future)
// local lane we'd pass everything via the `engine: 'ollama'` switch.

import { getState } from '../runtime.js';
import { getProject, listProjects } from '../projects.js';
import { recallForAgent } from '../agent-memory.js';
import { brandInstructionText, getBrandProfile } from '../brand.js';
import { getVoiceToolCatalog } from './tools.js';
import { summarizeProviders, getVoiceCapabilities } from './providers.js';

const MAIN_AGENT_ID = 'main';

function todayIso() {
  return new Date().toISOString().slice(0, 10);
}

function titleList(rows, max = 6) {
  return (rows || [])
    .filter(row => row && row.title)
    .slice(0, max)
    .map(row => `- ${row.title}`)
    .join('\n');
}

function memoryDigest(agentId, projectId) {
  // `engine: 'anthropic'` here is conservative: Realtime is a cloud LLM, so we
  // must NOT leak `local_only` memory rows. The recallForAgent helper redacts
  // them for any engine != 'ollama'.
  try {
    const rows = recallForAgent(agentId || MAIN_AGENT_ID, projectId || null, '', 'anthropic');
    return rows
      .filter(row => !row.redacted && row.content)
      .slice(0, 8)
      .map(row => `- (${row.scope}) ${String(row.content).slice(0, 220)}`)
      .join('\n');
  } catch {
    return '';
  }
}

// Universal human-speech rules applied to every voice session regardless of
// the chosen style. These exist because realtime models default to a flat,
// over-formal narration voice unless told otherwise. Concrete instructions
// (use contractions, no markdown out loud, no "as an AI") move the model
// far more than abstract ones ("sound natural").
const HUMAN_SPEECH_RULES = [
  "You are speaking out loud — not writing. Use spoken language, never markdown.",
  "Use contractions naturally: I'll, you're, don't, it's, we're, can't.",
  "Default to one or two short sentences. Long answers feel robotic.",
  "Read numbers, dates, and units the way a person says them out loud: \"fifteen thousand\", \"the seventeenth of May\", \"about ten minutes\".",
  "Never read out asterisks, dashes, bullets, hashes, slashes, or any markdown punctuation. If a list is unavoidable, say \"first… second… third\" instead.",
  "Never say \"as an AI\", \"as a language model\", \"I'd be happy to\", \"certainly\", \"sure thing\", \"of course\". Just answer.",
  "Acknowledge with a single word when it fits: \"got it\", \"okay\", \"right\", \"yep\".",
  "If you need a moment, say so out loud — \"give me a sec\", \"let me check that\" — never go silent.",
  "If a tool call is running, narrate it in one short clause — \"checking now\", \"pulling that up\".",
  "Vary your rhythm. Mix short clauses with the occasional longer one. Don't start every reply with the same word.",
  "When you don't know, say so plainly: \"I don't know\" or \"I can't confirm that\". Don't pad it.",
  "Address the operator directly — \"you\", not \"the user\".",
  "Never spell out URLs, IDs, hashes, or long tokens. Refer to them by what they are (\"the morning brief from yesterday\", \"that artifact you just opened\")."
];

const STYLE_PRESETS = {
  natural: [
    'Style — natural conversational human:',
    '- Talk like a smart friend who has been with you all day. Casual but sharp.',
    '- Contractions everywhere. Light fillers are fine in moderation ("okay so", "right", "hmm"), but never more than one per turn.',
    '- Short sentences. Real rhythm — a beat, a pause, a punchline.',
    '- Slight warmth, no sycophancy. No "great question".',
  ].join('\n'),
  warm: [
    'Style — warm and personable:',
    '- Softer phrasing, slight empathy markers when something is hard or stressful ("yeah, that is a lot", "okay, no worries").',
    '- Still concise. Warmth, not waffle.',
    '- A small acknowledging beat before delivering news.',
  ].join('\n'),
  concise: [
    'Style — concise, minimal words:',
    '- Bare-minimum sentences. Direct, no preamble.',
    '- Skip filler entirely. No "okay" or "right" openings.',
    '- Numbers, names, decisions — nothing else.',
  ].join('\n'),
  formal: [
    'Style — formal executive briefing:',
    '- Clean complete sentences. No fillers, minimal contractions.',
    '- Professional cadence — calm, clear, neutral.',
    '- Suited for read-aloud reports, meetings, on-record statements.',
  ].join('\n'),
};

function styleBlock(style) {
  const key = String(style || '').toLowerCase();
  return STYLE_PRESETS[key] || STYLE_PRESETS.natural;
}

function brandLine(brand) {
  if (!brand?.active) return '';
  const colors = (brand.colors || []).slice(0, 3).map(c => c.name).join(' / ');
  const voice = (brand.voice || []).slice(0, 4).join(', ');
  return `Brand voice: ${brand.company_name || 'madarX'}${voice ? ' — ' + voice : ''}${colors ? ' · palette ' + colors : ''}.`;
}

function spaceLine(activeSpace) {
  if (!activeSpace || !activeSpace.name) return '';
  const titles = Array.isArray(activeSpace.artifact_titles) ? activeSpace.artifact_titles : [];
  if (titles.length === 0) {
    return `Active space: ${activeSpace.name} (no artifacts currently in view).`;
  }
  const list = titles.slice(0, 8).map(t => `- ${t}`).join('\n');
  return `Active space: ${activeSpace.name} — artifacts currently in view (${titles.length}):\n${list}`;
}

function otherSpacesLine(allSpaces, activeId) {
  if (!Array.isArray(allSpaces) || allSpaces.length === 0) return '';
  const others = allSpaces.filter(s => s && s.id !== activeId);
  if (others.length === 0) return '';
  const list = others
    .slice(0, 8)
    .map(s => `- ${s.name} (${s.artifact_count || 0} artifacts)`)
    .join('\n');
  return `Other spaces the operator has open (UI tabs — artifacts grouped client-side):\n${list}`;
}

function projectLine(allProjects, activeProjectId) {
  if (!Array.isArray(allProjects) || allProjects.length === 0) return '';
  const others = allProjects.filter(p => p && p.id !== activeProjectId);
  if (others.length === 0) return '';
  const list = others
    .slice(0, 12)
    .map(p => `- ${p.name} (slug: ${p.slug})`)
    .join('\n');
  return `Other projects available — operator can ask you to query or act on any of them by name:\n${list}`;
}

/**
 * Build a Realtime / Anthropic-stream instructions block.
 *
 * @param {{ project_id?: string|null, agent_id?: string|null,
 *           active_space?: { id?: string, name?: string, artifact_titles?: string[] }|null,
 *           all_spaces?: Array<{ id: string, name: string, artifact_count: number }>|null }} [opts]
 * @returns {{ instructions: string, tools: object[], capabilities: object,
 *            date_iso: string, project: object|null, providers_configured: string[] }}
 */
export function buildVoiceContext(opts = {}) {
  const projectId = opts.project_id || null;
  const agentId = opts.agent_id || MAIN_AGENT_ID;
  const activeSpace = opts.active_space || null;
  const allSpaces = Array.isArray(opts.all_spaces) ? opts.all_spaces : null;
  const speakingStyle = opts.speaking_style || 'natural';

  const project = projectId ? getProject(projectId) : null;
  const state = getState({ project_id: projectId });
  const brand = getBrandProfile();
  const capabilities = getVoiceCapabilities();
  const providers = summarizeProviders(capabilities);
  const voiceToolNames = getVoiceToolCatalog().map(t => t.name);
  let allProjects = [];
  try { allProjects = listProjects({ status: 'all' }) || []; } catch { allProjects = []; }

  const recentArtifacts = titleList(state.artifacts || []);
  const openTasks = titleList((state.tasks || []).filter(t => t.status !== 'done' && t.status !== 'cancelled'), 6);
  const pendingApprovals = (state.pending_approvals || [])
    .slice(0, 4)
    .map(a => `- ${a.summary || a.action_type}`)
    .join('\n');
  const memory = memoryDigest(agentId, projectId);

  const sections = [];
  sections.push(
    `You are the madarX OS main agent speaking out loud as the operator's Jarvis. Today is ${todayIso()}. Keep replies short, calm, and useful; default to one or two sentences unless asked to elaborate.`,
  );
  // Human-speech baseline + style preset go up top — these shape every reply,
  // so they should reach the model before any context noise.
  sections.push('How to speak (applies to every reply):\n' + HUMAN_SPEECH_RULES.map(r => '- ' + r).join('\n'));
  sections.push(styleBlock(speakingStyle));
  if (project) {
    sections.push(`Active project: ${project.name} (${project.slug}). ${project.description || ''}`.trim());
  } else {
    sections.push('No project is currently selected — replies should reflect the personal workspace.');
  }
  const sl = spaceLine(activeSpace);
  if (sl) sections.push(sl);
  const osl = otherSpacesLine(allSpaces, activeSpace?.id);
  if (osl) sections.push(osl);
  const pl = projectLine(allProjects, project?.id);
  if (pl) sections.push(pl);
  const bl = brandLine(brand);
  if (bl) sections.push(bl);
  const bbody = brandInstructionText(brand);
  if (bbody) sections.push(bbody);
  if (openTasks) sections.push(`Open tasks:\n${openTasks}`);
  if (recentArtifacts) sections.push(`Recent artifacts in current scope (most recent first):\n${recentArtifacts}`);
  if (pendingApprovals) {
    sections.push(`Owner approvals waiting (do NOT auto-bypass — surface and confirm):\n${pendingApprovals}`);
  }
  if (memory) sections.push(`Durable memory the operator chose to share:\n${memory}`);
  if (providers.length) sections.push(`Voice providers configured: ${providers.join(', ')}.`);
  sections.push(
    `You have the FULL main-agent tool surface — same catalog the typed dashboard agent uses. Every call goes through the madarX approval, allowlist, rate-limit, and audit gates; never claim a tool ran until the call returns success. Available tools: ${voiceToolNames.join(', ')}.`,
  );
  sections.push(
    'Cross-scope access: find_artifacts searches every project, not just the active one. Call list_projects to enumerate projects on demand. Call get_state with project_id="<slug or id>" to read a specific project, or with project_id=null to read across all projects. Spaces are client-side UI tabs that group artifacts — they do not gate your reach; any artifact in any space is reachable via find_artifacts.',
  );
  sections.push(
    'Rules: never speak API keys, ephemeral tokens, or secret values aloud. If a tool returns approval_required, tell the operator one short sentence and stop. Prefer artifact creation for anything longer than ~30 spoken seconds. Never invent artifact IDs — call find_artifacts or get_state first. To read an artifact\'s body, call read_artifact with the id from find_artifacts; do NOT claim you have read it until that call returns content. ARTIFACT CREATION — choose the right tool: (1) For any VISUAL result the operator expects to look designed — a list page, snapshot, brief, report, dashboard, presentation, landing page, "make a page for X", "show me X" — call generate_ui with a specific description that includes the actual content/data to render. generate_ui produces a full styled HTML document using the madarX brand system (typography, color, layout). (2) For plain written notes, scratch summaries, or pure markdown exports the operator asked for explicitly, use create_artifact with kind="markdown". Never use create_artifact for visual outputs — it stores raw text and the dashboard renders it as a basic document, not a designed page. Whenever the operator asks for a summary, brief, page, doc, or report, create the artifact unprompted as the result of the turn — do not ask for permission first.',
  );

  const instructions = sections.join('\n\n').slice(0, 7800);
  return {
    instructions,
    tools: voiceToolNames,
    capabilities,
    date_iso: todayIso(),
    project: project ? { id: project.id, slug: project.slug, name: project.name } : null,
    active_space: activeSpace ? { id: activeSpace.id, name: activeSpace.name } : null,
    providers_configured: providers,
  };
}

export function buildVoiceInstructions(opts = {}) {
  return buildVoiceContext(opts).instructions;
}
