/* This file is a part of @mdn/browser-compat-data
 * See LICENSE file for more information. */

import bcd from '../index.js';

import {
  isBrowser,
  isFeature,
  descendantKeys,
  joinPath,
} from './walkingUtils.js';
import query from './query.js';

/** @import {CompatData, CompatStatement, Identifier, BrowserStatement, ReleaseStatement} from '../types/types.js' */
/** @import {DataType} from '../types/index.js' */

/**
 * @typedef {object} BrowserReleaseWalkOutput
 * @property {string} path
 * @property {DataType} data
 * @property {BrowserStatement} browser
 * @property {ReleaseStatement} browserRelease
 */

/**
 * @typedef {object} LowLevelWalkOutput
 * @property {string} path
 * @property {DataType} data
 * @property {BrowserStatement} [browser]
 * @property {CompatStatement} [compat]
 * @property {ReleaseStatement} [browserRelease]
 */

/**
 * @typedef {object} WalkOutput
 * @property {string} path
 * @property {DataType} data
 * @property {CompatStatement} compat
 */

/**
 * Walk through the browser releases
 * @param {BrowserStatement} data The data to iterate
 * @param {string} [path] The current path
 * @yields {BrowserReleaseWalkOutput} The release info
 * @returns {IterableIterator<BrowserReleaseWalkOutput>}
 */
export function* browserReleaseWalk(data, path) {
  for (const [release, releaseData] of Object.entries(data.releases)) {
    yield {
      path: joinPath(path, 'releases', release),
      data,
      browser: data,
      browserRelease: /** @type {ReleaseStatement} */ (releaseData),
    };
  }
}

/**
 * Walk through the compatibility statements
 * @param {DataType} [data] The data to iterate
 * @param {string} [path] The current path
 * @param {number} [depth] The maximum depth to iterate
 * @yields {LowLevelWalkOutput} The feature info
 * @returns {IterableIterator<LowLevelWalkOutput>}
 */
export function* lowLevelWalk(data = bcd, path, depth = Infinity) {
  if (path !== undefined && path !== '__meta') {
    /** @type {LowLevelWalkOutput} */
    const next = {
      path,
      data,
    };

    if (isBrowser(data)) {
      next.browser = data;
      yield next;
      yield* browserReleaseWalk(data, path);
    } else {
      if (isFeature(data)) {
        next.compat = data.__compat;
      }
      yield next;
    }
  }

  if (depth > 0) {
    for (const key of descendantKeys(data)) {
      yield* lowLevelWalk(data[key], joinPath(path, key), depth - 1);
    }
  }
}

/**
 * Walk the data for compat features
 * @param {string | string[]} [entryPoints] Entry points to iterate
 * @param {CompatData | CompatStatement | Identifier} [data] The data to iterate
 * @yields {WalkOutput} The feature info
 * @returns {IterableIterator<WalkOutput>}
 */
export default function* walk(entryPoints, data = bcd) {
  /** @type {IterableIterator<LowLevelWalkOutput>[]} */
  const walkers = [];

  if (entryPoints === undefined) {
    walkers.push(lowLevelWalk(data));
  } else {
    entryPoints = Array.isArray(entryPoints) ? entryPoints : [entryPoints];
    walkers.push(
      ...entryPoints.map((entryPoint) =>
        lowLevelWalk(query(entryPoint, data), entryPoint),
      ),
    );
  }

  for (const walker of walkers) {
    for (const step of walker) {
      if (step.compat) {
        yield /** @type {WalkOutput} */ (step);
      }
    }
  }
}
