'use strict';

const fs = require('fs');

const PENDING_PERMISSION_TAIL_BYTES = 1024 * 1024;

function cloneToolCalls(toolCalls) {
  return JSON.parse(JSON.stringify(toolCalls || []));
}

const RAW_DSML_MARKER = '(?:[|\\uFF5C]\\s*){1,2}DSML\\s*(?:[|\\uFF5C]\\s*){1,2}';
const RAW_DSML_TOOL_CALLS_RE = new RegExp('<\\s*' + RAW_DSML_MARKER + '\\s*tool_calls\\s*>[\\s\\S]*?<\\/\\s*' + RAW_DSML_MARKER + '\\s*tool_calls\\s*>', 'gi');
const RAW_DSML_INVOKE_RE = new RegExp('<\\s*' + RAW_DSML_MARKER + '\\s*invoke\\b[^>]*>[\\s\\S]*?<\\/\\s*' + RAW_DSML_MARKER + '\\s*invoke\\s*>', 'gi');
const RAW_DSML_OPEN_TAG_RE = new RegExp('<\\s*' + RAW_DSML_MARKER + '\\s*(?:tool_calls|invoke|parameter)\\b', 'gi');
const RAW_DSML_CLOSE_TAG_RE = new RegExp('<\\/\\s*' + RAW_DSML_MARKER + '\\s*(?:tool_calls|invoke|parameter)\\s*>', 'gi');

function countMatches(text, regex) {
  regex.lastIndex = 0;
  let count = 0;
  while (regex.exec(text)) count += 1;
  regex.lastIndex = 0;
  return count;
}

