// 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 Platform from '../../core/platform/platform.js';

import {ContentData} from './ContentData.js';
import type {DeferredContent} from './ContentProvider.js';

interface FunctionBodyOffset {
  start: number;
  end: number;
}

/**
 * Metadata to map between bytecode #offsets and line numbers in the
 * disassembly for WebAssembly modules.
 */
export class WasmDisassembly extends ContentData {
  readonly lines: string[];
  readonly #offsets: number[];
  #functionBodyOffsets: FunctionBodyOffset[];

  // Wasm can be potentially very large, so we calculate `text' lazily.
  #cachedText?: string;

  constructor(lines: string[], offsets: number[], functionBodyOffsets: FunctionBodyOffset[]) {
    super('', /* isBase64 */ false, 'text/x-wast', 'utf-8');
    if (lines.length !== offsets.length) {
      throw new Error('Lines and offsets don\'t match');
    }
    this.lines = lines;
    this.#offsets = offsets;
    this.#functionBodyOffsets = functionBodyOffsets;
  }

  override get text(): string {
    if (typeof this.#cachedText === 'undefined') {
      this.#cachedText = this.lines.join('\n');
    }
    return this.#cachedText;
  }

  override get isEmpty(): boolean {
    // Don't trigger unnecessary concatenating. Only check whether we have no lines, or a single empty line.
    return this.lines.length === 0 || (this.lines.length === 1 && this.lines[0].length === 0);
  }

  get lineNumbers(): number {
    return this.#offsets.length;
  }

  bytecodeOffsetToLineNumber(bytecodeOffset: number): number {
    return Platform.ArrayUtilities.upperBound(
               this.#offsets, bytecodeOffset, Platform.ArrayUtilities.DEFAULT_COMPARATOR) -
        1;
  }

  lineNumberToBytecodeOffset(lineNumber: number): number {
    return this.#offsets[lineNumber];
  }

  /**
   * returns an iterable enumerating all the non-breakable line numbers in the disassembly
   */
  * nonBreakableLineNumbers(): Iterable<number> {
    let lineNumber = 0;
    let functionIndex = 0;
    while (lineNumber < this.lineNumbers) {
      if (functionIndex < this.#functionBodyOffsets.length) {
        const offset = this.lineNumberToBytecodeOffset(lineNumber);
        if (offset >= this.#functionBodyOffsets[functionIndex].start) {
          lineNumber = this.bytecodeOffsetToLineNumber(this.#functionBodyOffsets[functionIndex++].end) + 1;
          continue;
        }
      }
      yield lineNumber++;
    }
  }

  /**
   * @deprecated Used during migration from `DeferredContent` to `ContentData`.
   */
  override asDeferedContent(): DeferredContent {
    return {content: '', isEncoded: false, wasmDisassemblyInfo: this};
  }
}
