/**
 * MORPH-SPEC State Manager Library
 *
 * Manages state.json for tracking features, progress, agents, and checkpoints.
 * Used both by CLI commands and internal automation.
 */

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

const STATE_FILE_NAME = '.morph/state.json';

// ============================================================================
// Core Functions
// ============================================================================

/**
 * Get the state file path (looks in current working directory)
 */
export function getStatePath() {
  return join(process.cwd(), STATE_FILE_NAME);
}

/**
 * Check if state file exists
 */
export function stateExists() {
  return existsSync(getStatePath());
}

/**
 * Load state from disk
 * @param {boolean} throwOnError - If false, returns null instead of throwing
 * @returns {Object|null} State object or null
 */
export function loadState(throwOnError = true) {
  const statePath = getStatePath();

  if (!existsSync(statePath)) {
    if (throwOnError) {
      throw new Error(`State file not found: ${statePath}\nRun 'morph-spec state init' first.`);
    }
    return null;
  }

  try {
    const content = readFileSync(statePath, 'utf8');
    return JSON.parse(content);
  } catch (err) {
    if (throwOnError) {
      throw new Error(`Failed to parse state.json: ${err.message}`);
    }
    return null;
  }
}

/**
 * Save state to disk
 * @param {Object} state - State object to save
 */
export function saveState(state) {
  state.metadata = state.metadata || {};
  state.metadata.lastUpdated = new Date().toISOString();

  const statePath = getStatePath();
  const stateDir = dirname(statePath);

  // Ensure .morph directory exists
  if (!existsSync(stateDir)) {
    mkdirSync(stateDir, { recursive: true });
  }

  writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
}

/**
 * Initialize new state file
 * @param {Object} options - Options
 * @param {boolean} options.force - Overwrite existing file
 * @param {string} options.projectName - Project name
 * @param {string} options.projectType - Project type (e.g., 'blazor-server')
 * @returns {Object} Initial state
 */
export function initState(options = {}) {
  const { force = false, projectName = '{PROJECT_NAME}', projectType = 'blazor-server' } = options;

  if (stateExists() && !force) {
    throw new Error('State file already exists. Use force=true to overwrite.');
  }

  const initialState = {
    version: "2.1.1",
    project: {
      name: projectName,
      type: projectType,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    },
    features: {},
    metadata: {
      totalFeatures: 0,
      completedFeatures: 0,
      totalCostEstimated: 0,
      totalTimeSpent: 0,
      lastUpdated: new Date().toISOString()
    }
  };

  saveState(initialState);
  return initialState;
}

// ============================================================================
// Feature Operations
// ============================================================================

/**
 * Get feature from state
 * @param {string} featureName - Feature name
 * @returns {Object|null} Feature object or null
 */
export function getFeature(featureName) {
  const state = loadState();
  return state.features[featureName] || null;
}

/**
 * Create or get feature with default structure
 * @param {string} featureName - Feature name
 * @returns {Object} Feature object
 */
function ensureFeature(featureName) {
  const state = loadState();

  if (!state.features[featureName]) {
    state.features[featureName] = {
      status: "draft",
      phase: "proposal",
      workflow: "auto", // auto | fast-track | standard | full-morph
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
      activeAgents: [],
      outputs: {
        proposal: { created: false, path: `.morph/project/outputs/${featureName}/proposal.md` },
        spec: { created: false, path: `.morph/project/outputs/${featureName}/spec.md` },
        contracts: { created: false, path: `.morph/project/outputs/${featureName}/contracts.cs` },
        tasks: { created: false, path: `.morph/project/outputs/${featureName}/tasks.json` },
        uiDesignSystem: { created: false, path: `.morph/project/outputs/${featureName}/ui-design-system.md` },
        uiMockups: { created: false, path: `.morph/project/outputs/${featureName}/ui-mockups.md` },
        uiComponents: { created: false, path: `.morph/project/outputs/${featureName}/ui-components.md` },
        uiFlows: { created: false, path: `.morph/project/outputs/${featureName}/ui-flows.md` },
        decisions: { created: false, path: `.morph/project/outputs/${featureName}/decisions.md` },
        recap: { created: false, path: `.morph/project/outputs/${featureName}/recap.md` }
      },
      tasks: {
        total: 0,
        completed: 0,
        inProgress: 0,
        pending: 0
      },
      checkpoints: [],
      costs: {
        estimated: 0,
        approved: false,
        approvedBy: null,
        approvedAt: null
      }
    };

    state.metadata.totalFeatures++;
    saveState(state);
  }

  return state.features[featureName];
}

