// wall-e/tools/session-sharing.js
// Inspired by OpenCode's SessionShare service
// (https://github.com/anomalyco/opencode, packages/opencode/src/share/session.ts)
// Export/import coding sessions as portable JSON for sharing between agents.
'use strict';

const crypto = require('node:crypto');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');

/**
 * Session sharing — export coding sessions as JSON and import them back.
 *
 * OpenCode approach: SessionShare.share() publishes a session to a cloud service
 * with a secret URL. Wall-E adapts this as a local-first JSON export with a
 * UUID secret token for sharing via URL or file.
 *
 * Export format:
 * {
 *   version: 1,
 *   sessionId: string,
 *   shareToken: string,
 *   plan: string,
 *   subtasks: Array<{ id, title, status, startedAt?, completedAt? }>,
 *   events: Array<{ eventType, payload, timestamp }>,
 *   diffs: Array<{ file, patch }>,
 *   createdAt: string (ISO),
 *   exportedAt: string (ISO),
 * }
 */

const EXPORT_VERSION = 1;

/**
 * Export a coding session from the brain DB as a portable JSON object.
 * Pulls data from coding_events table (if available) and task records.
 *
 * @param {string} sessionId - The session/task ID to export
 * @param {object} brain - Brain module (must expose getDb())
 * @returns {{ version, sessionId, shareToken, plan, subtasks, events, diffs, createdAt, exportedAt }}
 */
function exportSession(sessionId, brain) {
  if (!sessionId) throw new Error('sessionId is required');
  if (!brain || typeof brain.getDb !== 'function') {
    throw new Error('brain module with getDb() is required');
  }

  const db = brain.getDb();
  const shareToken = crypto.randomUUID();

  // Try to fetch task record for plan/metadata
  let plan = '';
  let createdAt = new Date().toISOString();
  let subtasks = [];

  const task = _safeGet(db, 'SELECT * FROM tasks WHERE id = ?', sessionId);
  if (task) {
    plan = task.description || task.title || '';
    createdAt = task.created_at || createdAt;
    if (task.skill_config) {
      try {
        const cfg = JSON.parse(task.skill_config);
        if (cfg.request) plan = cfg.request;
        if (cfg.subtasks) subtasks = cfg.subtasks;
      } catch { /* ignore parse errors */ }
    }
  }

  // Fetch events from coding_events table
  let events = [];
  const hasEventsTable = _tableExists(db, 'coding_events');
  if (hasEventsTable) {
    events = db.prepare(
      'SELECT event_type, payload, timestamp FROM coding_events WHERE session_id = ? ORDER BY timestamp ASC'
    ).all(sessionId).map(row => ({
      eventType: row.event_type,
      payload: _safeParse(row.payload),
      timestamp: row.timestamp,
    }));
  }

  // Extract diffs from events if present
  const diffs = [];
  for (const evt of events) {
    if (evt.eventType === 'file.edited' || evt.eventType === 'diff') {
      const p = evt.payload || {};
      if (p.file || p.filePath) {
        diffs.push({ file: p.file || p.filePath, patch: p.patch || p.diff || '' });
      }
    }
  }

  const transcript = _readWalleTranscriptMetadata(sessionId);

  // Store the share token in kv_store for later verification
  if (typeof brain.setKv === 'function') {
    brain.setKv(`share:${sessionId}`, JSON.stringify({
      token: shareToken,
      exportedAt: new Date().toISOString(),
    }));
  }

  return {
    version: EXPORT_VERSION,
    sessionId,
    shareToken,
    plan,
    subtasks,
    events,
    parts: transcript.parts,
    compactions: transcript.compactions,
    snapshots: transcript.snapshots,
    transcriptPath: transcript.transcriptPath,
    diffs,
    createdAt,
    exportedAt: new Date().toISOString(),
  };
}

/**
 * Import a previously exported session into the brain DB.
 * Stores events in coding_events table and creates a task record.
 *
 * @param {object} data - Exported session JSON
 * @param {object} brain - Brain module
 * @returns {{ sessionId: string, eventsImported: number }}
 */
