'use strict';

const fs = require('fs');
const path = require('path');

const DEFAULT_TAIL_BYTES = 2 * 1024 * 1024;
const MAX_PARSED_LINES = 400;

function _asString(value) {
  return String(value || '').trim();
}

function _safeJson(value) {
  try { return JSON.parse(value); } catch { return null; }
}

function _readJsonlTail(filePath, opts = {}) {
  const target = _asString(filePath);
  if (!target) return { entries: [], error: 'missing_path' };
  try {
    const stat = fs.statSync(target);
    if (!stat.isFile() || stat.size <= 0) return { entries: [], error: 'empty_file' };
    const tailBytes = Math.max(64 * 1024, Number(opts.tailBytes) || DEFAULT_TAIL_BYTES);
    const start = Math.max(0, stat.size - tailBytes);
    const fd = fs.openSync(target, 'r');
    try {
      const len = stat.size - start;
      const buf = Buffer.allocUnsafe(len);
      fs.readSync(fd, buf, 0, len, start);
      let raw = buf.toString('utf8');
      if (start > 0) {
        const nl = raw.indexOf('\n');
        raw = nl >= 0 ? raw.slice(nl + 1) : '';
      }
      const lines = raw.split('\n').filter(Boolean).slice(-MAX_PARSED_LINES);
      const entries = [];
      for (const line of lines) {
        const parsed = _safeJson(line);
        if (parsed && typeof parsed === 'object') entries.push(parsed);
      }
      return { entries, size: stat.size, mtimeMs: stat.mtimeMs || 0 };
    } finally {
      try { fs.closeSync(fd); } catch {}
    }
  } catch (e) {
    return { entries: [], error: e.message || 'read_failed' };
  }
}

function _entryText(entry) {
  const content = entry?.message?.content ?? entry?.content;
  if (typeof content === 'string') return content;
  if (Array.isArray(content)) {
    return content.map((block) => {
      if (!block || typeof block !== 'object') return '';
      if (typeof block.text === 'string') return block.text;
      if (typeof block.content === 'string') return block.content;
      if (block.type === 'tool_result') return `[tool_result:${block.tool_use_id || block.name || ''}]`;
      if (block.type === 'tool_use') return `[tool_use:${block.name || ''}]`;
      return '';
    }).filter(Boolean).join('\n');
  }
  if (typeof entry?.text === 'string') return entry.text;
  if (typeof entry?.summary === 'string') return entry.summary;
  if (typeof entry?.content === 'string') return entry.content;
  return '';
}

function _codexContentText(content, wantedTypes) {
  if (typeof content === 'string') return content;
  if (!Array.isArray(content)) return '';
  const types = new Set(Array.isArray(wantedTypes) ? wantedTypes : []);
  return content.map((block) => {
    if (!block || typeof block !== 'object') return '';
    if (types.size && !types.has(block.type)) return '';
    if (typeof block.text === 'string') return block.text;
    if (typeof block.content === 'string') return block.content;
    return '';
  }).filter(Boolean).join('\n').trim();
}

function _codexUserText(entry) {
  const payload = entry?.payload || {};
  if (payload.type === 'user_message') return _asString(payload.message);
  if (payload.type === 'message' && payload.role === 'user') {
    return _codexContentText(payload.content, ['input_text', 'text']);
  }
  return '';
}

function _isCodexProviderContextUserText(text) {
  const cleaned = _asString(text);
  if (!cleaned) return true;
  return /^<environment_context>/i.test(cleaned) ||
    /^<permissions instructions>/i.test(cleaned) ||
    /^<collaboration_mode>/i.test(cleaned) ||
    /^<skills_instructions>/i.test(cleaned) ||
    /^<plugins_instructions>/i.test(cleaned) ||
    /^<skill>\s*<name>/i.test(cleaned) ||
    /^# AGENTS\.md instructions for\b/i.test(cleaned);
}

function _isCodexTurnAbortedText(text) {
  return /^<turn_aborted>\s*[\s\S]*?<\/turn_aborted>\s*$/i.test(_asString(text));
}

