'use strict';

const fs = require('node:fs');
const path = require('node:path');
const { AcpAdapter } = require('./acp-adapter');
const { extractText } = require('./workspace-replay');

const ACP_PROTOCOL_VERSION = 1;

class AcpJsonRpcBridge {
  constructor({ adapter = null, notify = null, agentInfo = null } = {}) {
    this.adapter = adapter || new AcpAdapter();
    this.notify = typeof notify === 'function' ? notify : () => {};
    this.agentInfo = {
      name: 'wall-e',
      title: 'Wall-E Coding',
      version: packageVersion(),
      ...(agentInfo || {}),
    };
  }

  async handle(message) {
    if (Array.isArray(message)) {
      const responses = [];
      for (const item of message) {
        const response = await this._handleOne(item);
        if (response) responses.push(response);
      }
      return responses;
    }
    return this._handleOne(message);
  }

  async _handleOne(message) {
    const id = message?.id;
    try {
      if (!message || message.jsonrpc !== '2.0' || typeof message.method !== 'string') {
        throw rpcError(-32600, 'Invalid Request');
      }
      const params = message.params || {};
      const isNotification = id === undefined || id === null;
      const result = await this._dispatch(message.method, params, { notification: isNotification });
      if (isNotification) return null;
      return { jsonrpc: '2.0', id, result };
    } catch (error) {
      if (id === undefined || id === null) return null;
      return {
        jsonrpc: '2.0',
        id,
        error: {
          code: Number.isFinite(error.code) ? error.code : -32603,
          message: error.message || 'Internal error',
          data: error.data,
        },
      };
    }
  }

  async _dispatch(method, params, meta) {
    switch (method) {
      case 'initialize':
        return this.initialize(params);
      case 'authenticate':
        return {};
      case 'session/new':
        return this.newSession(params);
      case 'session/load':
        return this.loadSession(params);
      case 'session/list':
        return this.listSessions(params);
      case 'session/prompt':
        return this.prompt(params);
      case 'session/set_mode':
        return this.setMode(params);
      case 'session/cancel':
        this.cancel(params);
        return meta.notification ? undefined : {};
      default:
        throw rpcError(-32601, `Method not found: ${method}`);
    }
  }

  initialize(params = {}) {
    const requested = Number(params.protocolVersion || ACP_PROTOCOL_VERSION);
    return {
      protocolVersion: requested === ACP_PROTOCOL_VERSION ? requested : ACP_PROTOCOL_VERSION,
      agentCapabilities: {
        loadSession: true,
        mcpCapabilities: { http: false, sse: false },
        promptCapabilities: {
          audio: false,
          embeddedContext: true,
          image: false,
        },
        sessionCapabilities: {
          list: true,
          close: false,
        },
      },
      agentInfo: this.agentInfo,
      authMethods: [],
    };
  }

  async newSession(params = {}) {
    const cwd = requireAbsoluteCwd(params.cwd);
    const created = await this.adapter.createSession({
      sessionId: params.sessionId,
      cwd,
      model: params.model || '',
      agent: params.agent || 'build',
      mode: params.mode || 'build',
    });
    return {
      sessionId: created.sessionId,
      modes: modeState(created.mode),
      configOptions: [],
    };
  }

  async loadSession(params = {}) {
    const sessionId = requireSessionId(params.sessionId);
    const state = this.adapter.loadSession(sessionId, {
      cwd: params.cwd || '',
      transcriptPath: params.transcriptPath || '',
    });
    if (!state.transcriptPath) throw rpcError(-32004, `Unknown session: ${sessionId}`);
    const replay = this.adapter.replay.replayRange(sessionId, {
      transcriptPath: state.transcriptPath,
      offset: 0,
      sequence: 0,
      limitBytes: Number.MAX_SAFE_INTEGER,
    });
    for (const entry of replay.records) {
      for (const update of acpUpdatesFromRecord(sessionId, entry.record)) {
        this._sessionUpdate(sessionId, update);
      }
    }
    return {
      modes: modeState(state.meta.mode || 'build'),
      configOptions: [],
    };
  }

  listSessions(params = {}) {
    const listed = this.adapter.listSessions({
      cwd: params.cwd || '',
      query: params.query || '',
      limit: params.limit || 50,
      cursor: params.cursor || 0,
    });
    return {
      sessions: listed.sessions.map((session) => ({
        sessionId: session.sessionId,
        cwd: session.cwd,
        title: session.title,
        updatedAt: session.updatedAt,
      })),
      nextCursor: listed.nextCursor,
    };
  }

  async prompt(params = {}) {
    const sessionId = requireSessionId(params.sessionId);
    const prompt = promptText(params.prompt);
    if (!prompt) throw rpcError(-32602, 'session/prompt requires at least one text prompt block');
    this._sessionUpdate(sessionId, {
      sessionUpdate: 'user_message_chunk',
      content: { type: 'text', text: prompt },
    });
    const result = await this.adapter.promptSession({
      sessionId,
      prompt,
      cwd: params.cwd || '',
      model: params.model || '',
      agent: params.agent || 'build',
      mode: params.mode || 'build',
    });
    if (result.result?.output) {
      this._sessionUpdate(sessionId, {
        sessionUpdate: 'agent_message_chunk',
        content: { type: 'text', text: String(result.result.output) },
      });
    }
    return { stopReason: result.stopReason || 'end_turn' };
  }

