import {
  AsukaRendererError,
  type AsukaRenderEngine,
  type AsukaRenderEngineKind,
} from './renderEngine';

export interface AsukaRendererContractIssue {
  path: string;
  message: string;
}

export interface AsukaRendererContractReport {
  ok: boolean;
  kind: AsukaRenderEngineKind | null;
  issues: AsukaRendererContractIssue[];
}

const REQUIRED_SERVICES: Record<string, readonly string[]> = {
  lifecycle: ['mount', 'unmount', 'activateView', 'deactivateView', 'loadView', 'exportView'],
  events: ['subscribe'],
  history: ['getInfo', 'commit', 'exportTrack', 'importTrack', 'undo', 'redo', 'reset', 'goTo'],
  scene: ['getGraph'],
  objects: [
    'list', 'get', 'addImage', 'addText', 'addDesign', 'addElement', 'removeElement',
    'remove', 'update', 'reorder', 'duplicate', 'setVisible', 'setLocked', 'bringForward',
    'sendBackward', 'getTransform', 'updateTransform', 'getBounds', 'measure', 'preview', 'clearPreview',
  ],
  selection: ['list', 'getAnchorBox', 'select'],
  placement: ['getStatus', 'begin', 'update', 'commit', 'cancel', 'clear'],
  viewport: ['zoomIn', 'zoomOut', 'fit'],
  exports: [
    'isFormatAvailable', 'canLoadSupport', 'ensureSupport', 'download', 'downloadAllViews',
    'downloadAllAreas', 'downloadAllObjects', 'png',
  ],
};

const CAPABILITY_SERVICES: Record<string, { service: string; methods: readonly string[] }> = {
  canvas2d: { service: 'canvas2d', methods: ['getSelectedText', 'updateSelectedText', 'updateSelectedTextCurve', 'setSelectedTextCurved', 'getColorSelection', 'updateColorSelection', 'addShape', 'createAreaDraft', 'startAreaDraft', 'clear', 'enterTextEditing', 'getSnapping', 'setSnapping', 'subscribeTextEditRequests', 'subscribeInteractions', 'attachMagnifier'] },
  meshes: { service: 'models', methods: ['listMeshes', 'inspectParts', 'preload'] },
  materials: { service: 'materials', methods: ['list', 'select', 'update'] },
  parts: { service: 'parts', methods: ['list', 'get', 'select', 'getSelected', 'listVariations', 'getActiveVariation', 'applyVariation', 'focus'] },
  parametricProducts: { service: 'products', methods: ['list', 'get', 'add', 'remove', 'select', 'update', 'syncCatalog', 'getParameters', 'validateParameters', 'setParameter', 'setParameters'] },
  assembly3d: { service: 'assembly', methods: ['syncCatalog', 'getState', 'configure', 'listComponents', 'getComponent', 'previewPlacement', 'place', 'autoPlace', 'connect', 'disconnect', 'validate', 'getBom', 'getQuote', 'export'] },
  printAreas: { service: 'areas', methods: ['list', 'get', 'pickForAdd', 'create', 'update', 'remove', 'select', 'getActive', 'focus', 'getBounds'] },
  surfaceConstraints: { service: 'areas', methods: ['contains', 'constrain', 'moveObject', 'transformObject'] },
  surfacePicking: { service: 'surface', methods: ['pick', 'getInteractionDebug'] },
};

const inspectMethods = (
  owner: Record<string, unknown>,
  service: string,
  methods: readonly string[],
  issues: AsukaRendererContractIssue[],
): void => {
  const candidate = owner[service] as Record<string, unknown> | null | undefined;
  if (!candidate || typeof candidate !== 'object') {
    issues.push({ path: service, message: `Missing ${service} service.` });
    return;
  }
  for (const method of methods) {
    if (typeof candidate[method] !== 'function') {
      issues.push({ path: `${service}.${method}`, message: `Missing ${service}.${method}().` });
    }
  }
};

export function inspectAsukaRendererContract(value: unknown): AsukaRendererContractReport {
  const engine = value as Record<string, unknown> | null;
  const issues: AsukaRendererContractIssue[] = [];
  const kind = engine?.kind === 'fabric' || engine?.kind === '3d' ? engine.kind : null;

  if (!engine || typeof engine !== 'object') {
    return { ok: false, kind: null, issues: [{ path: '', message: 'Renderer must be an object.' }] };
  }
  if (!kind) issues.push({ path: 'kind', message: 'Renderer kind must be fabric or 3d.' });
  if (!engine.capabilities || typeof engine.capabilities !== 'object') {
    issues.push({ path: 'capabilities', message: 'Missing capabilities object.' });
  }

  for (const [service, methods] of Object.entries(REQUIRED_SERVICES)) {
    inspectMethods(engine, service, methods, issues);
  }

  const capabilities = engine.capabilities as Record<string, unknown> | null;
  for (const [capability, requirement] of Object.entries(CAPABILITY_SERVICES)) {
    if (capabilities?.[capability] === true) {
      inspectMethods(engine, requirement.service, requirement.methods, issues);
    }
  }

  return { ok: issues.length === 0, kind, issues };
}

export function assertAsukaRendererContract(value: unknown): asserts value is AsukaRenderEngine {
  const report = inspectAsukaRendererContract(value);
  if (report.ok) return;
  throw new AsukaRendererError(
    'RENDERER_CONTRACT_VIOLATION',
    `Renderer contract violation: ${report.issues.map((issue) => issue.path || issue.message).join(', ')}.`,
    {
      engine: report.kind ?? undefined,
      operation: 'renderer.register',
      details: { issues: report.issues },
    },
  );
}
