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

import * as i18n from '../../core/i18n/i18n.js';
import * as Platform from '../../core/platform/platform.js';
import * as Trace from '../../models/trace/trace.js';
import * as TraceBounds from '../../services/trace_bounds/trace_bounds.js';

import type {AnnotationModifiedEvent} from './ModificationsManager.js';

const UIStrings = {
  /**
   * @description text used to announce to a screen reader that they have entered the mode to edit the label
   */
  srEnterLabelEditMode: 'Editing the annotation label text',
  /**
   * @description text used to announce to a screen reader that the entry label text has been updated
   * @example {Hello world} PH1
   */
  srLabelTextUpdated: 'Label updated to {PH1}',
  /**
   * @description text used to announce to a screen reader that the bounds of a time range annotation have been upodated
   * @example {13ms} PH1
   * @example {20ms} PH2
   */
  srTimeRangeBoundsUpdated: 'Time range updated, starting at {PH1} and ending at {PH2}',
  /**
   * @description label for a time range overlay
   */
  timeRange: 'time range',
  /**
   * @description label for a entry label overlay
   */
  entryLabel: 'entry label',
  /**
   * @description label for a connected entries overlay
   */
  entriesLink: 'connected entries',
  /**
   * @description screen reader text to announce that an annotation has been removed
   * @example {Entry Label} PH1
   */
  srAnnotationRemoved: 'The {PH1} annotation has been removed',
  /**
   * @description screen reader text to announce that an annotation has been added
   * @example {Entry Label} PH1
   */
  srAnnotationAdded: 'The {PH1} annotation has been added',
  /**
   * @description screen reader text to announce the two events that the connected entries annotation links to
   * @example {Paint} PH1
   * @example {Function call} PH2
   */
  srEntriesLinked: 'The connected entries annotation now links from {PH1} to {PH2}',

} as const;
const str_ = i18n.i18n.registerUIStrings('panels/timeline/AnnotationHelpers.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

export function getAnnotationEntries(
    annotation: Trace.Types.File.Annotation,
    ): Trace.Types.Events.Event[] {
  const entries: Trace.Types.Events.Event[] = [];
  switch (annotation.type) {
    case 'ENTRY_LABEL':
      entries.push(annotation.entry);
      break;
    case 'TIME_RANGE':
      break;
    case 'ENTRIES_LINK':
      entries.push(annotation.entryFrom);
      if (annotation.entryTo) {
        entries.push(annotation.entryTo);
      }
      break;
    default:
      Platform.assertNever(annotation, 'Unsupported annotation type');
  }
  return entries;
}

/**
 * Gets a trace window that contains the given annotation. May return `null`
 * if there is no valid window (an ENTRIES_LINK without a `to` entry for
 * example.)
 */
export function getAnnotationWindow(
    annotation: Trace.Types.File.Annotation,
    ): Trace.Types.Timing.TraceWindowMicro|null {
  let annotationWindow: Trace.Types.Timing.TraceWindowMicro|null = null;
  const minVisibleEntryDuration = Trace.Types.Timing.Milli(1);

  switch (annotation.type) {
    case 'ENTRY_LABEL': {
      const eventDuration = annotation.entry.dur ?? Trace.Helpers.Timing.milliToMicro(minVisibleEntryDuration);

      annotationWindow = Trace.Helpers.Timing.traceWindowFromMicroSeconds(
          annotation.entry.ts,
          Trace.Types.Timing.Micro(annotation.entry.ts + eventDuration),
      );

      break;
    }
    case 'TIME_RANGE': {
      annotationWindow = annotation.bounds;
      break;
    }
    case 'ENTRIES_LINK': {
      // If entryTo does not exist, the annotation is in the process of being created.
      // Do not allow to zoom into it in this case.
      if (!annotation.entryTo) {
        break;
      }

      const fromEventDuration = (annotation.entryFrom.dur) ?? minVisibleEntryDuration;
      const toEventDuration = annotation.entryTo.dur ?? minVisibleEntryDuration;

      // To choose window max, check which entry ends later
      const fromEntryEndTS = (annotation.entryFrom.ts + fromEventDuration);
      const toEntryEndTS = (annotation.entryTo.ts + toEventDuration);
      const maxTimestamp = Math.max(fromEntryEndTS, toEntryEndTS);

      annotationWindow = Trace.Helpers.Timing.traceWindowFromMicroSeconds(
          annotation.entryFrom.ts,
          Trace.Types.Timing.Micro(maxTimestamp),
      );
      break;
    }
    default:
      Platform.assertNever(annotation, 'Unsupported annotation type');
  }

  return annotationWindow;
}

export function isTimeRangeLabel(overlay: Trace.Types.Overlays.Overlay):
    overlay is Trace.Types.Overlays.TimeRangeLabel {
  return overlay.type === 'TIME_RANGE';
}

export function isEntriesLink(overlay: Trace.Types.Overlays.Overlay): overlay is Trace.Types.Overlays.EntriesLink {
  return overlay.type === 'ENTRIES_LINK';
}

export function isEntryLabel(overlay: Trace.Types.Overlays.Overlay): overlay is Trace.Types.Overlays.EntryLabel {
  return overlay.type === 'ENTRY_LABEL';
}

function labelForOverlay(overlay: Trace.Types.Overlays.Overlay): string|null {
  if (isTimeRangeLabel(overlay) || isEntryLabel(overlay)) {
    return overlay.label;
  }
  return null;
}

export function ariaDescriptionForOverlay(overlay: Trace.Types.Overlays.Overlay): string|null {
  if (isTimeRangeLabel(overlay)) {
    return i18nString(UIStrings.timeRange);
  }
  if (isEntriesLink(overlay)) {
    return i18nString(UIStrings.entriesLink);
  }
  if (isEntryLabel(overlay)) {
    // Don't announce an empty label
    return overlay.label.length > 0 ? i18nString(UIStrings.entryLabel) : null;
  }

  // Not an annotation overlay: ignore.
  return null;
}

export function ariaAnnouncementForModifiedEvent(event: AnnotationModifiedEvent): string|null {
  if (event.muteAriaNotifications) {
    return null;
  }
  const {overlay, action} = event;
  switch (action) {
    case 'Remove': {
      const text = ariaDescriptionForOverlay(overlay);
      if (text) {
        return (i18nString(UIStrings.srAnnotationRemoved, {PH1: text}));
      }
      break;
    }
    case 'Add': {
      const text = ariaDescriptionForOverlay(overlay);
      if (text) {
        return (i18nString(UIStrings.srAnnotationAdded, {PH1: text}));
      }
      break;
    }
    case 'UpdateLabel': {
      const label = labelForOverlay(overlay);
      if (label) {
        return i18nString(UIStrings.srLabelTextUpdated, {PH1: label});
      }
      break;
    }
    case 'UpdateTimeRange': {
      if (overlay.type !== 'TIME_RANGE') {
        return '';
      }
      const traceBounds = TraceBounds.TraceBounds.BoundsManager.instance().state()?.micro.entireTraceBounds;
      if (!traceBounds) {
        return '';
      }

      const {min, max} = overlay.bounds;
      const minText = i18n.TimeUtilities.formatMicroSecondsAsMillisFixed(
          Trace.Types.Timing.Micro(min - traceBounds.min),
      );
      const maxText =
          i18n.TimeUtilities.formatMicroSecondsAsMillisFixed(Trace.Types.Timing.Micro(max - traceBounds.min));

      return i18nString(UIStrings.srTimeRangeBoundsUpdated, {
        PH1: minText,
        PH2: maxText,
      });
    }
    case 'UpdateLinkToEntry': {
      if (isEntriesLink(overlay) && overlay.entryFrom && overlay.entryTo) {
        const from = Trace.Name.forEntry(overlay.entryFrom);
        const to = Trace.Name.forEntry(overlay.entryTo);
        return (i18nString(UIStrings.srEntriesLinked, {PH1: from, PH2: to}));
      }
      break;
    }
    case 'EnterLabelEditState': {
      return (i18nString(UIStrings.srEnterLabelEditMode));
    }
    case 'LabelBringForward': {
      break;
    }
    default:
      Platform.assertNever(action, 'Unsupported action for AnnotationModifiedEvent');
  }

  return null;
}
