/**
 * @fileoverview Context Generator - Auto-generates CONTEXT.md files
 *
 * Populates CONTEXT.md templates with real project data from:
 * - state.json (features, phases, tasks, checkpoints)
 * - config.json (project settings, Azure resources, agent config)
 * - agents.json (hierarchical agent structure)
 * - project.md (project description)
 * - standards/*.md (project-specific standards)
 *
 * @module context-generator
 */

import fs from 'fs/promises';
import path from 'path';

/**
 * Load project state from state.json
 * @param {string} projectPath - Root path of the project
 * @returns {Promise<Object>} Parsed state
 */
async function loadState(projectPath) {
  const statePath = path.join(projectPath, '.morph/state.json');
  try {
    const content = await fs.readFile(statePath, 'utf8');
    return JSON.parse(content);
  } catch (err) {
    return { features: [], checkpoints: [] };
  }
}

/**
 * Load project config from config.json
 * @param {string} projectPath - Root path of the project
 * @returns {Promise<Object>} Parsed config
 */
async function loadConfig(projectPath) {
  const configPath = path.join(projectPath, '.morph/config/config.json');
  try {
    const content = await fs.readFile(configPath, 'utf8');
    return JSON.parse(content);
  } catch (err) {
    return null;
  }
}

/**
 * Load agents hierarchy from agents.json
 * @param {string} projectPath - Root path of the project
 * @returns {Promise<Object>} Parsed agents config
 */
async function loadAgents(projectPath) {
  const { resolveAgentsConfigPath } = await import('../stacks/stack-resolver.js');
  const agentsPath = resolveAgentsConfigPath(projectPath);
  try {
    const content = await fs.readFile(agentsPath, 'utf8');
    return JSON.parse(content);
  } catch (err) {
    return null;
  }
}

/**
 * Load project description from project.md
 * @param {string} projectPath - Root path of the project
 * @returns {Promise<string>} Project description
 */
async function loadProjectDescription(projectPath) {
  const projectMdPath = path.join(projectPath, '.morph/project.md');
  try {
    return await fs.readFile(projectMdPath, 'utf8');
  } catch (err) {
    return '';
  }
}

/**
 * Load project-specific standards
 * @param {string} projectPath - Root path of the project
 * @returns {Promise<Array>} Array of {name, path, description}
 */
async function loadProjectStandards(projectPath) {
  const standardsDir = path.join(projectPath, '.morph/project/standards');
  try {
    const files = await fs.readdir(standardsDir);
    return files
      .filter(f => f.endsWith('.md'))
      .map(f => ({
        name: f.replace('.md', ''),
        path: `standards/${f}`,
        description: null
      }));
  } catch (err) {
    return [];
  }
}

/**
 * Get nested property value from object using dot notation
 * @param {Object} obj - Object to get value from
 * @param {string} path - Dot-notation path (e.g., 'tasks.completed')
 * @returns {*} Value at path or empty string
 */
function getNestedValue(obj, path) {
  const keys = path.split('.');
  let value = obj;

  for (const key of keys) {
    if (value && typeof value === 'object' && key in value) {
      value = value[key];
    } else {
      return '';
    }
  }

  return value !== null && value !== undefined ? value : '';
}

/**
 * Simple template renderer (Handlebars-like syntax)
 * @param {string} template - Template string with {{VAR}} placeholders
 * @param {Object} data - Data object with values
 * @returns {string} Rendered template
 */