function isMarkdownFenceLine(line) {
  return /^\s{0,3}(```+|~~~+)/.test(line);
}

function isMarkdownThematicBreakLine(line) {
  return /^\s{0,3}(?:-{3,}|\*{3,}|_{3,})\s*$/.test(line);
}

function normalizeWalleDisplayContent(text) {
  if (!text) return '';
  const withoutClosedControlBlocks = String(text)
    .replace(RAW_DSML_TOOL_CALLS_RE, '')
    .replace(RAW_DSML_INVOKE_RE, '');

  const out = [];
  let dsmlDepth = 0;
  let inFence = false;

  for (const line of withoutClosedControlBlocks.split('\n')) {
    const opens = countMatches(line, RAW_DSML_OPEN_TAG_RE);
    const closes = countMatches(line, RAW_DSML_CLOSE_TAG_RE);
    if (dsmlDepth > 0 || opens || closes) {
      dsmlDepth = Math.max(0, dsmlDepth + opens - closes);
      continue;
    }

    if (isMarkdownFenceLine(line)) {
      inFence = !inFence;
      out.push(line);
      continue;
    }

    if (!inFence && isMarkdownThematicBreakLine(line)) continue;
    out.push(line);
  }

  return out.join('\n').replace(/\n{3,}/g, '\n\n').trim();
}

function comparableAssistantContent(text) {
  return normalizeWalleDisplayContent(text)
    .toLowerCase()
    .replace(/[`*_#>\[\](){}"']/g, ' ')
    .replace(/[-–—:;,.!?|/\\]+/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
}

function looseAssistantContentMatch(a, b) {
  const left = comparableAssistantContent(a);
  const right = comparableAssistantContent(b);
  if (!left || !right) return false;
  if (left === right) return true;
  const shorter = left.length <= right.length ? left : right;
  const longer = left.length <= right.length ? right : left;
  if (shorter.length < 160) return false;
  const probe = shorter.slice(0, Math.min(shorter.length, 900));
  return longer.includes(probe);
}

function unwrapCodingNoChangeWrapper(text) {
  const content = String(text || '').trim();
  if (!content) return null;
  if (!/\bI did not make code changes, so I am not marking this coding task complete\./i.test(content)) return null;
  if (!/\bBlocked reason:\s*(?:coding_task_finished_without_changes|edit_tool_produced_no_diff)\b/i.test(content)) return null;
  const marker = /\nLast agent output:\n/i.exec(content);
  if (!marker) return '';
  return normalizeWalleDisplayContent(content.slice(marker.index + marker[0].length));
}

function immediateAssistantDuplicateIndex(messages, content, timestamp) {
  const candidateMs = messageTimeMs({ timestamp });
  for (let i = messages.length - 1; i >= 0; i -= 1) {
    const message = messages[i];
    if (!message || message.role === 'user') return -1;
    if (message.role !== 'assistant') continue;
    const existingMs = messageTimeMs(message);
    if (candidateMs && existingMs && Math.abs(candidateMs - existingMs) > 2 * 60 * 1000) return -1;
    return looseAssistantContentMatch(message.content, content) ? i : -1;
  }
  return -1;
}

function toolEventId(event) {
  return event.id || event.tool_call_id || event.toolCallId || '';
}

function toolEventName(event) {
  return event.tool || event.name || 'tool';
}

function findToolCallForResult(toolCalls, event) {
  const id = toolEventId(event);
  if (id) {
    for (let i = toolCalls.length - 1; i >= 0; i -= 1) {
      if (toolCalls[i].id === id) return i;
    }
  }

  const name = toolEventName(event);
  for (let i = toolCalls.length - 1; i >= 0; i -= 1) {
    if (toolCalls[i].status === 'working' && toolCalls[i].name === name) return i;
  }

  for (let i = toolCalls.length - 1; i >= 0; i -= 1) {
    if (toolCalls[i].status === 'working') return i;
  }

  return toolCalls.length - 1;
}

function applyWalleToolEvent(toolCalls, event) {
  if (!event || typeof event !== 'object') return toolCalls;
  if (event.type === 'skill_loaded' || event.type === 'skill_load_failed') {
    const skillName = event.skill || event.skill_name || event.input?.skill_name || event.args?.skill_name || 'skill';
    const input = event.input || event.args || {
      skill_name: skillName,
      source: event.source || '',
      prefix: event.prefix || '',
      agent_type: event.agentType || event.agent_type || '',
    };
    toolCalls.push({
      name: 'skill_loaded',
      summary: event.summary || (event.type === 'skill_load_failed' ? `Failed to load skill: ${skillName}` : `Loaded skill: ${skillName}`),
      args: input,
      status: event.type === 'skill_load_failed' ? 'error' : 'done',
      output: event.error || '',
    });
    return toolCalls;
  }
  if (event.type === 'tool_call') {
    const id = toolEventId(event);
    const toolCall = {
      name: toolEventName(event),
      summary: event.summary || '',
      args: event.args || event.input || null,
      status: 'working',
      output: '',
    };
    if (id) toolCall.id = id;
    toolCalls.push(toolCall);
    return toolCalls;
  }
  if (event.type === 'tool_result' || event.type === 'tool_done') {
    if (!toolCalls.length) return toolCalls;
    const idx = findToolCallForResult(toolCalls, event);
    const tc = toolCalls[idx];
    tc.status = event.error ? 'error' : 'done';
    tc.summary = event.summary || tc.summary || '';
    tc.output = event.output || event.result || '';
  }
  return toolCalls;
}

function extractText(content) {
  if (!content) return '';
  if (typeof content === 'string') return content;
  if (Array.isArray(content)) {
    return content
      .filter((block) => block && block.type === 'text' && block.text)
      .map((block) => block.text)
      .join('\n');
  }
  if (typeof content.text === 'string') return content.text;
  return '';
}

function messageText(entry) {
  if (!entry || typeof entry !== 'object') return '';
  if (entry.message && entry.message.content !== undefined) return extractText(entry.message.content);
  if (entry.content !== undefined) return extractText(entry.content);
  return '';
}

function normalizeToolEvent(entry) {
  if (!entry || typeof entry !== 'object') return null;
  if (entry.type === 'tool_call' || entry.type === 'tool_result' || entry.type === 'tool_done' || entry.type === 'skill_loaded' || entry.type === 'skill_load_failed') {
    return entry;
  }
  if (entry.type !== 'walle_part') return null;
  const data = entry.data && typeof entry.data === 'object' ? entry.data : {};
  if (entry.partType === 'skill_loaded' || entry.partType === 'skill_load_failed') {
    return {
      type: entry.partType,
      skill: data.skill || data.skill_name || data.input?.skill_name || data.args?.skill_name || '',
      summary: data.summary || '',
      input: data.input || data.args || null,
      args: data.args || data.input || null,
      source: data.source || '',
      prefix: data.prefix || '',
      agentType: data.agentType || data.agent_type || '',
      error: data.error || '',
    };
  }
  if (entry.partType !== 'tool_call' && entry.partType !== 'tool_result' && entry.partType !== 'tool_done') return null;
  return {
    type: entry.partType,
    id: data.id || data.tool_call_id || data.toolCallId || '',
    tool_call_id: data.tool_call_id || data.id || data.toolCallId || '',
    tool: data.tool || data.name || 'tool',
    name: data.name || data.tool || 'tool',
    summary: data.summary || '',
    args: data.args || data.input || null,
    input: data.input || data.args || null,
    output: data.output || data.result || '',
    result: data.result || data.output || '',
    error: data.error || null,
  };
}

function normalizeRuntimeEventData(entry) {
  if (!entry || typeof entry !== 'object' || entry.type !== 'walle_part' || entry.partType !== 'runtime_event') return null;
  const data = entry.data && typeof entry.data === 'object' ? entry.data : {};
  const payload = data.data && typeof data.data === 'object' ? data.data : {};
  const eventType = String(data.type || data.name || '').trim();
  if (!eventType) return null;
  return {
    eventType,
    eventId: String(data.eventId || data.event_id || ''),
    status: String(data.status || data.state || ''),
    timestamp: entry.timestamp || data.timestamp || '',
    payload,
  };
}

function runtimeEventSeverity(eventType, payload = {}, status = '') {
  if (eventType === 'permission_denied' || eventType === 'task_failed' || eventType === 'tool_failed' || eventType === 'hook_failed') return 'error';
  if (eventType === 'diagnostic_recorded') {
    const level = String(payload.level || payload.severity || status || '').toLowerCase();
    if (level === 'error' || level === 'fatal') return 'error';
    return 'warn';
  }
  if (/failed|denied|blocked/.test(eventType)) return 'warn';
  return 'info';
}

function runtimeEventSummary(eventType, payload = {}) {
  const tool = String(payload.tool || payload.name || '').trim();
  const reason = String(payload.reason || payload.message || payload.error || '').trim();
  if (eventType === 'permission_requested') return `Permission requested${tool ? ` for ${tool}` : ''}.`;
  if (eventType === 'permission_resolved') {
    const decision = String(payload.decision || 'allowed').trim();
    return `Permission ${decision}${tool ? ` for ${tool}` : ''}.`;
  }
  if (eventType === 'permission_denied') return `Permission denied${tool ? ` for ${tool}` : ''}${reason ? `: ${reason}` : ''}.`;
  if (eventType === 'task_started') return `Child task started${payload.agent ? ` (${payload.agent})` : ''}.`;
  if (eventType === 'task_updated') return `Child task ${payload.state || payload.status || 'updated'}.`;
  if (eventType === 'task_finished') return `Child task finished${payload.status ? `: ${payload.status}` : ''}.`;
  if (eventType === 'task_failed') return `Child task failed${reason ? `: ${reason}` : ''}.`;
  if (eventType === 'tool_failed') return `Tool failed${tool ? `: ${tool}` : ''}${reason ? ` — ${reason}` : ''}.`;
  if (eventType === 'tool_blocked') return `Tool blocked${tool ? `: ${tool}` : ''}${reason ? ` — ${reason}` : ''}.`;
  if (eventType === 'hook_failed') return `Hook failed${reason ? `: ${reason}` : ''}.`;
  if (eventType === 'diagnostic_recorded') return reason || String(payload.summary || 'Runtime diagnostic recorded.');
  if (eventType === 'prompt_built') {
    const stable = Number(payload.stableSectionCount || 0);
    const dynamic = Number(payload.dynamicSectionCount || 0);
    const tokens = Number(payload.tokenEstimate || 0);
    return `Prompt built: ${stable} stable, ${dynamic} dynamic${tokens ? `, ~${tokens} tokens` : ''}.`;
  }
  return eventType.replace(/_/g, ' ');
}

function runtimeMessageFromEvent(entry) {
  const event = normalizeRuntimeEventData(entry);
  if (!event) return null;
  const severity = runtimeEventSeverity(event.eventType, event.payload, event.status);
  if (severity === 'info') return null;
  const summary = runtimeEventSummary(event.eventType, event.payload);
  if (!summary) return null;
  return {
    role: 'system',
    content: `Wall-E runtime: ${summary}`,
    timestamp: event.timestamp || 0,
    runtimeDiagnostic: {
      severity,
      eventType: event.eventType,
      eventId: event.eventId,
      permissionContextId: event.payload.permissionContextId || '',
      taskId: event.payload.taskId || '',
      tool: event.payload.tool || event.payload.name || '',
    },
  };
}

function compactWalleToolCallSummary(call = {}) {
  const name = String(call.name || call.tool || 'tool').trim() || 'tool';
  const status = String(call.status || '').trim();
  const summary = String(call.summary || call.result_summary || '').trim();
  const pieces = [name];
  if (status) pieces.push(status);
  const head = pieces.join(' ');
  return summary ? `${head}: ${summary}` : head;
}

function openTurnActivityMessage(toolCalls = [], sawThinking = false, timestamp = '') {
  const calls = Array.isArray(toolCalls) ? toolCalls.filter(Boolean) : [];
  if (!sawThinking && calls.length === 0) return null;
  const lines = ['Wall-E is working on this prompt.'];
  const visibleCalls = calls.slice(-6);
  if (visibleCalls.length) {
    for (const call of visibleCalls) lines.push(`- ${compactWalleToolCallSummary(call)}`);
    if (calls.length > visibleCalls.length) lines.push(`- +${calls.length - visibleCalls.length} earlier activities`);
  } else if (sawThinking) {
    lines.push('- Wall-E is thinking');
  }
  return {
    role: 'system',
    content: lines.join('\n'),
    timestamp,
    toolCalls: calls.length
      ? cloneToolCalls(calls)
      : [{ name: 'think', tool: 'think', summary: 'Wall-E is thinking', status: 'working' }],
    liveActivity: true,
    agentLabel: 'Wall-E',
    metadata: { source: 'walle-open-turn-activity' },
  };
}

function terminalBoundaryMessageFromEntry(entry) {
  if (!entry || typeof entry !== 'object') return null;
  const type = String(entry.type || '').trim();
  const partType = String(entry.partType || '').trim();
  const boundaryType = type === 'walle_part' ? partType : type;
  if (boundaryType === 'cancelled') return null;
  if (boundaryType !== 'error' && boundaryType !== 'runtime_error' && boundaryType !== 'aborted') {
    return null;
  }

  const data = entry.data && typeof entry.data === 'object' ? entry.data : {};
  const rawMessage = data.message || data.error || entry.message || entry.error || '';
  const fallback = boundaryType === 'aborted'
    ? 'Wall-E aborted before producing a response.'
    : 'Wall-E failed before producing a response.';
  const message = String(rawMessage || fallback).trim();
  const prefix = boundaryType === 'aborted' ? '[Wall-E aborted]' : '[Wall-E error]';

  return {
    role: 'system',
    content: `${prefix} ${message}`.trim(),
    timestamp: entry.timestamp || data.timestamp || 0,
    runtimeDiagnostic: {
      severity: 'error',
      eventType: boundaryType,
      providerError: data.providerError || null,
    },
  };
}

function walleTextPartContent(entry) {
  if (!entry || typeof entry !== 'object' || entry.type !== 'walle_part' || entry.partType !== 'text') return '';
  const data = entry.data && typeof entry.data === 'object' ? entry.data : {};
  return normalizeWalleDisplayContent(data.text || data.content || entry.text || '');
}

function summarizePromptManifest(data = {}, timestamp = '') {
  if (!data || typeof data !== 'object') return null;
  return {
    promptManifestId: data.promptManifestId || '',
    stableHash: data.stableHash || '',
    dynamicHash: data.dynamicHash || '',
    stableSectionCount: Number(data.stableSectionCount || 0),
    dynamicSectionCount: Number(data.dynamicSectionCount || 0),
    tokenEstimate: Number(data.tokenEstimate || 0),
    cacheBoundaryAfter: data.cacheBoundaryAfter || '',
    sections: Array.isArray(data.sections)
      ? data.sections.map((section) => ({
        id: section.id || '',
        kind: section.kind || '',
        source: section.source || '',
        cacheable: section.cacheable !== false,
        tokenEstimate: Number(section.tokenEstimate || 0),
      }))
      : [],
    timestamp,
  };
}

function extractWalleRuntimeDiagnostics(filePath, options = {}) {
  let raw = '';
  try { raw = fs.readFileSync(filePath, 'utf8'); } catch { return { promptManifest: null, events: [], eventCount: 0, errorCount: 0 }; }
  const limit = Number.isFinite(Number(options.limit)) ? Math.max(0, Number(options.limit)) : 200;
  const events = [];
  let promptManifest = null;
  let eventCount = 0;
  let errorCount = 0;

  for (const line of raw.split('\n')) {
    if (!line.trim()) continue;
    let entry;
    try { entry = JSON.parse(line); } catch { continue; }
    if (entry.type !== 'walle_part') continue;
    if (entry.partType === 'prompt_manifest') {
      promptManifest = summarizePromptManifest(entry.data, entry.timestamp || '');
      continue;
    }
    const event = normalizeRuntimeEventData(entry);
    if (!event) continue;
    eventCount += 1;
    const severity = runtimeEventSeverity(event.eventType, event.payload, event.status);
    if (severity === 'error') errorCount += 1;
    events.push({
      eventType: event.eventType,
      eventId: event.eventId,
      severity,
      summary: runtimeEventSummary(event.eventType, event.payload),
      status: event.status,
      timestamp: event.timestamp,
      permissionContextId: event.payload.permissionContextId || '',
      tool: event.payload.tool || event.payload.name || '',
      taskId: event.payload.taskId || '',
      agent: event.payload.agent || '',
      source: event.payload.source || '',
    });
  }

  return {
    promptManifest,
    events: limit > 0 ? events.slice(-limit) : events,
    eventCount,
    errorCount,
  };
}

function isTerminalToolBoundary(entry) {
  if (!entry || typeof entry !== 'object') return false;
  if (entry.type === 'cancelled' || entry.type === 'error' || entry.type === 'runtime_error' || entry.type === 'aborted') return true;
  if (entry.type !== 'walle_part') return false;
  return entry.partType === 'cancelled'
    || entry.partType === 'error'
    || entry.partType === 'runtime_error'
    || entry.partType === 'aborted';
}

function readWalleCtmHistory(filePath) {
  let raw = '';
  try { raw = fs.readFileSync(filePath, 'utf8'); } catch { return []; }
  const messages = [];
  let pendingToolCalls = [];
  let openTurnSawThinking = false;
  let openTurnActivityTimestamp = '';
  let openTurnHasUser = false;
  let pendingAssistantText = '';
  let pendingAssistantTimestamp = '';
  // Durable approval cards: a permission_request part becomes a card message; a
  // later permission_resolved part marks that same card resolved (so a reload
  // shows an answered request as resolved, and a still-pending one as actionable).
  const permissionCards = new Map();
  const flushPendingAssistantText = (options = {}) => {
    const content = normalizeWalleDisplayContent(pendingAssistantText);
    if (!content) return false;
    messages.push({
      role: 'assistant',
      content,
      timestamp: pendingAssistantTimestamp || openTurnActivityTimestamp || 0,
      toolCalls: cloneToolCalls(pendingToolCalls),
      metadata: {
        source: 'walle-text-part',
        partial: options.partial !== false,
        boundary: options.boundary || '',
      },
    });
    pendingAssistantText = '';
    pendingAssistantTimestamp = '';
    pendingToolCalls = [];
    openTurnSawThinking = false;
    openTurnActivityTimestamp = '';
    openTurnHasUser = false;
    return true;
  };
  for (const line of raw.split('\n')) {
    if (!line.trim()) continue;
    let entry;
    try { entry = JSON.parse(line); } catch { continue; }
    if (entry.type === 'user') {
      flushPendingAssistantText({ boundary: 'next-user' });
      pendingToolCalls = [];
      openTurnSawThinking = false;
      openTurnActivityTimestamp = entry.timestamp || 0;
      openTurnHasUser = true;
      const userMessage = {
        role: 'user',
        content: messageText(entry),
        timestamp: entry.timestamp || 0,
      };
      if (entry.attachments) {
        try {
          userMessage.attachments = typeof entry.attachments === 'string'
            ? JSON.parse(entry.attachments)
            : JSON.parse(JSON.stringify(entry.attachments));
        } catch {
          userMessage.attachments = entry.attachments;
        }
      }
      messages.push(userMessage);
      continue;
    }
    if (isTerminalToolBoundary(entry)) {
      const boundaryType = entry.type === 'walle_part' ? entry.partType : entry.type;
      flushPendingAssistantText({ boundary: boundaryType });
      pendingToolCalls = [];
      openTurnSawThinking = false;
      openTurnActivityTimestamp = '';
      openTurnHasUser = false;
      const boundaryMessage = terminalBoundaryMessageFromEntry(entry);
      if (boundaryMessage) messages.push(boundaryMessage);
      continue;
    }
    if (entry.type === 'walle_part' && entry.partType === 'thinking') {
      openTurnSawThinking = true;
      openTurnActivityTimestamp = entry.timestamp || openTurnActivityTimestamp;
      continue;
    }
    if (entry.type === 'walle_part' && entry.partType === 'permission_request') {
      const data = entry.data && typeof entry.data === 'object' ? entry.data : {};
      const permId = String(data.permId || '');
      if (permId && !permissionCards.has(permId)) {
        const input = data.input && typeof data.input === 'object' ? data.input : {};
        const tool = data.tool || '';
        const command = data.command || input.command || '';
        const args = Array.isArray(input.args) ? input.args.map((a) => String(a)) : [];
        const permission = {
          permId,
          tool,
          command,
          args,
          reason: data.reason || 'Requires approval',
          status: 'pending',
          decision: '',
        };
        const permMessage = {
          role: 'assistant',
          content: `Permission required: ${tool || 'tool'}${command ? ` — ${command}` : ''}`,
          timestamp: entry.timestamp || 0,
          metadata: { permission },
        };
        permissionCards.set(permId, permMessage);
        messages.push(permMessage);
      }
      openTurnActivityTimestamp = entry.timestamp || openTurnActivityTimestamp;
      continue;
    }
    if (entry.type === 'walle_part' && entry.partType === 'permission_resolved') {
      const data = entry.data && typeof entry.data === 'object' ? entry.data : {};
      const permId = String(data.permId || '');
      const card = permId ? permissionCards.get(permId) : null;
      if (card && card.metadata && card.metadata.permission) {
        card.metadata.permission.status = 'resolved';
        card.metadata.permission.decision = data.decision || 'allow';
        card.metadata.permission.source = data.source || '';
      }
      continue;
    }
    const textPart = walleTextPartContent(entry);
    if (textPart) {
      pendingAssistantText = pendingAssistantText
        ? `${pendingAssistantText}\n\n${textPart}`
        : textPart;
      pendingAssistantTimestamp = entry.timestamp || pendingAssistantTimestamp || openTurnActivityTimestamp;
      openTurnActivityTimestamp = entry.timestamp || openTurnActivityTimestamp;
      continue;
    }
    const toolEvent = normalizeToolEvent(entry);
    if (toolEvent) {
      applyWalleToolEvent(pendingToolCalls, toolEvent);
      openTurnActivityTimestamp = entry.timestamp || openTurnActivityTimestamp;
      continue;
    }
    const runtimeMessage = runtimeMessageFromEvent(entry);
    if (runtimeMessage) {
      messages.push(runtimeMessage);
      continue;
    }
    if (entry.type === 'assistant') {
      pendingAssistantText = '';
      pendingAssistantTimestamp = '';
      const displayContent = normalizeWalleDisplayContent(messageText(entry));
      const unwrappedContent = unwrapCodingNoChangeWrapper(displayContent);
      if (unwrappedContent === '') {
        pendingToolCalls = [];
        openTurnSawThinking = false;
        openTurnActivityTimestamp = '';
        openTurnHasUser = false;
        continue;
      }
      const candidateContent = unwrappedContent || displayContent;
      const duplicateIndex = immediateAssistantDuplicateIndex(messages, candidateContent, entry.timestamp || 0);
      if (duplicateIndex >= 0 && messages[duplicateIndex]?.metadata?.source === 'walle-text-part') {
        messages.splice(duplicateIndex, 1);
      } else if (duplicateIndex >= 0) {
        pendingToolCalls = [];
        openTurnSawThinking = false;
        openTurnActivityTimestamp = '';
        openTurnHasUser = false;
        continue;
      }
      messages.push({
        role: 'assistant',
        content: candidateContent,
        model: entry.message?.model || entry.model || entry.modelId || '',
        latency_ms: entry.latencyMs || entry.latency_ms || 0,
        timestamp: entry.timestamp || 0,
        toolCalls: Array.isArray(entry.toolCalls) ? cloneToolCalls(entry.toolCalls) : cloneToolCalls(pendingToolCalls),
      });
      pendingToolCalls = [];
      openTurnSawThinking = false;
      openTurnActivityTimestamp = '';
      openTurnHasUser = false;
    }
  }
  flushPendingAssistantText({ boundary: 'eof' });
  const openActivity = openTurnHasUser
    ? openTurnActivityMessage(pendingToolCalls, openTurnSawThinking, openTurnActivityTimestamp)
    : null;
  if (openActivity) messages.push(openActivity);
  return mergeWalleHistories(messages, { duplicateWindowMs: 10 * 60 * 1000 });
}

function normalizeWhitespace(value) {
  return String(value || '').trim().replace(/\s+/g, ' ');
}

function messageContent(message) {
  if (!message || typeof message !== 'object') return '';
  if (message.content !== undefined) return extractText(message.content);
  if (message.text !== undefined) return extractText(message.text);
  if (message.message && message.message.content !== undefined) return extractText(message.message.content);
  return '';
}

function messageTimestamp(message) {
  return message?.timestamp || message?.created_at || message?.createdAt || '';
}

function messageTimeMs(message) {
  const raw = messageTimestamp(message);
  if (!raw) return 0;
  const normalized = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(String(raw))
    ? String(raw).replace(' ', 'T') + 'Z'
    : raw;
  const ms = new Date(normalized).getTime();
  return Number.isFinite(ms) ? ms : 0;
}

function readJsonlTailEntries(filePath, maxBytes = PENDING_PERMISSION_TAIL_BYTES) {
  if (!filePath) return [];
  let stat;
  try { stat = fs.statSync(filePath); } catch { return []; }
  if (!stat || !stat.size) return [];
  const bytes = Math.min(stat.size, Math.max(1, Number(maxBytes) || PENDING_PERMISSION_TAIL_BYTES));
  let fd;
  try {
    fd = fs.openSync(filePath, 'r');
    const buf = Buffer.alloc(bytes);
    fs.readSync(fd, buf, 0, bytes, stat.size - bytes);
    let text = buf.toString('utf8');
    if (stat.size > bytes) {
      const firstNewline = text.indexOf('\n');
      text = firstNewline >= 0 ? text.slice(firstNewline + 1) : '';
    }
    return text
      .split('\n')
      .filter(line => line.trim())
      .map((line) => {
        try { return JSON.parse(line); } catch { return null; }
      })
      .filter(Boolean);
  } catch {
    return [];
  } finally {
    if (fd !== undefined) {
      try { fs.closeSync(fd); } catch {}
    }
  }
}

function permissionKey(data = {}) {
  return String(data.permId || data.permissionId || data.permission_context_id || data.permissionContextId || data.toolCallId || data.tool_call_id || '');
}

function pendingPermissionFromEntries(entries) {
  const pending = new Map();
  const clearPermission = (data = {}) => {
    const key = permissionKey(data);
    if (key) pending.delete(key);
    const toolCallId = String(data.toolCallId || data.tool_call_id || '');
    if (toolCallId) {
      for (const [candidateKey, value] of pending) {
        if (String(value.toolCallId || '') === toolCallId) pending.delete(candidateKey);
      }
    }
  };
  const clearAll = () => pending.clear();

  for (const entry of Array.isArray(entries) ? entries : []) {
    const data = entry?.data && typeof entry.data === 'object' ? entry.data : {};
    if (entry?.type === 'assistant' || isTerminalToolBoundary(entry)) {
      clearAll();
      continue;
    }
    if (entry?.type !== 'walle_part') continue;

    if (entry.partType === 'lane_event') {
      const name = String(data.name || '').toLowerCase();
      if (name === 'session.waiting') {
        const key = permissionKey(data.data || data) || `lane:${entry.timestamp || pending.size}`;
        pending.set(key, {
          permId: String((data.data || data).permId || ''),
          permissionContextId: String((data.data || data).permissionContextId || ''),
          toolCallId: String((data.data || data).toolCallId || ''),
          tool: String((data.data || data).name || data.tool || ''),
          command: '',
          reason: String(data.reason || 'Waiting for your approval'),
          waitingFor: String((data.data || data).waitingFor || ''),
          timestamp: entry.timestamp || data.timestamp || '',
          transcriptPath: entry.__ctmFilePath || '',
          source: 'lane_event',
        });
      } else if (name === 'session.running' || name === 'session.idle' || name === 'session.finished' || name === 'session.failed' || name === 'session.blocked') {
        clearAll();
      }
      continue;
    }

    if (entry.partType === 'permission_request') {
      const input = data.input && typeof data.input === 'object' ? data.input : {};
      const key = permissionKey(data) || `permission:${entry.timestamp || pending.size}`;
      const existing = pending.get(key) || {};
      pending.set(key, {
        ...existing,
        permId: String(data.permId || existing.permId || ''),
        permissionContextId: String(data.permissionContextId || existing.permissionContextId || ''),
        toolCallId: String(data.toolCallId || existing.toolCallId || ''),
        tool: String(data.tool || data.name || existing.tool || ''),
        command: String(data.command || input.command || existing.command || ''),
        args: Array.isArray(input.args) ? input.args.map(String) : (existing.args || []),
        reason: String(data.reason || existing.reason || 'Requires approval'),
        waitingFor: 'permission',
        timestamp: entry.timestamp || data.timestamp || existing.timestamp || '',
        transcriptPath: entry.__ctmFilePath || existing.transcriptPath || '',
        source: 'permission_request',
      });
      continue;
    }

    if (entry.partType === 'permission_resolved') {
      clearPermission(data);
      continue;
    }

    if (entry.partType === 'tool') {
      const state = String(data.state || '').toLowerCase();
      if (state === 'permission_allowed' || state === 'permission_denied') clearPermission(data);
    }
  }

  const values = Array.from(pending.values());
  if (!values.length) return null;
  values.sort((a, b) => messageTimeMs(a) - messageTimeMs(b));
  return values[values.length - 1] || null;
}

function findPendingWallePermission(filePath, options = {}) {
  const entries = readJsonlTailEntries(filePath, options.tailBytes || PENDING_PERMISSION_TAIL_BYTES);
  return pendingPermissionFromEntries(entries);
}

function findPendingWallePermissionInFiles(filePaths, options = {}) {
  const entries = [];
  const seen = new Set();
  let sequence = 0;
  for (const rawPath of Array.isArray(filePaths) ? filePaths : []) {
    const filePath = String(rawPath || '').trim();
    if (!filePath || seen.has(filePath)) continue;
    seen.add(filePath);
    for (const entry of readJsonlTailEntries(filePath, options.tailBytes || PENDING_PERMISSION_TAIL_BYTES)) {
      entries.push({
        ...entry,
        __ctmFilePath: filePath,
        __ctmSequence: sequence++,
      });
    }
  }
  if (!entries.length) return null;
  entries.sort((a, b) => {
    const byTime = messageTimeMs(a) - messageTimeMs(b);
    if (byTime) return byTime;
    return (a.__ctmSequence || 0) - (b.__ctmSequence || 0);
  });
  return pendingPermissionFromEntries(entries);
}

function cloneMessage(message) {
  const role = String(message?.role || message?.message?.role || '').trim();
  const content = role === 'assistant'
    ? normalizeWalleDisplayContent(messageContent(message))
    : messageContent(message);
  const timestamp = messageTimestamp(message);
  const out = {
    role,
    content,
  };
  if (timestamp) out.timestamp = timestamp;
  if (message?.created_at) out.created_at = message.created_at;
  const model = message?.model_id || message?.model || message?.message?.model || '';
  if (model) {
    out.model = model;
    out.model_id = model;
  }
  const latency = message?.latency_ms ?? message?.latencyMs;
  if (latency !== undefined && latency !== null) out.latency_ms = latency;
  if (Array.isArray(message?.toolCalls)) out.toolCalls = cloneToolCalls(message.toolCalls);
  if (Array.isArray(message?.tool_calls)) out.toolCalls = cloneToolCalls(message.tool_calls);
  if (message?.metadata) {
    try { out.metadata = JSON.parse(JSON.stringify(message.metadata)); } catch { out.metadata = message.metadata; }
  }
  if (message?.runtimeDiagnostic) {
    try { out.runtimeDiagnostic = JSON.parse(JSON.stringify(message.runtimeDiagnostic)); } catch { out.runtimeDiagnostic = message.runtimeDiagnostic; }
  }
  if (message?.liveActivity !== undefined) out.liveActivity = message.liveActivity;
  if (message?.agentLabel) out.agentLabel = message.agentLabel;
  if (message?.attachments) {
    try {
      out.attachments = typeof message.attachments === 'string'
        ? JSON.parse(message.attachments)
        : JSON.parse(JSON.stringify(message.attachments));
    } catch {
      out.attachments = message.attachments;
    }
  }
  return out;
}

function messageKey(message) {
  return `${String(message?.role || '').trim()}\n${normalizeWhitespace(messageContent(message))}`;
}

function messageRichness(message) {
  let score = 0;
  if (message?.model || message?.model_id) score += 1;
  if (message?.latency_ms || message?.latencyMs) score += 1;
  if (Array.isArray(message?.toolCalls) && message.toolCalls.length) score += 5 + message.toolCalls.length;
  if (Array.isArray(message?.attachments) && message.attachments.length) score += 3 + message.attachments.length;
  return score;
}

function shouldTreatAsDuplicate(a, b, windowMs) {
  if (!a || !b) return false;
  if (messageKey(a) !== messageKey(b)) return false;
  const aMs = messageTimeMs(a);
  const bMs = messageTimeMs(b);
  if (!aMs || !bMs) return true;
  return Math.abs(aMs - bMs) <= windowMs;
}

function mergeWalleHistories(...inputs) {
  const options = inputs.length && inputs[inputs.length - 1] && !Array.isArray(inputs[inputs.length - 1])
    ? inputs.pop()
    : {};
  const duplicateWindowMs = Number.isFinite(options.duplicateWindowMs)
    ? options.duplicateWindowMs
    : 2 * 60 * 1000;
  const merged = [];

  for (const history of inputs) {
    if (!Array.isArray(history)) continue;
    for (const original of history) {
      const candidate = cloneMessage(original);
      if (!candidate.role || !candidate.content) continue;
      const existingIdx = merged.findIndex((item) => shouldTreatAsDuplicate(item, candidate, duplicateWindowMs));
      if (existingIdx >= 0) {
        const existing = merged[existingIdx];
        if (messageRichness(candidate) > messageRichness(existing)) {
          merged[existingIdx] = {
            ...existing,
            ...candidate,
            timestamp: existing.timestamp || candidate.timestamp,
            created_at: existing.created_at || candidate.created_at,
          };
        }
        continue;
      }
      merged.push(candidate);
    }
  }

  merged.sort((a, b) => {
    const byTime = messageTimeMs(a) - messageTimeMs(b);
    if (byTime) return byTime;
    return 0;
  });
  return merged;
}

module.exports = {
  applyWalleToolEvent,
  cloneToolCalls,
  extractWalleRuntimeDiagnostics,
  extractText,
  findPendingWallePermission,
  findPendingWallePermissionInFiles,
  mergeWalleHistories,
  normalizeWalleDisplayContent,
  readWalleCtmHistory,
};
