/**
 * Thread Manager — Core thread lifecycle management
 *
 * Manages the full lifecycle of MORPH-SPEC threads:
 * - Types: base, parallel, fusion, long-running, zero-touch
 * - Status transitions: pending → running → completed | failed | killed
 * - Persists thread state to state.json threads{} section
 */

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

const STATE_PATH = join(process.cwd(), '.morph/state.json');

// ============================================================================
// Thread Types and Status
// ============================================================================

export const THREAD_TYPES = {
  BASE: 'base',
  PARALLEL: 'parallel',
  FUSION: 'fusion',
  LONG_RUNNING: 'long-running',
  ZERO_TOUCH: 'zero-touch'
};

export const THREAD_STATUS = {
  PENDING: 'pending',
  RUNNING: 'running',
  COMPLETED: 'completed',
  FAILED: 'failed',
  KILLED: 'killed'
};

// ============================================================================
// State I/O
// ============================================================================

function loadState() {
  if (!existsSync(STATE_PATH)) {
    throw new Error(`State file not found: ${STATE_PATH}`);
  }
  return JSON.parse(readFileSync(STATE_PATH, 'utf8'));
}

function saveState(state) {
  state.metadata = state.metadata || {};
  state.metadata.lastUpdated = new Date().toISOString();
  writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), 'utf8');
}

function ensureThreadsSection(state) {
  if (!state.threads) {
    state.threads = {};
  }
  return state;
}

// ============================================================================
// Thread CRUD
// ============================================================================

/**
 * Create a new thread
 * @param {Object} opts
 * @param {string} opts.feature - Feature name this thread belongs to
 * @param {string} opts.type - Thread type (base|parallel|fusion|long-running|zero-touch)
 * @param {string} [opts.agent] - Agent assigned to this thread
 * @param {string} [opts.mission] - Thread mission description
 * @param {Object} [opts.meta] - Additional metadata
 * @returns {Object} Created thread object
 */
export function createThread({ feature, type = THREAD_TYPES.BASE, agent = null, mission = '', meta = {} }) {
  const state = ensureThreadsSection(loadState());
  const id = randomUUID();
  const now = new Date().toISOString();

  const thread = {
    id,
    feature,
    type,
    agent,
    mission,
    status: THREAD_STATUS.PENDING,
    createdAt: now,
    startedAt: null,
    completedAt: null,
    duration: null,
    events: [],
    metrics: {
      toolCalls: 0,
      tokensUsed: 0,
      checkpointsPassed: 0,
      checkpointsFailed: 0
    },
    meta
  };

  state.threads[id] = thread;
  saveState(state);
  return thread;
}

/**
 * Update thread fields
 * @param {string} id - Thread ID
 * @param {Object} updates - Fields to update
 * @returns {Object} Updated thread
 */
export function updateThread(id, updates) {
  const state = ensureThreadsSection(loadState());

  if (!state.threads[id]) {
    throw new Error(`Thread not found: ${id}`);
  }

  state.threads[id] = { ...state.threads[id], ...updates };
  saveState(state);
  return state.threads[id];
}

/**
 * Get a thread by ID
 * @param {string} id - Thread ID
 * @returns {Object|null} Thread or null
 */
export function getThread(id) {
  const state = ensureThreadsSection(loadState());
  return state.threads[id] || null;
}

/**
 * List threads, optionally filtered
 * @param {Object} [filter]
 * @param {string} [filter.feature] - Filter by feature name
 * @param {string} [filter.status] - Filter by status
 * @param {string} [filter.type] - Filter by type
 * @returns {Array} Array of thread objects
 */
