// Copyright 2018-2026 the Deno authors. MIT license.
// This module is browser compatible.
import { resolveOptions, titleCaseSegment } from "./_title_case_util.ts";
import type { BaseTitleCaseOptions } from "./_title_case_util.ts";
export type { BaseTitleCaseOptions };
export type { TrailingCase } from "./_title_case_util.ts";

/**
 * A function that filters words in the string. If a word returns `true` from this function, it will not be title-cased.
 * @param value The word to be filtered.
 * @param index The index of the word in the array.
 * @param array The array of words.
 * @returns `true` if the word should be excluded from title casing, `false` otherwise.
 */
export type ExcludeWordFilter = (
  value: Intl.SegmentData,
  index: number,
  array: Intl.SegmentData[],
) => boolean;

/**
 * A filter function or array of stop words to exclude them from title casing,
 * or multiple filter functions and stop word arrays to combine for filtering.
 */
export type ExcludeWordConfig =
  | ExcludeWordFilter
  | readonly string[]
  | (ExcludeWordFilter | readonly string[])[];

/** Options for {@linkcode toTitleCase} */
export interface TitleCaseOptions extends BaseTitleCaseOptions {
  /**
   * A filter function or array of stop words to exclude them from title casing,
   * or multiple filter functions and stop word arrays to combine for filtering.
   *
   * @default {() => false} (no filtering)
   */
  exclude?: ExcludeWordConfig;
}

/**
 * Converts a string into Title Case.
 *
 * > [!NOTE]
 * > This function preserves punctuation and does not insert spaces or other
 * > characters where none exist in the input (e.g. it doesn't split up
 * > `camelCase` words). This is in contrast to some other `to{X}Case`
 * > functions, such as `toSnakeCase`.
 *
 * @experimental **UNSTABLE**: New API, yet to be vetted.
 *
 * @param input The string that is going to be converted into Title Case
 * @param options Optional settings to customize the conversion
 * @returns The string as Title Case
 *
 * @example Usage
 * ```ts
 * import { toTitleCase } from "@std/text/unstable-to-title-case";
 * import { assertEquals } from "@std/assert/equals";
 *
 * assertEquals(toTitleCase("deno is awesome"), "Deno Is Awesome");
 * ```
 */
export function toTitleCase(input: string, options?: TitleCaseOptions): string {
  const opts = resolveOptions(options);
  // [3.13.2 Default Case Conversion `toTitlecase`](https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/#G34078)
  // toTitlecase(X): Find the word boundaries in X according to Unicode Standard Annex #29, “Unicode Text Segmentation.”
  const segments = [...opts.words.segment(input)];
  const words = segments.filter((x) => x.isWordLike);
  const exclude = toExcludeFilter(options?.exclude);
  let out = "";
  let i = 0;

  for (const s of segments) {
    if (s.isWordLike) {
      out += !exclude(s, i++, words)
        ? titleCaseSegment(s.segment, opts)
        : opts.trailingCase === "lower"
        ? s.segment.toLocaleLowerCase(opts.locale)
        : s.segment;
    } else {
      out += s.segment;
    }
  }

  return out;
}

function excludeStopWords(stopWords: readonly string[]): ExcludeWordFilter {
  const words = new Set(stopWords);
  return (s, i, w) => i !== 0 && i !== w.length - 1 && words.has(s.segment);
}

function toExcludeFilter(exclude?: ExcludeWordConfig): ExcludeWordFilter {
  if (exclude == null) return () => false;
  if (typeof exclude === "function") return exclude;
  if (isStrings(exclude)) return excludeStopWords(exclude);

  const filters = exclude
    .map((x) => typeof x === "function" ? x : excludeStopWords(x));

  return (s, i, w) => {
    for (const filter of filters) {
      if (filter(s, i, w)) return true;
    }
    return false;
  };
}

function isStrings(x: readonly unknown[]): x is readonly string[] {
  return typeof x[0] === "string";
}
