// src/core/prims-io.ts
// Node.js-dependent IO primitives extracted from prims.ts (Job 035b).
// This file contains sync LLM call helpers that depend on child_process, fs, path, os.
// The main prims.ts remains pure (no Node.js imports).
//
// Vendor-specific adapters removed in JOB-06.
// Configure via OMEGA_LLM_ENDPOINT, OMEGA_LLM_API_KEY, OMEGA_LLM_MODEL env vars,
// or use DriverRegistry for async calls.

import { execSync } from "child_process";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import type { Val } from "./eval/values";
// Note: We accept registerSyncEffectHandler as a parameter to avoid
// circular imports (prims.ts -> prims-io.ts -> prims.ts).
type SyncEffectHandler = (args: Val[], state: any) => Val;
type RegisterFn = (op: string, handler: SyncEffectHandler) => void;

/**
 * Extract prompt text from a Val payload.
 */
function extractPromptText(payload: Val): string {
  let prompt = "";
  if (payload && typeof payload === "object" && "tag" in payload) {
    if (payload.tag === "Str") {
      prompt = (payload as { tag: "Str"; s: string }).s;
    } else if (payload.tag === "Vector") {
      // Flatten list to string
      let cur: Val = payload;
      while (
        cur.tag === "Vector" &&
        "items" in cur &&
        Array.isArray((cur as { tag: "Vector"; items: Val[] }).items) &&
        (cur as { tag: "Vector"; items: Val[] }).items.length >= 2
      ) {
        const items = (cur as { tag: "Vector"; items: Val[] }).items;
        if (items[0].tag === "Str") {
          prompt += (items[0] as { tag: "Str"; s: string }).s;
        }
        cur = items[1];
      }
    }
  }
  return prompt;
}

/**
 * LLM provider configuration for sync calls.
 */
interface SyncLLMConfig {
  /** API endpoint hostname (e.g., "api.example.com") */
  hostname: string;
  /** API endpoint path (e.g., "/v1/chat/completions") */
  path: string;
  /** Build request headers given the API key */
  buildHeaders(apiKey: string, bodyLength: number): Record<string, string>;
  /** Build the JSON request body given a prompt */
  buildBody(prompt: string, model: string): string;
  /** Extract the response text from the parsed JSON response */
  extractText(json: any): string;
}

/**
 * Call an LLM API synchronously using execSync.
 * Writes script to temp file to avoid shell escaping issues.
 */
function callLLMSync(prompt: string, apiKey: string, config: SyncLLMConfig, model: string): string {
  const tempDir = os.tmpdir();
  const scriptFile = path.join(tempDir, `omega-llm-${Date.now()}.js`);
  const script = `
const https = require('https');
const prompt = ${JSON.stringify(prompt)};
const apiKey = ${JSON.stringify(apiKey)};

const data = ${JSON.stringify(config.buildBody(prompt, model))};

const options = {
  hostname: ${JSON.stringify(config.hostname)},
  port: 443,
  path: ${JSON.stringify(config.path)},
  method: 'POST',
  headers: ${JSON.stringify(config.buildHeaders(apiKey, 0))}
};
// Fix Content-Length for actual data
options.headers['Content-Length'] = Buffer.byteLength(data);

const req = https.request(options, (res) => {
  let body = '';
  res.on('data', d => body += d);
  res.on('end', () => {
    try {
      const json = JSON.parse(body);
      const extractText = ${config.extractText.toString()};
      process.stdout.write(extractText(json));
    } catch (e) {
      process.stdout.write('[LLM Error: ' + e.message + ']');
    }
  });
});

req.on('error', (e) => {
  process.stdout.write('[LLM Error: ' + e.message + ']');
});

req.write(data);
req.end();
`;

  try {
    fs.writeFileSync(scriptFile, script, "utf-8");
    const result = execSync(`node "${scriptFile}"`, {
      encoding: "utf-8",
      timeout: 60000,
      maxBuffer: 1024 * 1024,
    });
    fs.unlinkSync(scriptFile);
    return result;
  } catch (error) {
    try { fs.unlinkSync(scriptFile); } catch { /* ignore */ }
    const msg = error instanceof Error ? error.message : String(error);
    return `[LLM Error: ${msg}]`;
  }
}

