import { locationUtil, SetPanelAttentionEvent, LegacyGraphHoverClearEvent, dateTime } from '@grafana/data';
import { config, locationService } from '@grafana/runtime';
import { behaviors, sceneGraph, type VizPanel } from '@grafana/scenes';
import { appEvents } from 'app/core/app_events';
import { KeybindingSet } from 'app/core/services/KeybindingSet';
import { contextSrv } from 'app/core/services/context_srv';
import { InspectTab } from 'app/features/inspector/types';
import { AccessControlAction } from 'app/types/accessControl';

import { shareDashboardType } from '../../dashboard/components/ShareModal/utils';
import { PanelInspectDrawer } from '../inspect/PanelInspectDrawer';
import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { DashboardInteractions } from '../utils/interactions';
import { findVizPanelByPathId } from '../utils/pathId';
import { getEditPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders';
import { getPanelIdForVizPanel } from '../utils/utils';

import { DashboardScene } from './DashboardScene';
import { onRemovePanel, toggleVizPanelLegend } from './PanelMenuBehavior';
import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
import { RowsLayoutManager } from './layout-rows/RowsLayoutManager';

export function setupKeyboardShortcuts(scene: DashboardScene) {
  const keybindings = new KeybindingSet();
  let vizPanelPathId: string | null = null;

  const canEdit = scene.canEditDashboard();
  const canSave = Boolean(scene.state.meta.canSave);

  const panelAttentionSubscription = appEvents.subscribe(SetPanelAttentionEvent, (event) => {
    if (typeof event.payload.panelId === 'string') {
      vizPanelPathId = event.payload.panelId;
    }
  });

  function withFocusedPanel(scene: DashboardScene, fn: (vizPanel: VizPanel) => void) {
    return () => {
      if (vizPanelPathId == null) {
        return;
      }

      const vizPanel = findVizPanelByPathId(scene, vizPanelPathId);
      if (vizPanel) {
        fn(vizPanel);
        return;
      }
    };
  }

  // View panel
  keybindings.addBinding({
    key: 'v',
    onTrigger: withFocusedPanel(scene, (vizPanel: VizPanel) => {
      const panelId = getPanelIdForVizPanel(vizPanel);
      DashboardInteractions.panelActionClicked('view', panelId, 'keyboard');
      if (scene.state.viewPanel) {
        locationService.partial({ viewPanel: undefined });
      } else {
        locationService.partial({ viewPanel: vizPanel.getPathId(), editPanel: undefined });
      }
    }),
  });

  // Panel share
  keybindings.addBinding({
    key: 'p u',
    onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
      const drawer = new ShareDrawer({
        shareView: shareDashboardType.link,
        panelRef: vizPanel.getRef(),
      });

      scene.showModal(drawer);
    }),
  });
  keybindings.addBinding({
    key: 'p e',
    onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
      const drawer = new ShareDrawer({
        shareView: shareDashboardType.embed,
        panelRef: vizPanel.getRef(),
      });

      scene.showModal(drawer);
    }),
  });

  if (
    contextSrv.isSignedIn &&
    config.snapshotEnabled &&
    contextSrv.hasPermission(AccessControlAction.SnapshotsCreate)
  ) {
    keybindings.addBinding({
      key: 'p s',
      onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
        const drawer = new ShareDrawer({
          shareView: shareDashboardType.snapshot,
          panelRef: vizPanel.getRef(),
        });

        scene.showModal(drawer);
      }),
    });
  }

  // Panel inspect
  keybindings.addBinding({
    key: 'i',
    onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
      scene.showModal(new PanelInspectDrawer({ panelRef: vizPanel.getRef(), currentTab: InspectTab.Data }));
    }),
  });

  // Got to Explore for panel
  keybindings.addBinding({
    key: 'p x',
    onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
      const url = await tryGetExploreUrlForPanel(vizPanel);
      if (url) {
        locationService.push(url);
      }
    }),
  });

  // Toggle legend
  keybindings.addBinding({
    key: 'p l',
    onTrigger: withFocusedPanel(scene, toggleVizPanelLegend),
  });

  // Refresh
  keybindings.addBinding({
    key: 'd r',
    onTrigger: () => sceneGraph.getTimeRange(scene).onRefresh(),
  });

  if (config.featureToggles.newTimeRangeZoomShortcuts) {
    keybindings.addBinding({
      key: 't +',
      onTrigger: () => {
        handleZoom(scene, 0.5);
      },
    });

    keybindings.addBinding({
      key: 't =',
      onTrigger: () => {
        handleZoom(scene, 0.5);
      },
    });

    keybindings.addBinding({
      key: 't -',
      type: 'keypress', // NOTE: Because some browsers/OS identify minus symbol differently.
      onTrigger: () => {
        handleZoomOut(scene);
      },
    });
  } else {
    keybindings.addBinding({
      key: 't z',
      onTrigger: () => {
        handleZoomOut(scene);
      },
    });
  }

  keybindings.addBinding({
    key: 'ctrl+z',
    onTrigger: () => {
      handleZoomOut(scene);
    },
  });

  // Relative -> Absolute time range
  keybindings.addBinding({
    key: 't a',
    onTrigger: () => {
      const timePicker = dashboardSceneGraph.getTimePicker(scene);
      timePicker?.toAbsolute();
    },
  });

  keybindings.addBinding({
    key: 't left',
    onTrigger: () => {
      handleTimeRangeShift(scene, 'left');
    },
  });

  keybindings.addBinding({
    key: 't right',
    onTrigger: () => {
      handleTimeRangeShift(scene, 'right');
    },
  });

  keybindings.addBinding({
    key: 'mod+o',
    onTrigger: () => {
      const cursorSync = scene.state.$behaviors?.find((b) => b instanceof behaviors.CursorSync);
      if (cursorSync instanceof behaviors.CursorSync) {
        const currentSync = cursorSync.state.sync;
        const nextSync = (currentSync + 1) % 3;
        cursorSync.setState({ sync: nextSync });
        appEvents.publish(new LegacyGraphHoverClearEvent());
        sceneGraph.getTimeRange(scene).onRefresh();
      }
    },
  });

  if (canEdit) {
    // Panel edit
    keybindings.addBinding({
      key: 'e',
      onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
        const panelId = getPanelIdForVizPanel(vizPanel);
        DashboardInteractions.panelActionClicked('edit', panelId, 'keyboard');
        const sceneRoot = vizPanel.getRoot();
        if (sceneRoot instanceof DashboardScene) {
          if (scene.state.editPanel) {
            locationService.push(
              locationUtil.getUrlForPartial(locationService.getLocation(), {
                editPanel: undefined,
              })
            );
          } else {
            const url = locationUtil.stripBaseFromUrl(getEditPanelUrl(panelId));
            locationService.push(url);
          }
        }
      }),
    });

    // Dashboard settings
    keybindings.addBinding({
      key: 'd s',
      onTrigger: scene.onOpenSettings,
    });

    // Open save drawer
    if (canSave) {
      keybindings.addBinding({
        key: 'mod+s',
        onTrigger: () => scene.openSaveDrawer({}),
      });
    }

    // delete panel
    keybindings.addBinding({
      key: 'p r',
      onTrigger: withFocusedPanel(scene, (vizPanel: VizPanel) => {
        if (scene.state.isEditing) {
          const panelId = getPanelIdForVizPanel(vizPanel);
          DashboardInteractions.panelActionClicked('delete', panelId, 'keyboard');
          onRemovePanel(scene, vizPanel);
        }
      }),
    });

    // duplicate panel
    keybindings.addBinding({
      key: 'p d',
      onTrigger: withFocusedPanel(scene, (vizPanel: VizPanel) => {
        DashboardInteractions.panelActionClicked('duplicate', getPanelIdForVizPanel(vizPanel), 'keyboard');
        if (scene.state.isEditing) {
          scene.duplicatePanel(vizPanel);
        }
      }),
    });

    // collapse all rows
    keybindings.addBinding({
      key: 'd shift+c',
      onTrigger: () => {
        if (scene.state.body instanceof DefaultGridLayoutManager) {
          scene.state.body.collapseAllRows();
        } else if (scene.state.body instanceof RowsLayoutManager) {
          scene.state.body.collapseAllRows();
        }
      },
    });

    // expand all rows
    keybindings.addBinding({
      key: 'd shift+e',
      onTrigger: () => {
        if (scene.state.body instanceof DefaultGridLayoutManager) {
          scene.state.body.expandAllRows();
        } else if (scene.state.body instanceof RowsLayoutManager) {
          scene.state.body.expandAllRows();
        }
      },
    });
  }

  // toggle all panel legends (TODO)
  // toggle all exemplars (TODO)

  return () => {
    keybindings.removeAll();
    panelAttentionSubscription.unsubscribe();
  };
}

function handleZoom(scene: DashboardScene, scale: number) {
  const timeRange = sceneGraph.getTimeRange(scene);
  const currentRange = timeRange.state.value;
  const timespan = currentRange.to.valueOf() - currentRange.from.valueOf();

  if (timespan === 0) {
    return;
  }

  const center = currentRange.to.valueOf() - timespan / 2;
  const newTimespan = timespan * scale;

  const to = center + newTimespan / 2;
  const from = center - newTimespan / 2;

  timeRange.onTimeRangeChange({
    from: dateTime(from),
    to: dateTime(to),
    raw: {
      from: dateTime(from),
      to: dateTime(to),
    },
  });
}

function handleZoomOut(scene: DashboardScene) {
  const timePicker = dashboardSceneGraph.getTimePicker(scene);
  timePicker?.onZoom();
}

function handleTimeRangeShift(scene: DashboardScene, direction: 'left' | 'right') {
  const timePicker = dashboardSceneGraph.getTimePicker(scene);

  if (!timePicker) {
    return;
  }

  if (direction === 'left') {
    timePicker.onMoveBackward();
  }
  if (direction === 'right') {
    timePicker.onMoveForward();
  }
}
