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

import type * as Platform from '../../core/platform/platform.js';
import type * as Protocol from '../../generated/protocol.js';

export class ProfileNode {
  callFrame: Protocol.Runtime.CallFrame;
  callUID: string;
  self: number;
  total: number;
  id: number;
  parent: ProfileNode|null;
  children: this[];
  originalFunctionName: string|null = null;
  depth!: number;
  deoptReason!: string|null;
  constructor(callFrame: Protocol.Runtime.CallFrame) {
    this.callFrame = callFrame;
    this.callUID = `${callFrame.functionName}@${callFrame.scriptId}:${callFrame.lineNumber}:${callFrame.columnNumber}`;
    this.self = 0;
    this.total = 0;
    this.id = 0;
    this.parent = null;
    this.children = [];
  }

  get scriptId(): Protocol.Runtime.ScriptId {
    return String(this.callFrame.scriptId) as Protocol.Runtime.ScriptId;
  }

  get url(): Platform.DevToolsPath.UrlString {
    return this.callFrame.url as Platform.DevToolsPath.UrlString;
  }

  get lineNumber(): number {
    return this.callFrame.lineNumber;
  }

  get columnNumber(): number {
    return this.callFrame.columnNumber;
  }

  get functionName(): string {
    return this.originalFunctionName ?? this.callFrame.functionName;
  }

  setOriginalFunctionName(name: string|null): void {
    this.originalFunctionName = name;
  }
}

export class ProfileTreeModel {
  root!: ProfileNode;
  total!: number;
  maxDepth!: number;

  initialize(root: ProfileNode): void {
    this.root = root;
    this.assignDepthsAndParents();
    this.total = this.calculateTotals(this.root);
  }

  private assignDepthsAndParents(): void {
    const root = this.root;
    root.depth = -1;
    root.parent = null;
    this.maxDepth = 0;
    const nodesToTraverse = [root];
    while (nodesToTraverse.length) {
      const parent = (nodesToTraverse.pop() as ProfileNode);
      const depth = parent.depth + 1;
      if (depth > this.maxDepth) {
        this.maxDepth = depth;
      }
      const children = parent.children;
      for (const child of children) {
        child.depth = depth;
        child.parent = parent;
        nodesToTraverse.push(child);
      }
    }
  }

  private calculateTotals(root: ProfileNode): number {
    const nodesToTraverse = [root];
    const dfsList = [];
    while (nodesToTraverse.length) {
      const node = (nodesToTraverse.pop() as ProfileNode);
      node.total = node.self;
      dfsList.push(node);
      nodesToTraverse.push(...node.children);
    }
    while (dfsList.length > 1) {
      const node = (dfsList.pop() as ProfileNode);
      if (node.parent) {
        node.parent.total += node.total;
      }
    }
    return root.total;
  }
}
