import { chatWithClaude } from './claude.js';
import { currentDateInstruction } from './agent-config.js';
import { executeTool, getTools } from './orchestrator-tools.js';
import { handleOrchestratorCommand } from './runtime.js';
import { publishLiveEvent } from './live-events.js';
import { resolveAnthropicApiKey } from './anthropic-key.js';
import { INTENT_WITHOUT_ACTION_RE } from './intent-without-action.js';

function summarizeInput(toolName, input = {}) {
  if (!input || typeof input !== 'object') return '';
  if (toolName === 'run_agent') return input.agent_name || input.agent_id || '';
  if (toolName === 'web_search') return input.query || '';
  if (toolName === 'web_fetch' || toolName === 'web_extract') return input.url || '';
  if (toolName === 'file_read' || toolName === 'file_search' || toolName === 'directory_list') return input.path || input.pattern || '';
  if (toolName === 'file_write' || toolName === 'file_patch') return input.path || '';
  if (toolName === 'terminal_run' || toolName === 'process_start') return `${input.command || ''} ${Array.isArray(input.args) ? input.args.join(' ') : ''}`.trim();
  if (toolName === 'create_agent') return input.name || '';
  if (toolName === 'whatsapp_send') return input.chat_id || '';
  return JSON.stringify(input).slice(0, 140);
}

