// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as Common from '../common/common.js';
import * as ComponentHelpers from '../component_helpers/component_helpers.js';
import * as LitHtml from '../third_party/lit-html/lit-html.js';

import {Angle, AngleUnit, get2DTranslationsForAngle, getAngleFromRadians, getNewAngleFromEvent, getRadiansFromAngle} from './CSSAngleUtils.js';

const {render, html} = LitHtml;
const styleMap = LitHtml.Directives.styleMap;

const CLOCK_DIAL_LENGTH = 6;

export interface CSSAngleEditorData {
  angle: Angle;
  onAngleUpdate: (angle: Angle) => void;
  background: string;
}

export class CSSAngleEditor extends HTMLElement {
  private readonly shadow = this.attachShadow({mode: 'open'});
  private angle: Angle = {
    value: 0,
    unit: AngleUnit.Rad,
  };
  private onAngleUpdate?: (angle: Angle) => void;
  private background = '';
  private clockRadius = 77 / 2;  // By default the clock is 77 * 77.
  private dialTemplates?: LitHtml.TemplateResult[];
  private mousemoveThrottler = new Common.Throttler.Throttler(16.67 /* 60fps */);

  connectedCallback(): void {
    ComponentHelpers.SetCSSProperty.set(this, '--clock-dial-length', `${CLOCK_DIAL_LENGTH}px`);
  }
  set data(data: CSSAngleEditorData) {
    this.angle = data.angle;
    this.onAngleUpdate = data.onAngleUpdate;
    this.background = data.background;
    this.render();
  }

  private updateAngleFromMousePosition(mouseX: number, mouseY: number, shouldSnapToMultipleOf15Degrees: boolean): void {
    const clock = this.shadow.querySelector('.clock');
    if (!clock || !this.onAngleUpdate) {
      return;
    }

    const {top, right, bottom, left} = clock.getBoundingClientRect();
    this.clockRadius = (right - left) / 2;
    const clockCenterX = (left + right) / 2;
    const clockCenterY = (bottom + top) / 2;
    const radian = -Math.atan2(mouseX - clockCenterX, mouseY - clockCenterY) + Math.PI;
    if (shouldSnapToMultipleOf15Degrees) {
      const multipleInRadian = getRadiansFromAngle({
        value: 15,
        unit: AngleUnit.Deg,
      });
      const closestMultipleOf15Degrees = Math.round(radian / multipleInRadian) * multipleInRadian;
      this.onAngleUpdate(getAngleFromRadians(closestMultipleOf15Degrees, this.angle.unit));
    } else {
      this.onAngleUpdate(getAngleFromRadians(radian, this.angle.unit));
    }
  }

  private onEditorMousedown(event: MouseEvent): void {
    event.stopPropagation();
    this.updateAngleFromMousePosition(event.pageX, event.pageY, event.shiftKey);
  }

  private onEditorMousemove(event: MouseEvent): void {
    const isPressed = event.buttons === 1;
    if (!isPressed) {
      return;
    }

    this.mousemoveThrottler.schedule(() => {
      this.updateAngleFromMousePosition(event.pageX, event.pageY, event.shiftKey);
      return Promise.resolve();
    });
  }

  private onEditorWheel(event: WheelEvent): void {
    if (!this.onAngleUpdate) {
      return;
    }

    const newAngle = getNewAngleFromEvent(this.angle, event);
    if (newAngle) {
      this.onAngleUpdate(newAngle);
    }

    event.preventDefault();
  }

