'use strict';

function stripAnsi(value) {
  return String(value || '')
    .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
    .replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, '')
    .replace(/\x1b[@-Z\\-_]/g, '');
}

function normalizeSubmissionText(text) {
  return String(text || '').replace(/\r\n?/g, '\n').replace(/\s+/g, ' ').trim();
}

const TERMINAL_BRACKETED_PASTE_START = '\x1b[200~';
const TERMINAL_BRACKETED_PASTE_END = '\x1b[201~';

function bracketedPastePayload(text) {
  return `${TERMINAL_BRACKETED_PASTE_START}${String(text || '').replace(/\x1b/g, '')}${TERMINAL_BRACKETED_PASTE_END}`;
}

// Decide the exact bytes CTM writes into the PTY for a phone reply. Terminal
// line editors (Claude/Codex/Gemini/opencode) enable bracketed-paste mode; a
// real terminal wraps pasted multi-line text in ESC[200~ … ESC[201~ so embedded
// newlines stay literal text instead of being interpreted as submit/newline
// keypresses that fragment the message and leave it stuck in the composer.
// Single-line replies and plain shells are written verbatim (the proven path).
function remoteSubmissionWritePayload(text, options = {}) {
  const normalized = String(text || '');
  if (!normalized) return '';
  if (normalized.includes('\n') && options.bracketedPasteCapable) {
    return bracketedPastePayload(normalized);
  }
  return normalized;
}

function submissionTextMatches(candidate, submitted) {
  const left = normalizeSubmissionText(candidate);
  const right = normalizeSubmissionText(submitted);
  if (!left || !right) return false;
  if (left === right) return true;
  if (right.length >= 80 && left.includes(right)) return true;
  if (left.length >= 80 && right.includes(left)) return true;
  return false;
}

function promptMessageText(message) {
  if (message == null) return '';
  if (typeof message === 'string') return message;
  return message.text || message.content || message.message || '';
}

function timestampMs(value) {
  if (value == null || value === '') return 0;
  if (typeof value === 'number' && Number.isFinite(value)) {
    return value > 0 && value < 100000000000 ? value * 1000 : value;
  }
  const text = String(value || '').trim();
  if (!text) return 0;
  if (/^\d+(?:\.\d+)?$/.test(text)) {
    const n = Number(text);
    return n > 0 && n < 100000000000 ? n * 1000 : n;
  }
  const parsed = Date.parse(text);
  return Number.isFinite(parsed) ? parsed : 0;
}

function promptMessageTimestampMs(message) {
  if (!message || typeof message !== 'object') return 0;
  return timestampMs(message.timestamp || message.created_at || message.createdAt || message.time || message.ts);
}

function durablePromptMatch(messages, submitted, options = {}) {
  const minTimestampMs = Math.max(0, Number(options.minTimestampMs || options.min_timestamp_ms || 0) || 0);
  const toleranceMs = Math.max(0, Number(options.timestampToleranceMs ?? options.timestamp_tolerance_ms ?? 0) || 0);
  const requireFreshTimestamp = minTimestampMs > 0 && options.requireFreshTimestamp !== false;
  for (const message of (Array.isArray(messages) ? messages : [])) {
    const text = promptMessageText(message);
    if (!submissionTextMatches(text, submitted)) continue;
    const messageTimestampMs = promptMessageTimestampMs(message);
    if (requireFreshTimestamp) {
      if (!messageTimestampMs) continue;
      if (messageTimestampMs + toleranceMs < minTimestampMs) continue;
    }
    return {
      matched: true,
      text,
      timestampMs: messageTimestampMs,
    };
  }
  return {
    matched: false,
    text: '',
    timestampMs: 0,
  };
}

function promptSuffixMatchesSubmission(candidate, submitted) {
  const left = normalizeSubmissionText(candidate);
  const right = normalizeSubmissionText(submitted);
  if (!left || !right) return false;
  if (left === right) return true;
  if (left.endsWith(right)) return true;
  return submissionTextMatches(left, right);
}

function normalizeTerminalRows(rows) {
  return (Array.isArray(rows) ? rows : [])
    .map((row) => {
      const source = typeof row === 'string' ? { text: row } : (row || {});
      return {
        text: stripAnsi(String(source.text || source.line || '')).replace(/\u00a0/g, ' ').replace(/\s+$/g, ''),
        wrapped: !!(source.wrapped || source.isWrapped),
      };
    })
    .filter((row) => row.text.trim());
}

function terminalRowsText(rows) {
  return normalizeTerminalRows(rows).map((row) => row.text).join('\n');
}

