-- mod-version:3
local common = require "core.common"
local config = require "core.config"
local syntax = require "core.syntax"

-- Documentation for annotations:
-- https://github.com/LuaLS/lua-language-server/wiki/Annotations
local annotations_syntax = {
  patterns = {
    -- look-aheads for table and function types
    { regex = [[@[\w_]+()\s+(?=(?:table\s*<|fun\s*\())]],
      type = { "annotation", "comment" }
    },
    { regex = [[@[\w_]+\s+()[\w\._]+()\??()\s*(?=(?:table\s*<|fun\s*\())]],
      type = {
        "annotation", "annotation.param", "annotation.operator", "comment"
      }
    },
    { regex = "@field"
        .. [[()\s+(?:protected|public|private|package)\s+]]
        .. [[()[\w\._]+]]
        .. [[()\??]]
        .. [[()\s*(?=(?:table\s*<|fun\s*\())]],
      type = {
        "annotation", "annotation", "annotation.param",
        "annotation.operator", "comment"
      }
    },
    -- table and function types
    { pattern = "table%s*%b<>", type = "annotation.type" },
    { pattern = "fun%s*%b()%s*:%s*[%S]+", type = "annotation.type" },
    { pattern = "fun%s*%b()", type = "annotation.type" },
    -- @alias with string type
    { pattern = "@alias%s+()[%w%._]+()%s+%b''",
      type = { "annotation", "annotation.param", "annotation.string" }
    },
    { pattern = "@alias%s+()[%w%._]+()%s+%b\"\"",
      type = { "annotation", "annotation.param", "annotation.string" }
    },
    -- @alias with type
    { pattern = "@alias%s+()[%w%._]+()%s+[%S]+",
      type = { "annotation", "annotation.param", "annotation.type" }
    },
    -- @alias without type
    { pattern = "@alias%s+()[%w%._]+",
      type = { "annotation", "annotation.param" }
    },
    -- @cast with type
    { pattern = "@cast%s+()[%w%._]+%s+()[%w%.%[%]_]+()%??",
      type = {
        "annotation", "annotation.param",
        "annotation.type", "annotation.operator"
      }
    },
    -- @cast without type
    { pattern = "@cast%s+()[%w%._]+",
      type = { "annotation", "annotation.param" }
    },
    -- @class with parent
    { pattern = "@class%s+()[%w%._]+%s*():()%s*[%S]+",
      type = {
        "annotation", "annotation.param",
        "annotation.operator", "annotation.type"
      }
    },
    -- @class without parent
    { pattern = "@class%s+()[%S]+",
      type = { "annotation", "annotation.param" }
    },
    -- @diagnostic with state and diagnostic type
    { pattern = "@diagnostic%s+()[%S]+()%s*:%s*()[%S]+",
      type = {
        "annotation", "annotation.function",
        "annotation.operator", "annotation.string"
      }
    },
    -- @diagnostic with state only
    { pattern = "@diagnostic%s+()[%S]+",
      type = { "annotation", "annotation.function" }
    },
    -- @enum doc type
    { pattern = "@enum%s+()[%S]+",
      type = { "annotation", "annotation.param" }
    },
    -- @field with access specifier
    { regex = "@field"
        .. [[()\s+(?:protected|public|private|package)\s+]]
        .. [[()[\w\._]+]]
        .. [[()\??]]
        .. [[()\s*'[^']*']],
      type = {
        "annotation", "annotation", "annotation.param",
        "annotation.operator", "annotation.string"
      }
    },
    { regex = "@field"
        .. [[()\s+(?:protected|public|private|package)\s+]]
        .. [[()[\w\._]+]]
        .. [[()\??]]
        .. [[()\s*"[^"]*"]],
      type = {
        "annotation", "annotation", "annotation.param",
        "annotation.operator", "annotation.string"
      }
    },
    { regex = "@field"
        .. [[()\s+(?:protected|public|private|package)\s+]]
        .. [[()[\w\._]+]]
        .. [[()\??]]
        .. [[()\s*[\w\.\[\]_]+]]
        .. [[()\??]],
      type = {
        "annotation", "annotation", "annotation.param",
        "annotation.operator", "annotation.type", "annotation.operator"
      }
    },
    -- @field with type
    { pattern = "@field%s+()[%w%._]+()%??()%s+[%w%.%[%]_]+()%??",
      type = {
        "annotation", "annotation.param", "annotation.operator",
        "annotation.type", "annotation.operator"
      }
    },
    -- @field with string types
    { pattern = "@field%s+()[%w%._]+()%??()%s+%b''",
      type = {
        "annotation", "annotation.param",
        "annotation.operator", "annotation.string"
      }
    },
    { pattern = "@field%s+()[%w%._]+()%??()%s+%b\"\"",
      type = {
        "annotation", "annotation.param",
        "annotation.operator", "annotation.string"
      }
    },
    -- @generic with single type
    { pattern = "@generic%s+()[%w%._]+%s*():()%s*[%w%._]+",
      type = {
        "annotation", "annotation.param",
        "annotation.operator", "annotation.type"
      }
    },
    -- @generic without type
    { pattern = "@generic%s+()[%w%._]+",
      type = { "annotation", "annotation.param" }
    },
    -- @module doc type
    { pattern = "@module%s+()%b''",
      type = { "annotation", "annotation.string" }
    },
    { pattern = "@module%s+()%b\"\"",
      type = { "annotation", "annotation.string" }
    },
    -- @operator doc type
    { pattern = "@operator%s+()[%w_]+",
      type = { "annotation", "annotation.function" }
    },
    -- @param doc type
    { pattern = "@param%s+()[%w%._]+()%??()%s+%b''",
      type = {
        "annotation", "annotation.param",
        "annotation.operator", "annotation.string"
      }
    },
    { pattern = "@param%s+()[%w%._]+()%??()%s+%b\"\"",
      type = {
        "annotation", "annotation.param",
        "annotation.operator", "annotation.string"
      }
    },
    { pattern = "@param%s+()[%w%._]+()%??()%s+[%w%.%[%]_]+",
      type = {
        "annotation", "annotation.param",
        "annotation.operator", "annotation.type"
      }
    },
    -- @return with name
    { pattern = "@return%s+()[%w%.%[%]_]+()%??()%s+[%w%.%[%]_]+",
      type = {
        "annotation", "annotation.type",
        "annotation.operator", "annotation.param"
      }
    },
    -- @return with string
    { pattern = "@return%s+()%b''",
      type = { "annotation", "annotation.string" }
    },
    { pattern = "@return%s+()%b\"\"",
      type = { "annotation", "annotation.string" }
    },
    -- @return without name
    { pattern = "@return%s+()[%w%.%[%]_]+()%??",
      type = { "annotation", "annotation.type", "annotation.operator" }
    },
    -- type doc tag
    { pattern = "@type%s+()%b''",
      type = { "annotation", "annotation.string" }
    },
    { pattern = "@type%s+()%b\"\"",
      type = { "annotation", "annotation.string" }
    },
    { pattern = "@type%s+()[%w%._%[%]]+()%??",
      type = { "annotation", "annotation.type", "annotation.operator" }
    },
    -- @vararg doc type (deprecated)
    { pattern = "@vararg%s+()[%w%.%[%]_]+()%??",
      type = { "annotation", "annotation.type", "annotation.operator" }
    },
    -- match additional types
    { pattern = "|>?%s*()%b``",
      type = { "annotation.operator", "annotation.string" }
    },
    { pattern = "|>?%s*()%b\"\"",
      type = { "annotation.operator", "annotation.string" }
    },
    { pattern = "|>?%s*()%b''",
      type = { "annotation.operator", "annotation.string" }
    },
    { pattern = "|%s*()table%s*%b<>",
      type = { "annotation.operator", "annotation.type" }
    },
    { pattern = "|%s*()fun%s*%b()%s*:%s*[^%s|]+",
      type = { "annotation.operator", "annotation.type" }
    },
    { pattern = "|%s*()fun%s*%b()",
      type = { "annotation.operator", "annotation.type" }
    },
    { pattern = "|%s*()[^%s|?]+()%??",
      type = { "annotation.operator", "annotation.type", "annotation.operator" }
    },
    -- match doc tags syntax for symbols table to properly work
    { pattern = "@[%a_]+%w*",         type = "comment" },
    -- match everything else as normal comment
    { pattern = "[%w%p]+",            type = "comment" },
    -- just in case, prevent the subsyntax from getting outside
    { pattern = "%f[\n]",             type = "comment" }
  },
  symbols = {
    ["@alias"] = "annotation",
    ["@async"] = "annotation",
    ["@class"] = "annotation",
    ["@cast"] = "annotation",
    ["@deprecated"] = "annotation",
    ["@diagnostic"] = "annotation",
    ["@enum"] = "annotation",
    ["@field"] = "annotation",
    ["@generic"] = "annotation",
    ["@meta"] = "annotation",
    ["@module"] = "annotation",
    ["@nodiscard"] = "annotation",
    ["@operator"] = "annotation",
    ["@overload"] = "annotation",
    ["@package"] = "annotation",
    ["@param"] = "annotation",
    ["@private"] = "annotation",
    ["@protected"] = "annotation",
    ["@return"] = "annotation",
    ["@see"] = "annotation",
    ["@source"] = "annotation",
    ["@type"] = "annotation",
    ["@vararg"] = "annotation",
    ["@version"] = "annotation"
  }
}

local annotations_pattern = {
  pattern = { "%-%-%-%s*%f[@|]", "\n" },
  syntax = annotations_syntax,
  type = "comment"
}

config.plugins.language_lua = common.merge({
  annotations = true,
  -- The config specification used by the settings gui
  config_spec = {
    name = "Language Lua",
    {
      label = "Annotations",
      description = "Toggle highlighting of annotation comments.",
      path = "annotations",
      type = "toggle",
      default = true,
      on_apply = function(enabled)
        local syntax_lua = syntax.get("file.lua")
        if enabled then
          if not syntax_lua.patterns[4].syntax then
            table.insert(syntax_lua.patterns, 4, annotations_pattern)
          end
        elseif syntax_lua.patterns[4].syntax then
          table.remove(syntax_lua.patterns, 4)
        end
      end
    }
  }
}, config.plugins.language_lua)

syntax.add {
  name = "Lua",
  files = { "%.lua$", "%.rockspec$" },
  headers = "^#!.*[ /]lua",
  comment = "--",
  block_comment = { "--[[", "]]" },
  patterns = {
    { pattern = { '"', '"', '\\' },          type = "string" },
    { pattern = { "'", "'", '\\' },          type = "string" },
    { pattern = { "%[%[", "%]%]" },          type = "string" },
    { pattern = { "%[=", "=%]" },            type = "string" },
    { pattern = { "ffi%.()cdef%s*()%[%[", "()()%]%]" },
      type = { "normal", "function", "string" },
      syntax = ".c"
    },
    { pattern = { "ffi%.()cdef%s*()%[%[", "()()%]%]" },
      type = { "normal", "function", "string" },
      syntax = ".c"
    },
    { pattern = { "%-%-%[%[", "%]%]"},       type = "comment" },
    { pattern = "%-%-.-\n",                  type = "comment" },
    { pattern = "0x%x+%.%x*[pP][-+]?%d+",    type = "number" },
    { pattern = "0x%x+%.%x*",                type = "number" },
    { pattern = "0x%.%x+[pP][-+]?%d+",       type = "number" },
    { pattern = "0x%.%x+",                   type = "number" },
    { pattern = "0x%x+[pP][-+]?%d+",         type = "number" },
    { pattern = "0x%x+",                     type = "number" },
    { pattern = "%d%.%d*[eE][-+]?%d+",       type = "number" },
    { pattern = "%d%.%d*",                   type = "number" },
    { pattern = "%.?%d*[eE][-+]?%d+",        type = "number" },
    { pattern = "%.?%d+",                    type = "number" },
    { pattern = "<%a+>",                     type = "keyword2" },
    { pattern = "%.%.%.?",                   type = "operator" },
    { pattern = "[<>~=]=",                   type = "operator" },
    { pattern = "[%+%-=/%*%^%%#<>]",         type = "operator" },
    -- Metamethods
    { pattern = "__[%a][%w_]*",              type = "function" },
    { pattern = "%.()__[%a][%w_]*",
      type = { "normal", "function" }
    },
    -- Function parameters
    {
      regex = {
        "(?="
          .. "(?:local)?\\s*"
          .. "(?:function\\s*)"
          .. "(?:[a-zA-Z_][a-zA-Z0-9_\\.\\:]*\\s*)?"
          .. "\\("
        .. ")",
        "\\)"
      },
      type = "normal",
      syntax = {
        patterns = {
          { pattern = "%.%.%.", type = "operator" },
          { pattern = "[%a_][%w_]*%s*(),%s*",
            type = { "operator", "normal" }
          },
          { pattern = "[%a_][%w_]*%s*%f[%s)]",
            type = "operator"
          },
          { pattern = "%.()[%a_][%w_]*()%s*%f[(]",
            type = { "normal", "function", "normal" }
          },
          { pattern = "[%a_][%w_]*()%s*%f[(]",
            type = { "function", "normal" }
          },
          { pattern = "%.()[%a_][%w_]*",
            type = { "normal", "symbol" }
          },
          { pattern = "[%a_][%w_]*",
            type = "symbol"
          },
        },
        symbols = {
          ["local"] = "keyword",
          ["function"] = "keyword",
          ["self"] = "keyword2"
        }
      }
    },
    -- require statements
    { pattern = "local()%s+()[%a_][%w_]*()%s*()=()%s*()require",
      type = { "keyword", "normal", "keyword2", "normal", "operator", "normal", "function" }
    },
    -- Placeholder
    { pattern = "%f[%g]_%f[%G,%)]",
      type = "operator"
    },
    { pattern = "%f[^,%(]_%f[%G,%)]",
      type = "operator"
    },
    -- Function calls
    { pattern = "[%a_][%w_]+()%s*%f[(\"'{]", type = { "function", "normal" } },
    { pattern = "[%a_][%w_%.]+()%.()[%a_][%w_]+()%s*%f[(\"'{]",
      type = { "normal", "normal", "function", "normal" }
    },
    -- Sub fields
    { pattern = "%.()[%a_][%w_]*",
      type = { "normal", "symbol" }
    },
    -- Uppercase constants of at least 2 chars in len
    {
        pattern = "%u[%u_][%u%d_]*%f[%s%[%+%*%-%.%(%)%?%^%%=/<>~|&:,~]",
        type = "number"
    },
    -- Labels
    { pattern = "::[%a_][%w_]*::",           type = "function" },
    -- Other text
    { pattern = "[%a_][%w_]*",               type = "normal" },
  },
  symbols = {
    ["if"]       = "keyword",
    ["then"]     = "keyword",
    ["else"]     = "keyword",
    ["elseif"]   = "keyword",
    ["end"]      = "keyword",
    ["do"]       = "keyword",
    ["function"] = "keyword",
    ["repeat"]   = "keyword",
    ["until"]    = "keyword",
    ["while"]    = "keyword",
    ["for"]      = "keyword",
    ["break"]    = "keyword",
    ["return"]   = "keyword",
    ["local"]    = "keyword",
    ["in"]       = "keyword",
    ["not"]      = "keyword",
    ["and"]      = "keyword",
    ["or"]       = "keyword",
    ["goto"]     = "keyword",
    ["self"]     = "keyword2",
    ["true"]     = "literal",
    ["false"]    = "literal",
    ["nil"]      = "literal",
  },
}

-- insert annotations rule after "%[%[", "%]%]"
if config.plugins.language_lua.annotations then
  local syntax_lua = syntax.get("file.lua")
  table.insert(syntax_lua.patterns, 4, annotations_pattern)
end