function _codexToolPreview(payload) {
  if (!payload || typeof payload !== 'object') return '';
  if (payload.name) return `[tool:${payload.name}]`;
  if (payload.call_id) return `[tool:${payload.call_id}]`;
  return '';
}

function _contentBlocks(entry) {
  const content = entry?.message?.content ?? entry?.content;
  return Array.isArray(content) ? content : [];
}

function _hasToolResult(entry) {
  if (!entry || typeof entry !== 'object') return false;
  if (entry.type === 'tool_result') return true;
  return _contentBlocks(entry).some((block) => block && block.type === 'tool_result');
}

function _hasToolUse(entry) {
  if (!entry || typeof entry !== 'object') return false;
  if (entry.type === 'tool_use') return true;
  return _contentBlocks(entry).some((block) => block && block.type === 'tool_use');
}

function _isMetadataNoise(entry) {
  const type = _asString(entry?.type).toLowerCase();
  return !type ||
    type === 'summary' ||
    type === 'metadata' ||
    type === 'last-prompt' ||
    type === 'ai-title' ||
    type === 'title' ||
    type === 'message-stat' ||
    type === 'session-meta' ||
    type === 'model';
}

function _semanticEvent(entry) {
  const type = _asString(entry?.type).toLowerCase();
  const timestamp = _asString(entry?.timestamp);
  const payload = entry?.payload || {};
  const payloadType = _asString(payload?.type).toLowerCase();

  if (type === 'event_msg') {
    if (payloadType === 'user_message') {
      const text = _codexUserText(entry);
      if (!text || _isCodexProviderContextUserText(text)) return null;
      if (_isCodexTurnAbortedText(text)) return { kind: 'codex_turn_aborted', timestamp, text };
      return { kind: 'user', timestamp, text };
    }
    if (payloadType === 'agent_message') {
      const text = _asString(payload.message);
      const phase = _asString(payload.phase).toLowerCase();
      if (!text) return null;
      return {
        kind: phase === 'commentary' ? 'codex_assistant_commentary' : 'assistant',
        timestamp,
        text,
        phase,
      };
    }
    if (payloadType === 'task_complete') {
      const text = _asString(payload.last_agent_message);
      return { kind: 'assistant', timestamp, text, phase: 'task_complete' };
    }
    if (payloadType === 'turn_aborted') {
      return { kind: 'codex_turn_aborted', timestamp, text: payload.reason || payload.message || 'turn_aborted' };
    }
    if (payloadType === 'patch_apply_end') {
      return { kind: 'tool_result', timestamp, text: payload.stdout || payload.stderr || 'patch_apply_end' };
    }
  }

  if (type === 'response_item') {
    if (payloadType === 'message' && payload.role === 'user') {
      const text = _codexUserText(entry);
      if (!text || _isCodexProviderContextUserText(text)) return null;
      if (_isCodexTurnAbortedText(text)) return { kind: 'codex_turn_aborted', timestamp, text };
      return { kind: 'user', timestamp, text };
    }
    if (payloadType === 'message' && payload.role === 'assistant') {
      const text = _codexContentText(payload.content, ['output_text', 'text']);
      if (!text) return null;
      const phase = _asString(payload.phase).toLowerCase();
      return {
        kind: phase === 'commentary' ? 'codex_assistant_commentary' : 'assistant',
        timestamp,
        text,
        phase,
      };
    }
    if (payloadType === 'function_call' || payloadType === 'custom_tool_call') {
      return { kind: 'assistant_tool_use', timestamp, text: _codexToolPreview(payload), stopReason: 'tool_use' };
    }
    if (payloadType === 'function_call_output' || payloadType === 'custom_tool_call_output') {
      return { kind: 'tool_result', timestamp, text: payload.output || _codexToolPreview(payload) };
    }
  }

  if (type === 'assistant' || entry?.message?.role === 'assistant') {
    const stopReason = _asString(entry?.message?.stop_reason || entry?.stop_reason).toLowerCase();
    return {
      kind: _hasToolUse(entry) || stopReason === 'tool_use' ? 'assistant_tool_use' : 'assistant',
      timestamp,
      stopReason,
      text: _entryText(entry),
    };
  }
  if (type === 'user' || entry?.message?.role === 'user') {
    if (_hasToolResult(entry)) {
      return { kind: 'tool_result', timestamp, text: _entryText(entry) };
    }
    const text = _entryText(entry);
    if (!text) return null;
    return { kind: 'user', timestamp, text };
  }
  if (type === 'tool_result') {
    return { kind: 'tool_result', timestamp, text: _entryText(entry) };
  }
  if (type === 'queue-operation') {
    const operation = _asString(entry.operation).toLowerCase();
    const content = _entryText(entry);
    if (operation === 'enqueue' && /<status>\s*completed\s*<\/status>|completed/i.test(content)) {
      return { kind: 'background_task_completed', timestamp, text: content };
    }
    return null;
  }
  if (_isMetadataNoise(entry)) return null;
  return null;
}

