local B=require(script.Parent.dLib)local i=B.import("\085\116\105\108")local e={}function e.new(B)local e=B or{}local u={}local F={}F.__index=function(B,i)return e[i]or B[i]end function F.length(B)return i.tableLength(e)end function F.get(u,B)if typeof(B)=="\110\117\109\098\101\114"then return e[B]end return B==true and e or i.treePath(e,B,"\046")end function F.listen(e,B)assert(typeof(B)=="\102\117\110\099\116\105\111\110","\067\097\108\108\098\097\099\107 \097\114\103\117\109\101\110\116 \109\117\115\116 \098\101 \097 \102\117\110\099\116\105\111\110")local F=i.randomString(8)u[F]=B return F end function F.unlisten(i,B)assert(u[B],"\083\116\097\116\101 \108\105\115\116\101\110\101\114 \100\111\101\115 \110\111\116 \101\120\105\115\116")u[B]=nil end function F.pushUpdates(i,B)for i,u in pairs(u)do pcall(u,B,e)end end function F.push(F,B,u)local k=i.extend({},e)local j=u~=nil and B or#e+1 e[j]=u~=nil and u or B F:pushUpdates(k)return j end function F.reset(B)e={}end function F.set(F,B,u)local k=i.extend({},e)if typeof(B)=="\102\117\110\099\116\105\111\110"then e=B(e)or e elseif typeof(B)=="\116\097\098\108\101"then for B,i in pairs(B)do e[B]=i end elseif typeof(B)=="\115\116\114\105\110\103"then local F=i.split(B,"\046",true)local k=e for B,i in ipairs(F)do local e=tonumber(i)if e then i=e end if k[i]and B~=#F then k=k[i]elseif B==#F then k[i]=u else break end end else return end F:pushUpdates(k)end function F.remove(u,B)local F=i.extend({},e)local k=i.split(B,"\046",true)local j=e for B,i in ipairs(k)do local e=tonumber(i)if e then i=e end if j[i]and B~=#k then j=j[i]elseif B==#k then j[i]=nil else break end end u:pushUpdates(F)end return setmetatable({},{__index=F})end return e
--[[
-- Usage example

local state = red.State.new({
	val = 1,
	time = tick(),
	nest = {
		val = 123123
	}
})

state:listen(function(prevState, newState)
	print(prevState.val, newState.val)
end)

-- Module
local module = {}

function module.fn(_state)
	_state:get('nest.val')
end

return module
]]

local dLib = require(script.Parent.dLib)
local Util = dLib.import("Util")

local State = {}

function State.new(initialState)
	local _context = initialState or {}
	local _listeners = {}

	local self = {}
	--self.__index = self

	self.__index = function(self, key)
		return _context[key] or self[key]
	end

	--[[local self = setmetatable({}, {
		__index = function(tbl, k)
			return rawget(_context, k)
		end;
		
		__newindex = function(self, k, v)
			rawset(_context, k, v)
		end;
	})]]

	function self:length()
		return Util.tableLength(_context)
	end

	function self:get(path) -- Todo: support for nested tables
		if typeof(path) == "number" then
			return _context[path] -- Return the index, if exists
		end

		return path == true and _context or Util.treePath(_context, path, ".")
		--return key and _context[key] or (key == true and _context)
	end

	function self:listen(fn)
		assert(typeof(fn) == "function", "Callback argument must be a function")

		local id = Util.randomString(8) -- Unique reference ID used for unsubscribing

		_listeners[id] = fn

		return id
	end

	function self:unlisten(id)
		assert(_listeners[id], "State listener does not exist")

		_listeners[id] = nil
	end

	function self:pushUpdates(prevState)
		for _, callback in pairs(_listeners) do
			pcall(callback, prevState, _context)
		end
	end

	function self:push(a, b)
		local prevState = Util.extend({}, _context)
		local key = b ~= nil and a or #_context + 1

		_context[key] = b ~= nil and b or a

		self:pushUpdates(prevState)

		return key
	end

	function self:reset()
		_context = {}
	end

	function self:set(newState, value)
		local prevState = Util.extend({}, _context) -- Create a local copy

		if typeof(newState) == "function" then
			_context = newState(_context) or _context
		elseif typeof(newState) == "table" then
			for k, v in pairs(newState) do
				_context[k] = v
			end
		elseif typeof(newState) == "string" then
			local path = Util.split(newState, ".", true)
			local res = _context

			-- Go through nest until the last nest level is reached
			for i, childName in ipairs(path) do
				local numberIndex = tonumber(childName)

				if numberIndex then
					childName = numberIndex
				end

				if res[childName] and i ~= #path then
					res = res[childName]
				elseif i == #path then
					-- Change the value if end of the path was reached
					res[childName] = value
				else
					break
				end
			end
		else
			return
		end

		self:pushUpdates(prevState)
	end

	function self:remove(path)
		local prevState = Util.extend({}, _context) -- Create a local copy

		local treePath = Util.split(path, ".", true)
		local res = _context

		-- Dig through nest until the last nest level is reached
		for i, childName in ipairs(treePath) do
			local numberIndex = tonumber(childName)

			if numberIndex then
				childName = numberIndex
			end

			if res[childName] and i ~= #treePath then
				res = res[childName]
			elseif i == #treePath then
				-- Remove the value if end of the path was reached
				res[childName] = nil
			else
				break
			end
		end

		self:pushUpdates(prevState)
	end

	return setmetatable({}, { __index = self })
end

return State
