/**
 * Module: API for RSLanguage.
 */

import { type Tree } from '@lezer/common';

import { cursorNode } from '@/utils/codemirror';
import { PARAMETER } from '@/utils/constants';
import { type RO } from '@/utils/meta';

import { CstType, type IRSErrorDescription, type RSErrorType } from '../backend/types';
import { labelCstTypification } from '../labels';

import { type IRSForm } from './rsform';
import { type AliasMapping, type IArgumentValue, RSErrorClass, type SyntaxTree } from './rslang';

// cspell:disable
const LOCALS_REGEXP = /[_a-zα-ω][a-zα-ω]*\d*/g;
const GLOBALS_REGEXP = /[XCSADFPTN]\d+/g;
const COMPLEX_SYMBOLS_REGEXP = /[∀∃×ℬ;|:]/g;
const TYPIFICATION_SET = /^ℬ+\([ℬ\(X\d+\)×]*\)$/g;
// cspell:enable

/** Extracts global variable names from a given expression. */
export function extractGlobals(expression: string): Set<string> {
  return new Set(expression.match(GLOBALS_REGEXP) ?? []);
}

/** Check if expression is simple derivation. */
export function isSimpleExpression(text: string): boolean {
  return !text.match(COMPLEX_SYMBOLS_REGEXP);
}

/** Check if expression is set typification. */
export function isSetTypification(text: string): boolean {
  return !!text.match(TYPIFICATION_SET);
}

/** Infers type of constituent for a given template and arguments. */
export function inferTemplatedType(templateType: CstType, args: RO<IArgumentValue[]>): CstType {
  if (args.length === 0 || args.some(arg => !arg.value)) {
    return templateType;
  } else if (templateType === CstType.PREDICATE) {
    return CstType.AXIOM;
  } else {
    return CstType.TERM;
  }
}

/**
 * Splits a string containing a template definition into its head and body parts.
 *
 * A template definition is expected to have the following format: `[head] body`.
 * If the input string does not contain the opening square bracket '[', the entire
 * string is treated as the body, and an empty string is assigned to the head.
 * If the opening bracket is present, the function attempts to find the matching
 * closing bracket ']' to determine the head and body parts.
 *
 * @example
 * const template = '[header] body content';
 * const result = splitTemplateDefinition(template);
 * // result: `{ head: 'header', body: 'body content' }`
 */
export function splitTemplateDefinition(target: string) {
  let start = 0;
  for (; start < target.length && target[start] !== '['; ++start);
  if (start < target.length) {
    for (let counter = 0, end = start + 1; end < target.length; ++end) {
      if (target[end] === '[') {
        ++counter;
      } else if (target[end] === ']') {
        if (counter !== 0) {
          --counter;
        } else {
          return {
            head: target.substring(start + 1, end).trim(),
            body: target.substring(end + 1).trim()
          };
        }
      }
    }
  }
  return {
    head: '',
    body: target
  };
}

/**
 * Substitutes values for template arguments in a given expression.
 *
 * This function takes an input mathematical expression and a list of argument values.
 * It replaces template argument placeholders in the expression with their corresponding values
 * from the provided arguments.
 */
export function substituteTemplateArgs(expression: string, args: RO<IArgumentValue[]>): string {
  if (args.every(arg => !arg.value)) {
    return expression;
  }

  const mapping: AliasMapping = {};
  args
    .filter(arg => !!arg.value)
    .forEach(arg => {
      mapping[arg.alias] = arg.value!;
    });

  let { head, body } = splitTemplateDefinition(expression);
  body = applyPattern(body, mapping, LOCALS_REGEXP);
  const argTexts = head.split(',').map(text => text.trim());
  head = argTexts
    .filter(arg => [...arg.matchAll(LOCALS_REGEXP)].every(local => local.every(match => !(match in mapping))))
    .join(', ');

  if (!head) {
    return body;
  } else {
    return `[${head}] ${body}`;
  }
}

/**
 * Generate ErrorID label.
 */