export function listThreads({ feature, status, type } = {}) {
  const state = ensureThreadsSection(loadState());
  let threads = Object.values(state.threads);

  if (feature) threads = threads.filter(t => t.feature === feature);
  if (status) threads = threads.filter(t => t.status === status);
  if (type) threads = threads.filter(t => t.type === type);

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

/**
 * Start a thread (pending → running)
 * @param {string} id - Thread ID
 * @returns {Object} Updated thread
 */
export function startThread(id) {
  const now = new Date().toISOString();
  return updateThread(id, {
    status: THREAD_STATUS.RUNNING,
    startedAt: now
  });
}

/**
 * Complete a thread (running → completed)
 * @param {string} id - Thread ID
 * @param {Object} [result] - Optional result metadata
 * @returns {Object} Updated thread
 */
export function completeThread(id, result = {}) {
  const thread = getThread(id);
  if (!thread) throw new Error(`Thread not found: ${id}`);

  const now = new Date();
  const startedAt = thread.startedAt ? new Date(thread.startedAt) : now;
  const duration = Math.round((now - startedAt) / 1000); // seconds

  return updateThread(id, {
    status: THREAD_STATUS.COMPLETED,
    completedAt: now.toISOString(),
    duration,
    result
  });
}

/**
 * Mark a thread as failed
 * @param {string} id - Thread ID
 * @param {string} reason - Failure reason
 * @returns {Object} Updated thread
 */
export function failThread(id, reason = '') {
  const thread = getThread(id);
  if (!thread) throw new Error(`Thread not found: ${id}`);

  const now = new Date();
  const startedAt = thread.startedAt ? new Date(thread.startedAt) : now;
  const duration = Math.round((now - startedAt) / 1000);

  return updateThread(id, {
    status: THREAD_STATUS.FAILED,
    completedAt: now.toISOString(),
    duration,
    failureReason: reason
  });
}

/**
 * Kill (terminate) a running thread
 * @param {string} id - Thread ID
 * @returns {Object} Updated thread
 */
export function killThread(id) {
  const thread = getThread(id);
  if (!thread) throw new Error(`Thread not found: ${id}`);
  if (thread.status === THREAD_STATUS.COMPLETED || thread.status === THREAD_STATUS.KILLED) {
    throw new Error(`Cannot kill thread in status: ${thread.status}`);
  }

  const now = new Date();
  const startedAt = thread.startedAt ? new Date(thread.startedAt) : now;
  const duration = Math.round((now - startedAt) / 1000);

  return updateThread(id, {
    status: THREAD_STATUS.KILLED,
    completedAt: now.toISOString(),
    duration
  });
}

/**
 * Add an event to a thread's event log
 * @param {string} id - Thread ID
 * @param {string} type - Event type
 * @param {Object} [data] - Event data
 */
export function addThreadEvent(id, type, data = {}) {
  const state = ensureThreadsSection(loadState());
  const thread = state.threads[id];
  if (!thread) throw new Error(`Thread not found: ${id}`);

  thread.events = thread.events || [];
  thread.events.push({
    type,
    timestamp: new Date().toISOString(),
    data
  });

  saveState(state);
}

/**
 * Increment thread metrics counter
 * @param {string} id - Thread ID
 * @param {string} metric - Metric name (toolCalls|tokensUsed|checkpointsPassed|checkpointsFailed)
 * @param {number} [amount=1] - Amount to increment
 */
export function incrementThreadMetric(id, metric, amount = 1) {
  const state = ensureThreadsSection(loadState());
  const thread = state.threads[id];
  if (!thread) throw new Error(`Thread not found: ${id}`);

  thread.metrics = thread.metrics || {};
  thread.metrics[metric] = (thread.metrics[metric] || 0) + amount;
  saveState(state);
}

// ============================================================================
// Analytics
// ============================================================================

/**
 * Get analytics for threads belonging to a feature
 * @param {string} feature - Feature name
 * @returns {Object} Analytics summary
 */
export function getThreadAnalytics(feature) {
  const threads = listThreads({ feature });

  const byStatus = threads.reduce((acc, t) => {
    acc[t.status] = (acc[t.status] || 0) + 1;
    return acc;
  }, {});

  const byType = threads.reduce((acc, t) => {
    acc[t.type] = (acc[t.type] || 0) + 1;
    return acc;
  }, {});

  const completed = threads.filter(t => t.status === THREAD_STATUS.COMPLETED);
  const avgDuration = completed.length > 0
    ? Math.round(completed.reduce((sum, t) => sum + (t.duration || 0), 0) / completed.length)
    : 0;

  const totalToolCalls = threads.reduce((sum, t) => sum + (t.metrics?.toolCalls || 0), 0);
  const parallelThreads = threads.filter(t => t.type === THREAD_TYPES.PARALLEL);
  const avgParallel = parallelThreads.length;

  return {
    feature,
    total: threads.length,
    byStatus,
    byType,
    avgDuration,
    totalToolCalls,
    parallelEfficiency: {
      avgParallel,
      maxParallel: Math.max(...threads.map(() => parallelThreads.length), 0)
    },
    checkpointPassRate: threads.reduce((sum, t) => {
      const p = t.metrics?.checkpointsPassed || 0;
      const f = t.metrics?.checkpointsFailed || 0;
      return p + f > 0 ? sum + (p / (p + f)) : sum;
    }, 0) / Math.max(threads.filter(t => (t.metrics?.checkpointsPassed || 0) + (t.metrics?.checkpointsFailed || 0) > 0).length, 1)
  };
}