const SYSTEM_PROMPT_BASE = `You are the madarX Main Agent, replying to the owner over WhatsApp.

WhatsApp is a quick conversational channel — NOT the canvas. You CAN still create agents, artifacts, schedules, and long-running Hermes jobs from here — they land in the dashboard at http://127.0.0.1:8787/ and you reply via WhatsApp with a short summary + where to find them.

WHATSAPP REPLY RULES (non-negotiable for the text you send back):
- Plain text only. WhatsApp does not render markdown — no **bold**, no headings, no code fences, no tables, no bullets with - or *.
- For lists, use natural prose ("First, ... Second, ... Third, ...") or a numbered list with "1." "2." "3." each on a new line.
- Aim for 1-4 short paragraphs. Hard cap ~1500 characters.
- Never paste artifact CONTENT (HTML, long markdown reports, full tables) into the reply. The artifact lives in the dashboard; the reply is a 1-3 sentence summary plus where to view it.
- Artifacts you create (generate_ui, create_artifact, run_agent results) are auto-attached as .html files in the same chat — you do NOT need to tell the user "see attached" or copy-paste content. Just say what you made and where it also lives (e.g. "Built '<title>' — file is attached and viewable in the dashboard").

ACTION-IN-SAME-TURN RULE (non-negotiable, load-bearing):
- NEVER reply with intent-only text like "Let me create one for you", "I'll generate it now", "Creating that now…", "One moment, building…", "Hold on, fetching…". This channel cannot follow up on its own — once you stop emitting tool calls, your turn ends and the user is left hanging.
- If you intend to perform an action (create an artifact, generate UI, run an agent, search the web, fetch a URL, start a Hermes job, send a WhatsApp message), you MUST emit the tool_use call in the SAME assistant turn. Text saying "I will do X" without a tool_use beside it is a bug — pick the tool, call it, and only then summarize.
- Concrete example: user asks "send me the latest AI news brief". You call find_artifacts. Result is empty. Your NEXT turn must directly call generate_ui (or web_search → generate_ui) WITH text describing what you're building — not a standalone "Let me create one for you" reply.
- If the request is too ambiguous to act, ask ONE short clarifying question. Otherwise act.

WHAT YOU CAN DO (call the tool, then reply with a short summary):
- Live data: web_search + web_fetch for news, prices, schedules, addresses. Reply with the answer in prose.
- Inspect state: get_state for "what agents do I have", "what's running", "pending approvals".
- Retrieve past artifacts: find_artifacts (filters by title_query, agent_name, agent_id, kind) returns recent matches. Use this when the owner asks for "the latest X", "yesterday's brief", "send me the conflict report" — search FIRST, then send.
- Deliver a past artifact: send_artifact_to_whatsapp (pass artifact_id from find_artifacts; chat_id defaults to the owner's current chat on this channel). If the bridge is not paired the tool errors and you should pass that message back. After a successful send, reply with one line ("Sent '<title>' — also viewable in the dashboard") — do NOT also try to paste content.
- Create / schedule sub-agents: create_agent + upsert_schedule. Reply: "Created '<name>'. Runs <schedule>. View at /agents".
  If the owner says they want the scheduled output sent to them on WhatsApp ("send it to me on whatsapp", "deliver it here", "ping me with the report"), pass whatsapp_notify_chat_id = the owner's chat id on create_agent. For existing agents, use set_agent_whatsapp_notify to enable, change, or disable (pass empty string to disable).
  Leave engine unset by default — the system default is "claude-code" (Claude Code CLI with .claude/settings.json permissions). Only override to "anthropic" when the agent specifically needs madarx marketplace tools that don't exist under CC: create_artifact, generate_ui, whatsapp_send, competitor_report, marketing_plan.
- Run an existing sub-agent on demand: run_agent. The owner saying "run X", "trigger X", "execute X now", "do today's X run" means this tool, NOT competitor_report/marketing_plan. Pass agent_name (fuzzy match) or agent_id. The agent's tool calls stream live to the dashboard runtime popover. Reply with a short summary of what it produced — point to /agents or /reports for the full result.
- Generate a designed page or document artifact: generate_ui (preferred for landing pages, news feeds, reports, dashboards, decks). Reply: "Built '<title>' — open the dashboard to view it on the canvas".
- Create a raw artifact when generate_ui is not appropriate: create_artifact.
- Long-running browser/code work: start_hermes_job (e.g., "run this build", "scrape this site", "QA this URL"). Reply: "Hermes job started — id <short-id>, I'll let you know when it's done".
- Canned runs: competitor_report, marketing_plan, heartbeat_tick.
- Local workspace inspection (read-only, no approval): file_read, directory_list, file_search.
- whatsapp_send is for proactive owner alerts later. Do NOT call it in response to an inbound message — just reply directly through the normal channel.

APPROVAL-GATED TOOLS:
- file_write, file_patch, terminal_run, process_start/kill, vercel_create_deployment all require owner approval. If you call one, the response will be {status: "approval_required", approval_request_id}. Tell the owner: "Queued <action> for your approval. Approve at /approvals (id: <short>)" and stop.

CONVERSATION CONTEXT (load-bearing):
- The messages above are the recent history of THIS WhatsApp conversation with the owner. Treat references like "it", "that", "the news", "the report", "the one you made" as pointing to what was discussed earlier in this thread — do NOT ask "what do you mean?" if the referent is obvious from the history.
- Tool calls and their results from earlier turns are NOT in the history (only the text you sent). So you may know the owner asked about "the latest AI news" but NOT the artifact_id of anything you made. Resolve referents at action time:
  - When the owner says add / move / save / put "it" / "that" / "the X" into a space (or asks where it is): FIRST call find_artifacts (title_query derived from the conversation, e.g. "AI news") to locate it.
  - If find_artifacts returns nothing, the thing was likely only chat text and was never saved — CREATE it now from the conversation content (generate_ui for a page/report, or create_artifact), THEN call move_artifact_to_space (or create it directly in the target space).
  - Drive this to completion in the SAME turn-loop: find/create → then place. Never reply "what do you want to add?" when the history already says what — and never promise-and-stop.

WHEN UNSURE:
- Greeting ("hi", "hello") → one-line ack + 2-3 example things you can do.
- Ambiguous request → ask ONE short clarifying question.
- Never invent data. If a tool fails, say so plainly with the error.`;

// Built per call so the date is fresh in the long-running WhatsApp daemon.
function systemPrompt() {
  return `${currentDateInstruction()}\n\n${SYSTEM_PROMPT_BASE}`;
}

const MAX_TURNS = 6;
const MAX_TOKENS = 1536;
const TOOL_RESULT_CHARS = 8000;

function textFromResponse(response) {
  return (response?.content || [])
    .filter(part => part?.type === 'text' || typeof part?.text === 'string')
    .map(part => part.text || '')
    .join('\n')
    .trim();
}

function toolUsesFromResponse(response) {
  return (response?.content || []).filter(part => part?.type === 'tool_use' && part.name && part.id);
}

