import { spawn, execSync } from 'child_process';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const AGENT_ROOT = join(__dirname, '..');
const DEFAULT_WORKSPACE = join(AGENT_ROOT, 'workspaces', 'default');

export const name = 'gemini';

export const modelMap = {
  fast: 'gemini-2.5-flash',
  balanced: 'gemini-2.5-pro',
  powerful: 'gemini-2.5-pro'
};

function resolveModel(model) {
  const key = String(model || 'balanced').trim().toLowerCase();
  if (key === 'fast' || key === 'balanced' || key === 'powerful') {
    return modelMap[key];
  }
  return String(model || modelMap.balanced);
}

// Resolved full path to the CLI binary (set by isAvailable)
let resolvedBin = 'gemini';

/**
 * Platform-aware spawn helper.
 * Uses the resolved binary path from isAvailable() to avoid ENOENT when
 * the CLI is installed in a directory not in Node's default PATH.
 */
function spawnGemini(args, options = {}) {
  if (process.platform === 'win32') {
    return spawn('cmd.exe', ['/d', '/s', '/c', resolvedBin, ...args], {
      ...options,
      shell: false
    });
  }
  return spawn(resolvedBin, args, {
    ...options,
    shell: false
  });
}

export function isAvailable() {
  try {
    if (process.platform === 'win32') {
      resolvedBin = execSync('where gemini.cmd 2>nul || where gemini', { encoding: 'utf-8' }).trim().split(/\r?\n/)[0];
    } else {
      resolvedBin = execSync('which gemini', { encoding: 'utf-8' }).trim();
    }
    return true;
  } catch {
    return false;
  }
}

/**
 * Run Gemini CLI in headless mode and return the result.
 *
 * @param {string} prompt
 * @param {object} opts
 * @param {string} opts.model
 * @param {number} opts.timeout - ms before killing
 * @param {boolean} opts.sandboxed - if true (default), run in workspace/
 * @param {function} opts.onStream - callback(partialText) for streaming updates
 */
export async function ask(prompt, { model = 'balanced', timeout = 600_000, sandboxed = true, onStream } = {}) {
  return new Promise((resolve, reject) => {
    const selectedModel = resolveModel(model);
    const args = [
      '--model', selectedModel,
      '--output-format', 'stream-json',
      '-p', prompt
    ];

    const proc = spawnGemini(args, {
      cwd: sandboxed ? DEFAULT_WORKSPACE : AGENT_ROOT,
      env: { ...process.env },
      stdio: ['pipe', 'pipe', 'pipe']
    });

    let accumulatedText = '';
    let buffer = '';
    let killed = false;

    const timer = setTimeout(() => {
      killed = true;
      proc.kill('SIGTERM');
      setTimeout(() => proc.kill('SIGKILL'), 5000);
    }, timeout);

    proc.stdout.on('data', (chunk) => {
      buffer += chunk.toString();
      const lines = buffer.split('\n');
      buffer = lines.pop();

      for (const line of lines) {
        if (!line.trim()) continue;
        try {
          const event = JSON.parse(line);

          // Gemini stream-json: look for text content in various event shapes
          if (event.type === 'result' && event.result) {
            accumulatedText = event.result;
            if (onStream) onStream(accumulatedText);
          } else if (event.type === 'assistant' && event.message?.content) {
            for (const block of event.message.content) {
              if (block.type === 'text' && block.text) {
                accumulatedText += block.text;
              }
            }
            if (onStream) onStream(accumulatedText);
          } else if (event.text) {
            // Simple text event
            accumulatedText += event.text;
            if (onStream) onStream(accumulatedText);
          } else if (event.content) {
            // Direct content field
            accumulatedText = typeof event.content === 'string' ? event.content : accumulatedText;
            if (onStream) onStream(accumulatedText);
          }
        } catch {
          // Non-JSON line — treat as plain text output
          if (line.trim()) {
            accumulatedText += line;
            if (onStream) onStream(accumulatedText);
          }
        }
      }
    });

    let stderr = '';
    proc.stderr.on('data', (chunk) => { stderr += chunk.toString(); });

    proc.on('close', (code) => {
      clearTimeout(timer);

      // Process remaining buffer
      if (buffer.trim()) {
        try {
          const event = JSON.parse(buffer);
          if (event.type === 'result' && event.result) {
            accumulatedText = event.result;
          } else if (event.text) {
            accumulatedText += event.text;
          }
        } catch {
          if (buffer.trim()) accumulatedText += buffer.trim();
        }
      }

      const result = accumulatedText.trim();

      if (killed) {
        reject(new Error(`Gemini CLI timed out after ${timeout}ms`));
      } else if (code !== 0 && !result) {
        reject(new Error(`Gemini CLI exited with code ${code}\n${stderr}`));
      } else if (code === 0 && !result) {
        reject(new Error(`Gemini CLI returned empty output\n${stderr}`.trim()));
      } else {
        resolve(result);
      }
    });

    proc.on('error', (err) => {
      clearTimeout(timer);
      reject(new Error(`Gemini CLI spawn error: ${err.message}`));
    });

    proc.stdin.end();
  });
}

/**
 * Chat-mode completion with controlled tool access.
 *
 * Gemini CLI does not have MCP or fine-grained tool control flags,
 * so this is a pass-through to ask() with a shorter timeout.
 * Consistent interface with other adapters.
 *
 * @param {string} prompt
 * @param {object} opts
 * @param {string} opts.model
 * @param {number} opts.timeout
 * @param {function} opts.onStream
 * @param {string[]|null} opts.allowedTools - accepted for interface consistency
 */
export async function streamChat(prompt, { model = 'balanced', timeout = 300_000, onStream, allowedTools = null } = {}) {
  return ask(prompt, { model, timeout, onStream });
}

/**
 * Spawn Gemini in interactive mode (for meta-develop).
 * Returns the spawned child process.
 */
export function spawnInteractive({ cwd, model = 'balanced' } = {}) {
  const selectedModel = resolveModel(model);
  return spawnGemini([
    '--model', selectedModel,
    '--sandbox'
  ], {
    cwd: cwd || DEFAULT_WORKSPACE,
    env: { ...process.env },
    stdio: ['pipe', 'pipe', 'pipe']
  });
}
