text: // Copyright 2019 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 {assert} from './chrome_util.js';
  import * as Comlink from './lib/comlink.js';
  import * as loadTimeData from './models/load_time_data.js';
  import * as localStorage from './models/local_storage.js';
  import {ChromeHelper} from './mojo/chrome_helper.js';
  import * as state from './state.js';
  import {
    Facing,  // eslint-disable-line no-unused-vars
    Mode,
    PerfEvent,        // eslint-disable-line no-unused-vars
    PerfInformation,  // eslint-disable-line no-unused-vars
    Resolution,       // eslint-disable-line no-unused-vars
  } from './type.js';
  // eslint-disable-next-line no-unused-vars
  import {GAHelperInterface} from './untrusted_helper_interfaces.js';
  import * as util from './util.js';
  import {WaitableEvent} from './waitable_event.js';
  
  /**
   * The tracker ID of the GA metrics.
   * @type {string}
   */
  const GA_ID = 'UA-134822711-1';
  
  /**
   * @type {?Map<number, !Object>}
   */
  let baseDimen = null;
  
  /**
   * @type {!WaitableEvent}
   */
  const ready = new WaitableEvent();
  
  /**
   * @type {!Promise<!GAHelperInterface>}
   */
  const gaHelper = (async () => {
    return /** @type {!GAHelperInterface} */ (
        await util.createUntrustedJSModule('/js/untrusted_ga_helper.js'));
  })();
  
  /**
   * Send the event to GA backend.
   * @param {!UniversalAnalytics.FieldsObject} event The event to send.
   * @param {?Map<number, !Object>=} dimen Optional object contains dimension
   *     information.
   */
  async function sendEvent(event, dimen = null) {
    const assignDimension = (e, d) => {
      d.forEach((value, key) => e[`dimension${key}`] = value);
    };
  
    assert(baseDimen !== null);
    assignDimension(event, baseDimen);
    if (dimen !== null) {
      assignDimension(event, dimen);
    }
  
    await ready.wait();
  
    // This value reflects the logging constent option in OS settings.
    const canSendMetrics =
        await ChromeHelper.getInstance().isMetricsAndCrashReportingEnabled();
    if (canSendMetrics) {
      (await gaHelper).sendGAEvent(event);
    }
  }
  
  /**
   * Set if the metrics is enabled. Note that the metrics will only be sent if it
   * is enabled AND the logging consent option is enabled in OS settings.
   * @param {boolean} enabled True if the metrics is enabled.
   */
  export async function setMetricsEnabled(enabled) {
    await ready.wait();
    await (await gaHelper).setMetricsEnabled(GA_ID, enabled);
  }
  
  const SCHEMA_VERSION = 2;
  
  /**
   * Initializes metrics with parameters.
   */
  export async function initMetrics() {
    const board = loadTimeData.getBoard();
    const boardName = /^(x86-)?(\w*)/.exec(board)[0];
    const match = navigator.appVersion.match(/CrOS\s+\S+\s+([\d.]+)/);
    const osVer = match ? match[1] : '';
    baseDimen = new Map([
      [1, boardName],
      [2, osVer],
      [31, SCHEMA_VERSION],
    ]);
  
    const GA_LOCAL_STORAGE_KEY = 'google-analytics.analytics.user-id';
    const clientId = localStorage.getString(GA_LOCAL_STORAGE_KEY);
  
    const setClientId = (id) => {
      localStorage.set(GA_LOCAL_STORAGE_KEY, id);
    };
  
    await (await gaHelper).initGA(GA_ID, clientId, Comlink.proxy(setClientId));
    ready.signal();
  }
  
  /**
   * Types of different ways to launch CCA.
   * @enum {string}
   */
  export const LaunchType = {
    DEFAULT: 'default',
    ASSISTANT: 'assistant',
  };
  
  /**
   * Parameters for logging launch event. |launchType| stands for how CCA is
   * launched.
   * @typedef {{launchType: !LaunchType}}
   */
  export let LaunchEventParam;
  
  /**
   * Sends launch type event.
   * @param {!LaunchEventParam} param
   */
  export function sendLaunchEvent({launchType}) {
    sendEvent(
        {
          eventCategory: 'launch',
          eventAction: 'start',
          eventLabel: '',
        },
        new Map([
          [32, launchType],
        ]));
  }
  
  /**
   * Types of intent result dimension.
   * @enum {string}
   */
  export const IntentResultType = {
    NOT_INTENT: '',
    CANCELED: 'canceled',
    CONFIRMED: 'confirmed',
  };
  
  /**
   * Types of document scanning result dimension.
   * @enum {string}
   */
  export const DocResultType = {
    NOT_DOCUMENT: '',
    CANCELED: 'canceled',
    SAVE_AS_PHOTO: 'save-as-photo',
    SAVE_AS_PDF: 'save-as-pdf',
    SHARE: 'share',
  };
  
  /**
   * Types of user interaction with fix document page.
   * @enum {number}
   */
  export const DocFixType = {
    NONE: 0,
    NO_FIX: 1,
    FIX_ROTATION: 2,
    FIX_POSITION: 3,
    FIX_BOTH: 4,
  };
  
  /**
   * Types of gif recording result dimension.
   * @enum {number}
   */
  export const GifResultType = {
    NOT_GIF_RESULT: 0,
    RETAKE: 1,
    SHARE: 2,
    SAVE: 3,
  };
  
  /**
   * Types of recording in video mode.
   * @enum {number}
   */
  export const RecordType = {
    NOT_RECORDING: 0,
    NORMAL_VIDEO: 1,
    GIF: 2,
  };
  
  /**
   * Types of different ways to trigger shutter button.
   * @enum {string}
   */
  export const ShutterType = {
    UNKNOWN: 'unknown',
    MOUSE: 'mouse',
    KEYBOARD: 'keyboard',
    TOUCH: 'touch',
    VOLUME_KEY: 'volume-key',
    ASSISTANT: 'assistant',
  };
  
  /**
   * Parameters of capture metrics event.
   * @record
   */
  export class CaptureEventParam {
    /**
     * @public
     */
    constructor() {
      /**
       * @type {!Facing} Camera facing of the capture.
       */
      this.facing;
  
      /**
       * @type {(number|undefined)} Length of duration for captured motion result
       *     in milliseconds.
       */
      this.duration;
  
      /**
       * @type {!Resolution} Capture resolution.
       */
      this.resolution;
  
      /**
       * @type {!IntentResultType|undefined}
       */
      this.intentResult;
  
      /**
       * @type {!ShutterType}
       */
      this.shutterType;
  
      /**
       * Whether the event is for video snapshot.
       * @type {boolean|undefined}
       */
      this.isVideoSnapshot;
  
      /**
       * Whether the video have ever paused and resumed in the recording.
       * @type {boolean|undefined}
       */
      this.everPaused;
  
      /**
       * @type {!DocResultType|undefined}
       */
      this.docResult;
  
      /**
       * @type {!DocFixType|undefined}
       */
      this.docFixType;
  
      /**
       * @type {!GifResultType|undefined}
       */
      this.gifResult;
  
      /**
       * @type {!RecordType|undefined}
       */
      this.recordType;
    }
  }
  
  /**
   * Sends capture type event.
   * @param {!CaptureEventParam} param
   */
  export function sendCaptureEvent({
    facing,
    duration = 0,
    resolution,
    intentResult = IntentResultType.NOT_INTENT,
    shutterType,
    isVideoSnapshot = false,
    everPaused = false,
    docResult = DocResultType.NOT_DOCUMENT,
    docFixType,
    recordType = RecordType.NOT_RECORDING,
    gifResult = GifResultType.NOT_GIF_RESULT,
  }) {
    /**
     * @param {!Array<!state.StateUnion>} states
     * @param {!state.StateUnion=} cond
     * @param {boolean=} strict
     * @return {string}
     */
    const condState = (states, cond = undefined, strict = undefined) => {
      // Return the first existing state among the given states only if there is
      // no gate condition or the condition is met.
      const prerequisite = !cond || state.get(cond);
      if (strict && !prerequisite) {
        return '';
      }
      return prerequisite && states.find((s) => state.get(s)) || 'n/a';
    };
  
    const State = state.State;
    sendEvent(
        {
          eventCategory: 'capture',
          eventAction: condState(Object.values(Mode)),
          eventLabel: facing,
          eventValue: duration,
        },
        new Map([
          // Skips 3rd dimension for obsolete 'sound' state.
          [4, condState([State.MIRROR])],
          [
            5,
            condState(
                [State.GRID_3x3, State.GRID_4x4, State.GRID_GOLDEN], State.GRID),
          ],
          [6, condState([State.TIMER_3SEC, State.TIMER_10SEC], State.TIMER)],
          [7, condState([State.MIC], Mode.VIDEO, true)],
          [8, condState([State.MAX_WND])],
          [9, condState([State.TALL])],
          [10, resolution.toString()],
          [11, condState([State.FPS_30, State.FPS_60], Mode.VIDEO, true)],
          [12, intentResult],
          [21, shutterType],
          [22, isVideoSnapshot],
          [23, everPaused],
          [27, docResult],
          [28, recordType],
          [29, gifResult],
          [30, duration],
          // [31, SCHEMA_VERSION]
          [32, docFixType ?? ''],
        ]));
  }
  
  
  /**
   * Parameters for logging perf event.
   * @record
   */
  export class PerfEventParam {
    /**
     * @public
     */
    constructor() {
      /**
       * @type {!PerfEvent} Target event type.
       */
      this.event;
  
      /**
       * @type {number} Duration of the event in ms.
       */
      this.duration;
  
      /**
       * @type {!PerfInformation|undefined} Optional information for the event.
       */
      this.perfInfo;
    }
  }
  
  /**
   * Sends perf type event.
   * @param {!PerfEventParam} param
   */
  export function sendPerfEvent({event, duration, perfInfo = {}}) {
    const resolution = perfInfo['resolution'] || '';
    const facing = perfInfo['facing'] || '';
    sendEvent(
        {
          eventCategory: 'perf',
          eventAction: event,
          eventLabel: facing,
          // Round the duration here since GA expects that the value is an
          // integer. Reference:
          // https://support.google.com/analytics/answer/1033068
          eventValue: Math.round(duration),
        },
        new Map([
          [10, `${resolution}`],
        ]));
  }
  
  /**
   * See Intent class in intent.js for the descriptions of each field.
   * TODO(b/131133953): Pass an Intent directly once the type-only import feature
   * is implemented in Closure Compiler.
   * @typedef {{
   *   mode: !Mode,
   *   result: !IntentResultType,
   *   shouldHandleResult: boolean,
   *   shouldDownScale: boolean,
   *   isSecure: boolean,
   * }}
   */
  export let IntentEventParam;
  
  /**
   * Sends intent type event.
   * @param {!IntentEventParam} param
   */
  export function sendIntentEvent(
      {mode, result, shouldHandleResult, shouldDownScale, isSecure}) {
    const getBoolValue = (b) => b ? '1' : '0';
    sendEvent(
        {
          eventCategory: 'intent',
          eventAction: mode,
          eventLabel: result,
        },
        new Map([
          [12, result],
          [13, getBoolValue(shouldHandleResult)],
          [14, getBoolValue(shouldDownScale)],
          [15, getBoolValue(isSecure)],
        ]));
  }
  
  /**
   * @typedef {{
   *   type: string,
   *   level: string,
   *   errorName: string,
   *   fileName: string,
   *   funcName: string,
   *   lineNo: string,
   *   colNo: string,
   * }}
   */
  export let ErrorEventParam;
  
  /**
   * Sends error type event.
   * @param {!ErrorEventParam} param
   */
  export function sendErrorEvent(
      {type, level, errorName, fileName, funcName, lineNo, colNo}) {
    sendEvent(
        {
          eventCategory: 'error',
          eventAction: type,
          eventLabel: level,
        },
        new Map([
          [16, errorName],
          [17, fileName],
          [18, funcName],
          [19, lineNo],
          [20, colNo],
        ]));
  }
  
  /**
   * Sends the barcode enabled event.
   */
  export function sendBarcodeEnabledEvent() {
    sendEvent({
      eventCategory: 'barcode',
      eventAction: 'enable',
    });
  }
  
  /**
   * Types of the decoded barcode content.
   * @enum {string}
   */
  export const BarcodeContentType = {
    TEXT: 'text',
    URL: 'url',
  };
  
  /**
   * @typedef {{
   *   contentType: !BarcodeContentType,
   * }}
   */
  export let BarcodeDetectedEventParam;
  
  /**
   * Sends the barcode detected event.
   * @param {!BarcodeDetectedEventParam} param
   */
  export function sendBarcodeDetectedEvent({contentType}) {
    sendEvent({
      eventCategory: 'barcode',
      eventAction: 'detect',
      eventLabel: contentType,
    });
  }
  
  /**
   * Sends the open ptz panel event.
   * @param {{pan: boolean, tilt: boolean, zoom: boolean}} capabilities
   */
  export function sendOpenPTZPanelEvent(capabilities) {
    sendEvent(
        {
          eventCategory: 'ptz',
          eventAction: 'open-panel',
        },
        new Map([
          [24, capabilities.pan],
          [25, capabilities.tilt],
          [26, capabilities.zoom],
        ]));
  }
  