import { test, expect, invokeInRenderer, stubOpenImageDialog } from './fixtures/electron';

type Project = {
  id: string;
  currentSceneId: string | null;
  scenes: Record<
    string,
    {
      tokens: unknown[];
      npcTokens: Array<{ id: string; npcId: string; nx: number; ny: number; sizeN: number }>;
    }
  >;
  npcs: Array<{ id: string; name: string }>;
};

async function seedProjectWithNpcAndPlacement(
  page: import('@playwright/test').Page,
  sampleImagePath: string,
) {
  await invokeInRenderer<{ project: Project }>(page, 'project.create', {
    name: `E2E Scene ${Date.now()}`,
  });

  const sceneId = `scene_e2e_${Date.now()}`;
  await invokeInRenderer(page, 'project.updateScene', {
    sceneId,
    patch: {
      title: 'Map',
      description: '',
      media: { videos: [], audios: [] },
      settings: { autoplayVideo: false, autoplayAudio: false, loopVideo: false, loopAudio: false },
      layout: { x: 0, y: 0 },
      previewAssetId: null,
      previewAssetType: null,
      previewVideoAutostart: false,
      npcTokens: [],
      tokens: [],
      traps: [],
    },
  });
  await invokeInRenderer(page, 'project.setCurrentScene', { sceneId });
  await invokeInRenderer(page, 'project.importScenePreview', {
    sceneId,
    filePath: sampleImagePath,
  });

  const npcUpsert = await invokeInRenderer<{ project: Project }>(page, 'project.upsertNpc', {
    name: `Guard ${Date.now()}`,
    filePath: sampleImagePath,
    disposition: 'neutral',
    imageOffset: { x: 0, y: 0 },
  });
  const npc = npcUpsert.project.npcs[npcUpsert.project.npcs.length - 1]!;
  const placementId = `snt_e2e_${Date.now()}`;

  await invokeInRenderer(page, 'project.updateScene', {
    sceneId,
    patch: {
      npcTokens: [
        {
          id: placementId,
          npcId: npc.id,
          nx: 0.35,
          ny: 0.4,
          sizeN: 0.12,
          disposition: 'neutral',
        },
      ],
    },
  });

  const refreshed = await invokeInRenderer<{ project: Project }>(page, 'project.get', {});
  return {
    sceneId,
    npcId: npc.id,
    placementId,
    project: refreshed.project,
  };
}

test.describe('Scene NPC tokens', () => {
  test('scene editor shows circular NPC token; clear removes both kinds', async ({
    electronApp,
    editorWindow,
    sampleImagePath,
  }) => {
    await stubOpenImageDialog(electronApp, sampleImagePath);
    const seeded = await seedProjectWithNpcAndPlacement(editorWindow, sampleImagePath);

    const token = await invokeInRenderer<{ token: { id: string } }>(editorWindow, 'tokens.upsert', {
      name: `Prop ${Date.now()}`,
      filePath: sampleImagePath,
    });
    await invokeInRenderer(editorWindow, 'project.updateScene', {
      sceneId: seeded.sceneId,
      patch: {
        tokens: [
          {
            id: `st_${Date.now()}`,
            tokenId: token.token.id,
            nx: 0.7,
            ny: 0.7,
            sizeN: 0.1,
            rotationDeg: 0,
          },
        ],
        npcTokens: [
          {
            id: seeded.placementId,
            npcId: seeded.npcId,
            nx: 0.35,
            ny: 0.4,
            sizeN: 0.12,
          },
        ],
      },
    });

    const winPromise = electronApp.waitForEvent('window');
    await invokeInRenderer(editorWindow, 'windows.openSceneEditor', {});
    const sceneWin = await winPromise;
    await sceneWin.waitForLoadState('domcontentloaded');

    const accordion = sceneWin.getByTestId('scene-npc-accordion');
    await expect(accordion).toBeVisible({ timeout: 30_000 });
    // Expand if collapsed
    await accordion.locator('button').first().click().catch(() => undefined);
    await expect(sceneWin.getByTestId(`scene-npc-tile-${seeded.npcId}`)).toBeVisible({
      timeout: 15_000,
    });
    await expect(sceneWin.getByTestId(`scene-npc-token-${seeded.placementId}`)).toBeVisible();

    await sceneWin.getByTestId('scene-clear-btn').click();
    await expect(sceneWin.getByTestId(`scene-npc-token-${seeded.placementId}`)).toHaveCount(0);

    await expect
      .poll(
        async () => {
          const after = await invokeInRenderer<{ project: Project }>(editorWindow, 'project.get', {});
          const scene = after.project.scenes[seeded.sceneId]!;
          return { tokens: scene.tokens.length, npcTokens: scene.npcTokens.length };
        },
        { timeout: 10_000 },
      )
      .toEqual({ tokens: 0, npcTokens: 0 });

    await invokeInRenderer(editorWindow, 'windows.closeSceneEditor', {}).catch(() => undefined);
  });

  test('session move resets after re-open multiwindow', async ({
    editorWindow,
    sampleImagePath,
    electronApp,
  }) => {
    await stubOpenImageDialog(electronApp, sampleImagePath);
    const seeded = await seedProjectWithNpcAndPlacement(editorWindow, sampleImagePath);

    // Need a graph start node for session; open multiwindow may still work with currentSceneId.
    await invokeInRenderer(editorWindow, 'project.addSceneGraphNode', {
      sceneId: seeded.sceneId,
      x: 0,
      y: 0,
    });
    const withNode = await invokeInRenderer<{ project: { sceneGraphNodes: Array<{ id: string }> } }>(
      editorWindow,
      'project.get',
      {},
    );
    const nodeId = withNode.project.sceneGraphNodes[0]?.id;
    if (nodeId) {
      await invokeInRenderer(editorWindow, 'project.setSceneGraphNodeStart', {
        graphNodeId: nodeId,
      });
    }

    await invokeInRenderer(editorWindow, 'windows.openMultiWindow', {});
    await editorWindow.waitForTimeout(1000);

    await invokeInRenderer(editorWindow, 'sceneNpcTokensSession.dispatch', {
      event: { kind: 'move', placementId: seeded.placementId, nx: 0.8, ny: 0.8 },
    });

    let session = await invokeInRenderer<{
      state: { byPlacementId: Record<string, { nx: number; ny: number }> };
    }>(editorWindow, 'sceneNpcTokensSession.getState', {});
    expect(session.state.byPlacementId[seeded.placementId]?.nx).toBeCloseTo(0.8, 5);

    await invokeInRenderer(editorWindow, 'windows.closeMultiWindow', {});
    await editorWindow.waitForTimeout(500);

    await invokeInRenderer(editorWindow, 'windows.openMultiWindow', {});
    await editorWindow.waitForTimeout(500);
    session = await invokeInRenderer(editorWindow, 'sceneNpcTokensSession.getState', {});
    expect(session.state.byPlacementId[seeded.placementId]).toBeUndefined();

    const proj = await invokeInRenderer<{ project: Project }>(editorWindow, 'project.get', {});
    const placement = proj.project.scenes[seeded.sceneId]!.npcTokens.find(
      (t) => t.id === seeded.placementId,
    );
    expect(placement?.nx).toBeCloseTo(0.35, 5);
    expect(placement?.ny).toBeCloseTo(0.4, 5);

    await invokeInRenderer(editorWindow, 'windows.closeMultiWindow', {}).catch(() => undefined);
  });
});
