/**
 * @fileoverview LLMAnalyzer - Analyzes project context using Claude Code's LLM
 * @module morph-spec/llm/analyzer
 */

import { spawn } from 'child_process';
import { detectClaudeCode, isInteractive } from './environment-detector.js';
import { buildPrompt } from './prompt-builder.js';
import Ajv from 'ajv';
import projectConfigSchema from './project-config-schema.json' with { type: 'json' };

/**
 * @typedef {import('../types/index.js').SanitizedContext} SanitizedContext
 * @typedef {import('../types/index.js').ProjectConfig} ProjectConfig
 */

/**
 * LLM Analysis Error
 */
export class LLMAnalysisError extends Error {
  constructor(message, statusCode = 500, attempts = 0, originalError = null) {
    super(message);
    this.name = 'LLMAnalysisError';
    this.statusCode = statusCode;
    this.attempts = attempts;
    this.originalError = originalError;
  }
}

/**
 * Validation Error
 */
export class ValidationError extends Error {
  constructor(message, field, value, schema = null) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
    this.value = value;
    this.schema = schema;
  }
}

/**
 * LLMAnalyzer - Analyzes project context using Claude Code's embedded LLM
 * @class
 */
export class LLMAnalyzer {
  constructor() {
    this.ajv = new Ajv({ allErrors: true });
    this.validateSchema = this.ajv.compile(projectConfigSchema);
  }

  /**
   * Analyze project context using Claude Code's LLM
   * @param {SanitizedContext} context - Sanitized project context
   * @param {Object} [options] - Analysis options
   * @param {number} [options.timeout] - Timeout in ms (default: 60000 for user, no timeout for LLM)
   * @returns {Promise<ProjectConfig>}
   * @throws {LLMAnalysisError} If analysis fails
   */
  async analyze(context, options = {}) {
    // Check if Claude Code is available
    if (!detectClaudeCode()) {
      throw new LLMAnalysisError(
        'Claude Code environment not detected. This feature requires Claude Code CLI.',
        503,
        0
      );
    }

    // Build the prompt
    const prompt = buildPrompt(context);

    // Determine timeout: 60s for interactive (user present), no timeout for LLM-only
    const timeout = options.timeout || (isInteractive() ? 60000 : 0);

    // Invoke Claude Code LLM
    const response = await this.invokeLLM(prompt, { timeout });

    // Parse and validate JSON response
    const projectConfig = this.parseJsonResponse(response);

    return projectConfig;
  }

  /**
   * Build structured prompt for LLM
   * @param {SanitizedContext} context - Sanitized context
   * @returns {string} Prompt text
   */
  buildPrompt(context) {
    return buildPrompt(context);
  }

  /**
   * Invoke Claude Code's LLM via spawning a subprocess
   *
   * IMPLEMENTATION NOTE: This is a placeholder implementation.
   * The actual Claude Code invocation mechanism depends on how Claude Code exposes its LLM.
   *
   * Possible approaches:
   * 1. Spawn `claude` CLI with a special flag for JSON mode
   * 2. Use an internal API/IPC mechanism
   * 3. Write prompt to a temp file and invoke Claude Code agent
   *
   * For now, we'll simulate the response for testing purposes.
   *
   * @param {string} prompt - Prompt text
   * @param {Object} options - Invocation options
   * @param {number} options.timeout - Timeout in ms (0 = no timeout)
   * @returns {Promise<string>} JSON response from LLM
   * @throws {LLMAnalysisError}
   */
  async invokeLLM(prompt, options = {}) {
    return new Promise((resolve, reject) => {
      // TODO: Replace with actual Claude Code invocation
      // For now, this is a placeholder that would need to be implemented
      // based on how Claude Code exposes its LLM for programmatic access

      // Example approach (pseudo-code):
      // const claudeProcess = spawn('claude', ['--mode=json', '--prompt', prompt]);

      // Placeholder: Simulate a timeout if needed
      const { timeout } = options;

      let output = '';
      let errorOutput = '';

      // Simulated response for testing (to be replaced with actual Claude Code invocation)
      // In real implementation, this would spawn Claude Code process:
      /*
      const claudeProcess = spawn('claude-code', [
        '--json',
        '--prompt-from-stdin'
      ]);

      claudeProcess.stdin.write(prompt);
      claudeProcess.stdin.end();

      claudeProcess.stdout.on('data', (data) => {
        output += data.toString();
      });

      claudeProcess.stderr.on('data', (data) => {
        errorOutput += data.toString();
      });

      claudeProcess.on('close', (code) => {
        if (code !== 0) {
          reject(new LLMAnalysisError(
            `Claude Code exited with code ${code}: ${errorOutput}`,
            500,
            1
          ));
          return;
        }

        resolve(output);
      });

      if (timeout > 0) {
        setTimeout(() => {
          claudeProcess.kill();
          reject(new LLMAnalysisError('LLM analysis timeout', 504, 1));
        }, timeout);
      }
      */

      // For now, reject with a "not implemented" error
      // This will be replaced when we know the exact Claude Code invocation mechanism
      reject(new LLMAnalysisError(
        'LLM invocation not yet implemented. Awaiting Claude Code API documentation.',
        501,
        0
      ));
    });
  }

  /**
   * Parse and validate LLM JSON response
   * @param {string} response - Raw LLM response
   * @returns {ProjectConfig}
   * @throws {ValidationError} If response doesn't match schema
   */
  parseJsonResponse(response) {
    // Try to parse JSON
    let parsed;
    try {
      parsed = JSON.parse(response);
    } catch (error) {
      throw new ValidationError(
        'LLM response is not valid JSON',
        'response',
        response,
        null
      );
    }

    // Validate against schema
    const valid = this.validateSchema(parsed);
    if (!valid) {
      const errors = this.validateSchema.errors || [];
      const errorMessages = errors.map(err => `${err.instancePath} ${err.message}`).join(', ');

      throw new ValidationError(
        `Schema validation failed: ${errorMessages}`,
        'schema',
        parsed,
        projectConfigSchema
      );
    }

    return parsed;
  }
}
