local acquisition_service_factory = require("clever_f.acquisition_service") local domain = require("clever_f.domain") local feedback_service_factory = require("clever_f.feedback_service") local policy = require("clever_f.policy") local repeat_resolver_factory = require("clever_f.repeat_resolver") local sequence_state = require("clever_f.sequence_state") local state_transitions = require("clever_f.state_transitions") local M = {} local SequenceCoordinator = {} SequenceCoordinator.__index = SequenceCoordinator M.SequenceCoordinator = SequenceCoordinator local coordinator_records = setmetatable({}, { __mode = "k" }) local function fail(message, level) error(message, (level or 1) + 1) end local function descriptor_text(value) if domain.Descriptor.is(value) then return value.value end return tostring(value) end function M.validate_primary_descriptor(value) local descriptor = domain.Descriptor.try_from_string(value) if descriptor == nil then fail("clever-f: Invalid mapping '" .. descriptor_text(value) .. "'", 2) end return descriptor end function SequenceCoordinator.new(options) if SequenceCoordinator.is(options) then return options end if type(options) ~= "table" then fail("SequenceCoordinator options must be a table", 2) end local host = options.host or options local state = options.state or sequence_state.get() if not sequence_state.is(state) then fail("SequenceCoordinator state must be the plugin-global SequenceState", 2) end local transitions = options.transitions or options.state_transitions or state_transitions.new(state) local policy_service = options.policy or options.policy_service or policy.new(host) local resolver = options.repeat_resolver or options.resolver or repeat_resolver_factory.new({ state = state, transitions = transitions, policy = policy_service, clock = host, }) if type(resolver) ~= "table" or type(resolver.decide) ~= "function" then fail("SequenceCoordinator repeat resolver must provide decide", 2) end local feedback = options.feedback or options.feedback_service or feedback_service_factory.new({ host = host, state = state, transitions = transitions, policy = policy_service, }) local acquisition = options.acquisition or options.acquisition_service or acquisition_service_factory.new({ host = host, state = state, transitions = transitions, policy = policy_service, feedback = feedback, }) if type(acquisition) ~= "table" or type(acquisition.acquire) ~= "function" then fail("SequenceCoordinator acquisition service must provide acquire", 2) end local coordinator = setmetatable({}, SequenceCoordinator) coordinator_records[coordinator] = { host = host, state = state, transitions = transitions, policy = policy_service, repeat_resolver = resolver, feedback = feedback, acquisition = acquisition, } return coordinator end function SequenceCoordinator.is(value) return type(value) == "table" and coordinator_records[value] ~= nil end function SequenceCoordinator:validate_primary_descriptor(value) return M.validate_primary_descriptor(value) end local function require_primary_reader(host) if type(host) ~= "table" or type(host.read_mode) ~= "function" or type(host.read_cursor) ~= "function" or type(host.read_count) ~= "function" or type(host.read_macro_state) ~= "function" then fail("SequenceCoordinator host must provide primary action state", 3) end return host end function SequenceCoordinator:read_primary_invocation() local host = require_primary_reader(coordinator_records[self].host) local context = domain.ModeContext.from_full_mode(host:read_mode()) local position = domain.Position.coerce(host:read_cursor()) local count = domain.Count.new(host:read_count()) local macro_state = domain.MacroState.new(host:read_macro_state()) return { context = context, position = position, origin = position, count = count, macro_state = macro_state, } end function SequenceCoordinator:inspect_fold_open_policy(invocation) if type(invocation) ~= "table" or not domain.Position.is(invocation.position) then fail("fold preflight requires primary invocation state", 2) end local host = coordinator_records[self].host if type(host.read_fold_state) ~= "function" then fail("SequenceCoordinator host must provide fold state", 2) end local fold_state = host:read_fold_state() if not domain.FoldState.is(fold_state) then fail("SequenceCoordinator host must return FoldState", 2) end return fold_state end local function fold_open_enabled(fold_state) return fold_state:opens("horizontal") or fold_state:opens("all") end function SequenceCoordinator:open_enclosing_folds(invocation, fold_state) if type(invocation) ~= "table" or not domain.Position.is(invocation.position) then fail("fold opening requires primary invocation state", 2) end if not domain.FoldState.is(fold_state) then fail("fold opening requires FoldState", 2) end if not fold_open_enabled(fold_state) then return 0 end local host = coordinator_records[self].host if type(host.open_fold) ~= "function" then fail("SequenceCoordinator host must open folds", 2) end local opened = 0 while fold_state.closed_levels > 0 do if host:open_fold(invocation.position) ~= true then break end opened = opened + 1 fold_state = self:inspect_fold_open_policy(invocation) end return opened end function SequenceCoordinator:decide_primary(invocation) if type(invocation) ~= "table" or not domain.ModeContext.is(invocation.context) or not domain.Position.is(invocation.position) or not domain.MacroState.is(invocation.macro_state) then fail("primary decision requires invocation state", 2) end return coordinator_records[self].repeat_resolver:decide( invocation.context, invocation.position, invocation.macro_state ) end function SequenceCoordinator:acquire_primary(descriptor, invocation) descriptor = self:validate_primary_descriptor(descriptor) if type(invocation) ~= "table" then fail("primary acquisition requires invocation state", 2) end return coordinator_records[self].acquisition:acquire( descriptor, invocation.context, invocation.position, invocation.count, invocation.macro_state ) end function SequenceCoordinator:resolve_acquisition(descriptor, invocation) local result = self:acquire_primary(descriptor, invocation) if type(result.has_outcome) ~= "function" then fail("AcquisitionService must return an AcquisitionResult", 2) end if result:has_outcome() then return result.outcome end return result end function SequenceCoordinator:primary(value) local descriptor = self:validate_primary_descriptor(value) local invocation = self:read_primary_invocation() invocation.fold_state = self:inspect_fold_open_policy(invocation) invocation.opened_folds = self:open_enclosing_folds( invocation, invocation.fold_state ) invocation.repeat_decision = self:decide_primary(invocation) if invocation.repeat_decision == repeat_resolver_factory.Decision.ACQUIRE then return self:resolve_acquisition(descriptor, invocation) end return descriptor end function M.new(options) return SequenceCoordinator.new(options) end setmetatable(M, { __call = function(_, options) return SequenceCoordinator.new(options) end, }) return M