// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable @devtools/no-imperative-dom-api */

import type * as Common from '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as ThemeSupport from '../../ui/legacy/theme_support/theme_support.js';

import {DeviceModeWrapper} from './DeviceModeWrapper.js';
import {type Bounds, Events, InspectedPagePlaceholder} from './InspectedPagePlaceholder.js';

let appInstance: AdvancedApp;

export class AdvancedApp implements Common.App.App {
  private rootSplitWidget!: UI.SplitWidget.SplitWidget;
  private deviceModeView!: DeviceModeWrapper;
  private inspectedPagePlaceholder!: InspectedPagePlaceholder;
  private toolboxWindow?: Window|null;
  private toolboxRootView?: UI.RootView.RootView;
  private changingDockSide?: boolean;

  constructor() {
    UI.DockController.DockController.instance().addEventListener(
        UI.DockController.Events.BEFORE_DOCK_SIDE_CHANGED, this.openToolboxWindow, this);
  }

  /**
   * Note: it's used by toolbox.ts without real type checks.
   */
  static instance(): AdvancedApp {
    if (!appInstance) {
      appInstance = new AdvancedApp();
    }
    return appInstance;
  }

  presentUI(document: Document): void {
    const rootView = new UI.RootView.RootView();

    this.rootSplitWidget =
        new UI.SplitWidget.SplitWidget(false, true, 'inspector-view.split-view-state', 555, 300, true);
    this.rootSplitWidget.show(rootView.element);
    this.rootSplitWidget.setSidebarWidget(UI.InspectorView.InspectorView.instance());
    this.rootSplitWidget.setDefaultFocusedChild(UI.InspectorView.InspectorView.instance());
    UI.InspectorView.InspectorView.instance().setOwnerSplit(this.rootSplitWidget);

    this.inspectedPagePlaceholder = InspectedPagePlaceholder.instance();
    this.inspectedPagePlaceholder.addEventListener(Events.UPDATE, this.onSetInspectedPageBounds.bind(this), this);
    this.deviceModeView =
        DeviceModeWrapper.instance({inspectedPagePlaceholder: this.inspectedPagePlaceholder, forceNew: false});

    UI.DockController.DockController.instance().addEventListener(
        UI.DockController.Events.BEFORE_DOCK_SIDE_CHANGED, this.onBeforeDockSideChange, this);
    UI.DockController.DockController.instance().addEventListener(
        UI.DockController.Events.DOCK_SIDE_CHANGED, this.onDockSideChange, this);
    UI.DockController.DockController.instance().addEventListener(
        UI.DockController.Events.AFTER_DOCK_SIDE_CHANGED, this.onAfterDockSideChange, this);
    this.onDockSideChange();

    console.timeStamp('AdvancedApp.attachToBody');
    rootView.attachToDocument(document);
    rootView.focus();
    this.inspectedPagePlaceholder.update();
  }

  private openToolboxWindow(event: Common.EventTarget.EventTargetEvent<UI.DockController.ChangeEvent>): void {
    if (event.data.to !== UI.DockController.DockState.UNDOCKED) {
      return;
    }

    if (this.toolboxWindow) {
      return;
    }

    const url = window.location.href.replace('devtools_app.html', 'device_mode_emulation_frame.html');
    this.toolboxWindow = window.open(url, undefined);
  }

  deviceModeEmulationFrameLoaded(toolboxDocument: Document): void {
    ThemeSupport.ThemeSupport.instance().addDocumentToTheme(toolboxDocument);
    UI.UIUtils.initializeUIUtils(toolboxDocument);
    UI.UIUtils.addPlatformClass(toolboxDocument.documentElement);
    UI.UIUtils.installComponentRootStyles(toolboxDocument.body);
    UI.ContextMenu.ContextMenu.installHandler(toolboxDocument);

    this.toolboxRootView = new UI.RootView.RootView();
    this.toolboxRootView.attachToDocument(toolboxDocument);

    this.updateDeviceModeView();
  }

  private updateDeviceModeView(): void {
    if (this.isDocked()) {
      this.rootSplitWidget.setMainWidget(this.deviceModeView);
    } else if (this.toolboxRootView) {
      this.deviceModeView.show(this.toolboxRootView.element);
    }
  }