function importSession(data, brain) {
  if (!data || typeof data !== 'object') throw new Error('Session data object is required');
  if (!data.version || data.version > EXPORT_VERSION) {
    throw new Error(`Unsupported session format version: ${data.version}`);
  }
  if (!data.sessionId) throw new Error('sessionId is required in session data');
  if (!brain || typeof brain.getDb !== 'function') {
    throw new Error('brain module with getDb() is required');
  }

  const db = brain.getDb();
  const { EventLog } = require('./event-log');
  const eventLog = new EventLog(db);

  // Import events
  let eventsImported = 0;
  if (Array.isArray(data.events)) {
    for (const evt of data.events) {
      eventLog.append(
        data.sessionId,
        evt.eventType || 'imported',
        evt.payload || {},
        evt.timestamp
      );
      eventsImported++;
    }
  }

  // Create a task record if brain supports it and task doesn't exist
  if (typeof brain.insertTask === 'function') {
    const existing = _safeGet(db, 'SELECT id FROM tasks WHERE id = ?', data.sessionId);
    if (!existing) {
      try {
        brain.insertTask({
          title: `Imported session: ${data.sessionId.slice(0, 8)}`,
          description: data.plan || 'Imported coding session',
          priority: 'normal',
          type: 'once',
          execution: 'skill',
          skill: 'coding-agent',
          skill_config: JSON.stringify({
            request: data.plan || '',
            imported: true,
            originalSessionId: data.sessionId,
            subtasks: data.subtasks || [],
          }),
          source: 'import',
          source_ref: data.shareToken || '',
        });
      } catch { /* task table may not exist */ }
    }
  }

  // Store import metadata
  if (typeof brain.setKv === 'function') {
    brain.setKv(`import:${data.sessionId}`, JSON.stringify({
      importedAt: new Date().toISOString(),
      originalExportedAt: data.exportedAt,
      eventsImported,
    }));
  }

  return { sessionId: data.sessionId, eventsImported };
}

/**
 * Verify a share token for a session.
 * @param {string} sessionId
 * @param {string} token
 * @param {object} brain
 * @returns {boolean}
 */
function verifyShareToken(sessionId, token, brain) {
  if (!sessionId || !token || !brain) return false;
  try {
    const stored = brain.getKv(`share:${sessionId}`);
    if (!stored) return false;
    const parsed = JSON.parse(stored);
    return parsed.token === token;
  } catch {
    return false;
  }
}

// ── Helpers ──

function _safeGet(db, sql, ...params) {
  try {
    return db.prepare(sql).get(...params) || null;
  } catch {
    return null;
  }
}

function _safeParse(json) {
  if (!json) return {};
  if (typeof json === 'object') return json;
  try { return JSON.parse(json); } catch { return {}; }
}

function _tableExists(db, tableName) {
  try {
    const row = db.prepare(
      "SELECT name FROM sqlite_master WHERE type='table' AND name=?"
    ).get(tableName);
    return !!row;
  } catch {
    return false;
  }
}

function _readWalleTranscriptMetadata(sessionId) {
  const transcriptPath = _findWalleTranscriptPath(sessionId);
  if (!transcriptPath) return { transcriptPath: '', parts: [], compactions: [], snapshots: [] };

  const parts = [];
  let raw = '';
  try { raw = fs.readFileSync(transcriptPath, 'utf8'); } catch { raw = ''; }
  for (const line of raw.split('\n')) {
    if (!line.trim()) continue;
    let row;
    try { row = JSON.parse(line); } catch { continue; }
    if (row?.type !== 'walle_part') continue;
    const part = {
      uuid: row.uuid || '',
      partType: row.partType || 'event',
      timestamp: row.timestamp || '',
      data: row.data || {},
    };
    parts.push(part);
  }
  return {
    transcriptPath,
    parts,
    compactions: parts.filter((part) => part.partType === 'compaction'),
    snapshots: parts.filter((part) => part.partType === 'snapshot'),
  };
}

function _findWalleTranscriptPath(sessionId) {
  if (!sessionId) return '';
  for (const root of _candidateWalleSessionRoots()) {
    const candidate = path.join(root, `${sessionId}.jsonl`);
    try {
      if (fs.existsSync(candidate)) return candidate;
    } catch { /* ignore inaccessible roots */ }
  }
  return '';
}

function _candidateWalleSessionRoots() {
  const roots = [
    process.env.WALLE_SESSIONS_DIR,
    process.env.WALL_E_SESSIONS_DIR,
    process.env.WALLE_DEV_DIR ? path.join(process.env.WALLE_DEV_DIR, 'sessions') : '',
    process.env.WALL_E_DATA_DIR ? path.join(process.env.WALL_E_DATA_DIR, 'sessions') : '',
    process.env.CTM_DATA_DIR ? path.join(process.env.CTM_DATA_DIR, 'sessions') : '',
    path.join(os.homedir(), '.walle', 'sessions'),
  ].filter(Boolean);
  return [...new Set(roots)];
}

module.exports = {
  exportSession,
  importSession,
  verifyShareToken,
  EXPORT_VERSION,
  _readWalleTranscriptMetadata,
  _findWalleTranscriptPath,
};
