// Licensed to Cloudera, Inc. under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  Cloudera, Inc. 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 { ParsedSqlStatement } from '../../parse/sqlStatementsParser';
import {
  AutocompleteParser,
  IdentifierLocation,
  ParsedLocation,
  SqlParserProvider
} from '../../parse/types';

const handleStatement = (
  statement: ParsedSqlStatement,
  locations: IdentifierLocation[],
  autocompleteParser: AutocompleteParser,
  active: boolean
) => {
  // Statement locations come in the message to the worker and are generally more accurate
  locations.push(statement as unknown as IdentifierLocation);
  try {
    const sqlParseResult = autocompleteParser.parseSql(statement.statement + ' ', '');
    if (sqlParseResult.locations) {
      sqlParseResult.locations.forEach(location => {
        location.active = active;
        // Skip statement locations from the sql parser
        if (location.type !== 'statement') {
          if (location.location.first_line === 1) {
            location.location.first_column += statement.location.first_column;
            location.location.last_column += statement.location.first_column;
          }
          location.location.first_line += statement.location.first_line - 1;
          location.location.last_line += statement.location.first_line - 1;
          locations.push(location);
        }
      });
    }
  } catch (error) {}
};

/**
 * This function turns the relative nested location into an absolute location given the statement location.
 */
const toAbsoluteLocation = (statementLocation: ParsedLocation, nestedLocation: ParsedLocation) => {
  if (nestedLocation.first_line === 1) {
    nestedLocation.first_column += statementLocation.first_column;
  }
  if (nestedLocation.last_line === 1) {
    nestedLocation.last_column += statementLocation.first_column;
  }
  const lineAdjust = statementLocation.first_line - 1;
  nestedLocation.first_line += lineAdjust;
  nestedLocation.last_line += lineAdjust;
};

export const attachSyntaxListener = (
  ctx: DedicatedWorkerGlobalScope,
  parserProvider: SqlParserProvider,
  beforeMessage?: (message: MessageEvent) => void
): void => {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  let throttle: any = -1;

  ctx.addEventListener('message', msg => {
    if (beforeMessage) {
      beforeMessage(msg);
    }
    if (msg.data.ping) {
      ctx.postMessage({ ping: true });
      return;
    }
    clearTimeout(throttle);
    throttle = setTimeout(() => {
      parserProvider.getSyntaxParser(msg.data.connector.dialect).then(parser => {
        const syntaxError = parser.parseSyntax(
          msg.data.beforeCursor,
          msg.data.afterCursor
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
        ) as any;

        if (syntaxError) {
          toAbsoluteLocation(msg.data.statementLocation, syntaxError.loc);
        }
        ctx.postMessage({
          id: msg.data.id,
          connector: msg.data.connector,
          editorChangeTime: msg.data.editorChangeTime,
          syntaxError: syntaxError,
          statementLocation: msg.data.statementLocation
        });
      });
    }, 400);
  });
};

export const attachLocationListeners = (
  ctx: DedicatedWorkerGlobalScope,
  parserProvider: SqlParserProvider,
  beforeMessage?: (message: MessageEvent) => void
): void => {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  let throttle: any = -1;

  ctx.addEventListener('message', msg => {
    if (beforeMessage) {
      beforeMessage(msg);
    }
    if (msg.data.ping) {
      ctx.postMessage({ ping: true });
      return;
    }
    clearTimeout(throttle);
    throttle = setTimeout(() => {
      if (msg.data.statementDetails) {
        parserProvider.getAutocompleteParser(msg.data.connector.dialect).then(parser => {
          let locations: IdentifierLocation[] = [];
          const activeStatementLocations: IdentifierLocation[] = [];
          msg.data.statementDetails.precedingStatements.forEach((statement: ParsedSqlStatement) => {
            handleStatement(statement, locations, parser, false);
          });
          if (msg.data.statementDetails.activeStatement) {
            handleStatement(
              msg.data.statementDetails.activeStatement,
              activeStatementLocations,
              parser,
              true
            );
            locations = locations.concat(activeStatementLocations);
          }
          msg.data.statementDetails.followingStatements.forEach((statement: ParsedSqlStatement) => {
            handleStatement(statement, locations, parser, false);
          });

          // Add databases where missing in the table identifier chains
          if (msg.data.defaultDatabase) {
            locations.forEach(location => {
              if (
                location.identifierChain &&
                location.identifierChain.length &&
                location.identifierChain[0].name
              ) {
                if (location.tables) {
                  location.tables.forEach(table => {
                    if (
                      table.identifierChain &&
                      table.identifierChain.length === 1 &&
                      table.identifierChain[0].name
                    ) {
                      table.identifierChain.unshift({ name: msg.data.defaultDatabase });
                    }
                  });
                } else if (location.type === 'table' && location.identifierChain.length === 1) {
                  location.identifierChain.unshift({ name: msg.data.defaultDatabase });
                }
              }
            });
          }

          ctx.postMessage({
            id: msg.data.id,
            connector: msg.data.connector,
            namespace: msg.data.namespace,
            compute: msg.data.compute,
            editorChangeTime: msg.data.statementDetails.editorChangeTime,
            locations: locations,
            activeStatementLocations: activeStatementLocations,
            totalStatementCount: msg.data.statementDetails.totalStatementCount,
            activeStatementIndex: msg.data.statementDetails.activeStatementIndex
          });
        });
      }
    }, 400);
  });
};