  private onBeforeDockSideChange(event: Common.EventTarget.EventTargetEvent<UI.DockController.ChangeEvent>): void {
    if (event.data.to === UI.DockController.DockState.UNDOCKED && this.toolboxRootView) {
      // Hide inspectorView and force layout to mimic the undocked state.
      this.rootSplitWidget.hideSidebar();
      this.inspectedPagePlaceholder.update();
    }

    this.changingDockSide = true;
  }

  private onDockSideChange(event?: Common.EventTarget.EventTargetEvent<UI.DockController.ChangeEvent>): void {
    this.updateDeviceModeView();

    const toDockSide = event ? event.data.to : UI.DockController.DockController.instance().dockSide();
    if (toDockSide === undefined) {
      throw new Error('Got onDockSideChange event with unexpected undefined for dockSide()');
    }
    if (toDockSide === UI.DockController.DockState.UNDOCKED) {
      this.updateForUndocked();
    } else if (this.toolboxRootView && event && event.data.from === UI.DockController.DockState.UNDOCKED) {
      // Don't update yet for smooth transition.
      this.rootSplitWidget.hideSidebar();
    } else {
      this.updateForDocked(toDockSide);
    }
  }

  private onAfterDockSideChange(event: Common.EventTarget.EventTargetEvent<UI.DockController.ChangeEvent>): void {
    // We may get here on the first dock side change while loading without BeforeDockSideChange.
    if (!this.changingDockSide) {
      return;
    }
    if (event.data.from && event.data.from === UI.DockController.DockState.UNDOCKED) {
      this.updateForDocked(event.data.to);
    }
    this.changingDockSide = false;
    this.inspectedPagePlaceholder.update();
  }

  private updateForDocked(dockSide: UI.DockController.DockState): void {
    const resizerElement = (this.rootSplitWidget.resizerElement() as HTMLElement);
    resizerElement.style.transform = dockSide === UI.DockController.DockState.RIGHT ? 'translateX(2px)' :
        dockSide === UI.DockController.DockState.LEFT                               ? 'translateX(-2px)' :
                                                                                      '';
    this.rootSplitWidget.setVertical(
        dockSide === UI.DockController.DockState.RIGHT || dockSide === UI.DockController.DockState.LEFT);
    this.rootSplitWidget.setSecondIsSidebar(
        dockSide === UI.DockController.DockState.RIGHT || dockSide === UI.DockController.DockState.BOTTOM);
    this.rootSplitWidget.toggleResizer(this.rootSplitWidget.resizerElement(), true);
    this.rootSplitWidget.toggleResizer(
        UI.InspectorView.InspectorView.instance().topResizerElement(), dockSide === UI.DockController.DockState.BOTTOM);
    this.rootSplitWidget.showBoth();
  }

  private updateForUndocked(): void {
    this.rootSplitWidget.toggleResizer(this.rootSplitWidget.resizerElement(), false);
    this.rootSplitWidget.toggleResizer(UI.InspectorView.InspectorView.instance().topResizerElement(), false);
    this.rootSplitWidget.hideMain();
  }

  private isDocked(): boolean {
    return UI.DockController.DockController.instance().dockSide() !== UI.DockController.DockState.UNDOCKED;
  }

  private onSetInspectedPageBounds(event: Common.EventTarget.EventTargetEvent<Bounds>): void {
    if (this.changingDockSide) {
      return;
    }
    const window = this.inspectedPagePlaceholder.element.window();
    if (!window.innerWidth || !window.innerHeight) {
      return;
    }
    if (!this.inspectedPagePlaceholder.isShowing()) {
      return;
    }
    const bounds = event.data;
    console.timeStamp('AdvancedApp.setInspectedPageBounds');
    Host.InspectorFrontendHost.InspectorFrontendHostInstance.setInspectedPageBounds(bounds);
  }
}

// Export required for usage in toolbox.ts
// @ts-expect-error Exported for Tests.js
globalThis.Emulation = globalThis.Emulation || {};
// @ts-expect-error Exported for Tests.js
globalThis.Emulation.AdvancedApp = AdvancedApp;

let advancedAppProviderInstance: AdvancedAppProvider;

export class AdvancedAppProvider implements Common.AppProvider.AppProvider {
  static instance(opts: {
    forceNew: boolean|null,
  } = {forceNew: null}): AdvancedAppProvider {
    const {forceNew} = opts;
    if (!advancedAppProviderInstance || forceNew) {
      advancedAppProviderInstance = new AdvancedAppProvider();
    }

    return advancedAppProviderInstance;
  }

  createApp(): Common.App.App {
    return AdvancedApp.instance();
  }
}
