<commit_before>local json       = require "dkjson"
                   require "cosy.util.string"
local cosy       = require "cosy.util.cosy"
local tags       = require "cosy.util.tags"
local protocol   = require "cosy.protocol"
local proxy      = require "cosy.util.proxy"
local container  = require "cosy.util.container"
local ignore     = require "cosy.util.ignore"
local map        = require "cosy.util.map"
local set        = require "cosy.util.set"

local GLOBAL = _G or _ENV
local js  = GLOBAL.js
local env = js.global

GLOBAL.cosy = cosy
GLOBAL.tags = tags
env.cosy = cosy

local detect = proxy ()

local DATA    = tags.DATA
local PATH    = tags.PATH
local PATCHES = tags.PATCHES
local NODES   = tags.NODES

function detect:__newindex (key, value)
  local below = self [DATA]
  local path = self [PATH] .. key
  assert (#path >= 2)
  local model = path [1] [path [2]]
  model [PATCHES] = {}
  --
  local old_value = self [key]
  below [key] = value
  local new_value = self [key]
  --
  protocol.on_patch (model)
  --
  if self.type then
    if model [NODES] [self] then
      env:update_node (self)
    else
      env:add_node (self)
    end
  end
  if type (old_value) == "table" and old_value.type then
    model [NODES] [old_value] = model [NODES] [old_value] - 1
    if model [NODES] [old_value] == 0 then
      model [NODES] [old_value] = nil
      env:remove_node (old_value)
    end
  end
  if type (new_value) == "table" and new_value.type then
    model [NODES] [new_value] = (model [NODES] [new_value] or 0) + 1
    if model [NODES] [new_value] == 1 then
      env:add_node (new_value)
    end
  end
end

local interface_mt = {}

function interface_mt:log (message)
  ignore (self)
  env.console:log (message)
end

function interface_mt:err (message)
  ignore (self)
  env.console:error (message)
end

function interface_mt:ready ()
  env:ready (self.resource)
end

function interface_mt:send (message)
  if self.websocket.readyState == 1 then
    message.token = self.token
    self.websocket:send (json.encode (message))
  else
    self:log (json.encode (message))
  end
end

function interface_mt:close ()
  self.websocket = nil
end

interface_mt.__index = interface_mt


function env:connect (editor, resource, token)
  ignore (self)
  local websocket =
    js:run ([[new WebSocket ("${editor}", "cosy")]] % { editor = editor })
  local interface = setmetatable ({
    resource  = resource,
    token     = token,
    websocket = websocket,
--    wrapper   = detect,
  }, interface_mt)
  local model = protocol.on_connect (interface)
  function websocket:onopen ()
    ignore (self)
    protocol.on_open (model)
    interface:send {
      action   = "set-resource",
      resource = resource,
    }
    interface:send {
      action   = "get-patches",
    }
    cosy [resource] [NODES] = container {}
  end
  function websocket:onclose ()
    ignore (self)
    protocol.on_close (model)
  end
  function websocket:onmessage (event)
    ignore (self)
    protocol.on_message (model, json.decode (event.data))
  end
  function websocket:onerror ()
    ignore (self)
    websocket:close ()
  end
end

function env:count (x)
  ignore (self)
  return #x
end

function env:id (x)
  ignore (self)
  return tostring (x)
end

function env:keys (x)
  ignore (self)
  local result = {}
  for key, _ in map (x) do
    result [#result + 1] = key
  end
  return result
end

function env:elements (model)
  ignore (self)
  local result = {}
  for x in set (model [NODES]) do
    result [#result + 1] = x
  end
  return result
end

--[[<commit_msg>Add prototypes for functions useful in js.<commit_after>local json       = require "dkjson"
                   require "cosy.util.string"
local cosy       = require "cosy.util.cosy"
local tags       = require "cosy.util.tags"
local protocol   = require "cosy.protocol"
local proxy      = require "cosy.util.proxy"
local container  = require "cosy.util.container"
local ignore     = require "cosy.util.ignore"
local map        = require "cosy.util.map"
local set        = require "cosy.util.set"
local seq        = require "cosy.util.seq"

local GLOBAL = _G or _ENV
local js  = GLOBAL.js
local env = js.global

GLOBAL.cosy = cosy
GLOBAL.tags = tags
env.cosy = cosy

local detect = proxy ()

local DATA    = tags.DATA
local PATH    = tags.PATH
local PATCHES = tags.PATCHES
local NODES   = tags.NODES

function detect:__newindex (key, value)
  local below = self [DATA]
  local path = self [PATH] .. key
  assert (#path >= 2)
  local model = path [1] [path [2]]
  model [PATCHES] = {}
  --
  local old_value = self [key]
  below [key] = value
  local new_value = self [key]
  --
  protocol.on_patch (model)
  --
  if self.type then
    if model [NODES] [self] then
      env:update_node (self)
    else
      env:add_node (self)
    end
  end
  if type (old_value) == "table" and old_value.type then
    model [NODES] [old_value] = model [NODES] [old_value] - 1
    if model [NODES] [old_value] == 0 then
      model [NODES] [old_value] = nil
      env:remove_node (old_value)
    end
  end
  if type (new_value) == "table" and new_value.type then
    model [NODES] [new_value] = (model [NODES] [new_value] or 0) + 1
    if model [NODES] [new_value] == 1 then
      env:add_node (new_value)
    end
  end
end

local interface_mt = {}

function interface_mt:log (message)
  ignore (self)
  env.console:log (message)
end

function interface_mt:err (message)
  ignore (self)
  env.console:error (message)
end

function interface_mt:ready ()
  env:ready (self.resource)
end

function interface_mt:send (message)
  if self.websocket.readyState == 1 then
    message.token = self.token
    self.websocket:send (json.encode (message))
  else
    self:log (json.encode (message))
  end
end

function interface_mt:close ()
  self.websocket = nil
end

interface_mt.__index = interface_mt


function env:connect (editor, resource, token)
  ignore (self)
  local websocket =
    js:run ([[new WebSocket ("${editor}", "cosy")]] % { editor = editor })
  local interface = setmetatable ({
    resource  = resource,
    token     = token,
    websocket = websocket,
--    wrapper   = detect,
  }, interface_mt)
  local model = protocol.on_connect (interface)
  function websocket:onopen ()
    ignore (self)
    protocol.on_open (model)
    interface:send {
      action   = "set-resource",
      resource = resource,
    }
    interface:send {
      action   = "get-patches",
    }
    cosy [resource] [NODES] = container {}
  end
  function websocket:onclose ()
    ignore (self)
    protocol.on_close (model)
  end
  function websocket:onmessage (event)
    ignore (self)
    protocol.on_message (model, json.decode (event.data))
  end
  function websocket:onerror ()
    ignore (self)
    websocket:close ()
  end
end

function env:id (data)
  ignore (self)
  return tostring (data)
end

function env:type (data)
  ignore (self)
  if type (data) == "table" then
    return data [tags.TYPE]
  else
    return nil
  end
end

local function to_array (x)
  local elements = {}
  for element in set (x) do
    elements [#elements + 1] = '"' .. tostring (element) .. '"'
  end
  table.sort (elements)
  return js:run ("[ " .. table.concat (elements, ", ") .. " ]")
end

local function to_object (x)
  local elements = {}
  for key, value in map (x) do
    elements [#elements + 1] = [["${key}": ${value}]] % {
      key   = tostring (key),
      value = value,
    }
  end
  table.sort (elements)
  return js:run ("{ " .. table.concat (elements, ", ") .. " }")
end

function env:selected (data)
  ignore (self)
  if type (data) == "table" then
    local s = data [tags.SELECT]
    if type (s) == "table" then
      return to_array (s)
    elseif type (s) == "string" then
      return to_array { s = true }
    else
      return to_array {}
    end
  else
    return nil
  end
end

function env:select (data, name, value)
  ignore (self)
  local s = data [tags.SELECT] or {}
  if type (s) == "string" then
    s = { s = true }
  end
  s [name] = value
  data [tags.SELECT] = s
end

function env:highlighted (data)
  ignore (self)
  if type (data) == "table" then
    local s = data [tags.HIGHLIGHT]
    if type (s) == "table" then
      return to_array (s)
    elseif type (s) == "string" then
      return to_array { s = true }
    else
      return to_array {}
    end
  else
    return nil
  end
end

function env:highlight (data, name, value)
  ignore (self)
  local s = data [tags.HIGHLIGHT] or {}
  if type (s) == "string" then
    s = { s = true }
  end
  s [name] = value
  data [HIGHLIGHT] = s
end

function env:get_position (data)
  ignore (self)
  if type (data) == "table" then
    return data [tags.POSITION]
  else
    return nil
  end
end

function env:set_position (data, value)
  ignore (self)
  data [tags.POSITION] = value
end

function get_source (data)
end

function set_source (data, node, anchor)
end

function get_target (data)
end

function set_target (data, node, anchor)
end

function types (model)
end

function instantiate (container, element_type)
end


function env:count (x)
  ignore (self)
  return #x
end

function env:keys (x)
  ignore (self)
  local result = {}
  for key, _ in map (x) do
    result [#result + 1] = key
  end
  return result
end

function env:elements (model)
  ignore (self)
  local result = {}
  for x in set (model [NODES]) do
    result [#result + 1] = x
  end
  return result
end

--[[
{'fraction_non_alphanumeric': 0.11806740614334471, 'fraction_numerical': 0.002559726962457338, 'mean_word_length': 2.9022055763628796, 'pattern_counts': {'":': 1, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2941820', 'n_tokens_mistral': 3255, 'n_tokens_neox': 3226, 'n_words': 997}