export function getRSErrorPrefix(error: RO<IRSErrorDescription>): string {
  const id = error.errorType.toString(16);
  // prettier-ignore
  switch(inferErrorClass(error.errorType)) {
    case RSErrorClass.LEXER: return 'L' + id;
    case RSErrorClass.PARSER: return 'P' + id;
    case RSErrorClass.SEMANTIC: return 'S' + id;
    case RSErrorClass.UNKNOWN: return 'U' + id;
  }
}

/** Apply alias mapping. */
export function applyAliasMapping(target: string, mapping: RO<AliasMapping>): string {
  return applyPattern(target, mapping, GLOBALS_REGEXP);
}

/** Apply alias typification mapping. */
export function applyTypificationMapping(target: string, mapping: RO<AliasMapping>): string {
  const modified = applyAliasMapping(target, mapping);
  if (modified === target) {
    return target;
  }

  const deleteBrackets: number[] = [];
  const positions: number[] = [];
  const booleans: number[] = [];
  let boolCount: number = 0;
  let stackSize: number = 0;

  for (let i = 0; i < modified.length; i++) {
    const char = modified[i];
    if (char === 'ℬ') {
      boolCount++;
      continue;
    }
    if (char === '(') {
      stackSize++;
      positions.push(i);
      booleans.push(boolCount);
    }
    boolCount = 0;
    if (char === ')') {
      if (
        i < modified.length - 1 &&
        modified[i + 1] === ')' &&
        stackSize > 1 &&
        positions[stackSize - 2] + booleans[stackSize - 1] + 1 === positions[stackSize - 1]
      ) {
        deleteBrackets.push(i);
        deleteBrackets.push(positions[stackSize - 2]);
      }
      if (i === modified.length - 1 && stackSize === 1 && positions[0] === 0) {
        deleteBrackets.push(i);
        deleteBrackets.push(positions[0]);
      }
      stackSize--;
      positions.pop();
      booleans.pop();
    }
  }

  let result = '';
  for (let i = 0; i < modified.length; i++) {
    if (!deleteBrackets.includes(i)) {
      result += modified[i];
    }
  }

  return result;
}

/** Transform Tree to {@link SyntaxTree}. */
export function transformAST(tree: Tree): SyntaxTree {
  const result: SyntaxTree = [];
  const parents: number[] = [];
  const cursor = tree.cursor();
  let finished = false;
  let leave = true;
  while (!finished) {
    let node = cursorNode(cursor);
    node.isLeaf = !cursor.firstChild();

    leave = true;
    result.push({
      uid: result.length,
      parent: parents.length > 0 ? parents[parents.length - 1] : result.length,
      typeID: node.type.id,
      start: node.from,
      finish: node.to,
      data: {
        dataType: 'string',
        value: node.type.name == '⚠' ? PARAMETER.errorNodeLabel : node.type.name
      }
    });
    parents.push(result.length - 1);

    if (!node.isLeaf) continue;

    for (;;) {
      node = cursorNode(cursor, node.isLeaf);
      if (leave) {
        parents.pop();
      }

      leave = cursor.type.isAnonymous;
      node.isLeaf = false;
      if (cursor.nextSibling()) {
        break;
      }
      if (!cursor.parent()) {
        finished = true;
        break;
      }
      leave = true;
    }
  }
  return result;
}

