import { computerError } from './errors.js';
import type { ComputerInfo, CoordinatePoint, CoordinateRegion, DisplayInfo, ObserveTarget, Rect } from './types.js';

function finiteInteger(value: number): boolean {
  return Number.isFinite(value) && Number.isInteger(value);
}

function requireRect(rect: Rect, operation: string): void {
  if (![rect.x, rect.y, rect.width, rect.height].every(finiteInteger) || rect.width <= 0 || rect.height <= 0) {
    throw computerError('INVALID_INPUT', operation, 'Rectangle values must be finite integers and dimensions must be positive.', { rect });
  }
}

export function findDisplay(info: ComputerInfo, displayId: string, operation: string): DisplayInfo {
  const display = info.displays.find(candidate => candidate.id === displayId);
  if (display === undefined) {
    throw computerError('DISPLAY_NOT_FOUND', operation, `Display not found: ${displayId}`, { displayId });
  }
  return display;
}

export function containsPoint(rect: Rect, x: number, y: number): boolean {
  return x >= rect.x && y >= rect.y && x < rect.x + rect.width && y < rect.y + rect.height;
}

export function containsRect(outer: Rect, inner: Rect): boolean {
  return inner.x >= outer.x && inner.y >= outer.y
    && inner.x + inner.width <= outer.x + outer.width
    && inner.y + inner.height <= outer.y + outer.height;
}

export function resolvePoint(info: ComputerInfo, point: CoordinatePoint, operation = 'computer.act'): { x: number; y: number } {
  if (!finiteInteger(point.x) || !finiteInteger(point.y)) {
    throw computerError('INVALID_INPUT', operation, 'Coordinates must be finite integers.');
  }

  if (point.space === 'desktop') {
    if (!containsPoint(info.virtualBounds, point.x, point.y)) {
      throw computerError('OUT_OF_BOUNDS', operation, 'Desktop coordinate is outside the virtual desktop.', { point, virtualBounds: info.virtualBounds });
    }
    return { x: point.x, y: point.y };
  }

  const display = findDisplay(info, point.displayId, operation);
  if (point.x < 0 || point.y < 0 || point.x >= display.width || point.y >= display.height) {
    throw computerError('OUT_OF_BOUNDS', operation, 'Display-relative coordinate is outside the selected display.', { point, display });
  }
  return { x: display.x + point.x, y: display.y + point.y };
}

export function resolveRegion(info: ComputerInfo, target: ObserveTarget, operation = 'computer.observe'): Rect {
  if (target.kind === 'desktop') return { ...info.virtualBounds };
  if (target.kind === 'display') {
    const display = findDisplay(info, target.displayId, operation);
    return { x: display.x, y: display.y, width: display.width, height: display.height };
  }

  const region: CoordinateRegion = target.region;
  requireRect(region, operation);
  if (region.space === 'desktop') {
    const resolved = { x: region.x, y: region.y, width: region.width, height: region.height };
    if (!containsRect(info.virtualBounds, resolved)) {
      throw computerError('OUT_OF_BOUNDS', operation, 'Capture region is outside the virtual desktop.', { region, virtualBounds: info.virtualBounds });
    }
    return resolved;
  }

  const display = findDisplay(info, region.displayId, operation);
  const relative = { x: region.x, y: region.y, width: region.width, height: region.height };
  if (!containsRect({ x: 0, y: 0, width: display.width, height: display.height }, relative)) {
    throw computerError('OUT_OF_BOUNDS', operation, 'Display-relative capture region is outside the selected display.', { region, display });
  }
  return { x: display.x + region.x, y: display.y + region.y, width: region.width, height: region.height };
}