  private render(): void {
    const clockStyles = {
      background: this.background,
    };

    const {translateX, translateY} = get2DTranslationsForAngle(this.angle, this.clockRadius / 2);
    const handStyles = {
      transform: `translate(${translateX}px, ${translateY}px) rotate(${this.angle.value}${this.angle.unit})`,
    };

    // Disabled until https://crbug.com/1079231 is fixed.
    // clang-format off
    render(html`
      <style>
        .clock,
        .pointer,
        .center,
        .hand,
        .dial {
          position: absolute;
        }

        .clock {
          top: 6px;
          width: 6em;
          height: 6em;
          background-color: var(--color-background);
          border: 0.5em solid var(--border-color); /* stylelint-disable-line plugin/use_theme_colors */
          /* See: crbug.com/1152736 for color variable migration. */
          border-radius: 9em;
          box-shadow: var(--drop-shadow), inset 0 0 15px hsl(0deg 0% 0% / 25%); /* stylelint-disable-line plugin/use_theme_colors */
          /* See: crbug.com/1152736 for color variable migration. */
          transform: translateX(-3em);
        }

        .center,
        .hand {
          box-shadow: 0 0 2px hsl(0deg 0% 0% / 20%); /* stylelint-disable-line plugin/use_theme_colors */
          /* See: crbug.com/1152736 for color variable migration. */
        }

        .pointer {
          margin: auto;
          top: 0;
          left: -0.4em;
          right: 0;
          width: 0;
          height: 0;
          border-style: solid;
          border-width: 0 0.9em 0.9em 0.9em;
          border-color: transparent transparent var(--border-color) transparent;
        }

        .center,
        .hand,
        .dial {
          margin: auto;
          top: 0;
          left: 0;
          right: 0;
          bottom: 0;
        }

        .center {
          width: 0.7em;
          height: 0.7em;
          border-radius: 10px;
        }

        .dial {
          width: 2px;
          height: var(--clock-dial-length);
          background-color: var(--dial-color); /* stylelint-disable-line plugin/use_theme_colors */
          /* See: crbug.com/1152736 for color variable migration. */
          border-radius: 1px;
        }

        .hand {
          height: 50%;
          width: 0.3em;
          background: var(--accent-fg-color);
        }

        .hand::before {
          content: '';
          display: inline-block;
          position: absolute;
          top: -0.6em;
          left: -0.35em;
          width: 1em;
          height: 1em;
          border-radius: 1em;
          cursor: pointer;
          box-shadow: 0 0 5px hsl(0deg 0% 0% / 30%); /* stylelint-disable-line plugin/use_theme_colors */
          /* See: crbug.com/1152736 for color variable migration. */
        }

        .hand::before,
        .center {
          background-color: var(--accent-fg-color);
        }

        :host-context(.-theme-with-dark-background) .hand::before {
          box-shadow: 0 0 5px hsl(0deg 0% 0% / 80%);
        }

        :host-context(.-theme-with-dark-background) .center,
        :host-context(.-theme-with-dark-background) .hand {
          box-shadow: 0 0 2px hsl(0deg 0% 0% / 60%);
        }

        :host-context(.-theme-with-dark-background) .clock {
          background-color: hsl(225deg 5% 27%);
        }
      </style>

      <div class="editor">
        <span class="pointer"></span>
        <div
          class="clock"
          style=${styleMap(clockStyles)}
          @mousedown=${this.onEditorMousedown}
          @mousemove=${this.onEditorMousemove}
          @wheel=${this.onEditorWheel}>
          ${this.renderDials()}
          <div class="hand" style=${styleMap(handStyles)}></div>
          <span class="center"></span>
        </div>
      </div>
    `, this.shadow, {
      eventContext: this,
    });
    // clang-format on
  }

  private renderDials(): LitHtml.TemplateResult[] {
    if (!this.dialTemplates) {
      // Disabled until https://crbug.com/1079231 is fixed.
      // clang-format off
      this.dialTemplates = [0, 45, 90, 135, 180, 225, 270, 315].map(deg => {
        const radius = this.clockRadius - CLOCK_DIAL_LENGTH - 3 /* clock border */;
        const {translateX, translateY} = get2DTranslationsForAngle({
          value: deg,
          unit: AngleUnit.Deg,
        }, radius);
        const dialStyles = {
          transform: `translate(${translateX}px, ${translateY}px) rotate(${deg}deg)`,
        };
        return html`<span class="dial" style=${styleMap(dialStyles)}></span>`;
      });
      // clang-format on
    }

    return this.dialTemplates;
  }
}

if (!customElements.get('devtools-css-angle-editor')) {
  customElements.define('devtools-css-angle-editor', CSSAngleEditor);
}

declare global {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  interface HTMLElementTagNameMap {
    'devtools-css-angle-editor': CSSAngleEditor;
  }
}
