import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';

const here = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(here, '..', '..', '..', '..');

void test('SceneOverlayHost: один dim для materials + npcs в Control и Presentation', () => {
  const host = fs.readFileSync(path.join(here, 'SceneOverlayHost.tsx'), 'utf8');
  const control = fs.readFileSync(path.join(root, 'app/renderer/control/ControlApp.tsx'), 'utf8');
  const presentation = fs.readFileSync(path.join(root, 'app/renderer/shared/PresentationView.tsx'), 'utf8');
  const css = fs.readFileSync(
    path.join(root, 'app/renderer/shared/materials/MaterialOverlay.module.css'),
    'utf8',
  );

  assert.ok(host.includes('styles.dim'));
  assert.ok(host.includes('hostHitThrough'));
  // Dim только при active; жёлтая рамка без затемнения.
  assert.match(host, /\{active \? <div className=\{styles\.dim\}/);
  assert.ok(host.includes('showViewportGuide'));
  assert.ok(control.includes('SceneOverlayHost'));
  assert.ok(control.includes('embedded'));
  assert.ok(presentation.includes('SceneOverlayHost'));
  assert.ok(presentation.includes('embedded'));

  // Control/Presentation монтируют оверлеи только как embedded внутри host.
  assert.ok(control.includes('<MaterialOverlay'));
  assert.ok(control.includes('<NpcsSceneOverlay'));
  assert.ok(control.includes('embedded'));
  assert.ok(presentation.includes('embedded'));
  assert.match(css, /\.hostHitThrough\s*\{[^}]*pointer-events:\s*none/s);
  assert.match(css, /\.dim\s*\{[^}]*inset:\s*0/s);
});

void test('MaterialOverlay / NpcsSceneOverlay поддерживают embedded без собственного dim', () => {
  const material = fs.readFileSync(
    path.join(root, 'app/renderer/shared/materials/MaterialOverlay.tsx'),
    'utf8',
  );
  const npcs = fs.readFileSync(path.join(root, 'app/renderer/shared/npcs/NpcsSceneOverlay.tsx'), 'utf8');
  assert.ok(material.includes('embedded'));
  assert.ok(material.includes('useSceneOverlayView'));
  assert.ok(npcs.includes('embedded'));
  assert.ok(npcs.includes('useSceneOverlayView'));
  // В embedded-ветке не рисуем второй dim.
  assert.match(material, /if \(embedded\) \{\s*return frame;/);
  assert.match(npcs, /if \(embedded\) \{\s*return <>\{frames\}<\/>;/);
});

void test('overlays: layout IPC live через rAF coalesce, RO host coalesced', () => {
  const host = fs.readFileSync(path.join(here, 'SceneOverlayHost.tsx'), 'utf8');
  const material = fs.readFileSync(
    path.join(root, 'app/renderer/shared/materials/MaterialOverlay.tsx'),
    'utf8',
  );
  const npcs = fs.readFileSync(path.join(root, 'app/renderer/shared/npcs/NpcsSceneOverlay.tsx'), 'utf8');

  assert.ok(host.includes('requestAnimationFrame'));
  assert.ok(host.includes('prev.w === w && prev.h === h'));

  // Лайв: publishDraft шлёт onLayoutChange внутри rAF (не на каждый pointermove).
  assert.ok(material.includes('publishDraft'));
  assert.ok(material.includes('onLayoutChangeRef'));
  assert.match(material, /requestAnimationFrame\(\(\) => \{[\s\S]*?onLayoutChangeRef\.current\?\.\(pending\)/);
  assert.match(
    material,
    /if \(drag\.mode === 'move'\) \{[\s\S]*?publishDraft\(\{[\s\S]*?return;\s*\}/,
  );

  assert.ok(npcs.includes('publishDraft'));
  assert.ok(npcs.includes('onLayoutChangeRef'));
  assert.match(
    npcs,
    /requestAnimationFrame\(\(\) => \{[\s\S]*?onLayoutChangeRef\.current\?\.\(item\.npcId, pending\)/,
  );
  assert.match(
    npcs,
    /if \(drag\.mode === 'move'\) \{[\s\S]*?publishDraft\(\{[\s\S]*?return;\s*\}/,
  );
});
