# cost-calculator.js

    /**
     * MORPH-SPEC Cost Calculator Library
     *
     * Calculates Azure infrastructure costs from Bicep files.
     * Used both by CLI commands and internal automation.
     */

    import { readFileSync, existsSync } from 'fs';
    import { join, dirname } from 'path';
    import { globSync } from 'glob';
    import { fileURLToPath } from 'url';

    const __filename = fileURLToPath(import.meta.url);
    const __dirname = dirname(__filename);

    // ============================================================================
    // PRICING TABLE LOADER
    // ============================================================================

    /**
     * Load Azure pricing from JSON file
     * @param {string} pricingPath - Optional custom pricing file path
     * @returns {Object} Pricing table
     */
    export function loadAzurePricing(pricingPath = null) {
      const defaultPath = join(__dirname, '..', '..', 'content', '.morph', 'config', 'azure-pricing.json');
      const pathToTry = pricingPath || defaultPath;

      if (!existsSync(pathToTry)) {
        // Fallback to hardcoded pricing (backwards compatibility)
        return AZURE_PRICING_FALLBACK;
      }

      try {
        const content = readFileSync(pathToTry, 'utf8');
        const pricingData = JSON.parse(content);
        return pricingData.resources || {};
      } catch (err) {
        console.warn(`Warning: Could not load pricing from ${pathToTry}: ${err.message}`);
        return AZURE_PRICING_FALLBACK;
      }
    }

    /**
     * Fallback pricing table (hardcoded for backwards compatibility)
     * @private
     */
    const AZURE_PRICING_FALLBACK = {
      // Azure SQL Database
      'Microsoft.Sql/servers/databases': {
        'Free': 0,
        'Basic': 4.99,
        'S0': 14.72,
        'S1': 29.44,
        'S2': 73.61,
        'S3': 147.22,
        'S4': 294.44,
        'P1': 465.00,
        'P2': 930.00,
        'GP_Gen5_2': 328.00,
        'GP_Gen5_4': 656.00,
        'BC_Gen5_2': 1092.00,
      },

      // Azure Container Apps
      'Microsoft.App/containerApps': {
        'Consumption': 0,
        'Consumption_Active': 5.00,
        'Dedicated_D2': 55.00,
        'Dedicated_D4': 110.00,
      },

      // Azure Storage Account
      'Microsoft.Storage/storageAccounts': {
        'Standard_LRS': 0.02,
        'Standard_GRS': 0.05,
        'Premium_LRS': 0.15,
      },

      // Azure Application Insights
      'Microsoft.Insights/components': {
        'Free': 0,
        'Basic': 2.30,
      },

      // Azure Key Vault
      'Microsoft.KeyVault/vaults': {
        'Standard': 0.03,
        'Premium': 1.00,
      },

      // Azure Cosmos DB
      'Microsoft.DocumentDB/databaseAccounts': {
        'Serverless': 0,
        'Provisioned_400RU': 23.36,
        'Provisioned_1000RU': 58.40,
      },

      // Azure App Service
      'Microsoft.Web/serverfarms': {
        'F1': 0,
        'D1': 9.67,
        'B1': 13.14,
        'B2': 26.28,
        'S1': 69.35,
        'P1v2': 146.00,
      },

      // Azure Service Bus
      'Microsoft.ServiceBus/namespaces': {
        'Basic': 0.05,
        'Standard': 10.00,
        'Premium': 677.00,
      },

      // Azure Redis Cache
      'Microsoft.Cache/redis': {
        'C0': 16.06,
        'C1': 61.32,
        'C2': 122.63,
        'P1': 486.00,
      },

      // Azure Functions (Consumption Plan)
      'Microsoft.Web/sites': {
        'Consumption': 0,
        'Premium_EP1': 146.00,
      }
    };

    // ============================================================================
    // PRICING TABLE - Azure (East US 2 / Brazil South)
    // ============================================================================

    /**
     * Azure pricing table (loaded from JSON)
     * @deprecated Use loadAzurePricing() for custom pricing paths
     */
    export const AZURE_PRICING = loadAzurePricing();

    // ============================================================================
    // DEFAULT CONFIGURATION
    // ============================================================================

    export const DEFAULT_LIMITS = {
      freeTierOnly: 0,
      withApproval: 10,
      requiresADR: 10
    };

    // ============================================================================
    // CONFIGURATION LOADER
    // ============================================================================

    /**
     * Load cost configuration from config.json
     * @param {string} configPath - Optional custom config path
     * @returns {Object} Cost configuration
     */
    export function loadCostConfig(configPath = null) {
      const defaultPath = join(process.cwd(), '.morph/config/config.json');
      const templatePath = join(process.cwd(), 'content/.morph/config/config.template.json');

      const pathsToTry = [
        configPath,
        defaultPath,
        templatePath
      ].filter(Boolean);

      for (const path of pathsToTry) {
        if (existsSync(path)) {
          try {
            const content = readFileSync(path, 'utf8');
            const config = JSON.parse(content);

            if (config.costs && config.costs.limits) {
              return {
                limits: config.costs.limits,
                enforcement: config.costs.enforcement || {},
                currency: config.costs.currency || 'USD',
                region: config.costs.pricing?.region || 'eastus2'
              };
            }
          } catch (err) {
            // Continue to next path
          }
        }
      }

      // Fallback to defaults
      return {
        limits: DEFAULT_LIMITS,
        enforcement: {
          blockCommitsAboveLimit: true,
          requireADRInDecisionsMd: true,
          alertOnApproach: 0.8
        },
        currency: 'USD',
        region: 'eastus2'
      };
    }

    // ============================================================================
    // BICEP PARSER
    // ============================================================================

    /**
     * Parse Bicep file and extract resources with SKUs
     * @param {string} filePath - Path to Bicep file
     * @returns {Array} Array of resource objects
     */
    export function parseBicepFile(filePath) {
      if (!existsSync(filePath)) {
        throw new Error(`File not found: ${filePath}`);
      }

      const content = readFileSync(filePath, 'utf8');
      const resources = [];

      // Regex para extrair resources com conteúdo completo (inclui blocos aninhados)
      // Formato: resource <name> '<type>@<apiVersion>' = { ... }
      const resourceRegex = /resource\s+(\w+)\s+'([^']+)@[^']+'\s*=\s*\{/g;

      let match;
      while ((match = resourceRegex.exec(content)) !== null) {
        const [, name, type] = match;
        const startIndex = match.index + match[0].length;

        // Encontrar o fechamento do bloco { }
        let braceCount = 1;
        let endIndex = startIndex;

        while (braceCount > 0 && endIndex < content.length) {
          if (content[endIndex] === '{') braceCount++;
          if (content[endIndex] === '}') braceCount--;
          endIndex++;
        }

        const body = content.substring(startIndex, endIndex - 1);

        // Extrair SKU (dentro de bloco sku: { name: '...' })
        const skuBlockMatch = /sku:\s*\{([^}]+)\}/s.exec(body);
        let sku = null;

        if (skuBlockMatch) {
          const skuBlock = skuBlockMatch[1];
          const skuNameMatch = /name:\s*'([^']+)'/s.exec(skuBlock);
          sku = skuNameMatch ? skuNameMatch[1] : null;
        }

        // Extrair tier (alternativa, fora ou dentro do bloco sku)
        if (!sku) {
          const tierMatch = /tier:\s*'([^']+)'/s.exec(body);
          sku = tierMatch ? tierMatch[1] : null;
        }

        // Extrair minReplicas (Container Apps, deep nested)
        const minReplicasMatch = /minReplicas:\s*(\d+)/s.exec(body);
        const minReplicas = minReplicasMatch ? parseInt(minReplicasMatch[1], 10) : null;

        resources.push({
          name,
          type,
          sku,
          minReplicas,
          file: filePath
        });
      }

      return resources;
    }

    /**
     * Parse multiple Bicep files (supports glob patterns)
     * @param {string|Array} patterns - File path(s) or glob pattern(s)
     * @returns {Array} Array of all resources
     */
    export function parseBicepFiles(patterns) {
      const patternArray = Array.isArray(patterns) ? patterns : [patterns];
      const allResources = [];

      for (const pattern of patternArray) {
        // Se for glob pattern, expandir
        const files = pattern.includes('*')
          ? globSync(pattern, { absolute: true })
          : [pattern];

        for (const file of files) {
          try {
            const resources = parseBicepFile(file);
            allResources.push(...resources);
          } catch (err) {
            // Silently skip files with errors (let caller handle)
            console.warn(`Warning: Could not parse ${file}: ${err.message}`);
          }
        }
      }

      return allResources;
    }

    // ============================================================================
    // COST CALCULATOR
    // ============================================================================

    /**
     * Calculate cost of a single resource
     * @param {Object} resource - Resource object with type, sku, minReplicas
     * @returns {Object} Cost result with cost, warning, estimated, details
     */
    export function calculateResourceCost(resource) {
      const pricing = AZURE_PRICING[resource.type];

      if (!pricing) {
        return {
          cost: 0,
          warning: `Unknown resource type: ${resource.type}`,
          estimated: false
        };
      }

      // Special case: Container Apps with minReplicas
      if (resource.type === 'Microsoft.App/containerApps') {
        const sku = resource.minReplicas > 0 ? 'Consumption_Active' : 'Consumption';
        const cost = pricing[sku] || 0;

        return {
          cost,
          warning: null,
          estimated: true,
          details: `minReplicas: ${resource.minReplicas}, scale-to-zero: ${resource.minReplicas === 0}`
        };
      }

      // General case: lookup by SKU
      if (resource.sku && pricing[resource.sku] !== undefined) {
        return {
          cost: pricing[resource.sku],
          warning: null,
          estimated: true
        };
      }

      // SKU not found
      return {
        cost: 0,
        warning: `Unknown SKU '${resource.sku}' for ${resource.type}`,
        estimated: false
      };
    }

    /**
     * Calculate total costs of all resources
     * @param {Array} resources - Array of resource objects
     * @param {Object} costConfig - Cost configuration
     * @returns {Object} Cost calculation result
     */
    export function calculateTotalCosts(resources, costConfig) {
      let total = 0;
      const breakdown = [];
      const warnings = [];

      for (const resource of resources) {
        const result = calculateResourceCost(resource);

        total += result.cost;

        breakdown.push({
          name: resource.name,
          type: resource.type,
          sku: resource.sku || 'N/A',
          cost: result.cost,
          file: resource.file,
          estimated: result.estimated,
          details: result.details
        });

        if (result.warning) {
          warnings.push(result.warning);
        }

        // Warning para recursos caros
        if (result.cost > costConfig.limits.withApproval) {
          warnings.push(
            `Resource '${resource.name}' (${resource.type} ${resource.sku}) costs $${result.cost.toFixed(2)}/month - exceeds approval threshold!`
          );
        }
      }

      return {
        monthly: parseFloat(total.toFixed(2)),
        currency: costConfig.currency,
        region: costConfig.region,
        breakdown,
        warnings,
        limits: costConfig.limits,
        requiresApproval: total > costConfig.limits.freeTierOnly,
        requiresADR: total > costConfig.limits.requiresADR,
        isFreeTier: total === 0
      };
    }

    /**
     * Calculate costs from Bicep file(s) - Main entry point
     * @param {string|Array} bicepFiles - Bicep file path(s) or glob pattern(s)
     * @param {Object} options - Options
     * @param {string} options.configPath - Optional custom config path
     * @returns {Object} Complete cost calculation result
     */
    export function calculateBicepCost(bicepFiles, options = {}) {
      const costConfig = loadCostConfig(options.configPath);
      const resources = parseBicepFiles(bicepFiles);

      if (resources.length === 0) {
        return {
          monthly: 0,
          currency: costConfig.currency,
          region: costConfig.region,
          breakdown: [],
          warnings: ['No resources found in Bicep files'],
          limits: costConfig.limits,
          requiresApproval: false,
          requiresADR: false,
          isFreeTier: true
        };
      }

      return calculateTotalCosts(resources, costConfig);
    }