// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import * as i18n from '../../../../core/i18n/i18n.js';
import * as Platform from '../../../../core/platform/platform.js';
import * as TextUtils from '../../../../models/text_utils/text_utils.js';
import {Directives, html, render} from '../../../lit/lit.js';
import * as VisualLogging from '../../../visual_logging/visual_logging.js';
import * as UI from '../../legacy.js';

import fontViewStyles from './fontView.css.js';

const UIStrings = {
  /**
   * @description Text that appears on a button for the font resource type filter.
   */
  font: 'Font',
  /**
   * @description Aria accessible name in Font View of the Sources panel
   * @example {https://example.com} PH1
   */
  previewOfFontFromS: 'Preview of font from {PH1}',
} as const;
const str_ = i18n.i18n.registerUIStrings('ui/legacy/components/source_frame/FontView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

const FONT_PREVIEW_LINES = ['ABCDEFGHIJKLM', 'NOPQRSTUVWXYZ', 'abcdefghijklm', 'nopqrstuvwxyz', '1234567890'];
const MEASURE_FONT_SIZE = 50;

export interface ViewInput {
  url: Platform.DevToolsPath.UrlString;
  fontFaceRule: string;
  fontFamily: string;
  previewFontSize: string;
  previewVisible: boolean;
}

export interface ViewOutput {
  measureDimensions?: () => {
    width: number, height: number,
  };
}

export type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void;

// clang-format off
export const DEFAULT_VIEW: View = (input, output, target) => {
  let dummyEl: HTMLElement|undefined;
  render(html`
    <style>${fontViewStyles}</style>
    <style>${input.fontFaceRule}</style>
    <div class="font-view"
      aria-label=${i18nString(UIStrings.previewOfFontFromS, {PH1: input.url})}
      style="font-family: ${input.fontFamily}; font-size: ${input.previewFontSize}"
      aria-hidden="true"
      ?hidden=${!input.previewVisible}
    >${FONT_PREVIEW_LINES.map((line, i) => html`${i > 0 ? html`<br>` : ''}${line}`)}</div>
    <div ${Directives.ref(el => { dummyEl = el as HTMLElement; })}
      style="visibility: hidden; z-index: -1; display: inline; position: absolute; font-family: ${input.fontFamily}; font-size: ${MEASURE_FONT_SIZE}px"
    >${FONT_PREVIEW_LINES.map((line, i) => html`${i > 0 ? html`<br>` : ''}${line}`)}</div>
  `, target);
  output.measureDimensions = () => {
    if (!dummyEl) {
      return {width: 0, height: 0};
    }
    return {width: dummyEl.offsetWidth, height: dummyEl.offsetHeight};
  };
};
// clang-format on

export class FontView extends UI.View.SimpleView {
  private readonly url: Platform.DevToolsPath.UrlString;
  private readonly contentProvider: TextUtils.ContentProvider.ContentProvider;
  private readonly mimeTypeLabel: UI.Toolbar.ToolbarText;
  readonly #view: View;
  #fontFaceRule = '';
  #fontFamily = '';
  #previewFontSize = '';
  #previewVisible = false;
  #contentLoaded = false;

  constructor(mimeType: string, contentProvider: TextUtils.ContentProvider.ContentProvider, view: View = DEFAULT_VIEW) {
    super({
      title: i18nString(UIStrings.font),
      viewId: 'font',
      jslog: `${VisualLogging.pane('font-view')}`,
    });
    this.#view = view;
    this.url = contentProvider.contentURL();
    this.contentProvider = contentProvider;
    this.mimeTypeLabel = new UI.Toolbar.ToolbarText(mimeType);
  }

  override async toolbarItems(): Promise<UI.Toolbar.ToolbarItem[]> {
    return [this.mimeTypeLabel];
  }

  #loadContentIfNeeded(): void {
    if (this.#contentLoaded) {
      return;
    }
    this.#contentLoaded = true;

    this.#fontFamily = `WebInspectorFontPreview${++fontId}`;
    void this.contentProvider.requestContentData().then(contentData => {
      const url = TextUtils.ContentData.ContentData.isError(contentData) ? this.url : contentData.asDataUrl();
      this.#fontFaceRule =
          Platform.StringUtilities.sprintf('@font-face { font-family: "%s"; src: url(%s); }', this.#fontFamily, url);
      this.#previewVisible = true;
      this.requestUpdate();
    });
  }

  override wasShown(): void {
    super.wasShown();
    this.#loadContentIfNeeded();
    this.requestUpdate();
  }

  override onResize(): void {
    this.requestUpdate();
  }

  #calculateFontPreviewSize(dimension: {
    width: number,
    height: number,
  }): string {
    if (!this.#previewVisible || !this.isShowing()) {
      return '';
    }

    const height = dimension.height;
    const width = dimension.width;

    // Subtract some padding. This should match the paddings in the CSS plus room for the scrollbar.
    const containerWidth = this.element.offsetWidth - 50;
    const containerHeight = this.element.offsetHeight - 30;

    if (!height || !width || !containerWidth || !containerHeight) {
      return '';
    }

    const widthRatio = containerWidth / width;
    const heightRatio = containerHeight / height;
    const finalFontSize = Math.floor(MEASURE_FONT_SIZE * Math.min(widthRatio, heightRatio)) - 2;

    return `${finalFontSize}px`;
  }

  override performUpdate(): void {
    const output: ViewOutput = {};
    this.#view(
        {
          url: this.url,
          fontFaceRule: this.#fontFaceRule,
          fontFamily: this.#fontFamily,
          previewFontSize: this.#previewFontSize,
          previewVisible: this.#previewVisible,
        },
        output,
        this.contentElement,
    );

    if (!output.measureDimensions) {
      return;
    }

    const requestedFontSize = this.#calculateFontPreviewSize(output.measureDimensions());
    if (requestedFontSize === this.#previewFontSize) {
      return;
    }

    this.#previewFontSize = requestedFontSize;
    this.requestUpdate();
  }
}

let fontId = 0;