function renderTemplate(template, data) {
  let result = template;

  // Handle {{#each VAR}}...{{/each}} blocks first (before variable replacement)
  result = result.replace(/{{#each\s+(\w+)}}([\s\S]*?){{\/each}}/g, (_, varName, itemTemplate) => {
    const array = data[varName];
    if (!Array.isArray(array) || array.length === 0) return '';

    return array.map(item => {
      let itemResult = itemTemplate;

      // Replace all {{prop}} and {{nested.prop}} in the item template
      itemResult = itemResult.replace(/{{([\w.]+)}}/g, (__, propPath) => {
        return String(getNestedValue(item, propPath));
      });

      return itemResult;
    }).join('');
  });

  // Handle {{#if VAR}}...{{/if}} blocks
  result = result.replace(/{{#if\s+(\w+)}}([\s\S]*?){{\/if}}/g, (_, varName, content) => {
    return data[varName] ? content : '';
  });

  // Handle {{#unless VAR}}...{{/unless}} blocks
  result = result.replace(/{{#unless\s+(\w+)}}([\s\S]*?){{\/unless}}/g, (_, varName, content) => {
    const value = data[varName];
    // Check if value is falsy, empty array, or empty object
    const isFalsy = !value ||
                    (Array.isArray(value) && value.length === 0) ||
                    (typeof value === 'object' && Object.keys(value).length === 0);
    return isFalsy ? content : '';
  });

  // Replace simple {{VAR}} and {{nested.prop}} placeholders
  result = result.replace(/{{([\w.]+)}}/g, (_, path) => {
    return String(getNestedValue(data, path));
  });

  return result;
}

/**
 * Generate project-level CONTEXT.md
 * @param {string} projectPath - Root path of the project
 * @returns {Promise<string>} Generated CONTEXT.md content
 */
export async function generateProjectContext(projectPath) {
  const [state, config, agents, projectDesc, standards] = await Promise.all([
    loadState(projectPath),
    loadConfig(projectPath),
    loadAgents(projectPath),
    loadProjectDescription(projectPath),
    loadProjectStandards(projectPath)
  ]);

  // Load template with fallback (stack-specific → framework universal)
  const { resolveTemplatePath } = await import('../stacks/stack-resolver.js');
  const templatePath = resolveTemplatePath(projectPath, 'context/CONTEXT.md');

  if (!templatePath) {
    throw new Error('CONTEXT.md template not found in stack templates or framework/templates/');
  }

  const template = await fs.readFile(templatePath, 'utf8');

  // Build data object
  const data = {
    PROJECT_NAME: config?.project?.name || 'Unknown Project',
    PROJECT_TYPE: config?.project?.type || 'blazor-server',
    PROJECT_DESCRIPTION: projectDesc || config?.project?.description || '',
    REPOSITORY_URL: config?.project?.repository || '',
    TIMESTAMP: new Date().toISOString(),

    // Stack detection
    BLAZOR_ENABLED: config?.project?.type?.includes('blazor') ? true : false,
    NEXTJS_ENABLED: config?.project?.type?.includes('nextjs') ? true : false,
    DOTNET_VERSION: '10.0',
    EF_VERSION: '10.0',

    // Azure config
    AZURE_LOCATION: config?.azure?.location || 'eastus2',
    CONTAINER_APPS_ENABLED: config?.azure?.resources?.containerApps?.enabled || false,
    CONTAINER_ENV: config?.azure?.resources?.containerApps?.environment || '',
    SQL_ENABLED: config?.azure?.resources?.sql?.enabled || false,
    SQL_SERVER: config?.azure?.resources?.sql?.server || '',
    SQL_DATABASE: config?.azure?.resources?.sql?.database || '',
    OPENAI_ENABLED: config?.azure?.resources?.openai?.enabled || false,
    OPENAI_MODEL: config?.azure?.resources?.openai?.model || '',
    MONITORING_ENABLED: config?.azure?.resources?.monitoring?.enabled || false,
    APP_INSIGHTS: config?.azure?.resources?.monitoring?.appInsights || '',

    // Active features (convert object to array if needed)
    ACTIVE_FEATURES: (Array.isArray(state?.features)
      ? state.features
      : Object.entries(state?.features || {}).map(([name, data]) => ({ name, ...data }))
    ).map(f => ({
      name: f.name,
      phase: f.phase,
      status: f.status,
      complexity: f.complexity || 'medium',
      activeAgents: (f.activeAgents || []).join(', '),
      description: f.description || '',
      tasks: {
        completed: f.tasks?.completed || 0,
        total: f.tasks?.total || 0
      },
      outputs: Array.isArray(f.outputs)
        ? f.outputs.join(', ')
        : (f.outputs ? Object.keys(f.outputs).join(', ') : '')
    })),

    // Project standards
    PROJECT_STANDARDS: standards,

    // Agents by tier
    AGENT_COUNT: agents?.total_agents || 0,
    TIER_1_AGENTS: Object.entries(agents?.agents || {})
      .filter(([id, agent]) => !id.startsWith('_') && agent.tier === 1)
      .map(([id, agent]) => ({
        id,
        title: agent.title,
        always_active: agent.always_active
      })),
    TIER_2_AGENTS: Object.entries(agents?.agents || {})
      .filter(([id, agent]) => !id.startsWith('_') && agent.tier === 2)
      .map(([id, agent]) => ({
        id,
        title: agent.title,
        domains: (agent.domains || []).join(', ')
      })),
    TIER_3_AGENTS: Object.entries(agents?.agents || {})
      .filter(([id, agent]) => !id.startsWith('_') && agent.tier === 3)
      .map(([id, agent]) => ({
        id,
        title: agent.title,
        reports_to: agent.relationships?.reports_to || 'N/A'
      })),

    // Agent Teams config
    AGENT_TEAMS_ENABLED: config?.agentTeams?.enabled || false,
    AGENT_TEAMS_DISPLAY_MODE: config?.agentTeams?.displayMode || 'auto',
    AGENT_TEAMS_THRESHOLDS: config?.agentTeams?.spawnThresholds || {},

    // Recent checkpoints
    RECENT_CHECKPOINTS: (state?.checkpoints || []).slice(-5).map(cp => ({
      timestamp: cp.timestamp,
      feature: cp.feature,
      phase: cp.phase,
      note: cp.note,
      validation: cp.validation
    })),

    // DevOps config
    DEVOPS_ENABLED: config?.devops ? true : false,
    DEVOPS_ORG: config?.devops?.organization || '',
    DEVOPS_PROJECT: config?.devops?.project || '',
    DEVOPS_BOARDS_ENABLED: config?.devops?.boards?.enabled || false,
    DEVOPS_AREA_PATH: config?.devops?.boards?.areaPath || '',
    DEVOPS_WIKI_ENABLED: config?.devops?.wiki?.enabled || false,
    DEVOPS_WIKI_NAME: config?.devops?.wiki?.wikiName || '',
    DEVOPS_BUILD_PIPELINE: config?.devops?.pipelines?.buildPipeline || '',
    DEVOPS_RELEASE_PIPELINE: config?.devops?.pipelines?.releasePipeline || '',

    // Version
    MORPH_VERSION: '3.0.0-hierarchical'
  };

  return renderTemplate(template, data);
}

/**
 * Generate feature-specific CONTEXT-{feature}.md
 * @param {string} projectPath - Root path of the project
 * @param {string} featureName - Feature name
 * @returns {Promise<string>} Generated CONTEXT-{feature}.md content
 */
export async function generateFeatureContext(projectPath, featureName) {
  const [state, agents] = await Promise.all([
    loadState(projectPath),
    loadAgents(projectPath)
  ]);

  // Find feature in state (handle both array and object format)
  let feature;
  if (Array.isArray(state?.features)) {
    feature = state.features.find(f => f.name === featureName);
  } else if (state?.features && typeof state.features === 'object') {
    feature = state.features[featureName];
    if (feature) {
      feature = { name: featureName, ...feature };
    }
  }

  if (!feature) {
    throw new Error(`Feature "${featureName}" not found in state.json`);
  }

  // Load feature outputs
  const outputsDir = path.join(projectPath, `.morph/project/outputs/${featureName}`);
  let decisions = [];
  let tasks = [];

  try {
    const decisionsPath = path.join(outputsDir, 'decisions.md');
    const decisionsContent = await fs.readFile(decisionsPath, 'utf8');
    // Parse decisions.md (simplified - just extract ADR headers)
    const adrMatches = decisionsContent.matchAll(/##\s+ADR-(\d+):\s+(.+)/g);
    decisions = Array.from(adrMatches).map(match => ({
      id: match[1],
      title: match[2],
      status: 'Accepted',
      date: new Date().toISOString().split('T')[0],
      context: '',
      decision: ''
    }));
  } catch (err) {
    // No decisions.md yet
  }

  try {
    const tasksPath = path.join(outputsDir, 'tasks.json');
    const tasksContent = await fs.readFile(tasksPath, 'utf8');
    tasks = JSON.parse(tasksContent).tasks || [];
  } catch (err) {
    // No tasks.json yet
  }

  // Load template with fallback (stack-specific → framework universal)
  const { resolveTemplatePath } = await import('../stacks/stack-resolver.js');
  const featureTemplatePath = resolveTemplatePath(projectPath, 'context/CONTEXT-FEATURE.md');

  if (!featureTemplatePath) {
    throw new Error('CONTEXT-FEATURE.md template not found in stack templates or framework/templates/');
  }

  const template = await fs.readFile(featureTemplatePath, 'utf8');

  // Build active agents list with hierarchy
  const activeAgentIds = feature.activeAgents || [];
  const activeAgents = activeAgentIds
    .filter(id => agents?.agents[id])
    .map(id => ({ id, ...agents.agents[id] }));

  // Group by tier and build squads
  const teamLead = activeAgents.find(a => a.tier === 1 && a.role === 'orchestrator');
  const domainLeaders = activeAgents.filter(a => a.tier === 2 && a.role === 'domain-leader');
  const specialists = activeAgents.filter(a => a.tier === 3 && a.role === 'specialist');
  const validators = activeAgents.filter(a => a.tier === 4 && a.role === 'validator');

  const squads = domainLeaders.map(leader => ({
    leader: {
      id: leader.id,
      title: leader.title
    },
    members: specialists
      .filter(s => s.relationships?.reports_to === leader.id)
      .map(s => ({
        id: s.id,
        title: s.title
      }))
  }));

  // Build data object
  const data = {
    FEATURE_NAME: feature.name,
    PHASE: feature.phase,
    WORKFLOW_TYPE: feature.workflow || 'standard',
    STATUS: feature.status,
    COMPLEXITY: feature.complexity || 'medium',
    DESCRIPTION: feature.description || '',
    TIMESTAMP: new Date().toISOString(),

    // Phase tracking
    PHASE_DESCRIPTION: `Current phase: ${feature.phase}`,
    COMPLETED_PHASES: [], // TODO: track phase history
    UPCOMING_PHASES: [], // TODO: calculate remaining phases

    // Tasks
    TASKS_TOTAL: feature.tasks?.total || tasks.length,
    TASKS_COMPLETED: feature.tasks?.completed || tasks.filter(t => t.status === 'completed').length,
    TASKS_IN_PROGRESS: tasks.filter(t => t.status === 'in_progress').length,
    TASKS_BLOCKED: tasks.filter(t => t.status === 'blocked').length,
    TASKS_PROGRESS: feature.tasks?.total > 0
      ? Math.round((feature.tasks.completed / feature.tasks.total) * 100)
      : 0,
    CURRENT_TASK: tasks.find(t => t.status === 'in_progress') || null,

    // Active agents
    ACTIVE_AGENTS_COUNT: activeAgents.length,
    TEAM_LEAD: teamLead ? {
      id: teamLead.id,
      title: teamLead.title
    } : null,
    SQUADS: squads,
    VALIDATORS: validators.map(v => ({
      id: v.id,
      title: v.title
    })),

    // Outputs (handle both object and array format)
    OUTPUTS: (Array.isArray(feature.outputs)
      ? feature.outputs.map(output => ({
          type: output,
          generated: true,
          path: `.morph/project/outputs/${featureName}/${output}`,
          generatedAt: null
        }))
      : Object.entries(feature.outputs || {}).map(([type, data]) => ({
          type,
          generated: data.created || false,
          path: data.path || `.morph/project/outputs/${featureName}/${type}`,
          generatedAt: data.createdAt || null
        }))
    ),

    // Decisions
    DECISIONS: decisions,

    // Validation
    VALIDATION_RESULTS: feature.lastValidation?.results || null,
    VALIDATION_PASSED: feature.lastValidation?.passed || false,
    VALIDATION_TIMESTAMP: feature.lastValidation?.timestamp || null,

    // Tasks list
    TASKS: tasks,

    // Checkpoints
    CHECKPOINTS: (state?.checkpoints || [])
      .filter(cp => cp.feature === featureName)
      .map(cp => ({
        timestamp: cp.timestamp,
        phase: cp.phase,
        tasksCompleted: cp.tasksCompleted,
        tasksTotal: cp.tasksTotal,
        note: cp.note,
        validation: cp.validation
      })),

    // Related standards
    RELATED_STANDARDS: [], // TODO: link standards based on activeAgents

    // File paths
    CURRENT_TASK_ID: tasks.find(t => t.status === 'in_progress')?.id || '',
    PROPOSAL_PATH: `.morph/project/outputs/${featureName}/proposal.md`,
    SPEC_PATH: `.morph/project/outputs/${featureName}/spec.md`,
    CONTRACTS_PATH: `.morph/project/outputs/${featureName}/contracts.cs`,
    TASKS_PATH: `.morph/project/outputs/${featureName}/tasks.json`,
    DECISIONS_PATH: `.morph/project/outputs/${featureName}/decisions.md`,
    RECAP_PATH: `.morph/project/outputs/${featureName}/recap.md`,

    // Version
    MORPH_VERSION: '3.0.0-hierarchical',
    FEATURE_ID: feature.id || featureName
  };

  return renderTemplate(template, data);
}

/**
 * Write generated context to file
 * @param {string} projectPath - Root path of the project
 * @param {string} content - Generated content
 * @param {string} filename - Output filename
 * @returns {Promise<string>} Path to written file
 */
export async function writeContext(projectPath, content, filename = 'CONTEXT.md') {
  const contextDir = path.join(projectPath, '.morph/project/context');
  await fs.mkdir(contextDir, { recursive: true });

  const outputPath = path.join(contextDir, filename);
  await fs.writeFile(outputPath, content, 'utf8');

  return outputPath;
}

/**
 * Generate and write both project and feature context files
 * @param {string} projectPath - Root path of the project
 * @param {string} featureName - Optional feature name (if null, only generates project context)
 * @returns {Promise<Object>} Paths to generated files
 */
export async function generateContext(projectPath, featureName = null) {
  const results = {};

  // Always generate project context
  const projectContext = await generateProjectContext(projectPath);
  results.projectContext = await writeContext(projectPath, projectContext, 'CONTEXT.md');

  // Generate feature context if requested
  if (featureName) {
    const featureContext = await generateFeatureContext(projectPath, featureName);
    results.featureContext = await writeContext(
      projectPath,
      featureContext,
      `CONTEXT-${featureName}.md`
    );
  }

  return results;
}
