/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * The OpenSearch Contributors require contributions made to
 * this file be licensed under the Apache-2.0 license or a
 * compatible open source license.
 *
 * Modifications Copyright OpenSearch Contributors. See
 * GitHub history for details.
 */

/*
 * Licensed to Elasticsearch B.V. under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch B.V. licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import React, { Fragment, HTMLAttributes, FunctionComponent } from 'react';
import { CommonProps } from '../common';
import { OuiMark } from '../mark';

interface OuiHighlightChunk {
  /**
   * Start of the chunk
   */
  start: number;
  /**
   * End of the chunk
   */
  end: number;
  /**
   * Whether to highlight chunk or not
   */
  highlight?: boolean;
}

export type OuiHighlightProps = HTMLAttributes<HTMLSpanElement> &
  CommonProps & {
    /**
     * string to highlight as this component's content
     */
    children: string;

    /**
     * What to search for
     */
    search: string;

    /**
     * Should the search be strict or not
     */
    strict?: boolean;

    /**
     * Should highlight all matches
     */
    highlightAll?: boolean;
  };

const highlight = (
  searchSubject: string,
  searchValue: string,
  isStrict: boolean,
  highlightAll: boolean
) => {
  if (!searchValue) {
    return searchSubject;
  }

  if (!searchSubject) {
    return null;
  }

  if (highlightAll) {
    const chunks = getHightlightWords(searchSubject, searchValue, isStrict);
    return (
      <Fragment>
        {chunks.map((chunk) => {
          const { end, highlight, start } = chunk;
          const value = searchSubject.substr(start, end - start);
          if (highlight) {
            return <OuiMark key={start}>{value}</OuiMark>;
          }
          return value;
        })}
      </Fragment>
    );
  }

  const normalizedSearchSubject: string = isStrict
    ? searchSubject
    : searchSubject.toLowerCase();
  const normalizedSearchValue: string = isStrict
    ? searchValue
    : searchValue.toLowerCase();

  const indexOfMatch: number = normalizedSearchSubject.indexOf(
    normalizedSearchValue
  );
  if (indexOfMatch === -1) {
    return searchSubject;
  }

  const preMatch: string = searchSubject.substr(0, indexOfMatch);
  const match: string = searchSubject.substr(indexOfMatch, searchValue.length);
  const postMatch: string = searchSubject.substr(
    indexOfMatch + searchValue.length
  );

  return (
    <Fragment>
      {preMatch}
      <OuiMark>{match}</OuiMark>
      {postMatch}
    </Fragment>
  );
};

const getHightlightWords = (
  searchSubject: string,
  searchValue: string,
  isStrict: boolean
) => {
  const regex = new RegExp(searchValue, isStrict ? 'g' : 'gi');
  const matches = [];
  let match;
  while ((match = regex.exec(searchSubject)) !== null) {
    matches.push({
      start: match.index,
      end: (match.index || 0) + match[0].length,
    });
  }
  return fillInChunks(matches, searchSubject.length);
};

const fillInChunks = (
  chunksToHighlight: OuiHighlightChunk[],
  totalLength: number
) => {
  const allChunks: OuiHighlightChunk[] = [];
  const append = (start: number, end: number, highlight: boolean) => {
    if (end - start > 0) {
      allChunks.push({
        start,
        end,
        highlight,
      });
    }
  };
  if (chunksToHighlight.length === 0) {
    append(0, totalLength, false);
  } else {
    let lastIndex = 0;
    chunksToHighlight.forEach((chunk) => {
      append(lastIndex, chunk.start, false);
      append(chunk.start, chunk.end, true);
      lastIndex = chunk.end;
    });
    append(lastIndex, totalLength, false);
  }
  return allChunks;
};

export const OuiHighlight: FunctionComponent<OuiHighlightProps> = ({
  children,
  className,
  search,
  strict = false,
  highlightAll = false,
  ...rest
}) => {
  return (
    <span className={className} {...rest}>
      {highlight(children, search, strict, highlightAll)}
    </span>
  );
};