/**
 * Generate a mock response (for when no API key is available).
 */
function generateMockResponse(prompt: string): string {
  const words = prompt.split(/\s+/).filter((w: string) => w.length > 3);
  const firstWords = words.slice(0, 15).join(" ");
  return firstWords.length > 0
    ? `[Mock LLM] ${firstWords.substring(0, 100)}...`
    : "[Mock LLM] No content provided.";
}

/**
 * Detect LLM configuration from environment variables.
 *
 * Priority:
 * 1. OMEGA_LLM_ENDPOINT + OMEGA_LLM_API_KEY (fully generic)
 * 2. Well-known API key env vars (auto-detect provider)
 * 3. No config → mock responses
 */
function detectLLMConfig(): { apiKey: string; config: SyncLLMConfig; model: string } | null {
  // Check for fully generic config
  const genericEndpoint = process.env.OMEGA_LLM_ENDPOINT;
  const genericKey = process.env.OMEGA_LLM_API_KEY;
  const genericModel = process.env.OMEGA_LLM_MODEL || "default";

  if (genericEndpoint && genericKey) {
    // Parse endpoint URL
    const url = new URL(genericEndpoint);
    return {
      apiKey: genericKey,
      model: genericModel,
      config: {
        hostname: url.hostname,
        path: url.pathname,
        buildHeaders: (key: string, _len: number) => ({
          "Content-Type": "application/json",
          "Authorization": `Bearer ${key}`,
        }),
        buildBody: (prompt: string, model: string) => JSON.stringify({
          model,
          max_tokens: 1024,
          messages: [{ role: "user", content: prompt }],
        }),
        extractText: (json: any) => json.choices?.[0]?.message?.content || json.content?.[0]?.text || "",
      },
    };
  }

  // Auto-detect from well-known env var patterns
  // Check all env vars matching *_API_KEY pattern
  for (const [envName, envValue] of Object.entries(process.env)) {
    if (!envName.endsWith("_API_KEY") || typeof envValue !== "string" || !envValue) continue;
    // Skip generic omega key (already checked above)
    if (envName === "OMEGA_LLM_API_KEY" || envName === "OMEGA_API_KEY") continue;

    // Return a generic chat completions config with bearer auth
    return {
      apiKey: envValue,
      model: genericModel,
      config: {
        hostname: process.env.OMEGA_LLM_HOSTNAME || "localhost",
        path: process.env.OMEGA_LLM_PATH || "/v1/chat/completions",
        buildHeaders: (key: string, _len: number) => ({
          "Content-Type": "application/json",
          "Authorization": `Bearer ${key}`,
        }),
        buildBody: (prompt: string, model: string) => JSON.stringify({
          model,
          max_tokens: 1024,
          messages: [
            { role: "system", content: "You are a helpful assistant. Be concise." },
            { role: "user", content: prompt },
          ],
        }),
        extractText: (json: any) => json.choices?.[0]?.message?.content || json.content?.[0]?.text || "",
      },
    };
  }

  return null;
}

/**
 * Register sync effect handlers for solve.
 * These allow effects like infer.op to work inside solve algebra procedures.
 *
 * Uses OMEGA_LLM_ENDPOINT + OMEGA_LLM_API_KEY env vars for LLM calls,
 * or falls back to mock responses if no configuration is available.
 */
export function registerIoPrims(registerSyncEffectHandler: RegisterFn): void {
  registerSyncEffectHandler("infer.op", (args: Val[], _state: any) => {
    const payload = args[0];
    const prompt = extractPromptText(payload);

    const llmConfig = detectLLMConfig();

    let response: string;
    if (llmConfig) {
      response = callLLMSync(prompt, llmConfig.apiKey, llmConfig.config, llmConfig.model);
    } else {
      response = generateMockResponse(prompt);
    }

    // infer.op returns denotation directly (backward compatible)
    return { tag: "Str", s: response } as Val;
  });
}

// Note: registerIoPrims() must be called explicitly from prims.ts
// to avoid circular import issues.
