// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========

import { PreviewPanel } from '@/components/Session/PreviewPanel';
import {
  registerPreviewWebview,
  unregisterPreviewWebview,
} from '@/components/Session/PreviewPanel/tabs/browser/webviewRegistry';
import { HostProvider } from '@/host';
import { getSessionPreviewSlice, usePageTabStore } from '@/store/pageTabStore';
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { beforeEach, describe, expect, it, vi } from 'vitest';

vi.mock('@/components/Folder/FilePreview', () => ({
  FilePreview: ({ file }: { file: FileInfo | null }) => (
    <div data-testid="file-preview">{file?.name || 'No file selected'}</div>
  ),
}));

// React Flow needs layout APIs jsdom lacks; the canvas tab is exercised
// elsewhere, so stub it to keep this suite focused on the router/tab strip.
vi.mock('@/components/Session/PreviewPanel/tabs/CanvasTab', () => ({
  CanvasTab: () => <div data-testid="canvas-tab" />,
}));

// xterm needs real layout/canvas APIs; the terminal tab has its own suite.
vi.mock('@/components/Session/PreviewPanel/tabs/terminal/TerminalTab', () => ({
  TerminalTab: () => <div data-testid="terminal-tab" />,
}));

// Monaco and the live project stores are covered by the ReviewTab suite.
vi.mock('@/components/Session/PreviewPanel/tabs/ReviewTab', () => ({
  ReviewTab: () => <div data-testid="review-tab" />,
}));

// The chooser lists the project's agent terminal streams via this hook; keep
// the suite off the chat-store dependency chain and drive it with fixtures.
let mockTerminalSources: Array<{
  id: string;
  agentName: string;
  taskLabel: string;
  lines: string[];
  status: 'running' | 'idle';
}> = [];
vi.mock(
  '@/components/Session/PreviewPanel/tabs/terminal/useSessionTerminalSources',
  () => ({
    useSessionTerminalSources: () => mockTerminalSources,
  })
);

// The desktop host is detected by electronAPI presence; embedded browsing
// itself is <webview>-tag based and driven through the webview registry.
const openExternal = vi.fn();
const host = { ipcRenderer: null, electronAPI: { openExternal } };

function renderPanel() {
  return render(
    <HostProvider host={host}>
      <PreviewPanel />
    </HostProvider>
  );
}

function previewSlice() {
  return getSessionPreviewSlice(usePageTabStore.getState());
}

function activeType() {
  const slice = previewSlice();
  return slice.tabs.find((tab) => tab.id === slice.activeTabId)?.type;
}

