/**
 * 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';
import { detectWorkflow } from '../workflows/workflow-detector.js';

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: "3.0.0",
    project: {
      name: projectName,
      type: projectType,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    },
    features: {},
    threads: {},
    metadata: {
      totalFeatures: 0,
      completedFeatures: 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
 * @param {Object} options - Options
 * @param {string} options.userRequest - User's feature request for workflow detection
 * @param {string} options.workflow - Manual workflow override
 * @param {string} options.projectPath - Project path
 * @returns {Promise<Object>} Feature object
 */
async function ensureFeature(featureName, options = {}) {
  const state = loadState();

  if (!state.features[featureName]) {
    // Detect workflow if userRequest provided
    let workflowId = 'auto';
    let workflowDetection = {
      auto: false,
      confidence: 0,
      userOverride: false
    };

    if (options.workflow) {
      // Manual override
      workflowId = options.workflow;
      workflowDetection.userOverride = true;
    } else if (options.userRequest) {
      // Auto-detect
      try {
        const detection = await detectWorkflow({
          userRequest: options.userRequest,
          projectPath: options.projectPath || '.',
          featureName
        });
        workflowId = detection.workflowId;
        workflowDetection.auto = true;
        workflowDetection.confidence = detection.confidence;
        workflowDetection.matchedKeywords = detection.matchedKeywords;
        workflowDetection.estimatedComplexity = detection.estimatedComplexity;
        workflowDetection.reasoning = detection.reasoning;
      } catch (err) {
        // If detection fails, fall back to auto
        console.warn('Workflow detection failed:', err.message);
      }
    }

    state.features[featureName] = {
      status: "draft",
      phase: "proposal",
      workflow: workflowId, // auto | fast-track | standard | full-morph | design-impl | ui-refresh
      workflowDetection,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
      activeAgents: [],
      approvalGates: {
        proposal: { approved: false, timestamp: null, approvedBy: null },
        uiux: { approved: false, timestamp: null, approvedBy: null },
        design: { approved: false, timestamp: null, approvedBy: null },
        tasks: { approved: false, timestamp: null, approvedBy: null }
      },
      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.md` },
        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: [],
      threadMetrics: {
        totalThreads: 0,
        parallelPeak: 0,
        avgDuration: 0,
        checkpointPassRate: 100
      },
      trustConfig: {
        level: 'low',
        history: [],
        autoApprove: {
          design: false,
          tasks: false
        }
      },
      contextBundles: []
    };

    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 async function updateFeature(featureName, key, value) {
  await 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 async function updateFeatureMultiple(featureName, updates) {
  await 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 async function addCheckpoint(featureName, note) {
  await ensureFeature(featureName);
  const state = loadState();
  const feature = state.features[featureName];

  const checkpoint = {
    passed: true,
    checkpointNum: feature.checkpoints.length + 1,
    timestamp: new Date().toISOString(),
    phase: feature.phase,
    results: [],
    summary: {
      note: note,
      completedTasks: feature.tasks.completed
    }
  };

  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 async function addAgent(featureName, agentId) {
  await 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;
}

/**
 * Normalize output type from kebab-case to camelCase for UI types (BUG #12 fix)
 * @param {string} type - Output type (e.g., 'ui-design-system' or 'uiDesignSystem')
 * @returns {string} Normalized type in camelCase
 */
function normalizeOutputType(type) {
  const kebabMap = {
    'ui-design-system': 'uiDesignSystem',
    'ui-mockups': 'uiMockups',
    'ui-components': 'uiComponents',
    'ui-flows': 'uiFlows'
  };
  return kebabMap[type] || type;
}

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

  const normalized = normalizeOutputType(outputType);

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

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

  // If marking tasks output, try to sync task count from state tasks array
  if (normalized === 'tasks') {
    syncTasksCount(state.features[featureName]);
  }

  saveState(state);
}

/**
 * Sync progress counters from taskList array into feature.progress
 * @param {Object} feature - Feature object
 */
function syncTasksCount(feature) {
  const list = feature.taskList;
  if (!Array.isArray(list) || list.length === 0) return;
  const completed = list.filter(t => t.status === 'completed').length;
  feature.progress = {
    total: list.length,
    completed,
    inProgress: list.filter(t => t.status === 'in_progress').length,
    pending: list.filter(t => t.status === 'pending').length,
    percentage: Math.round((completed / list.length) * 100)
  };
}

/**
 * Skip a phase and record it
 * @param {string} featureName - Feature name
 * @param {string} phase - Phase to skip
 * @param {string} reason - Reason for skipping
 */
export async function skipPhase(featureName, phase, reason = '') {
  await ensureFeature(featureName);
  const state = loadState();

  if (!state.features[featureName].skippedPhases) {
    state.features[featureName].skippedPhases = [];
  }

  // Don't add duplicates
  const existing = state.features[featureName].skippedPhases.find(s => s.phase === phase);
  if (!existing) {
    state.features[featureName].skippedPhases.push({
      phase,
      reason,
      timestamp: new Date().toISOString()
    });
  }

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

/**
 * Get skipped phases for a feature
 * @param {string} featureName - Feature name
 * @returns {Array} Array of skipped phases
 */
export function getSkippedPhases(featureName) {
  const state = loadState();
  if (!state.features[featureName]) {
    return [];
  }
  return state.features[featureName].skippedPhases || [];
}

/**
 * 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
  };
}

// ============================================================================
// Approval Gates Operations
// ============================================================================

/**
 * Set approval gate status
 * @param {string} featureName - Feature name
 * @param {string} gate - Gate name (proposal, uiux, design, tasks)
 * @param {boolean} approved - Approval status
 * @param {Object} metadata - Additional metadata (approvedBy, reason, etc.)
 */
export async function setApprovalGate(featureName, gate, approved, metadata = {}) {
  const state = loadState();
  const feature = await ensureFeature(featureName);

  if (!feature.approvalGates) {
    feature.approvalGates = {
      proposal: { approved: false, timestamp: null, approvedBy: null },
      uiux: { approved: false, timestamp: null, approvedBy: null },
      design: { approved: false, timestamp: null, approvedBy: null },
      tasks: { approved: false, timestamp: null, approvedBy: null }
    };
  }

  feature.approvalGates[gate] = {
    approved,
    timestamp: metadata.approvedAt || metadata.rejectedAt || new Date().toISOString(),
    approvedBy: metadata.approvedBy || metadata.rejectedBy || null,
    ...metadata
  };

  state.features[featureName] = feature;
  saveState(state);
}

/**
 * Get approval gate status
 * @param {string} featureName - Feature name
 * @param {string} gate - Gate name
 * @returns {Object|null} Gate object or null
 */
export function getApprovalGate(featureName, gate) {
  const state = loadState();
  const feature = state.features[featureName];

  if (!feature || !feature.approvalGates) {
    return null;
  }

  return feature.approvalGates[gate] || null;
}

/**
 * Check if feature is pending approval
 * @param {string} featureName - Feature name
 * @returns {boolean} True if any gate is pending approval
 */
export function isPendingApproval(featureName) {
  const state = loadState();
  const feature = state.features[featureName];

  if (!feature || !feature.approvalGates) {
    return false;
  }

  const currentPhase = feature.phase;

  // Check if current phase has an approval gate
  const phaseGateMap = {
    'design': 'design',
    'tasks': 'tasks',
    'uiux': 'uiux'
  };

  const relevantGate = phaseGateMap[currentPhase];
  if (!relevantGate) {
    return false;
  }

  const gate = feature.approvalGates[relevantGate];
  return gate && !gate.approved;
}

/**
 * Get approval history for a feature
 * @param {string} featureName - Feature name
 * @returns {Array} Array of approval events
 */
export function getApprovalHistory(featureName) {
  const state = loadState();
  const feature = state.features[featureName];

  if (!feature || !feature.approvalGates) {
    return [];
  }

  const history = [];

  Object.entries(feature.approvalGates).forEach(([gate, data]) => {
    if (data.timestamp) {
      history.push({
        gate,
        approved: data.approved,
        timestamp: data.timestamp,
        approvedBy: data.approvedBy || data.rejectedBy,
        reason: data.reason
      });
    }
  });

  // Sort by timestamp
  return history.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
}