export function generatePrompt(schema: IRSForm): string {
  const intro =
    'Концептуальная схема — это формализованная модель предметной области, выраженная с помощью языка родов структур, основанного на аппарате формальной логики и теории множеств, и дополненная естественно-языковыми пояснениями. Она представляет собой систему взаимосвязанных определений, где каждое понятие или утверждение задаётся в строгом формате Обозначение - "Типизация" - "Термин" - "Определение в языке родов структур" - "Определение в естественном языке" - "Конвенция или комментарий".\nОбозначение — уникальный идентификатор понятия (например, X1, S3, F14).\nТипизация — структура элементов множества, моделирующего данное понятие (например, ℬ(X1) для подмножества индивидов или ℬ(X1×X1) для бинарных отношений или ℬ(X1) 🠔 [ℬ(X1×ℬ(X1))] — для терм-функции).\nТермин — название понятия в естественном языке.\nКонвенция описывает неопределяемые понятия предметным языком, включая уточнения, ограничения или примеры, включая ссылки на внешние данные (например, документы).\nВ формальном выражении используется ряд формальных конструкций.\npr1(α) — малая проекция, возвращающая компонент кортежа, соответствующий индексу.\nPr2,1(S1) — большая проекция, которая превращает множество кортежей в новую структуру в соответствии с набором индексов. В данном примере элементы S1 превращаются в пары, где на 1 месте стоит второй компонент, а на 2 месте — первый.\nFi1[S1](S2) — фильтр, который по заданным в квадратных скобках параметрам отбирает из множества кортежей в круглых скобках подмножество такое, что соответствующие заданным индексам компоненты кортежей из S2 включены в соответствующий параметр в квадратных скобках.\nD{ξ∈X1 | P1[ξ]} — декларативное объявление, которое позволяет задать область определения (слева от вертикальной черты) и логическое выражение (справа от черты), которое используется для отбора подмножества из области определения.\nI{(σ, γ) | σ:∈X1; γ:=F1[σ]; P1[σ, γ]} — императивное определения, задаваемое вычисляемым выражением элемента (слева от черты) и последовательностью действия (справа от черты), позволяющих сформировать необходимые для вычисления выражения переменные. Действия разделены точкой с запятой и представлены 3 видами: перебор элементов множества через :∈, вычисление выражения и присвоения значения переменной через := и логического блока, проверяющего некоторое условие, аналогично декларативному определению.\ndebool — преобразует одноэлементное множество в элемент (например, {x} → x)\nR{ξ:=D1 | F1[ξ]≠∅ | ξ∪F1[ξ]} — рекурсивное (циклическое) определение, состоящее из трех (иногда двух) блоков, разделенных вертикальной чертой. Первый блок — определение начального значения. Второй блок — условие продолжения цикла. Третий блок — следующее значение выражение вычисленное через текущее значение. Второй блок может быть пропущен, поскольку по умолчанию цикл останавливается если следующее значение равно текущему.\n------------\nДалее приведена концептуальная схема, описывающая некоторую предметную область.\n';
  const outro =
    '\n------\nПри ответе на следующий вопрос используй представленные в концептуальной схеме понятия и определения.\n';

  let body = `Название концептуальной схемы: ${schema.title}\n`;
  body += `[${schema.alias}] Описание: "${schema.description}"\n\n`;
  body += 'Понятия:\n';
  schema.items.forEach(item => {
    body += `${item.alias} - "${labelCstTypification(item)}" - "${item.term_resolved}" - "${
      item.definition_formal
    }" - "${item.definition_resolved}" - "${item.convention}"\n`;
  });
  return `${intro} ${body} ${outro}`;
}

// ====== Internals =========
/** Text substitution guided by mapping and regular expression. */
function applyPattern(text: string, mapping: AliasMapping, pattern: RegExp): string {
  if (text === '' || pattern === null) {
    return text;
  }
  let posInput = 0;
  let output = '';
  const patternMatches = text.matchAll(pattern);
  for (const segment of patternMatches) {
    const entity = segment[0];
    const start = segment.index ?? 0;
    if (entity in mapping) {
      output += text.substring(posInput, start);
      output += mapping[entity];
      posInput = start + segment[0].length;
    }
  }
  output += text.substring(posInput);
  return output;
}

const ERROR_LEXER_MASK = 512;
const ERROR_PARSER_MASK = 1024;
const ERROR_SEMANTIC_MASK = 2048;

/** Infers error class from error type (code). */
function inferErrorClass(error: RSErrorType): RSErrorClass {
  if ((error & ERROR_LEXER_MASK) !== 0) {
    return RSErrorClass.LEXER;
  } else if ((error & ERROR_PARSER_MASK) !== 0) {
    return RSErrorClass.PARSER;
  } else if ((error & ERROR_SEMANTIC_MASK) !== 0) {
    return RSErrorClass.SEMANTIC;
  } else {
    return RSErrorClass.UNKNOWN;
  }
}