  setMode(params = {}) {
    const sessionId = requireSessionId(params.sessionId);
    const mode = params.modeId || params.mode || 'build';
    this.adapter.setSessionOptions(sessionId, { mode });
    this._sessionUpdate(sessionId, {
      sessionUpdate: 'mode_update',
      mode,
    });
    return { modes: modeState(mode) };
  }

  cancel(params = {}) {
    const sessionId = requireSessionId(params.sessionId);
    this.adapter.cancelSession(sessionId, params.reason || 'cancelled');
    this._sessionUpdate(sessionId, {
      sessionUpdate: 'agent_message_chunk',
      content: { type: 'text', text: '[cancelled]' },
    });
  }

  _sessionUpdate(sessionId, update) {
    this.notify('session/update', { sessionId, update });
  }
}

function acpUpdatesFromRecord(sessionId, record = {}) {
  if (record.type === 'user') {
    const text = extractText(record.message?.content ?? record.content);
    return text ? [{ sessionUpdate: 'user_message_chunk', content: { type: 'text', text } }] : [];
  }
  if (record.type === 'assistant') {
    const text = extractText(record.message?.content ?? record.content);
    return text ? [{ sessionUpdate: 'agent_message_chunk', content: { type: 'text', text } }] : [];
  }
  if (record.type !== 'walle_part') return [];
  const data = record.data || {};
  if (record.partType === 'tool_call') {
    const id = data.toolCallId || data.tool_call_id || data.id || '';
    return [{
      sessionUpdate: 'tool_call',
      toolCallId: id,
      title: data.title || data.summary || data.name || data.tool || 'Tool call',
      kind: data.kind || 'other',
      status: acpToolStatus(data.status || data.state || 'pending'),
    }];
  }
  if (record.partType === 'tool_result' || record.partType === 'tool_done') {
    const id = data.toolCallId || data.tool_call_id || data.id || '';
    const text = typeof data.output === 'string'
      ? data.output
      : typeof data.result === 'string'
        ? data.result
        : JSON.stringify(data.result ?? data.output ?? '');
    return [{
      sessionUpdate: 'tool_call_update',
      toolCallId: id,
      status: acpToolStatus(data.status || data.state || (data.error ? 'error' : 'completed')),
      content: text ? [{ type: 'content', content: { type: 'text', text } }] : [],
    }];
  }
  if (record.partType === 'runtime_error' || record.partType === 'error') {
    return [{
      sessionUpdate: 'agent_message_chunk',
      content: { type: 'text', text: `[error] ${data.message || data.errors || 'runtime error'}` },
    }];
  }
  return [];
}

function promptText(prompt) {
  if (typeof prompt === 'string') return prompt;
  if (!Array.isArray(prompt)) return '';
  return prompt
    .map((block) => {
      if (!block) return '';
      if (block.type === 'text') return block.text || '';
      if (block.type === 'resource' && block.resource?.text) return block.resource.text;
      return '';
    })
    .filter(Boolean)
    .join('\n');
}

function requireSessionId(sessionId) {
  if (!sessionId) throw rpcError(-32602, 'sessionId is required');
  return String(sessionId);
}

function requireAbsoluteCwd(cwd) {
  if (!cwd || !path.isAbsolute(cwd)) throw rpcError(-32602, 'cwd must be an absolute path');
  return cwd;
}

function acpToolStatus(status) {
  const value = String(status || '').toLowerCase();
  if (value === 'working' || value === 'running' || value === 'in_progress') return 'in_progress';
  if (value === 'done' || value === 'complete' || value === 'completed') return 'completed';
  if (value === 'error' || value === 'failed') return 'failed';
  if (value === 'cancelled' || value === 'canceled') return 'cancelled';
  return 'pending';
}

function modeState(mode) {
  const current = mode || 'build';
  return {
    currentModeId: current,
    availableModes: [
      { id: 'build', name: 'Build', description: 'Make code changes and verify them.' },
      { id: 'plan', name: 'Plan', description: 'Plan work without applying changes.' },
      { id: 'review', name: 'Review', description: 'Review code and report findings.' },
    ],
  };
}

function rpcError(code, message, data) {
  const error = new Error(message);
  error.code = code;
  error.data = data;
  return error;
}

function packageVersion() {
  try {
    const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
    return pkg.version || '0.0.0';
  } catch {
    return '0.0.0';
  }
}

module.exports = {
  ACP_PROTOCOL_VERSION,
  AcpJsonRpcBridge,
  acpUpdatesFromRecord,
  promptText,
  acpToolStatus,
};
