import { LoadStrategy, PopulatePath, ReferenceKind } from '../enums.js';
import { Utils } from '../utils/Utils.js';
/**
 * Expands `books.perex` like populate to use `children` array instead of the dot syntax
 */
function expandNestedPopulate(parentProp, parts, strategy, all) {
  const meta = parentProp.targetMeta;
  const field = parts.shift();
  const prop = meta.properties[field];
  const ret = { field, strategy, all };
  if (parts.length > 0) {
    ret.children = [expandNestedPopulate(prop, parts, strategy)];
  }
  return ret;
}
/**
 * @internal
 */
export function expandDotPaths(meta, populate, normalized = false) {
  const ret = normalized
    ? populate
    : Utils.asArray(populate).map(field => {
        if (typeof field === 'string') {
          return { field };
        }
        /* v8 ignore next */
        return typeof field === 'boolean' || field.field === PopulatePath.ALL
          ? { all: !!field, field: meta.primaryKeys[0] }
          : field;
      });
  for (const p of ret) {
    if (!p.field.includes('.')) {
      continue;
    }
    const [f, ...parts] = p.field.split('.');
    p.field = f;
    p.children ??= [];
    const prop = meta.properties[p.field];
    if (parts[0] === PopulatePath.ALL) {
      prop.targetMeta.props
        .filter(prop => prop.lazy || prop.kind !== ReferenceKind.SCALAR)
        .forEach(prop => p.children.push({ field: prop.name, strategy: p.strategy }));
    } else if (prop.kind === ReferenceKind.EMBEDDED) {
      const embeddedProp = Object.values(prop.embeddedProps).find(c => c.embedded[1] === parts[0]);
      ret.push({
        ...p,
        field: embeddedProp.name,
        children: parts.length > 1 ? [expandNestedPopulate(embeddedProp, parts.slice(1), p.strategy, p.all)] : [],
      });
      p.children.push(expandNestedPopulate(prop, parts, p.strategy, p.all));
    } else {
      p.children.push(expandNestedPopulate(prop, parts, p.strategy, p.all));
    }
  }
  return ret;
}
/**
 * Returns the loading strategy based on the provided hint.
 * If `BALANCED` strategy is used, it will return JOINED if the property is a to-one relation.
 * @internal
 */
export function getLoadingStrategy(strategy, kind) {
  if (strategy === LoadStrategy.BALANCED) {
    return [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(kind)
      ? LoadStrategy.JOINED
      : LoadStrategy.SELECT_IN;
  }
  return strategy;
}
function findPopulateEntry(populate, parts) {
  let current = populate;
  for (let i = 0; i < parts.length; i++) {
    const entry = current.find(p => p.field.split(':')[0] === parts[i]);
    if (!entry) {
      return undefined;
    }
    if (i === parts.length - 1) {
      return entry;
    }
    current = entry.children ?? [];
  }
  /* v8 ignore next */
  return undefined;
}
/**
 * Applies per-relation overrides from `populateHints` to the normalized populate tree.
 * @internal
 */
export function applyPopulateHints(populate, hints) {
  for (const [path, hint] of Object.entries(hints)) {
    const entry = findPopulateEntry(populate, path.split('.'));
    if (!entry) {
      continue;
    }
    if (hint.strategy != null) {
      entry.strategy = hint.strategy;
    }
    if (hint.joinType != null) {
      entry.joinType = hint.joinType;
    }
  }
}