describe('PreviewPanel', () => {
  beforeEach(() => {
    mockTerminalSources = [];
    openExternal.mockReset();
    openExternal.mockResolvedValue({ success: true });
    usePageTabStore.setState({
      sessionPreviewProjectId: null,
      sessionPreviewByProject: {},
      previewBrowserViewport: null,
    });
    usePageTabStore.getState().setSessionPreviewProject('project-test');
    usePageTabStore.getState().toggleSessionPreview();
  });

  it('opens on the chooser tab listing the available content kinds', () => {
    renderPanel();
    expect(screen.getByRole('tab', { name: 'New tab' })).toBeInTheDocument();
    // Vertical options use the same product copy users see in the chooser.
    for (const label of ['Browser', 'Files', 'Review', 'Terminal']) {
      expect(
        screen.getByRole('button', {
          name: new RegExp(`^${label}\\b`),
        })
      ).toBeInTheDocument();
    }
    // Reserved kinds stay hidden from the chooser until a later version.
    for (const label of ['Canvas']) {
      expect(
        screen.queryByRole('button', {
          name: new RegExp(`^${label}\\b`),
        })
      ).not.toBeInTheDocument();
    }
  });

  it('lists the project’s agent streams in the chooser and opens one in place', async () => {
    const user = userEvent.setup();
    mockTerminalSources = [
      {
        id: 'chat-1:turn-1:sub-1',
        agentName: 'Developer Agent',
        taskLabel: 'Start dev server',
        lines: ['npm run dev'],
        status: 'running',
      },
    ];
    renderPanel();

    expect(screen.getByText('From this project')).toBeInTheDocument();
    await user.click(screen.getByRole('button', { name: /Start dev server/ }));

    const slice = previewSlice();
    const active = slice.tabs.find((tab) => tab.id === slice.activeTabId);
    expect(active).toMatchObject({
      type: 'terminal',
      title: 'Developer Agent',
      agentSourceId: 'chat-1:turn-1:sub-1',
    });
    // The chooser was converted in place, not left behind.
    expect(slice.tabs).toHaveLength(1);
    expect(screen.getByTestId('terminal-tab')).toBeInTheDocument();
  });

  it('picking a chooser option turns the tab into that content kind', async () => {
    const user = userEvent.setup();
    renderPanel();

    await user.click(screen.getByRole('button', { name: /^Browser\b/ }));
    expect(activeType()).toBe('browser');
    // Address bar of the browser tab is now shown.
    expect(
      screen.getByRole('textbox', { name: 'Enter a URL' })
    ).toBeInTheDocument();
  });

  it('routes to the file tab and reuses its tab by path', () => {
    const file = { name: 'notes.md', path: '/tmp/notes.md' } as FileInfo;
    usePageTabStore.getState().openFilePreview(file);
    usePageTabStore.getState().openFilePreview({ ...file });
    renderPanel();

    expect(screen.getByTestId('file-preview')).toHaveTextContent('notes.md');
    expect(screen.getAllByRole('tab', { name: 'notes.md' })).toHaveLength(1);
  });

  it('routes review, terminal, and canvas tabs to their surfaces', () => {
    const store = usePageTabStore.getState();
    const chooserId = previewSlice().tabs[0].id;
    act(() => store.choosePreviewTabType(chooserId, 'review'));
    const { rerender } = renderPanel();
    expect(screen.getByTestId('review-tab')).toBeInTheDocument();

    act(() =>
      store.choosePreviewTabType(previewSlice().activeTabId!, 'terminal')
    );
    rerender(
      <HostProvider host={host}>
        <PreviewPanel />
      </HostProvider>
    );
    expect(screen.getByTestId('terminal-tab')).toBeInTheDocument();

    act(() =>
      store.choosePreviewTabType(previewSlice().activeTabId!, 'canvas')
    );
    rerender(
      <HostProvider host={host}>
        <PreviewPanel />
      </HostProvider>
    );
    expect(screen.getByTestId('canvas-tab')).toBeInTheDocument();
  });

  it('the + button adds a new chooser tab', async () => {
    const user = userEvent.setup();
    renderPanel();

    await user.click(screen.getByRole('button', { name: 'New tab' }));
    expect(screen.getAllByRole('tab', { name: 'New tab' })).toHaveLength(2);
    expect(activeType()).toBe('chooser');
  });

  it('drives back/forward/reload on the registered guest element', async () => {
    const user = userEvent.setup();
    const store = usePageTabStore.getState();
    const chooserId = previewSlice().tabs[0].id;
    act(() => store.choosePreviewTabType(chooserId, 'browser'));
    const browserTab = previewSlice().tabs.find(
      (tab) => tab.type === 'browser'
    )!;
    act(() =>
      store.updateBrowserPreviewTab(browserTab.id, {
        url: 'https://example.com/a',
        navigation: {
          url: 'https://example.com/a',
          title: 'A',
          isLoading: false,
          canGoBack: true,
          canGoForward: true,
        },
      })
    );
    const goBack = vi.fn();
    const goForward = vi.fn();
    const reload = vi.fn();
    registerPreviewWebview(browserTab.webviewId, {
      goBack,
      goForward,
      reload,
    } as unknown as HTMLElement & { goBack: typeof goBack });

    try {
      renderPanel();
      await user.click(screen.getByRole('button', { name: 'Back' }));
      await user.click(screen.getByRole('button', { name: 'Forward' }));
      await user.click(screen.getByRole('button', { name: 'Reload' }));

      expect(goBack).toHaveBeenCalled();
      expect(goForward).toHaveBeenCalled();
      expect(reload).toHaveBeenCalled();
    } finally {
      unregisterPreviewWebview(browserTab.webviewId);
    }
  });

  it('opens desktop external links through the Electron external IPC', async () => {
    const user = userEvent.setup();
    const store = usePageTabStore.getState();
    const chooserId = previewSlice().tabs[0].id;
    act(() => store.choosePreviewTabType(chooserId, 'browser'));
    const browserTab = previewSlice().tabs.find(
      (tab) => tab.type === 'browser'
    )!;
    act(() =>
      store.updateBrowserPreviewTab(browserTab.id, {
        url: 'http://localhost:3000/',
      })
    );

    renderPanel();
    await user.click(screen.getByRole('button', { name: 'Open externally' }));

    expect(openExternal).toHaveBeenCalledWith('http://localhost:3000/');
  });

  it('closing the final tab closes the panel', async () => {
    const user = userEvent.setup();
    renderPanel();

    await user.click(screen.getByRole('button', { name: 'Close tab' }));

    expect(previewSlice()).toMatchObject({
      open: false,
      tabs: [],
      activeTabId: null,
    });
  });
});
