// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
/**
 * CodeMirror SQL 语法提示助手（hint: "sql"）
 * 支持 SQL 表名、列名（含别名）、关键词补全，依赖 SQL 语法模式和自定义表结构
 * 依赖 SQL 语法模式（../../mode/sql/sql）提供关键词库
 */
(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"), require("../../mode/sql/sql"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror", "../../mode/sql/sql"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
  "use strict";

  var tables;
  var defaultTable;
  var keywords;
  var CONS = {
    QUERY_DIV: ";",
    ALIAS_KEYWORD: "AS"
  };
  var Pos = CodeMirror.Pos;
  /**
   * 从 SQL 语法模式中获取关键词库
   * @param {CodeMirror} editor - 编辑器实例
   * @return {Object} SQL 关键词对象（键为关键词，值为 true）
   */
  function getKeywords(editor) {
    var mode = editor.doc.modeOption;
    if (mode === "sql") mode = "text/x-sql";
    return CodeMirror.resolveMode(mode).keywords;
  }
  /**
   * 工具函数：获取补全项的文本（兼容字符串和对象格式）
   * @param {string/Object} item - 补全项（字符串或含 text 属性的对象）
   * @return {string} 补全项文本
   */
  function getText(item) {
    return typeof item == "string" ? item : item.text;
  }
  /**
   * 工具函数：从列表/对象中查找指定项（精确匹配文本）
   * @param {Array/Object} list - 待查找的列表或对象
   * @param {string} item - 目标项文本
   * @return {*} 找到的项（无则返回 undefined）
   */
  function getItem(list, item) {
    if (!list.slice) return list[item];
    for (var i = list.length - 1; i >= 0; i--) if (getText(list[i]) == item)
      return list[i];
  }
  /**
   * 工具函数：浅克隆对象（避免修改原对象）
   * @param {Object} object - 待克隆对象
   * @return {Object} 克隆后的对象
   */
  function shallowClone(object) {
    var result = {};
    for (var key in object) if (object.hasOwnProperty(key))
      result[key] = object[key];
    return result;
  }
  /**
   * 工具函数：判断补全项是否以输入前缀开头（不区分大小写）
   * @param {string} string - 输入前缀
   * @param {string/Object} word - 补全项
   * @return {boolean} 匹配返回 true，否则 false
   */
  function match(string, word) {
    var len = string.length;
    var sub = getText(word).substr(0, len);
    return string.toUpperCase() === sub.toUpperCase();
  }
  /**
   * 工具函数：筛选匹配的补全项并添加到结果列表
   * @param {Array} result - 补全结果列表
   * @param {string} search - 输入前缀
   * @param {Array/Object} wordlist - 待筛选的单词列表/对象
   * @param {Function} formatter - 补全项格式化函数（如添加反引号）
   */
  function addMatches(result, search, wordlist, formatter) {
    for (var word in wordlist) {
      if (!wordlist.hasOwnProperty(word)) continue;
      if (Array.isArray(wordlist)) {
        word = wordlist[word];
      }
      if (match(search, word)) {
        result.push(formatter(word));
      }
    }
  }
  /**
   * 工具函数：清理名称（去除开头的点和反引号）
   * @param {string} name - 待清理的名称（如 ".`table`"）
   * @return {string} 清理后的名称（如 "table"）
   */
  function cleanName(name) {
    // Get rid name from backticks(`) and preceding dot(.)
    if (name.charAt(0) == ".") {
      name = name.substr(1);
    }
    return name.replace(/`/g, "");
  }
  /**
   * 工具函数：给名称添加反引号（适配 SQL 关键字作为表/列名的场景）
   * @param {string/Object} name - 待处理的名称（如 "table.name" 或含 text 的对象）
   * @return {string/Object} 加反引号后的结果（如 "`table`.`name`"）
   */
  function insertBackticks(name) {
    var nameParts = getText(name).split(".");
    for (var i = 0; i < nameParts.length; i++)
      nameParts[i] = "`" + nameParts[i] + "`";
    var escaped = nameParts.join(".");
    if (typeof name == "string") return escaped;
    name = shallowClone(name);
    name.text = escaped;
    return name;
  }
  /**
   * 表名/列名补全核心逻辑（支持别名匹配）
   * @param {CodeMirror.Pos} cur - 光标位置
   * @param {Object} token - 当前语法 token
   * @param {Array} result - 补全结果列表
   * @param {CodeMirror} editor - 编辑器实例
   * @return {number} 补全替换的起始列号
   */
  function nameCompletion(cur, token, result, editor) {
    // Try to complete table, colunm names and return start position of completion
    var useBacktick = false;
    var nameParts = [];
    var start = token.start;
    var cont = true;
    while (cont) {
      cont = (token.string.charAt(0) == ".");
      useBacktick = useBacktick || (token.string.charAt(0) == "`");

      start = token.start;
      nameParts.unshift(cleanName(token.string));

      token = editor.getTokenAt(Pos(cur.line, token.start));
      if (token.string == ".") {
        cont = true;
        token = editor.getTokenAt(Pos(cur.line, token.start));
      }
    }

    // Try to complete table names
    var string = nameParts.join(".");
    addMatches(result, string, tables, function(w) {
      return useBacktick ? insertBackticks(w) : w;
    });

    // Try to complete columns from defaultTable
    addMatches(result, string, defaultTable, function(w) {
      return useBacktick ? insertBackticks(w) : w;
    });

    // Try to complete columns
    string = nameParts.pop();
    var table = nameParts.join(".");

    // Check if table is available. If not, find table by Alias
    if (!getItem(tables, table))
      table = findTableByAlias(table, editor);

    var columns = getItem(tables, table);
    if (columns && Array.isArray(tables) && columns.columns)
      columns = columns.columns;

    if (columns) {
      addMatches(result, string, columns, function(w) {
        if (typeof w == "string") {
          w = table + "." + w;
        } else {
          w = shallowClone(w);
          w.text = table + "." + w.text;
        }
        return useBacktick ? insertBackticks(w) : w;
      });
    }

    return start;
  }

  function eachWord(lineText, f) {
    if (!lineText) return;
    var excepted = /[,;]/g;
    var words = lineText.split(" ");
    for (var i = 0; i < words.length; i++) {
      f(words[i]?words[i].replace(excepted, '') : '');
    }
  }
  /**
   * 工具函数：将 Pos 位置转换为数字（用于范围比较）
   * @param {CodeMirror.Pos} cur - 位置对象
   * @return {number} 转换后的数字（格式：行号.列号，列号占6位）
   */
  function convertCurToNumber(cur) {
    // max characters of a line is 999,999.
    return cur.line + cur.ch / Math.pow(10, 6);
  }

  function convertNumberToCur(num) {
    return Pos(Math.floor(num), +num.toString().split('.').pop());
  }
/**
   * 根据表别名查找真实表名（在当前 SQL 查询内匹配）
   * @param {string} alias - 表别名
   * @param {CodeMirror} editor - 编辑器实例
   * @return {string} 真实表名（无则返回空字符串）
   */
  function findTableByAlias(alias, editor) {
    var doc = editor.doc;
    var fullQuery = doc.getValue();
    var aliasUpperCase = alias.toUpperCase();
    var previousWord = "";
    var table = "";
    var separator = [];
    var validRange = {
      start: Pos(0, 0),
      end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length)
    };

    //add separator
    var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV);
    while(indexOfSeparator != -1) {
      separator.push(doc.posFromIndex(indexOfSeparator));
      indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1);
    }
    separator.unshift(Pos(0, 0));
    separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));

    //find valid range
    var prevItem = 0;
    var current = convertCurToNumber(editor.getCursor());
    for (var i=0; i< separator.length; i++) {
      var _v = convertCurToNumber(separator[i]);
      if (current > prevItem && current <= _v) {
        validRange = { start: convertNumberToCur(prevItem), end: convertNumberToCur(_v) };
        break;
      }
      prevItem = _v;
    }

    var query = doc.getRange(validRange.start, validRange.end, false);

    for (var i = 0; i < query.length; i++) {
      var lineText = query[i];
      eachWord(lineText, function(word) {
        var wordUpperCase = word.toUpperCase();
        if (wordUpperCase === aliasUpperCase && getItem(tables, previousWord))
          table = previousWord;
        if (wordUpperCase !== CONS.ALIAS_KEYWORD)
          previousWord = word;
      });
      if (table) break;
    }
    return table;
  }

  CodeMirror.registerHelper("hint", "sql", function(editor, options) {
    tables = (options && options.tables) || {};
    var defaultTableName = options && options.defaultTable;
    defaultTable = (defaultTableName && getItem(tables, defaultTableName)) || [];
    keywords = keywords || getKeywords(editor);

    var cur = editor.getCursor();
    var result = [];
    var token = editor.getTokenAt(cur), start, end, search;
    if (token.end > cur.ch) {
      token.end = cur.ch;
      token.string = token.string.slice(0, cur.ch - token.start);
    }

    if (token.string.match(/^[.`\w@]\w*$/)) {
      search = token.string;
      start = token.start;
      end = token.end;
    } else {
      start = end = cur.ch;
      search = "";
    }
    if (search.charAt(0) == "." || search.charAt(0) == "`") {
      start = nameCompletion(cur, token, result, editor);
    } else {
      addMatches(result, search, tables, function(w) {return w;});
      addMatches(result, search, defaultTable, function(w) {return w;});
      addMatches(result, search, keywords, function(w) {return w.toUpperCase();});
    }

    return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)};
  });
});
