/**
 * Context Bundler — Checkpoint compression (180K → 15K)
 *
 * Compresses checkpoint state into a compact bundle for session resumption.
 * Saves bundles to .morph/bundles/{bundleId}.json
 *
 * A bundle contains:
 *   - decisions[]: key architectural decisions made
 *   - artifacts[]: paths to important generated files
 *   - nextSteps[]: what to implement next
 *   - metadata: original vs bundled token counts
 */

import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
import { join } from 'path';
import { randomUUID } from 'crypto';

const BUNDLES_DIR = join(process.cwd(), '.morph/bundles');

// ============================================================================
// Bundle Creation
// ============================================================================

/**
 * Create a context bundle from checkpoint data
 * @param {Object} opts
 * @param {string} opts.feature - Feature name
 * @param {number} opts.checkpointNum - Checkpoint number this bundle captures
 * @param {Array} opts.decisions - Key decisions made: [{ id, title, summary }]
 * @param {Array} opts.artifacts - Important file paths: [{ path, description }]
 * @param {Array} opts.nextSteps - Next tasks to implement: [string]
 * @param {number} [opts.originalTokens=180000] - Estimated original context size
 * @param {Object} [opts.meta] - Additional metadata
 * @returns {Object} Bundle object with ID
 */
export function createBundle({
  feature,
  checkpointNum,
  decisions = [],
  artifacts = [],
  nextSteps = [],
  originalTokens = 180000,
  meta = {}
}) {
  const bundleId = `${feature}-cp${checkpointNum}-${randomUUID().substring(0, 8)}`;
  const now = new Date().toISOString();

  // Estimate bundle size (decisions + artifacts + nextSteps summary)
  const bundledContent = JSON.stringify({ decisions, artifacts, nextSteps, meta });
  const bundledTokens = Math.round(bundledContent.length / 4); // ~4 chars/token

  const bundle = {
    bundleId,
    feature,
    checkpointNum,
    createdAt: now,
    decisions,
    artifacts,
    nextSteps,
    meta,
    tokenMetrics: {
      originalTokens,
      bundledTokens,
      compressionRatio: Math.round(originalTokens / Math.max(bundledTokens, 1)),
      savingsTokens: originalTokens - bundledTokens
    },
    resumeInstructions: buildResumeInstructions(feature, checkpointNum, decisions, nextSteps)
  };

  return bundle;
}

/**
 * Save a bundle to disk
 * @param {Object} bundle - Bundle object (from createBundle)
 * @returns {string} Path to saved bundle file
 */
export function saveBundle(bundle) {
  if (!existsSync(BUNDLES_DIR)) {
    mkdirSync(BUNDLES_DIR, { recursive: true });
  }

  const filePath = join(BUNDLES_DIR, `${bundle.bundleId}.json`);
  writeFileSync(filePath, JSON.stringify(bundle, null, 2), 'utf8');

  // Also update feature state with bundle reference
  try {
    const statePath = join(process.cwd(), '.morph/state.json');
    if (existsSync(statePath)) {
      const state = JSON.parse(readFileSync(statePath, 'utf8'));
      if (state.features?.[bundle.feature]) {
        state.features[bundle.feature].contextBundles = state.features[bundle.feature].contextBundles || [];
        state.features[bundle.feature].contextBundles.push({
          bundleId: bundle.bundleId,
          checkpointNum: bundle.checkpointNum,
          createdAt: bundle.createdAt,
          originalTokens: bundle.tokenMetrics.originalTokens,
          bundledTokens: bundle.tokenMetrics.bundledTokens,
          path: filePath
        });
        state.metadata = state.metadata || {};
        state.metadata.lastUpdated = new Date().toISOString();
        writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
      }
    }
  } catch {
    // Non-fatal if state update fails
  }

  return filePath;
}

/**
 * Load a bundle from disk
 * @param {string} bundleId - Bundle ID
 * @returns {Object|null} Bundle object or null if not found
 */
export function loadBundle(bundleId) {
  const filePath = join(BUNDLES_DIR, `${bundleId}.json`);

  if (!existsSync(filePath)) {
    return null;
  }

  try {
    return JSON.parse(readFileSync(filePath, 'utf8'));
  } catch {
    return null;
  }
}

