import { VizPanel } from '@grafana/scenes';

import { dashboardEditActions } from '../../edit-pane/shared';
import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager';
import { RowItem } from '../layout-rows/RowItem';
import { RowsLayoutManager } from '../layout-rows/RowsLayoutManager';

import { TabItem } from './TabItem';
import { TabsLayoutManager } from './TabsLayoutManager';

let lastUndo: (() => void) | undefined;

jest.mock('../../edit-pane/shared', () => ({
  dashboardEditActions: {
    addElement: jest.fn(({ perform, undo }) => {
      perform();
      lastUndo = undo;
    }),
    removeElement: jest.fn(({ perform, undo }) => {
      perform();
      lastUndo = undo;
    }),
    moveElement: jest.fn(({ perform, undo }) => {
      perform();
      lastUndo = undo;
    }),
  },
  ObjectsReorderedOnCanvasEvent: jest.fn().mockImplementation(() => ({})),
}));

describe('TabsLayoutManager', () => {
  describe('url sync', () => {
    it('when on top level', () => {
      const tabsLayoutManager = new TabsLayoutManager({
        tabs: [new TabItem({ title: 'Performance' })],
      });

      // currentTabSlug is set during rendering so forcing here
      tabsLayoutManager.setState({ currentTabSlug: tabsLayoutManager.getCurrentTab()?.getSlug() });

      const urlState = tabsLayoutManager.getUrlState();
      expect(urlState).toEqual({ dtab: 'performance' });
    });

    it('when nested under row and parent tab', () => {
      const innerMostTabs = new TabsLayoutManager({
        tabs: [new TabItem({ title: 'Performance' })],
      });

      // currentTabSlug is set during rendering so forcing here
      innerMostTabs.setState({ currentTabSlug: innerMostTabs.getCurrentTab()?.getSlug() });

      new RowsLayoutManager({
        rows: [
          new RowItem({
            title: 'Overview',
            layout: new TabsLayoutManager({
              tabs: [
                new TabItem({
                  title: 'Frontend',
                  layout: innerMostTabs,
                }),
              ],
            }),
          }),
        ],
      });

      const urlState = innerMostTabs.getUrlState();
      expect(urlState).toEqual({
        ['overview-frontend-dtab']: 'performance',
      });
    });
  });

  describe('addNewTab', () => {
    beforeEach(() => {
      lastUndo = undefined;
    });

    it('should add a new tab with default title when no title is provided', () => {
      const manager = new TabsLayoutManager({ tabs: [] });
      const newTab = manager.addNewTab();

      expect(newTab).toBeInstanceOf(TabItem);
      expect(newTab.state.title).toBe('New tab');
      expect(manager.state.tabs).toHaveLength(1);
      expect(manager.state.tabs[0]).toBe(newTab);
    });

    it('should add a tab with the provided title if it is unique', () => {
      const manager = new TabsLayoutManager({ tabs: [] });
      const newTab = manager.addNewTab(new TabItem({ title: 'Unique Title' }));

      expect(newTab.state.title).toBe('Unique Title');
      expect(manager.state.tabs).toHaveLength(1);
      expect(manager.state.tabs[0]).toBe(newTab);
    });

    it('should generate a unique title when adding a tab with a duplicate title', () => {
      const manager = new TabsLayoutManager({ tabs: [] });
      const firstTab = manager.addNewTab(new TabItem({ title: 'Test Title' }));
      const secondTab = manager.addNewTab(new TabItem({ title: 'Test Title' }));

      expect(firstTab.state.title).toBe('Test Title');
      expect(secondTab.state.title).toBe('Test Title 1');
      expect(manager.state.tabs).toHaveLength(2);
    });

    it('should increment the number in the title for multiple duplicates', () => {
      const manager = new TabsLayoutManager({ tabs: [] });
      const firstTab = manager.addNewTab(new TabItem({ title: 'Test Title' }));
      const secondTab = manager.addNewTab(new TabItem({ title: 'Test Title' }));
      const thirdTab = manager.addNewTab(new TabItem({ title: 'Test Title' }));

      expect(firstTab.state.title).toBe('Test Title');
      expect(secondTab.state.title).toBe('Test Title 1');
      expect(thirdTab.state.title).toBe('Test Title 2');
      expect(manager.state.tabs).toHaveLength(3);
    });

    it('should handle undo action correctly', () => {
      const manager = new TabsLayoutManager({ tabs: [] });
      manager.addNewTab(new TabItem({ title: 'Test Title' }));

      expect(manager.state.tabs).toHaveLength(1);

      // Use the real undo function from the mock
      expect(typeof lastUndo).toBe('function');
      lastUndo && lastUndo();

      expect(manager.state.tabs).toHaveLength(0);
    });
  });

  describe('removeTab', () => {
    beforeEach(() => {
      lastUndo = undefined;
    });

    it('should remove the current tab using removeElement', () => {
      const manager = new TabsLayoutManager({ tabs: [] });
      const tab1 = manager.addNewTab(new TabItem({ title: 'Tab 1' }));
      const tab2 = manager.addNewTab(new TabItem({ title: 'Tab 2' }));

      expect(manager.state.tabs).toHaveLength(2);
      expect(manager.state.currentTabSlug).toBe(tab2.getSlug()); // tab2 is current

      manager.removeTab(tab2);

      expect(manager.state.tabs).toHaveLength(1);
      expect(manager.state.tabs[0]).toBe(tab1);
      expect(manager.state.currentTabSlug).toBe(tab1.getSlug());
      expect(dashboardEditActions.removeElement).toHaveBeenCalled();
    });

    it('should handle undo action correctly when removing the current tab', () => {
      const manager = new TabsLayoutManager({ tabs: [] });
      const tab1 = manager.addNewTab(new TabItem({ title: 'Tab 1' }));
      const tab2 = manager.addNewTab(new TabItem({ title: 'Tab 2' }));

      expect(manager.state.tabs).toHaveLength(2);
      expect(manager.state.currentTabSlug).toBe(tab2.getSlug()); // tab2 is current

      manager.removeTab(tab2);

      expect(manager.state.tabs).toHaveLength(1);
      expect(manager.state.tabs[0]).toBe(tab1);

      // Use the real undo function from the mock
      expect(typeof lastUndo).toBe('function');
      lastUndo && lastUndo();

      expect(manager.state.tabs).toHaveLength(2);
      expect(manager.state.tabs).toContain(tab1);
      expect(manager.state.tabs).toContain(tab2);
      expect(manager.state.currentTabSlug).toBe(tab2.getSlug()); // tab2 should be current again
    });
  });

  describe('moveTab', () => {
    beforeEach(() => {
      lastUndo = undefined;
      jest.clearAllMocks();
    });

    it('should move a tab to a new position', () => {
      const manager = new TabsLayoutManager({ tabs: [] });
      const tab1 = manager.addNewTab(new TabItem({ title: 'Tab 1' }));
      const tab2 = manager.addNewTab(new TabItem({ title: 'Tab 2' }));
      const tab3 = manager.addNewTab(new TabItem({ title: 'Tab 3' }));

      expect(manager.state.tabs).toEqual([tab1, tab2, tab3]);

      manager.moveTab(0, 2);

      expect(manager.state.tabs).toEqual([tab2, tab3, tab1]);
      expect(dashboardEditActions.moveElement).toHaveBeenCalled();
    });

    it('should handle undo action correctly when moving a tab', () => {
      const manager = new TabsLayoutManager({ tabs: [] });
      const tab1 = manager.addNewTab(new TabItem({ title: 'Tab 1' }));
      const tab2 = manager.addNewTab(new TabItem({ title: 'Tab 2' }));
      const tab3 = manager.addNewTab(new TabItem({ title: 'Tab 3' }));

      expect(manager.state.tabs).toEqual([tab1, tab2, tab3]);

      manager.moveTab(0, 2);

      expect(manager.state.tabs).toEqual([tab2, tab3, tab1]);

      // Use the real undo function from the mock
      expect(typeof lastUndo).toBe('function');
      lastUndo && lastUndo();

      expect(manager.state.tabs).toEqual([tab1, tab2, tab3]);
    });

    it('should update currentTabIndex when moving the current tab', () => {
      const manager = new TabsLayoutManager({ tabs: [] });
      const tab1 = manager.addNewTab(new TabItem({ title: 'Tab 1' }));
      const tab2 = manager.addNewTab(new TabItem({ title: 'Tab 2' }));
      const tab3 = manager.addNewTab(new TabItem({ title: 'Tab 3' }));

      // Set tab2 as current
      manager.setState({ currentTabSlug: tab2.getSlug() });

      manager.moveTab(1, 0);

      expect(manager.state.tabs).toEqual([tab2, tab1, tab3]);
      expect(manager.state.currentTabSlug).toBe(tab2.getSlug());

      // Undo should restore the original state
      lastUndo && lastUndo();

      expect(manager.state.tabs).toEqual([tab1, tab2, tab3]);
      expect(manager.state.currentTabSlug).toBe(tab2.getSlug());
    });
  });

  describe('getVizPanels', () => {
    it('Should not included repeated tabs', () => {
      const manager = new TabsLayoutManager({
        tabs: [
          new TabItem({
            title: 'Tab 1',
            layout: DefaultGridLayoutManager.fromVizPanels([new VizPanel({ key: 'panel-1' })]),
            repeatedTabs: [
              new TabItem({
                title: 'Tab 1 - Copy 1',
                layout: DefaultGridLayoutManager.fromVizPanels([new VizPanel({ key: 'panel-1' })]),
              }),
              new TabItem({
                title: 'Tab 1 - Copy 2',
                layout: DefaultGridLayoutManager.fromVizPanels([new VizPanel({ key: 'panel-1' })]),
              }),
            ],
          }),
        ],
      });
      expect(manager.getVizPanels().length).toBe(1);
    });
  });

  describe('createFromLayout', () => {
    it('should convert rows with titles to tabs', () => {
      const rowsLayout = new RowsLayoutManager({
        rows: [new RowItem({ title: 'Row 1' }), new RowItem({ title: 'Row 2' })],
      });

      const tabsManager = TabsLayoutManager.createFromLayout(rowsLayout);

      expect(tabsManager.state.tabs).toHaveLength(2);
      expect(tabsManager.state.tabs[0].state.title).toBe('Row 1');
      expect(tabsManager.state.tabs[1].state.title).toBe('Row 2');
    });

    it('should use default title when row has empty title', () => {
      const rowsLayout = new RowsLayoutManager({
        rows: [new RowItem({ title: '' })],
      });

      const tabsManager = TabsLayoutManager.createFromLayout(rowsLayout);

      expect(tabsManager.state.tabs).toHaveLength(1);
      expect(tabsManager.state.tabs[0].state.title).toBe('New tab');
    });

    it('should generate unique titles for multiple rows with empty titles', () => {
      const rowsLayout = new RowsLayoutManager({
        rows: [new RowItem({ title: '' }), new RowItem({ title: '' }), new RowItem({ title: '' })],
      });

      const tabsManager = TabsLayoutManager.createFromLayout(rowsLayout);

      expect(tabsManager.state.tabs).toHaveLength(3);
      expect(tabsManager.state.tabs[0].state.title).toBe('New tab');
      expect(tabsManager.state.tabs[1].state.title).toBe('New tab 1');
      expect(tabsManager.state.tabs[2].state.title).toBe('New tab 2');
    });

    it('should generate unique titles when mixing empty and existing titles', () => {
      const rowsLayout = new RowsLayoutManager({
        rows: [
          new RowItem({ title: 'New row' }), // existing title that matches default
          new RowItem({ title: '' }), // empty, should get unique title
        ],
      });

      const tabsManager = TabsLayoutManager.createFromLayout(rowsLayout);

      expect(tabsManager.state.tabs).toHaveLength(2);
      expect(tabsManager.state.tabs[0].state.title).toBe('New row');
      expect(tabsManager.state.tabs[1].state.title).toBe('New tab');
    });
  });
});