function _lastSemanticEvent(entries) {
  for (let i = entries.length - 1; i >= 0; i--) {
    const semantic = _semanticEvent(entries[i]);
    if (semantic) return semantic;
  }
  return null;
}

function _reasonForSemantic(semantic) {
  if (!semantic) return null;
  if (semantic.kind === 'tool_result') {
    return {
      interrupted: true,
      reason: 'tool_result_without_assistant_continuation',
      label: 'Interrupted after a tool result',
      detail: 'The last transcript event is a tool result, but no assistant message followed it.',
    };
  }
  if (semantic.kind === 'background_task_completed') {
    return {
      interrupted: true,
      reason: 'background_task_completed_without_assistant_continuation',
      label: 'Interrupted after background work completed',
      detail: 'A background task completion notification was written, but the assistant never processed it.',
    };
  }
  if (semantic.kind === 'assistant_tool_use') {
    return {
      interrupted: true,
      reason: 'assistant_tool_use_without_result',
      label: 'Interrupted while a tool was pending',
      detail: 'The assistant requested a tool and CTM restored before a later continuation was recorded.',
    };
  }
  if (semantic.kind === 'codex_assistant_commentary') {
    return {
      interrupted: true,
      reason: 'codex_commentary_without_final_response',
      label: 'Interrupted after a progress update',
      detail: 'Codex recorded an assistant commentary/progress message, but no final answer or task completion followed it.',
    };
  }
  if (semantic.kind === 'codex_turn_aborted') {
    return {
      interrupted: true,
      reason: 'codex_turn_aborted',
      label: 'Interrupted by Codex turn abort',
      detail: 'Codex recorded a turn-aborted control event before CTM restored the session.',
    };
  }
  if (semantic.kind === 'user') {
    return {
      interrupted: true,
      reason: 'user_prompt_without_assistant_response',
      label: 'Interrupted before the assistant responded',
      detail: 'The last transcript event is a user prompt with no later assistant response.',
    };
  }
  return null;
}

function detectRestoreInterruptionFromEntries(entries, opts = {}) {
  const semantic = _lastSemanticEvent(Array.isArray(entries) ? entries : []);
  const reason = _reasonForSemantic(semantic);
  if (!reason) {
    return {
      interrupted: false,
      lastEventKind: semantic?.kind || '',
      lastEventTimestamp: semantic?.timestamp || '',
    };
  }
  const observedAt = Date.parse(semantic.timestamp || '') || Number(opts.now) || Date.now();
  return {
    ...reason,
    lastEventKind: semantic.kind,
    lastEventTimestamp: semantic.timestamp || '',
    observedAt,
    preview: _asString(semantic.text).replace(/\s+/g, ' ').slice(0, 240),
  };
}

function _selectAgentRow(db, ctmSessionId, provider) {
  if (!db || !ctmSessionId) return null;
  try {
    const rows = db.prepare(`
      SELECT *
      FROM agent_sessions
      WHERE ctm_session_id = ?
      ORDER BY COALESCE(NULLIF(modified_at, ''), updated_at, created_at) DESC,
        updated_at DESC,
        created_at DESC
    `).all(ctmSessionId);
    const wanted = _asString(provider).toLowerCase();
    return rows.find((row) => {
      if (!row?.jsonl_path) return false;
      if (!wanted) return true;
      const rowProvider = _asString(row.provider).toLowerCase();
      return !rowProvider || rowProvider === wanted;
    }) || rows.find((row) => row?.jsonl_path) || null;
  } catch {
    return null;
  }
}