/**
 * Update feature property (supports nested keys like "tasks.completed")
 * @param {string} featureName - Feature name
 * @param {string} key - Property key (supports dot notation)
 * @param {any} value - Value to set
 */
export function updateFeature(featureName, key, value) {
  ensureFeature(featureName);
  const state = loadState(); // Load AFTER ensuring feature exists

  const keys = key.split('.');
  let target = state.features[featureName];

  for (let i = 0; i < keys.length - 1; i++) {
    if (!target[keys[i]]) {
      target[keys[i]] = {};
    }
    target = target[keys[i]];
  }

  const finalKey = keys[keys.length - 1];
  target[finalKey] = value;
  state.features[featureName].updatedAt = new Date().toISOString();

  saveState(state);
}

/**
 * Update multiple feature properties at once
 * @param {string} featureName - Feature name
 * @param {Object} updates - Object with key-value pairs to update
 */
export function updateFeatureMultiple(featureName, updates) {
  ensureFeature(featureName);
  const state = loadState(); // Load AFTER ensuring feature exists

  for (const [key, value] of Object.entries(updates)) {
    const keys = key.split('.');
    let target = state.features[featureName];

    for (let i = 0; i < keys.length - 1; i++) {
      if (!target[keys[i]]) {
        target[keys[i]] = {};
      }
      target = target[keys[i]];
    }

    const finalKey = keys[keys.length - 1];
    target[finalKey] = value;
  }

  state.features[featureName].updatedAt = new Date().toISOString();
  saveState(state);
}

/**
 * Add checkpoint to feature
 * @param {string} featureName - Feature name
 * @param {string} note - Checkpoint note
 * @returns {Object} Checkpoint object
 */
export function addCheckpoint(featureName, note) {
  ensureFeature(featureName);
  const state = loadState();
  const feature = state.features[featureName];

  const checkpoint = {
    timestamp: new Date().toISOString(),
    phase: feature.phase,
    completedTasks: feature.tasks.completed,
    note: note
  };

  feature.checkpoints.push(checkpoint);
  feature.updatedAt = new Date().toISOString();

  saveState(state);
  return checkpoint;
}

/**
 * Add agent to feature
 * @param {string} featureName - Feature name
 * @param {string} agentId - Agent ID
 * @returns {boolean} True if added, false if already exists
 */
export function addAgent(featureName, agentId) {
  ensureFeature(featureName);
  const state = loadState(); // Load AFTER ensuring feature exists

  if (!state.features[featureName].activeAgents.includes(agentId)) {
    state.features[featureName].activeAgents.push(agentId);
    state.features[featureName].updatedAt = new Date().toISOString();
    saveState(state);
    return true;
  }

  return false;
}

/**
 * Remove agent from feature
 * @param {string} featureName - Feature name
 * @param {string} agentId - Agent ID
 * @returns {boolean} True if removed, false if not found
 */
export function removeAgent(featureName, agentId) {
  const state = loadState();

  if (!state.features[featureName]) {
    throw new Error(`Feature '${featureName}' not found.`);
  }

  const index = state.features[featureName].activeAgents.indexOf(agentId);
  if (index > -1) {
    state.features[featureName].activeAgents.splice(index, 1);
    state.features[featureName].updatedAt = new Date().toISOString();
    saveState(state);
    return true;
  }

  return false;
}

/**
 * Mark output as created
 * @param {string} featureName - Feature name
 * @param {string} outputType - Output type (proposal, spec, contracts, etc.)
 */
export function markOutput(featureName, outputType) {
  ensureFeature(featureName);
  const state = loadState();

  if (!state.features[featureName].outputs[outputType]) {
    throw new Error(`Output type '${outputType}' not valid. Valid types: proposal, spec, contracts, tasks, uiDesignSystem, uiMockups, uiComponents, uiFlows, decisions, recap`);
  }

  state.features[featureName].outputs[outputType].created = true;
  state.features[featureName].updatedAt = new Date().toISOString();

  saveState(state);
}

/**
 * List all features
 * @returns {Array} Array of [featureName, featureObject] tuples
 */
export function listFeatures() {
  const state = loadState();
  return Object.entries(state.features);
}

/**
 * Get project summary
 * @returns {Object} Summary with metadata
 */
export function getSummary() {
  const state = loadState();
  return {
    project: state.project,
    metadata: state.metadata,
    featuresCount: Object.keys(state.features).length
  };
}
