import type { PropertyValues } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, query, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { BOTTOM_SHEET_ANIMATION_DURATION_MS } from "./ha-bottom-sheet";

/**
 * A bottom sheet component that slides up from the bottom of the screen.
 *
 * The bottom sheet provides a draggable interface that allows users to resize
 * the sheet by dragging the handle at the top. It supports both mouse and touch
 * interactions and automatically closes when dragged below a 20% of screen height.
 *
 * @fires bottom-sheet-closed - Fired when the bottom sheet is closed
 *
 * @cssprop --ha-bottom-sheet-border-width - Border width for the sheet
 * @cssprop --ha-bottom-sheet-border-style - Border style for the sheet
 * @cssprop --ha-bottom-sheet-border-color - Border color for the sheet
 */
@customElement("ha-resizable-bottom-sheet")
export class HaResizableBottomSheet extends LitElement {
  @query("dialog") private _dialog!: HTMLDialogElement;

  private _dragging = false;

  private _dragStartY = 0;

  private _initialSize = 0;

  @state() private _dialogMaxViewpointHeight = 70;

  @state() private _dialogMinViewpointHeight = 55;

  @state() private _dialogViewportHeight?: number;

  render() {
    return html`<dialog
      open
      @transitionend=${this._handleTransitionEnd}
      style=${`
        --height: ${this._dialogViewportHeight}vh;
        --height: ${this._dialogViewportHeight}dvh;
        --max-height: ${this._dialogMaxViewpointHeight}vh;
        --max-height: ${this._dialogMaxViewpointHeight}dvh;
        --min-height: ${this._dialogMinViewpointHeight}vh;
        --min-height: ${this._dialogMinViewpointHeight}dvh;
      `}
    >
      <div class="handle-wrapper">
        <div
          @mousedown=${this._handleMouseDown}
          @touchstart=${this._handleTouchStart}
          class="handle"
        ></div>
      </div>
      <slot></slot>
    </dialog>`;
  }

  protected firstUpdated(changedProperties: PropertyValues<this>) {
    super.firstUpdated(changedProperties);
    this._openSheet();
  }

  private _openSheet() {
    requestAnimationFrame(() => {
      // trigger opening animation
      this._dialog.classList.add("show");
    });
  }

  public closeSheet() {
    requestAnimationFrame(() => {
      this._dialog.classList.remove("show");
    });
  }

  private _handleTransitionEnd() {
    if (this._dialog.classList.contains("show")) {
      // after show animation is done
      // - set the height to the natural height, to prevent content shift when switch content
      // - set max height to 90vh, so it opens at max 70vh but can be resized to 90vh
      this._dialogViewportHeight =
        (this._dialog.offsetHeight / window.innerHeight) * 100;
      this._dialogMaxViewpointHeight = 90;
      this._dialogMinViewpointHeight = 20;
    } else {
      // after close animation is done close dialog element and fire closed event
      this._dialog.close();
      fireEvent(this, "bottom-sheet-closed");
    }
  }

  connectedCallback() {
    super.connectedCallback();

    // register event listeners for drag handling
    document.addEventListener("mousemove", this._handleMouseMove);
    document.addEventListener("mouseup", this._handleMouseUp);
    document.addEventListener("touchmove", this._handleTouchMove, {
      passive: false,
    });
    document.addEventListener("touchend", this._handleTouchEnd);
    document.addEventListener("touchcancel", this._handleTouchEnd);
  }

  disconnectedCallback() {
    super.disconnectedCallback();

    // unregister event listeners for drag handling
    document.removeEventListener("mousemove", this._handleMouseMove);
    document.removeEventListener("mouseup", this._handleMouseUp);
    document.removeEventListener("touchmove", this._handleTouchMove);
    document.removeEventListener("touchend", this._handleTouchEnd);
    document.removeEventListener("touchcancel", this._handleTouchEnd);
  }

  private _handleMouseDown = (ev: MouseEvent) => {
    this._startDrag(ev.clientY);
  };

  private _handleTouchStart = (ev: TouchEvent) => {
    // Prevent the browser from interpreting this as a scroll/PTR gesture.
    ev.preventDefault();
    this._startDrag(ev.touches[0].clientY);
  };

