'use strict';

// Shared "user-prompt index" compute for the Queue Builder prompt-preview. Extracted from
// server.js so the SAME logic runs on the main thread (live connection, fallback) AND on
// the read-pool worker (read-only connection) — keeping the SELECT-all-user-rows + sort +
// exclusions + sha1-per-prompt fingerprint OFF the main event loop (Phase 1). It is a pure
// read: takes a db handle + already-resolved inputs (sourceIds, exclusion rows, a flag for
// whether the session has a Codex source) so the worker needs no owner/source resolution.
//
// Output is identical by construction to the previous inline path, so moving it off-thread
// (or falling back to it on main) can never change the prompt list.

const crypto = require('crypto');
const { sortTimelineMessagesInPlace } = require('./timeline-order');
const { isProviderGeneratedUserContextText } = require('./provider-user-context');

// Lazy so a require cycle in session-history (large) can't break worker boot.
let _isCodexSyntheticUserText = null;
function _codexSynthetic(text) {
  if (!_isCodexSyntheticUserText) {
    try { _isCodexSyntheticUserText = require('./session-history').isCodexSyntheticUserText; }
    catch { _isCodexSyntheticUserText = () => false; }
  }
  return _isCodexSyntheticUserText(text);
}

function _normalizePromptVersionText(text) {
  return String(text || '').replace(/\s+/g, ' ').trim().slice(0, 500);
}
function _promptVersionHash(input) {
  return crypto.createHash('sha1').update(String(input || '')).digest('hex').slice(0, 16);
}
function _sessionMessageTextHash(text) {
  return crypto.createHash('sha256').update(String(text || '').replace(/\s+/g, ' ').trim()).digest('hex');
}
function _msgText(m) { return (m && (m.text || m.content || m.message)) || ''; }
function _msgTs(m) { return (m && (m.timestamp || m.created_at || m.createdAt)) || ''; }

// Mirrors server.js _applySessionMessageExclusions, but takes already-loaded exclusion rows
// and the codex-synthetic decision as inputs (no DB / owner resolution needed here).
function applyExclusions(messages, { exclusionRows = [], filterCodexSynthetic = false } = {}) {
  if (!Array.isArray(messages) || messages.length === 0) return messages || [];
  const visible = messages.filter((msg) => {
    const role = String(msg && msg.role || '').trim();
    if (role !== 'user') return true;
    const text = _msgText(msg);
    if (isProviderGeneratedUserContextText(text)) return false;
    if (filterCodexSynthetic && _codexSynthetic(text)) return false;
    return true;
  });
  if (!Array.isArray(exclusionRows) || exclusionRows.length === 0) return visible;
  const exact = new Set();
  const textOnly = new Set();
  for (const row of exclusionRows) {
    const role = String(row && row.role || '').trim();
    const hash = String(row && row.text_hash || '').trim();
    const ts = String(row && row.timestamp || '').trim();
    if (!hash) continue;
    if (ts) exact.add(`${role}\0${ts}\0${hash}`);
    else textOnly.add(`${role}\0${hash}`);
  }
  if (exact.size === 0 && textOnly.size === 0) return visible;
  return visible.filter((msg) => {
    const role = String(msg && msg.role || '').trim();
    const ts = String(_msgTs(msg)).trim();
    const hash = _sessionMessageTextHash(_msgText(msg));
    return !exact.has(`${role}\0${ts}\0${hash}`) && !textOnly.has(`${role}\0${hash}`);
  });
}

// Mirrors server.js _promptSummaryFromMessages.
function summarize(messages, sourceIds = []) {
  const prompts = (Array.isArray(messages) ? messages : [])
    .map((message, index) => ({
      text: _normalizePromptVersionText(_msgText(message)),
      timestamp: String(_msgTs(message) || ''),
      index,
    }))
    .filter((p) => p.text);
  const last = prompts.length ? prompts[prompts.length - 1] : null;
  const promptFingerprint = prompts.length > 0
    ? _promptVersionHash(prompts.map((p) => [p.index, _promptVersionHash(p.text), p.timestamp].join(':')).join('|'))
    : '';
  return {
    sourceIds,
    cacheAvailable: prompts.length > 0,
    userPromptCount: prompts.length,
    lastPromptPreview: last ? last.text.slice(0, 240) : '',
    lastPromptTimestamp: last ? last.timestamp : '',
    promptFingerprint,
  };
}

// Which table holds the per-message render rows. The row store (session_message_rows) is the
// default on current installs; the legacy session_messages index has been RETIRED (dropped by
// the de-dup migration). Mirrors db.js `_messageFreshnessTable()` so the prompt compute reads the
// SAME table the freshness key is computed from — otherwise this SELECT throws "no such table:
// session_messages", the off-thread index returns empty, and every prompt poll falls back to the
// heavy main-thread timeline projection (the multi-second apiSessionPrompts input-box freeze).
// Column names differ: the row store uses text/timestamp; the legacy table used content/created_at.
function _promptRowsSource(d) {
  let hasRows = false;
  try { hasRows = !!d.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_message_rows'").get(); }
  catch { hasRows = false; }
  return hasRows
    ? { table: 'session_message_rows', cols: 'role, text AS text, timestamp AS timestamp' }
    : { table: 'session_messages', cols: 'role, content AS text, created_at AS timestamp' };
}

// Load the raw user-prompt rows for the given source ids from the message index, ordered.
function loadUserPromptRows(d, sourceIds = []) {
  const src = _promptRowsSource(d);
  const stmt = d.prepare(
    `SELECT ${src.cols}
       FROM ${src.table}
      WHERE ctm_session_id = ? AND role = 'user'
      ORDER BY message_index ASC`
  );
  const prompts = [];
  for (const id of (Array.isArray(sourceIds) ? sourceIds : [])) {
    for (const row of stmt.all(String(id))) {
      prompts.push({ role: row.role, text: row.text || '', timestamp: row.timestamp || '' });
    }
  }
  sortTimelineMessagesInPlace(prompts);
  return prompts;
}

// The whole index-source compute: load rows → exclusions → summary. Returns the filtered
// user-prompt messages + the summary. Runs on the read-pool worker (off main) or on main.
function computeUserPromptIndex(d, { sourceIds = [], exclusionRows = [], filterCodexSynthetic = false } = {}) {
  const raw = loadUserPromptRows(d, sourceIds);
  const messages = applyExclusions(raw, { exclusionRows, filterCodexSynthetic });
  return { messages, summary: summarize(messages, sourceIds) };
}

module.exports = { computeUserPromptIndex, loadUserPromptRows, applyExclusions, summarize };
