'use strict';

const fs = require('node:fs');
const path = require('node:path');
const { WorkspaceReplay, defaultSessionsDir } = require('./workspace-replay');

class CodingSessionIndex {
  constructor({ replay = null, sessionsDir = defaultSessionsDir(), now = () => new Date().toISOString() } = {}) {
    this.sessionsDir = sessionsDir;
    this.replay = replay || new WorkspaceReplay({ sessionsDir, now });
  }

  listSessions(opts = {}) {
    const limit = clampLimit(opts.limit, 50, 200);
    const cursor = Math.max(0, Number(opts.cursor || 0) || 0);
    const query = normalizeQuery(opts.query || '');
    const cwd = opts.cwd ? path.resolve(opts.cwd) : '';
    const files = this._listFiles();
    const sessions = [];

    for (const file of files) {
      const state = this.replay.rebuildState(file.sessionId, { transcriptPath: file.filePath, cwd });
      const info = sessionInfoFromState(state, file);
      if (cwd && !pathMatchesCwd(info.cwd, cwd)) continue;
      if (query && !matchesSessionQuery(info, state, query)) continue;
      sessions.push(info);
    }

    const page = sessions.slice(cursor, cursor + limit);
    return {
      sessions: page,
      nextCursor: cursor + limit < sessions.length ? String(cursor + limit) : null,
      total: sessions.length,
      source: 'walle-jsonl',
      sessionsDir: this.sessionsDir,
    };
  }

  searchSessions(opts = {}) {
    const listed = this.listSessions(opts);
    const query = normalizeQuery(opts.query || '');
    return {
      ...listed,
      results: listed.sessions.map((session) => ({
        session_id: session.sessionId,
        title: session.title,
        cwd: session.cwd,
        updated_at: session.updatedAt,
        message_count: session.messageCount,
        tool_count: session.toolCount,
        snippet: query ? snippetForQuery(session, query) : session.lastMessage,
        source_layer: 'walle-jsonl',
      })),
    };
  }

  getSession(sessionId, opts = {}) {
    const state = this.replay.rebuildState(sessionId, opts);
    if (!state.transcriptPath) return null;
    const stat = safeStat(state.transcriptPath);
    const info = sessionInfoFromState(state, {
      sessionId,
      filePath: state.transcriptPath,
      mtimeMs: stat?.mtimeMs || 0,
      size: stat?.size || 0,
    });
    const limit = clampLimit(opts.limit, 200, 1000);
    return {
      session: info,
      messages: state.messages.slice(-limit),
      parts: state.parts.slice(-limit),
      tools: state.tools.slice(-limit),
      permissions: state.permissions.slice(-limit),
      usage: state.usage,
      errors: state.errors,
      workspace: state.workspace,
      transcriptPath: state.transcriptPath,
      source: 'walle-jsonl',
    };
  }

  _listFiles() {
    let entries;
    try {
      entries = fs.readdirSync(this.sessionsDir, { withFileTypes: true });
    } catch {
      return [];
    }
    const names = new Set(entries.map((entry) => entry.name));
    const files = [];
    for (const entry of entries) {
      if (!entry.isFile()) continue;
      if (!entry.name.endsWith('.jsonl') && !entry.name.endsWith('.jsonl.bak')) continue;
      if (entry.name.endsWith('.jsonl.bak') && names.has(entry.name.replace(/\.bak$/, ''))) continue;
      const filePath = path.join(this.sessionsDir, entry.name);
      const stat = safeStat(filePath);
      if (!stat) continue;
      files.push({
        sessionId: entry.name.replace(/\.jsonl(\.bak)?$/, ''),
        filePath,
        mtimeMs: stat.mtimeMs,
        size: stat.size,
      });
    }
    files.sort((a, b) => b.mtimeMs - a.mtimeMs || a.sessionId.localeCompare(b.sessionId));
    return files;
  }
}

function sessionInfoFromState(state, file) {
  const messages = state.messages || [];
  const parts = state.parts || [];
  const firstUser = messages.find((message) => message.role === 'user')?.text || '';
  const lastMessage = [...messages].reverse().find((message) => message.text)?.text || '';
  const timestamp = latestTimestamp([
    state.meta?.timestamp,
    ...messages.map((message) => message.timestamp),
    ...parts.map((part) => part.timestamp),
  ]);
  const title = String(state.meta?.label || firstUser || file.sessionId || 'Wall-E session').slice(0, 120);
  return {
    sessionId: state.sessionId || file.sessionId,
    title,
    cwd: state.cwd || state.meta?.cwd || '',
    modelId: state.meta?.modelId || '',
    modelProvider: state.meta?.modelProvider || '',
    agent: state.meta?.agent || '',
    mode: state.meta?.mode || '',
    createdAt: state.meta?.timestamp || messages[0]?.timestamp || '',
    updatedAt: timestamp || (file.mtimeMs ? new Date(file.mtimeMs).toISOString() : ''),
    messageCount: messages.length,
    toolCount: (state.tools || []).length,
    partCount: parts.length,
    transcriptPath: file.filePath || state.transcriptPath || '',
    fileSize: file.size || state.fileSize || 0,
    firstUserMessage: firstUser.slice(0, 500),
    lastMessage: lastMessage.slice(0, 500),
  };
}

function matchesSessionQuery(info, state, query) {
  const haystack = [
    info.title,
    info.cwd,
    info.modelId,
    info.modelProvider,
    info.firstUserMessage,
    info.lastMessage,
    ...(state.messages || []).map((message) => message.text),
    ...(state.tools || []).map((part) => `${part.data?.name || ''} ${part.data?.tool || ''} ${part.data?.summary || ''}`),
  ].join('\n').toLowerCase();
  return query.split(/\s+/).filter(Boolean).every((term) => haystack.includes(term));
}

function snippetForQuery(session, query) {
  const candidates = [session.firstUserMessage, session.lastMessage, session.title].filter(Boolean);
  const terms = query.split(/\s+/).filter(Boolean);
  return candidates.find((candidate) => terms.some((term) => candidate.toLowerCase().includes(term))) || candidates[0] || '';
}

function pathMatchesCwd(sessionCwd, cwd) {
  if (!sessionCwd) return false;
  const resolved = path.resolve(sessionCwd);
  if (resolved === cwd) return true;
  const rel = path.relative(cwd, resolved);
  return Boolean(rel) && !rel.startsWith('..') && !path.isAbsolute(rel);
}

function latestTimestamp(values) {
  let best = 0;
  let raw = '';
  for (const value of values) {
    const ms = new Date(value || '').getTime();
    if (Number.isFinite(ms) && ms > best) {
      best = ms;
      raw = value;
    }
  }
  return raw;
}

function normalizeQuery(query) {
  return String(query || '').trim().toLowerCase();
}

function clampLimit(value, fallback, max) {
  const n = Number(value);
  if (!Number.isFinite(n) || n <= 0) return fallback;
  return Math.min(max, Math.max(1, Math.floor(n)));
}

function safeStat(filePath) {
  try {
    return fs.statSync(filePath);
  } catch {
    return null;
  }
}

module.exports = {
  CodingSessionIndex,
  sessionInfoFromState,
  matchesSessionQuery,
};
