import { resolvePoint, resolveRegion } from '../coordinates.js';
import type { ChatGptComputerMcpConfig } from '../config.js';
import { computerError } from '../errors.js';
import type { ActionResult, ComputerAction, ComputerInfo, Observation, ObserveRequest } from '../types.js';
import type { ComputerBackend } from './computer-backend.js';
import { NativeWaylandHelperClient, type WaylandHelperClient, type WaylandNativeAction } from './wayland-helper.js';

export class WaylandBackend implements ComputerBackend {
  constructor(
    readonly config: Readonly<ChatGptComputerMcpConfig>,
    readonly helper: WaylandHelperClient = new NativeWaylandHelperClient(config),
  ) {}

  async info(): Promise<ComputerInfo> {
    const info = await this.helper.info();
    return {
      ...info,
      backend: 'wayland',
      sessionType: 'wayland',
      coordinateUnit: 'logical-pixel',
      capabilities: {
        observe: this.config.observe.enabled && info.capabilities.observe,
        pointer: this.config.action.enabled && info.capabilities.pointer,
        keyboard: this.config.action.enabled && info.capabilities.keyboard,
        text: this.config.action.enabled && info.capabilities.text,
      },
    };
  }

  async observe(request: ObserveRequest): Promise<Observation> {
    if (!this.config.observe.enabled) throw computerError('OBSERVE_DISABLED', 'computer.observe', 'Screen observation is disabled.');
    const info = await this.info();
    const region = resolveRegion(info, request.target);
    const observation = await this.helper.observe(region, request.includePointer);
    if (observation.data.byteLength === 0) throw computerError('OS_ERROR', 'computer.observe', 'Wayland capture returned no image data.');
    if (observation.data.byteLength > this.config.observe.maxImageBytes) {
      throw computerError('OUTPUT_LIMIT', 'computer.observe', 'Screenshot exceeds configured byte limit.', { bytes: observation.data.byteLength, maximum: this.config.observe.maxImageBytes });
    }
    return {
      backend: 'wayland',
      mimeType: 'image/png',
      data: observation.data,
      bytes: observation.data.byteLength,
      capturedAt: new Date().toISOString(),
      region,
      pixelWidth: observation.pixelWidth,
      pixelHeight: observation.pixelHeight,
    };
  }

  async act(action: ComputerAction): Promise<ActionResult> {
    if (!this.config.action.enabled) throw computerError('ACTION_DISABLED', 'computer.act', 'Computer actions are disabled.');
    const info = await this.info();
    let native: WaylandNativeAction;

    switch (action.type) {
      case 'move': {
        const resolved = resolvePoint(info, action.point);
        native = { type: 'move', ...resolved };
        break;
      }
      case 'click': native = action.point === undefined ? { type: 'click', button: action.button } : { type: 'click', button: action.button, point: resolvePoint(info, action.point) }; break;
      case 'doubleClick': native = action.point === undefined ? { type: 'doubleClick', button: action.button, intervalMs: action.intervalMs } : { type: 'doubleClick', button: action.button, intervalMs: action.intervalMs, point: resolvePoint(info, action.point) }; break;
      case 'scroll': {
        if (!Number.isInteger(action.deltaX) || !Number.isInteger(action.deltaY) || (action.deltaX === 0 && action.deltaY === 0)) {
          throw computerError('INVALID_INPUT', 'computer.act', 'Scroll deltas must be integers and at least one delta must be non-zero.');
        }
        const total = Math.abs(action.deltaX) + Math.abs(action.deltaY);
        if (total > this.config.action.maxScrollSteps) {
          throw computerError('INVALID_INPUT', 'computer.act', 'Scroll exceeds configured maximum steps.', { requested: total, maximum: this.config.action.maxScrollSteps });
        }
        native = action.point === undefined ? { type: 'scroll', deltaX: action.deltaX, deltaY: action.deltaY } : { type: 'scroll', deltaX: action.deltaX, deltaY: action.deltaY, point: resolvePoint(info, action.point) };
        break;
      }
      case 'drag': native = { type: 'drag', button: action.button, from: resolvePoint(info, action.from), to: resolvePoint(info, action.to) }; break;
      case 'mouseDown': native = action.point === undefined ? { type: 'mouseDown', button: action.button } : { type: 'mouseDown', button: action.button, point: resolvePoint(info, action.point) }; break;
      case 'mouseUp': native = action.point === undefined ? { type: 'mouseUp', button: action.button } : { type: 'mouseUp', button: action.button, point: resolvePoint(info, action.point) }; break;
      case 'key': {
        if (action.keys.length === 0 || action.keys.length > 32 || action.keys.some(key => key.length === 0 || key.length > 128 || key.includes('\0'))) {
          throw computerError('INVALID_INPUT', 'computer.act', 'keys must contain 1-32 non-empty key/chord names of at most 128 characters each.');
        }
        native = { type: 'key', keys: action.keys };
        break;
      }
      case 'text': {
        if (!Number.isInteger(action.delayMs) || action.delayMs < 0 || action.delayMs > 10_000) {
          throw computerError('INVALID_INPUT', 'computer.act', 'delayMs must be an integer between 0 and 10000.');
        }
        const bytes = Buffer.byteLength(action.text, 'utf8');
        if (bytes > this.config.action.maxTextBytes) {
          throw computerError('INVALID_INPUT', 'computer.act', 'Text exceeds configured byte limit.', { bytes, maximum: this.config.action.maxTextBytes });
        }
        native = { type: 'text', text: action.text, delayMs: action.delayMs };
        break;
      }
    }

    await this.helper.act(native);
    return { type: action.type, executed: true, executedAt: new Date().toISOString() };
  }

  async close(): Promise<void> {
    await this.helper.close?.();
  }
}