function stripPromptPrefix(text) {
  return String(text || '')
    .replace(/^\s*[›>]\s*/, '')
    .replace(/^\s*[$#]\s+/, '')
    .replace(/^[^\s]{1,80}\s+[$#]\s+/, '');
}

function terminalRowsPromptText(rows) {
  return normalizeTerminalRows(rows).map((row) => stripPromptPrefix(row.text)).join('\n');
}

function isPromptStartLine(text) {
  const t = String(text || '').trim();
  if (!t) return false;
  if (/^[›>]\s*/.test(t)) return true;
  if (/^[$#]\s+\S/.test(t)) return true;
  return /^[^\s]{1,80}\s+[$#]\s+\S/.test(t);
}

function isAgentProgressLine(text) {
  const t = String(text || '').trim();
  if (!t) return false;
  return /^(?:Working|Thinking|Explored|Ran|Read|Update|Edit|Write|Bash|Called|Context compacted|Result:|Commit:|Error:|Failed:|✓|✔|✗|✘|[-*]\s+\w)/i.test(t) ||
    /\b(?:esc to interrupt|tokens?|tool block|tool call|ctrl\+o to expand|almost done thinking)\b/i.test(t);
}

function isTerminalStatusLine(text) {
  const t = String(text || '').trim();
  if (!t) return false;
  if (/^(?:gpt|claude|gemini|deepseek|kimi|qwen|llama|mistral|codex)[\w.-]*\s+.*(?:·|•)\s*(?:[~./]|\/)/i.test(t)) return true;
  return false;
}

function activePromptRowsFromSuffix(suffixRows, submittedText = '') {
  const promptRows = [];
  const submitted = normalizeSubmissionText(submittedText);
  for (let idx = 0; idx < suffixRows.length; idx += 1) {
    const row = suffixRows[idx];
    if (idx === 0 || row.wrapped) {
      promptRows.push(row);
      continue;
    }
    const text = row.text.trim();
    if (isPromptStartLine(text) || isAgentProgressLine(text) || isTerminalStatusLine(text)) break;
    if (submitted) {
      const current = normalizeSubmissionText(promptRows.map((r) => stripPromptPrefix(r.text)).join('\n'));
      const candidate = normalizeSubmissionText([...promptRows, row].map((r) => stripPromptPrefix(r.text)).join('\n'));
      if (candidate && candidate.length > current.length && submitted.startsWith(candidate)) {
        promptRows.push(row);
        continue;
      }
    }
    break;
  }
  return promptRows;
}

function terminalSubmittedPromptState(rows, submittedText) {
  const cleanRows = normalizeTerminalRows(rows);
  let submittedVisible =
    submissionTextMatches(cleanRows.map((row) => row.text).join('\n'), submittedText) ||
    submissionTextMatches(cleanRows.map((row) => stripPromptPrefix(row.text)).join('\n'), submittedText);
  let lastPromptIndex = -1;
  for (let idx = 0; idx < cleanRows.length; idx += 1) {
    if (isPromptStartLine(cleanRows[idx].text)) lastPromptIndex = idx;
  }
  if (lastPromptIndex < 0) {
    return {
      submittedVisible,
      activePromptVisible: false,
      agentProgressAfterPrompt: false,
      lastPromptIndex,
      rowCount: cleanRows.length,
    };
  }

  const suffixRows = cleanRows.slice(lastPromptIndex);
  const promptRows = activePromptRowsFromSuffix(suffixRows, submittedText);
  const promptContainsSubmitted =
    promptSuffixMatchesSubmission(promptRows.map((row) => row.text).join('\n'), submittedText) ||
    promptSuffixMatchesSubmission(promptRows.map((row) => stripPromptPrefix(row.text)).join('\n'), submittedText);
  submittedVisible = submittedVisible || promptContainsSubmitted;
  let agentProgressAfterPrompt = false;
  if (promptContainsSubmitted) {
    for (let idx = promptRows.length; idx < suffixRows.length; idx += 1) {
      const row = suffixRows[idx];
      const text = row.text.trim();
      if (!text || isPromptStartLine(text)) continue;
      if (isAgentProgressLine(text)) {
        agentProgressAfterPrompt = true;
        break;
      }
      if (isTerminalStatusLine(text)) continue;
      if (!row.wrapped) {
        agentProgressAfterPrompt = true;
        break;
      }
    }
  }

  return {
    submittedVisible,
    activePromptVisible: !!(promptContainsSubmitted && !agentProgressAfterPrompt),
    agentProgressAfterPrompt,
    lastPromptIndex,
    rowCount: cleanRows.length,
  };
}

function terminalSubmissionAcceptedByMarker(options = {}) {
  const markerNow = String(options.markerNow || '');
  const beforeEnterMarker = String(options.beforeEnterMarker || '');
  if (beforeEnterMarker) {
    if (markerNow && markerNow !== beforeEnterMarker) {
      return {
        accepted: true,
        reason: 'terminal_output_advanced_after_enter',
      };
    }
    return {
      accepted: false,
      reason: '',
    };
  }

  const beforeMarker = String(options.beforeMarker || '');
  if (!options.promptStillVisible && beforeMarker && markerNow && markerNow !== beforeMarker) {
    return {
      accepted: true,
      reason: 'terminal_output_advanced',
    };
  }

  return {
    accepted: false,
    reason: '',
  };
}

module.exports = {
  bracketedPastePayload,
  remoteSubmissionWritePayload,
  isAgentProgressLine,
  isPromptStartLine,
  isTerminalStatusLine,
  normalizeSubmissionText,
  normalizeTerminalRows,
  durablePromptMatch,
  stripAnsi,
  stripPromptPrefix,
  submissionTextMatches,
  timestampMs,
  terminalRowsPromptText,
  terminalRowsText,
  terminalSubmissionAcceptedByMarker,
  terminalSubmittedPromptState,
};
