-- VDS.Environment
-- ============================================================================
-- Env injection: ambient values threaded via EnvMiddleware thunks. Replaces
-- global lookups in skinners/dispatchers with explicit parameter injection.
-- Spec: UI_WIDGET_TAXONOMY.md section 9. Ref: LUA_SAMPLES.md s1e.
--
-- Rules:
--   1. Static slots only -- per-frame state (hover, scroll) must NOT use env.
--   2. Every slot has a valid standalone default. No nil envs.
--   3. Read-only projection of Store state; writes go through Store:Dispatch.
--   4. No __index prototype chain (Roact's legacy context bug); explicit table.

VDS = VDS or {}
VDS.Environment = VDS.Environment or {
    _slots     = {},        -- [name] = { default, validator?, description? }
    _current   = nil,       -- the assembled env table, populated by Build()
    _sealed    = false,     -- once Build() runs, Declare() errors
}

local E = VDS.Environment

-- ===== Slot declaration ===================================================

-- Declare an environment slot. Slots are merged into env at Build() time.
-- def = { name (req), default (req, valid standalone), description?, validator? }
function E:Declare(def)
    if self._sealed then
        error(("VDS.Environment:Declare(%q) called after Build()"):format(
            tostring(def and def.name)), 2)  -- exception(false-positive): error-message composer; def may be the malformed input
    end
    if type(def) ~= "table" then
        error("VDS.Environment:Declare expected a table", 2)
    end
    if type(def.name) ~= "string" or def.name == "" then
        error("VDS.Environment:Declare missing required field 'name'", 2)
    end
    if def.default == nil then
        error(("VDS.Environment:Declare(%q) missing required 'default'"):format(def.name), 2)
    end
    if def.validator and not def.validator(def.default) then
        error(("VDS.Environment:Declare(%q): default value failed validator"):format(def.name), 2)
    end
    if self._slots[def.name] then
        error(("VDS.Environment:Declare(%q) duplicate slot"):format(def.name), 2)
    end
    self._slots[def.name] = def
end

-- ===== Build ===============================================================

-- Build env table. Call once at OnEnable after all slots are declared.
-- overrides: non-default values for specific slots (test mocks, scheme swap).
function E:Build(overrides)
    overrides = overrides or {}
    local env = {}
    for name, def in pairs(self._slots) do
        local v = overrides[name]
        if v == nil then v = def.default end
        if def.validator and not def.validator(v) then
            error(("VDS.Environment:Build: slot %q failed validator"):format(name), 2)
        end
        env[name] = v
    end

    -- Catch overrides for undeclared slots (spec section 12).
    for name in pairs(overrides) do
        if not self._slots[name] then
            error(("VDS.Environment:Build: override for undeclared slot %q"):format(name), 2)
        end
    end

    self._current = env
    self._sealed = true
    return env
end

-- ===== Access ==============================================================

-- Public read API for callers without env threaded explicitly.
function E:Current()
    if not self._current then
        error("VDS.Environment: not built yet (call Build() in OnEnable)", 2)
    end
    return self._current
end

-- Strict slot read; errors on undeclared slot instead of returning nil.
function E:Get(name)
    if not self._current then
        error("VDS.Environment: not built yet (call Build() in OnEnable)", 2)
    end
    if not self._slots[name] then
        error(("VDS.Environment:Get(%q): undeclared slot"):format(tostring(name)), 2)
    end
    return self._current[name]
end

-- ===== Sub-tree override ==================================================

-- Shallow-merge overrides onto current env; returns a new table. Does NOT mutate self._current.
-- Use for sub-tree builds (preview pane, alt scheme, test mocks).
function E:WithOverrides(overrides)
    if not self._current then
        error("VDS.Environment:WithOverrides: not built yet", 2)
    end
    overrides = overrides or {}
    local child = {}
    for k, v in pairs(self._current) do child[k] = v end
    for k, v in pairs(overrides) do
        if not self._slots[k] then
            error(("VDS.Environment:WithOverrides: undeclared slot %q"):format(k), 2)
        end
        local def = self._slots[k]
        if def.validator and not def.validator(v) then
            error(("VDS.Environment:WithOverrides: slot %q failed validator"):format(k), 2)
        end
        child[k] = v
    end
    return child
end

-- ===== Introspection / test helpers =======================================

function E:GetSlots()
    return self._slots
end

function E:_Reset()
    self._slots = {}
    self._current = nil
    self._sealed = false
end

-- ===== Central slot catalog ==============================================
-- All addon-level env slots (spec 9.3 rule 5: one place). Module-specific
-- slots may call Environment:Declare from onInitialize, but stay out of here.
E.SLOTS = E.SLOTS or {}

-- Register central slots. Called by Init.lua before module Phase1. Idempotent.
function E:DeclareAll()
    for _, def in ipairs(self.SLOTS) do
        if not self._slots[def.name] then
            self:Declare(def)
        end
    end
end
