import type { ChatRequest, ChatResponse, ChatStreamEvent, ModelClient, ModelToolCall, TokenUsage } from './types.js';
import { getMessageText } from './media.js';

export interface SyntheticClientConfig {
  /**
   * Controls behavior. Suggested values:
   * - "echo" (default): reply with the last user message text.
   * - "fixed:<text>": always reply with <text>.
   * - "error:<message>": always throw an error with <message>.
   */
  model: string;
}

function getLastUserText(request: ChatRequest): string {
  for (let i = request.messages.length - 1; i >= 0; i--) {
    const msg = request.messages[i];
    if (msg.role === 'user') {
      return getMessageText(msg);
    }
  }
  return '';
}

function estimateTokens(text: string): number {
  // Rough heuristic for deterministic, offline usage reporting.
  return Math.max(1, Math.ceil(text.length / 4));
}

function buildUsage(request: ChatRequest, content: string): TokenUsage {
  const inputText = request.messages.map(m => getMessageText(m)).join('\n');
  return {
    inputTokens: estimateTokens(inputText),
    outputTokens: estimateTokens(content),
  };
}

function parseToolDirective(text: string): ModelToolCall | null {
  // Dev/test escape hatch: "@tool <name> <json>"
  const trimmed = text.trim();
  if (!trimmed.startsWith('@tool ')) {return null;}

  const rest = trimmed.slice('@tool '.length).trim();
  const firstSpace = rest.indexOf(' ');
  const name = (firstSpace === -1 ? rest : rest.slice(0, firstSpace)).trim();
  const rawArgs = (firstSpace === -1 ? '' : rest.slice(firstSpace + 1)).trim();

  if (!name) {return null;}

  let args: unknown = {};
  if (rawArgs) {
    try {
      args = JSON.parse(rawArgs);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      throw new Error(`Synthetic tool directive JSON parse error: ${message}`);
    }
  }

  return { id: 'synthetic-tool-1', name, args };
}

function parseMode(model: string): { kind: 'echo' | 'fixed' | 'error'; value?: string } {
  if (model.startsWith('fixed:')) {
    return { kind: 'fixed', value: model.slice('fixed:'.length) };
  }
  if (model.startsWith('error:')) {
    return { kind: 'error', value: model.slice('error:'.length) };
  }
  if (model === 'echo' || !model) {
    return { kind: 'echo' };
  }
  // Unknown values default to echo to keep config forgiving.
  return { kind: 'echo' };
}

export class SyntheticClient implements ModelClient {
  private readonly mode: { kind: 'echo' | 'fixed' | 'error'; value?: string };

  constructor(config: SyntheticClientConfig) {
    this.mode = parseMode(config.model);
  }

  async chat(request: ChatRequest): Promise<ChatResponse> {
    const lastUserText = getLastUserText(request);
    const toolCall = parseToolDirective(lastUserText);

    if (toolCall) {
      const usage = buildUsage(request, '');
      return {
        content: '',
        stopReason: 'tool_use',
        usage,
        toolCalls: [toolCall],
        thinkingContent: request.thinking ? '[synthetic] thinking enabled' : undefined,
      };
    }

    if (this.mode.kind === 'error') {
      throw new Error(this.mode.value || 'Synthetic error');
    }

    const content = this.mode.kind === 'fixed'
      ? (this.mode.value ?? '')
      : lastUserText;

    const usage = buildUsage(request, content);
    return {
      content,
      stopReason: 'end_turn',
      usage,
      thinkingContent: request.thinking ? '[synthetic] thinking enabled' : undefined,
    };
  }

  async *chatStream(request: ChatRequest): AsyncIterable<ChatStreamEvent> {
    try {
      const response = await this.chat(request);
      if (response.toolCalls?.length) {
        for (const toolCall of response.toolCalls) {
          yield { type: 'tool_use', toolCall };
        }
        yield { type: 'done', usage: response.usage };
        return;
      }

      if (response.content) {
        yield { type: 'content', content: response.content };
      }
      yield { type: 'done', usage: response.usage };
    } catch (error) {
      yield { type: 'error', error: error instanceof Error ? error : new Error(String(error)) };
    }
  }
}

