// 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 * as Platform from '../../core/platform/platform.js';

export class SearchConfig {
  readonly #query: string;
  readonly #ignoreCase: boolean;
  readonly #isRegex: boolean;
  readonly #queries: string[];
  readonly #fileRegexQueries: RegexQuery[];

  constructor(query: string, ignoreCase: boolean, isRegex: boolean) {
    this.#query = query;
    this.#ignoreCase = ignoreCase;
    this.#isRegex = isRegex;

    const {queries, fileRegexQueries} = SearchConfig.#parse(query, ignoreCase, isRegex);
    this.#queries = queries;
    this.#fileRegexQueries = fileRegexQueries;
  }

  static fromPlainObject(object: {
    query: string,
    ignoreCase: boolean,
    isRegex: boolean,
  }): SearchConfig {
    return new SearchConfig(object.query, object.ignoreCase, object.isRegex);
  }

  filePathMatchesFileQuery(
      filePath: Platform.DevToolsPath.RawPathString|Platform.DevToolsPath.EncodedPathString|
      Platform.DevToolsPath.UrlString): boolean {
    return this.#fileRegexQueries.every(({regex, shouldMatch}) => (Boolean(filePath.match(regex)) === shouldMatch));
  }

  queries(): string[] {
    return this.#queries;
  }

  query(): string {
    return this.#query;
  }

  ignoreCase(): boolean {
    return this.#ignoreCase;
  }

  isRegex(): boolean {
    return this.#isRegex;
  }

  toPlainObject(): {
    query: string,
    ignoreCase: boolean,
    isRegex: boolean,
  } {
    return {query: this.query(), ignoreCase: this.ignoreCase(), isRegex: this.isRegex()};
  }

  static #parse(query: string, ignoreCase: boolean, isRegex: boolean):
      {queries: string[], fileRegexQueries: RegexQuery[]} {
    // Inside double quotes: any symbol except double quote and backslash or any symbol escaped with a backslash.
    const quotedPattern = /"([^\\"]|\\.)+"/;
    // A word is a sequence of any symbols except space and backslash or any symbols escaped with a backslash, that does not start with file:.
    const unquotedWordPattern = /(\s*(?!-?f(ile)?:)[^\\ ]|\\.)+/;
    const unquotedPattern = unquotedWordPattern.source + '(\\s+' + unquotedWordPattern.source + ')*';

    const pattern = [
      '(\\s*' + FilePatternRegex.source + '\\s*)',
      '(' + quotedPattern.source + ')',
      '(' + unquotedPattern + ')',
    ].join('|');
    const regexp = new RegExp(pattern, 'g');
    const queryParts = query.match(regexp) || [];

    const queries: string[] = [];
    const fileRegexQueries: RegexQuery[] = [];

    for (const queryPart of queryParts) {
      if (!queryPart) {
        continue;
      }
      const fileQuery = SearchConfig.#parseFileQuery(queryPart);
      if (fileQuery) {
        const regex = new RegExp(fileQuery.text, ignoreCase ? 'i' : '');
        fileRegexQueries.push({regex, shouldMatch: fileQuery.shouldMatch});
      } else if (isRegex) {
        queries.push(queryPart);
      } else if (queryPart.startsWith('"') && queryPart.endsWith('"')) {
        queries.push(SearchConfig.#parseQuotedQuery(queryPart));
      } else {
        queries.push(SearchConfig.#parseUnquotedQuery(queryPart));
      }
    }

    return {queries, fileRegexQueries};
  }

  static #parseUnquotedQuery(query: string): string {
    return query.replace(/\\(.)/g, '$1');
  }

  static #parseQuotedQuery(query: string): string {
    return query.substring(1, query.length - 1).replace(/\\(.)/g, '$1');
  }

  static #parseFileQuery(query: string): QueryTerm|null {
    const match = query.match(FilePatternRegex);
    if (!match) {
      return null;
    }
    query = match[3];
    let result = '';
    for (let i = 0; i < query.length; ++i) {
      const char = query[i];
      if (char === '*') {
        result += '.*';
      } else if (char === '\\') {
        ++i;
        const nextChar = query[i];
        if (nextChar === ' ') {
          result += ' ';
        }
      } else {
        if (Platform.StringUtilities.regexSpecialCharacters().indexOf(query.charAt(i)) !== -1) {
          result += '\\';
        }
        result += query.charAt(i);
      }
    }
    const shouldMatch = !Boolean(match[1]);
    return {text: result, shouldMatch};
  }
}

// After file: prefix: any symbol except space and backslash or any symbol escaped with a backslash.
const FilePatternRegex = /(-)?f(ile)?:((?:[^\\ ]|\\.)+)/;

interface QueryTerm {
  text: string;
  shouldMatch: boolean;
}

interface RegexQuery {
  regex: RegExp;
  shouldMatch: boolean;
}