  private _startDrag(clientY: number) {
    this._dragging = true;
    this._dragStartY = clientY;
    this._initialSize = (this._dialog.offsetHeight / window.innerHeight) * 100;
    document.body.style.setProperty("cursor", "grabbing");
  }

  private _handleMouseMove = (ev: MouseEvent) => {
    if (!this._dragging) {
      return;
    }
    this._updateSize(ev.clientY);
  };

  private _handleTouchMove = (ev: TouchEvent) => {
    if (!this._dragging) {
      return;
    }
    ev.preventDefault(); // Prevent scrolling
    this._updateSize(ev.touches[0].clientY);
  };

  private _updateSize(clientY: number) {
    const deltaY = this._dragStartY - clientY;
    const viewportHeight = window.innerHeight;
    const deltaVh = (deltaY / viewportHeight) * 100;

    // Calculate new size and clamp between 10vh and 90vh
    let newSize = this._initialSize + deltaVh;
    newSize = Math.max(10, Math.min(90, newSize));

    // on drag down and below 20vh
    if (newSize < 20 && deltaY < 0) {
      this._endDrag();
      this.closeSheet();
      return;
    }

    this._dialogViewportHeight = newSize;
  }

  private _handleMouseUp = () => {
    this._endDrag();
  };

  private _handleTouchEnd = () => {
    this._endDrag();
  };

  private _endDrag() {
    if (!this._dragging) {
      return;
    }
    this._dragging = false;
    document.body.style.removeProperty("cursor");
  }

  static styles = css`
    .handle-wrapper {
      position: absolute;
      top: 0;
      width: 100%;
      padding-bottom: 2px;
      display: flex;
      justify-content: center;
      align-items: center;
      cursor: grab;
      touch-action: none;
    }
    .handle-wrapper .handle {
      height: 20px;
      width: 200px;
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 7;
      padding-bottom: 76px;
    }
    .handle-wrapper .handle::after {
      content: "";
      border-radius: var(--ha-border-radius-md);
      height: 4px;
      background: var(--divider-color, #e0e0e0);
      width: 80px;
    }
    .handle-wrapper .handle:active::after {
      cursor: grabbing;
    }
    dialog {
      height: var(--height, auto);
      max-height: min(
        var(--max-height, 70vh),
        calc(100vh - var(--safe-area-inset-top))
      );
      max-height: min(
        var(--max-height, 70dvh),
        calc(100dvh - var(--safe-area-inset-top))
      );
      min-height: var(--min-height, 30vh);
      min-height: var(--min-height, 30dvh);
      background-color: var(
        --ha-bottom-sheet-surface-background,
        var(--ha-color-surface-default)
      );
      display: flex;
      flex-direction: column;
      top: 0;
      inset-inline-start: 0;
      position: fixed;
      width: calc(
        100% - 4px - var(--safe-area-inset-left) - var(--safe-area-inset-right)
      );
      max-width: 100%;
      border: none;
      box-shadow: var(--wa-shadow-l);
      padding: 0;
      margin: 0;
      top: auto;
      inset-inline-end: auto;
      bottom: 0;
      inset-inline-start: 0;
      box-shadow: 0px -8px 16px rgba(0, 0, 0, 0.2);
      border-top-left-radius: var(
        --ha-bottom-sheet-border-radius,
        var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
      );
      border-top-right-radius: var(
        --ha-bottom-sheet-border-radius,
        var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
      );
      transform: translateY(100%);
      transition: transform ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms ease;
      border-top-width: var(--ha-bottom-sheet-border-width);
      border-right-width: var(--ha-bottom-sheet-border-width);
      border-left-width: var(--ha-bottom-sheet-border-width);
      border-bottom-width: 0;
      border-style: var(--ha-bottom-sheet-border-style);
      border-color: var(--ha-bottom-sheet-border-color);
      margin-left: var(--safe-area-inset-left);
      margin-right: var(--safe-area-inset-right);
    }

    dialog.show {
      transform: translateY(0);
    }
  `;
}

declare global {
  interface HTMLElementTagNameMap {
    "ha-resizable-bottom-sheet": HaResizableBottomSheet;
  }

  interface HASSDomEvents {
    "bottom-sheet-closed": undefined;
  }
}
