import {StringAccumulator} from 'sentry/views/starfish/utils/sqlish/formatters/stringAccumulator';
import type {Token} from 'sentry/views/starfish/utils/sqlish/types';

interface Options {
  maxLineLength?: number;
}

export function string(tokens: Token[], options: Options = {}): string {
  const accumulator = new StringAccumulator();

  let precedingNonWhitespaceToken: Token | undefined = undefined;
  let parenthesisLevel: number = 0; // Tracks the current parenthesis nesting level
  const indentationLevels: number[] = []; // Tracks the parenthesis nesting levels at which we've incremented the indentation

  function contentize(token: Token): void {
    if (Array.isArray(token.content)) {
      token.content.forEach(contentize);
      return;
    }

    if (token.type === 'LeftParenthesis') {
      parenthesisLevel += 1;
      accumulator.add('(');

      // If the previous legible token is a meaningful keyword that triggers a
      // newline, increase the current indentation level and note the parenthesis level where this happened
      if (
        typeof precedingNonWhitespaceToken?.content === 'string' &&
        PARENTHESIS_NEWLINE_KEYWORDS.has(precedingNonWhitespaceToken.content)
      ) {
        accumulator.break();

        accumulator.indent();
        indentationLevels.push(parenthesisLevel);
      }
    }

    if (token.type === 'RightParenthesis') {
      // If this right parenthesis closes a left parenthesis at a level where
      // we incremented the indentation, decrement the indentation
      if (indentationLevels.at(-1) === parenthesisLevel) {
        accumulator.break();
        accumulator.unindent();
        indentationLevels.pop();
      }

      parenthesisLevel -= 1;
      accumulator.add(')');
    }

    if (typeof token.content === 'string') {
      if (token.type === 'Keyword' && NEWLINE_KEYWORDS.has(token.content)) {
        if (!accumulator.lastLine.isEmpty) {
          accumulator.break();
        }

        accumulator.add(token.content);
      } else if (token.type === 'Whitespace') {
        // Convert all whitespace to single spaces
        accumulator.space();
      } else if (['LeftParenthesis', 'RightParenthesis'].includes(token.type)) {
        // Parenthesis contents are appended above, so we can skip them here
      } else {
        accumulator.add(token.content);
      }
    }

    if (token.type !== 'Whitespace') {
      precedingNonWhitespaceToken = token;
    }

    return;
  }

  tokens.forEach(contentize);
  return accumulator.toString(options.maxLineLength);
}

// Keywords that always trigger a newline
const NEWLINE_KEYWORDS = new Set([
  'DELETE',
  'FROM',
  'GROUP',
  'INNER',
  'INSERT',
  'LEFT',
  'LIMIT',
  'OFFSET',
  'ORDER',
  'RETURNING',
  'RIGHT',
  'SELECT',
  'VALUES',
  'WHERE',
]);

// Keywords that may or may not trigger a newline, but they always trigger a newlines if followed by a parenthesis
const PARENTHESIS_NEWLINE_KEYWORDS = new Set([...NEWLINE_KEYWORDS, ...['IN']]);