function _normalizeArgs(value) {
  if (Array.isArray(value)) return value.map((item) => String(item || ''));
  if (typeof value === 'string') {
    const parsed = _safeJson(value);
    if (Array.isArray(parsed)) return parsed.map((item) => String(item || ''));
  }
  return [];
}

function _resumeIdFromArgs(args) {
  const list = _normalizeArgs(args);
  for (let i = 0; i < list.length; i++) {
    const arg = list[i];
    if ((arg === '--resume' || arg === '-r' || arg === '--session' || arg === '--session-id') && list[i + 1]) {
      return _asString(list[i + 1]);
    }
    const eq = arg.match(/^--(?:resume|session|session-id)=(.+)$/);
    if (eq) return _asString(eq[1]);
  }
  return '';
}

function _encodeProjectEntry(cwd) {
  return String(cwd || '').replace(/\/\./g, '/-').replace(/\//g, '-');
}

function _canonicalClaudeCwd(cwd) {
  const resolved = path.resolve(cwd || process.env.HOME || '.');
  try { return fs.realpathSync(resolved); } catch { return resolved; }
}

function _existingTranscriptPath(basePath) {
  if (!basePath) return '';
  try {
    if (fs.existsSync(basePath)) return basePath;
    const bakPath = `${basePath}.bak`;
    if (fs.existsSync(bakPath)) return bakPath;
  } catch {}
  return '';
}

function _fallbackTranscriptPathFromTask(task, provider, opts = {}) {
  if (!task || typeof task !== 'object') return '';
  const agentId =
    _asString(task.agent_session_id) ||
    _asString(task.claude_session_id) ||
    _asString(task.resume_session_id) ||
    _resumeIdFromArgs(task.args);
  if (!agentId) return '';

  const explicitProjectDir = _asString(task.agent_project_dir || task.claude_project_dir || task.project_dir);
  if (explicitProjectDir) {
    const explicit = _existingTranscriptPath(path.resolve(explicitProjectDir, `${agentId}.jsonl`));
    if (explicit) return explicit;
  }

  if (provider === 'claude') {
    const cwd = _asString(task.worktree_path || task.cwd);
    if (!cwd) return '';
    const homeDir = path.resolve(opts.homeDir || process.env.HOME || '');
    if (!homeDir) return '';
    const projectEntry = _encodeProjectEntry(_canonicalClaudeCwd(cwd));
    return _existingTranscriptPath(path.join(homeDir, '.claude', 'projects', projectEntry, `${agentId}.jsonl`));
  }

  return '';
}

function detectRestoreInterruption({ db, task, agentType, jsonlPath, now, homeDir } = {}) {
  const provider = _asString(agentType || task?.type || task?.cmd).toLowerCase();
  if (!['claude', 'codex'].includes(provider)) {
    return { interrupted: false, reason: 'unsupported_provider' };
  }
  const ctmSessionId = _asString(task?.ctm_session_id || task?.session_id);
  const row = jsonlPath ? null : _selectAgentRow(db, ctmSessionId, provider);
  const filePath = _asString(jsonlPath || row?.jsonl_path || _fallbackTranscriptPathFromTask(task, provider, { homeDir }));
  if (!filePath) return { interrupted: false, reason: 'missing_transcript_path' };
  const tail = _readJsonlTail(path.resolve(filePath));
  if (tail.error) return { interrupted: false, reason: tail.error, jsonlPath: filePath };
  const result = detectRestoreInterruptionFromEntries(tail.entries, { now });
  return {
    ...result,
    jsonlPath: filePath,
    fileSize: tail.size || 0,
    fileMtimeMs: tail.mtimeMs || 0,
    agentSessionId: row?.agent_session_id || '',
  };
}

module.exports = {
  detectRestoreInterruption,
  detectRestoreInterruptionFromEntries,
  _private: {
    _readJsonlTail,
    _semanticEvent,
    _lastSemanticEvent,
  },
};
