// Copyright 2026 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 Lit from '../../ui/lit/lit.js';
import {createIcon, type Icon} from '../kit/kit.js';
import * as VisualLogging from '../visual_logging/visual_logging.js';

import * as ARIAUtils from './ARIAUtils.js';
import type * as Toolbar from './Toolbar.js';
import {createTextChild} from './UIUtils.js';
import type {View} from './View.js';
import viewContainersStyles from './viewContainers.css.js';
import {type AnyWidget, VBox} from './Widget.js';

type CreateToolbarFn = (toolbarItems: Toolbar.ToolbarItem[]|Lit.TemplateResult) => Element|null;
type SetWidgetForViewFn = (view: View, widget: AnyWidget) => void;

export class ExpandableContainerWidget extends VBox {
  private titleElement: HTMLDivElement;
  private readonly titleExpandIcon: Icon;
  private readonly view: View;
  private widget?: AnyWidget;
  private materializePromise?: Promise<void>;

  constructor(
      view: View,
      private readonly createToolbar: CreateToolbarFn,
      private readonly setWidgetForView: SetWidgetForViewFn,
      private readonly onVisibilityChanged?: (isExpanded: boolean) => void,
  ) {
    super({useShadowDom: true});
    this.element.classList.add('flex-none');
    this.registerRequiredCSS(viewContainersStyles);

    this.onVisibilityChanged = onVisibilityChanged;
    this.createToolbar = createToolbar;

    this.titleElement = document.createElement('div');
    this.titleElement.classList.add('expandable-view-title');
    this.titleElement.setAttribute('jslog', `${VisualLogging.sectionHeader().context(view.viewId()).track({
                                     click: true,
                                     keydown: 'Enter|Space|ArrowLeft|ArrowRight',
                                   })}`);
    ARIAUtils.markAsTreeitem(this.titleElement);
    this.titleExpandIcon = createIcon('triangle-right', 'title-expand-icon');
    this.titleElement.appendChild(this.titleExpandIcon);
    const titleText = view.title();
    createTextChild(this.titleElement, titleText);
    ARIAUtils.setLabel(this.titleElement, titleText);
    ARIAUtils.setExpanded(this.titleElement, false);
    this.titleElement.tabIndex = 0;
    self.onInvokeElement(this.titleElement, this.toggleExpanded.bind(this));
    this.titleElement.addEventListener('keydown', this.onTitleKeyDown.bind(this), false);
    this.contentElement.insertBefore(this.titleElement, this.contentElement.firstChild);

    ARIAUtils.setControls(this.titleElement, this.contentElement.createChild('slot'));
    this.view = view;
    expandableContainerForView.set(view, this);
  }

  isExpanded(): boolean {
    return this.titleElement.classList.contains('expanded');
  }

  override wasShown(): void {
    super.wasShown();
    if (this.widget && this.materializePromise) {
      void this.materializePromise.then(() => {
        if (this.isExpanded() && this.widget) {
          this.widget.show(this.element);
        }
      });
    }
  }

  private materialize(): Promise<void> {
    if (this.materializePromise) {
      return this.materializePromise;
    }
    // TODO(crbug.com/1006759): Transform to async-await
    const promises = [];
    promises.push(this.view.toolbarItems().then(toolbarItems => {
      const toolbarElement = this.createToolbar(toolbarItems);
      if (toolbarElement) {
        this.titleElement.appendChild(toolbarElement);
      }
    }));
    promises.push(this.view.widget().then(widget => {
      this.widget = widget;
      this.setWidgetForView(this.view, widget);
    }));
    this.materializePromise = Promise.all(promises).then(() => {});
    return this.materializePromise;
  }

  expand(): Promise<void> {
    if (this.isExpanded()) {
      return this.materialize();
    }
    this.titleElement.classList.add('expanded');
    ARIAUtils.setExpanded(this.titleElement, true);
    this.titleExpandIcon.name = 'triangle-down';
    this.onVisibilityChanged?.(true);
    return this.materialize().then(() => {
      if (this.isExpanded() && this.widget) {
        this.widget.show(this.element);
      }
    });
  }

  private collapse(): void {
    if (!this.isExpanded()) {
      return;
    }
    this.titleElement.classList.remove('expanded');
    ARIAUtils.setExpanded(this.titleElement, false);
    this.titleExpandIcon.name = 'triangle-right';
    this.onVisibilityChanged?.(false);
    void this.materialize().then(() => {
      if (this.widget) {
        this.widget.detach();
      }
    });
  }

  private toggleExpanded(event: Event): void {
    if (event.type === 'keydown' && event.target !== this.titleElement) {
      return;
    }
    if (this.isExpanded()) {
      this.collapse();
    } else {
      void this.expand();
    }
  }

  private onTitleKeyDown(event: Event): void {
    if (event.target !== this.titleElement) {
      return;
    }
    const keyEvent = (event as KeyboardEvent);
    if (keyEvent.key === 'ArrowLeft') {
      this.collapse();
    } else if (keyEvent.key === 'ArrowRight') {
      if (!this.isExpanded()) {
        void this.expand();
      } else if (this.widget) {
        this.widget.focus();
      }
    }
  }
}

const expandableContainerForView = new WeakMap<View, ExpandableContainerWidget>();

export class StackedPane extends VBox {
  readonly expandableContainers = new Map<string, ExpandableContainerWidget>();
  constructor(
      private readonly createToolbar: CreateToolbarFn,
      private readonly setWidgetForView: SetWidgetForViewFn,
      private readonly onViewVisibilityChanged?: (viewId: string, isExpanded: boolean) => void,
  ) {
    super();
    this.createToolbar = createToolbar;
    this.onViewVisibilityChanged = onViewVisibilityChanged;
    ARIAUtils.markAsTree(this.element);
  }

  appendView(view: View, insertBefore?: View|null): void {
    let container = this.expandableContainers.get(view.viewId());
    if (!container) {
      container =
          new ExpandableContainerWidget(view, this.createToolbar, this.setWidgetForView,
                                        isExpanded => this.onViewVisibilityChanged?.(view.viewId(), isExpanded));
      let beforeElement: Node|null = null;
      if (insertBefore) {
        const beforeContainer = expandableContainerForView.get(insertBefore);
        beforeElement = beforeContainer ? beforeContainer.element : null;
      }
      container.show(this.contentElement, beforeElement);
      this.expandableContainers.set(view.viewId(), container);
    }
  }

  override wasShown(): void {
    super.wasShown();
    for (const [viewId, container] of this.expandableContainers) {
      if (container.isExpanded()) {
        this.onViewVisibilityChanged?.(viewId, true);
      }
    }
  }

  override willHide(): void {
    super.willHide();
    for (const [viewId, container] of this.expandableContainers) {
      if (container.isExpanded()) {
        this.onViewVisibilityChanged?.(viewId, false);
      }
    }
  }

  removeView(view: View): void {
    const container = this.expandableContainers.get(view.viewId());
    if (container) {
      container.detach();
      this.expandableContainers.delete(view.viewId());
    }
  }

  async expandView(view: View): Promise<void> {
    const container = this.expandableContainers.get(view.viewId());
    if (container) {
      await container.expand();
    }
  }

  isViewExpanded(viewId: string): boolean {
    const container = this.expandableContainers.get(viewId);
    return container ? container.isExpanded() : false;
  }

  getContainerForView(view: View): ExpandableContainerWidget|undefined {
    return this.expandableContainers.get(view.viewId());
  }
}