function safeJson(value) {
  try { return JSON.stringify(value).slice(0, TOOL_RESULT_CHARS); } catch { return String(value).slice(0, TOOL_RESULT_CHARS); }
}

// Exposed for tests (tests/whatsapp-agent-strip.test.js). Do not import this
// in production code paths — use stripMarkdown internally.
export const stripMarkdownForTest = (text) => stripMarkdown(text);

function stripMarkdown(text) {
  if (!text) return '';
  return text
    // Code fences: KEEP the inner content as plain text (install commands,
    // shell snippets, code samples — WhatsApp can't render syntax but the user
    // can still read and copy). Strip the fence markers + optional lang tag.
    .replace(/```(?:[a-zA-Z0-9_+\-]*)\n?([\s\S]*?)```/g, (_match, body) => body.trim())
    .replace(/`([^`]+)`/g, '$1')
    .replace(/\*\*([^*]+)\*\*/g, '$1')
    .replace(/\*([^*]+)\*/g, '$1')
    .replace(/__([^_]+)__/g, '$1')
    .replace(/^#{1,6}\s+/gm, '')
    .replace(/^\s*[-*]\s+/gm, '• ')
    .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1 ($2)')
    .replace(/\n{3,}/g, '\n\n')
    .trim();
}

function fallbackReply(prompt, from) {
  const run = handleOrchestratorCommand({ prompt, channel: 'whatsapp', from });
  return { reply_text: run?.reply_text || 'Received.', run_id: run?.id || null };
}

// Merge consecutive same-role turns (joining text with a blank line) then drop a
// leading assistant turn. The Anthropic Messages API requires roles to strictly
// alternate and start with `user`; reconstructed text history + the appended
// current turn can violate both (e.g. a prior reply's send() failed so the last
// logged row is inbound → two consecutive user turns). Normalizing the FULL array
// through one path guarantees the invariant regardless of how history ended.
// Exposed for tests.
export function normalizeConversation(turns) {
  const merged = [];
  for (const turn of turns) {
    const content = typeof turn?.content === 'string' ? turn.content.trim() : turn?.content;
    if (!content) continue;
    const last = merged[merged.length - 1];
    if (last && last.role === turn.role && typeof last.content === 'string' && typeof content === 'string') {
      last.content = `${last.content}\n\n${content}`;
    } else {
      merged.push({ role: turn.role, content });
    }
  }
  while (merged.length && merged[0].role !== 'user') merged.shift();
  return merged;
}

export async function runWhatsAppOrchestrator({ prompt, from, history = [] }) {
  if (!resolveAnthropicApiKey()) {
    return fallbackReply(prompt, from);
  }
  const tools = getTools();
  const messages = normalizeConversation([
    ...(Array.isArray(history) ? history : []),
    { role: 'user', content: String(prompt || '') },
  ]);
  // Empty prompt + empty history collapses to []; the API requires ≥1 message.
  if (!messages.length) messages.push({ role: 'user', content: String(prompt || '(empty message)') });
  let lastText = '';
  let turnsUsed = 0;
  let toolCalls = 0;
  let hitCap = false;

  let intentNudgeUsed = false;
  for (let turn = 0; turn < MAX_TURNS; turn += 1) {
    turnsUsed = turn + 1;
    let response;
    try {
      response = await chatWithClaude(messages, systemPrompt(), tools, { max_tokens: MAX_TOKENS });
    } catch (err) {
      return { reply_text: `I hit an error talking to the model: ${err.message}`, run_id: null };
    }
    const text = textFromResponse(response);
    if (text) lastText = text;
    const toolUses = toolUsesFromResponse(response);
    if (!toolUses.length) {
      // Safety net: model produced text-only "Let me create one for you" /
      // "I'll generate it now" style reply WITHOUT calling any tool. The
      // ACTION-IN-SAME-TURN rule in the system prompt forbids this, but
      // models occasionally still emit it — especially right after a "no
      // results" tool result. Re-prompt once with a direct instruction so
      // the user doesn't get a promise + silence.
      if (!intentNudgeUsed && text && INTENT_WITHOUT_ACTION_RE.test(text) && turn < MAX_TURNS - 1) {
        intentNudgeUsed = true;
        messages.push({ role: 'assistant', content: response.content });
        messages.push({
          role: 'user',
          content: 'You said you would do that, but no tool_use block was attached so nothing actually ran. Call the right tool NOW in this turn (generate_ui, create_artifact, run_agent, web_search, start_hermes_job, etc.). Do not reply with another promise — emit the tool_use.',
        });
        continue;
      }
      break;
    }
    if (turn === MAX_TURNS - 1) {
      // Loop will exit after this turn — keep the assistant message but skip executing
      // the tool calls; we'll force a synthesis turn next.
      messages.push({ role: 'assistant', content: response.content });
      hitCap = true;
      break;
    }

    messages.push({ role: 'assistant', content: response.content });
    const toolResults = [];
    for (const toolUse of toolUses) {
      toolCalls += 1;
      const inputSummary = summarizeInput(toolUse.name, toolUse.input);
      publishLiveEvent({
        type: 'agent_tool_start',
        agent_id: 'main-orchestrator',
        agent_name: 'madarX Main Agent',
        tool: toolUse.name,
        input_summary: inputSummary,
      });
      try {
        const result = await executeTool(toolUse.name, toolUse.input || {}, { chat_id: from || null, channel: 'whatsapp' });
        const isError = Boolean(result?.ok === false && result?.status !== 'approval_required');
        publishLiveEvent({
          type: 'agent_tool_result',
          agent_id: 'main-orchestrator',
          agent_name: 'madarX Main Agent',
          tool: toolUse.name,
          status: isError ? 'error' : 'done',
          isError,
        });
        toolResults.push({
          type: 'tool_result',
          tool_use_id: toolUse.id,
          content: safeJson(result),
          is_error: isError,
        });
      } catch (error) {
        publishLiveEvent({
          type: 'agent_tool_result',
          agent_id: 'main-orchestrator',
          agent_name: 'madarX Main Agent',
          tool: toolUse.name,
          status: 'error',
          isError: true,
          message: error.message,
        });
        toolResults.push({
          type: 'tool_result',
          tool_use_id: toolUse.id,
          content: error.message,
          is_error: true,
        });
      }
    }
    messages.push({ role: 'user', content: toolResults });
  }

  // Force a final synthesis turn if the loop ended without a textual reply
  // (e.g., model kept calling tools and we hit MAX_TURNS, or the last turn was
  // tool_use only). One more call with NO tools forces text output.
  if (!lastText.trim()) {
    // Need to satisfy the API contract: the last message must not be a hanging
    // assistant tool_use block. If it is, append synthetic tool_results saying
    // "skipped, please synthesize from what you already have".
    const tail = messages[messages.length - 1];
    if (tail?.role === 'assistant' && Array.isArray(tail.content)) {
      const hangingUses = tail.content.filter(b => b?.type === 'tool_use');
      if (hangingUses.length) {
        messages.push({
          role: 'user',
          content: hangingUses.map(b => ({
            type: 'tool_result',
            tool_use_id: b.id,
            content: 'Tool budget exhausted. Synthesize a plain-text answer for WhatsApp from the data you already gathered above. Do not call any more tools.',
            is_error: false,
          })),
        });
      }
    }
    messages.push({
      role: 'user',
      content: 'Wrap up: respond now in plain text only — no tool calls. Summarize what you found above for WhatsApp in 1-4 short paragraphs.',
    });
    try {
      const finalResp = await chatWithClaude(messages, systemPrompt(), [], { max_tokens: MAX_TOKENS });
      lastText = textFromResponse(finalResp);
    } catch (err) {
      lastText = `I gathered some info but ran out of room to write a clean summary: ${err.message}`;
    }
  }

  // Order: stripped clean version → raw lastText (preserves chars when strip
  // erases everything) → explicit "couldn't produce" fallback. We should
  // basically never reach the last branch, but keep it so the user never gets
  // total silence.
  const stripped = stripMarkdown(lastText);
  const reply = stripped || lastText.trim() || 'Sorry, I could not produce an answer.';
  return { reply_text: reply.slice(0, 3500), run_id: null, turns_used: turnsUsed, tool_calls: toolCalls, hit_cap: hitCap };
}