/**
 * List all bundles, optionally filtered by feature
 * @param {string} [feature] - Filter by feature name
 * @returns {Array} Array of bundle summaries
 */
export function listBundles(feature = null) {
  if (!existsSync(BUNDLES_DIR)) return [];

  const files = readdirSync(BUNDLES_DIR).filter(f => f.endsWith('.json'));
  const bundles = [];

  for (const file of files) {
    try {
      const bundle = JSON.parse(readFileSync(join(BUNDLES_DIR, file), 'utf8'));
      if (!feature || bundle.feature === feature) {
        bundles.push({
          bundleId: bundle.bundleId,
          feature: bundle.feature,
          checkpointNum: bundle.checkpointNum,
          createdAt: bundle.createdAt,
          originalTokens: bundle.tokenMetrics?.originalTokens,
          bundledTokens: bundle.tokenMetrics?.bundledTokens,
          compressionRatio: bundle.tokenMetrics?.compressionRatio,
          nextStepsCount: bundle.nextSteps?.length || 0
        });
      }
    } catch {
      // Skip malformed files
    }
  }

  return bundles.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
}

/**
 * Get the latest bundle for a feature at or before a checkpoint number
 * @param {string} feature - Feature name
 * @param {number} [checkpointNum] - Target checkpoint (latest if not specified)
 * @returns {Object|null} Bundle or null
 */
export function getLatestBundle(feature, checkpointNum = Infinity) {
  const bundles = listBundles(feature)
    .filter(b => b.checkpointNum <= checkpointNum)
    .sort((a, b) => b.checkpointNum - a.checkpointNum);

  if (bundles.length === 0) return null;
  return loadBundle(bundles[0].bundleId);
}

// ============================================================================
// Session Replay
// ============================================================================

/**
 * Generate minimal context for session replay from a bundle
 * @param {Object} bundle - Bundle object
 * @returns {string} Compact context string (~15K tokens) for session resumption
 */
export function generateResumeContext(bundle) {
  const lines = [];
  lines.push(`# Session Resume — ${bundle.feature} @ Checkpoint ${bundle.checkpointNum}`);
  lines.push(`> Bundle ID: ${bundle.bundleId} | Created: ${bundle.createdAt}`);
  lines.push(`> Context compression: ${bundle.tokenMetrics?.originalTokens?.toLocaleString()} → ${bundle.tokenMetrics?.bundledTokens?.toLocaleString()} tokens`);
  lines.push('');

  if (bundle.decisions?.length > 0) {
    lines.push('## Key Decisions Made');
    for (const d of bundle.decisions) {
      lines.push(`- **${d.id || ''}${d.id ? ':' : ''} ${d.title}** — ${d.summary}`);
      if (d.fullPath) lines.push(`  *(Full: ${d.fullPath})*`);
    }
    lines.push('');
  }

  if (bundle.artifacts?.length > 0) {
    lines.push('## Created Artifacts');
    for (const a of bundle.artifacts) {
      lines.push(`- \`${a.path}\` — ${a.description || ''}`);
    }
    lines.push('');
  }

  if (bundle.nextSteps?.length > 0) {
    lines.push('## Next Steps (continue from here)');
    bundle.nextSteps.forEach((step, i) => {
      lines.push(`${i + 1}. ${step}`);
    });
    lines.push('');
  }

  lines.push(bundle.resumeInstructions || '');

  return lines.join('\n');
}

// ============================================================================
// Helpers
// ============================================================================

function buildResumeInstructions(feature, checkpointNum, decisions, nextSteps) {
  return `## Resume Instructions

1. Load minimal standards: run \`morph-spec prime feature\`
2. Read spec: \`.morph/project/outputs/${feature}/spec.md\` (key sections only)
3. Check tasks: \`morph-spec state get ${feature}\`
4. Continue from: ${nextSteps[0] || `task after checkpoint ${checkpointNum}`}

> This bundle was created at checkpoint ${checkpointNum}. All decisions above are final.`;
}
