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

import {
  pierceQuerySelectorAll,
} from '../../../../third_party/puppeteer/package/lib/puppeteer/injected/PierceQuerySelector.js';

import {
  findMinMax,
  type QueryableNode,
  type RangeOps,
  SelectorRangeOps,
} from './CSSSelector.js';
import type {Selector} from './Selector.js';

class PierceSelectorRangeOpts implements RangeOps<QueryableNode, string[][]> {
  #selector: string[][] = [[]];
  #attributes: string[];
  #depth = 0;

  constructor(attributes: string[] = []) {
    this.#attributes = attributes;
  }

  inc(node: Node): Document|ShadowRoot {
    return node.getRootNode() as Document | ShadowRoot;
  }
  self(node: Document|ShadowRoot): QueryableNode {
    return node instanceof ShadowRoot ? node.host : node;
  }
  valueOf(node: QueryableNode): string[][] {
    const selector = findMinMax(
        [node, node.getRootNode()],
        new SelectorRangeOps(this.#attributes),
    );
    if (this.#depth > 1) {
      this.#selector.unshift([selector]);
    } else {
      this.#selector[0].unshift(selector);
    }
    this.#depth = 0;
    return this.#selector;
  }
  gte(selector: string[][], node: Node): boolean {
    ++this.#depth;
    // Note we use some insider logic here. `valueOf(node)` will always
    // correspond to `selector.flat().slice(1)`, so it suffices to check
    // uniqueness for `selector.flat()[0]`.
    return pierceQuerySelectorAll(node, selector[0][0]).length === 1;
  }
}

/**
 * Computes the pierce CSS selector for a node.
 *
 * @internal
 * @param node The node to compute.
 * @returns The computed pierce CSS selector.
 *
 */
export const computePierceSelector = (
    node: Node,
    attributes?: string[],
    ): string[]|undefined => {
  try {
    const ops = new PierceSelectorRangeOpts(attributes);
    return findMinMax([node, document], ops).flat();
  } catch {
    return undefined;
  }
};

export const queryPierceSelectorAll = (selectors: Selector): Element[] => {
  if (typeof selectors === 'string') {
    selectors = [selectors];
  } else if (selectors.length === 0) {
    return [];
  }
  let lists: Element[][] = [[document.documentElement]];
  do {
    const selector = selectors.shift() as string;
    const roots: Element[][] = [];
    for (const nodes of lists) {
      for (const node of nodes) {
        const list = pierceQuerySelectorAll(node.shadowRoot ?? node, selector);
        if (list.length > 0) {
          roots.push(list);
        }
      }
    }
    lists = roots;
  } while (selectors.length > 0 && lists.length > 0);
  return lists.flatMap(list => [...list]);
};
