%%%% lfmt: the public, multi-engine LFE formatter API.
%%%%
%%%% new/1 builds a validated, opaque opts handle; format/1,2 dispatch on the
%%%% selected engine. Only `fezzik` is wired at 0.4.0; `pe` (v0.5.0) and `pc`
%%%% (v0.6.0) are named in the engine() type and reserved — new/1 errors clearly
%%%% if they are selected, so nothing is silently dropped.
-module(lfmt).
-include("lfmt.hrl").
-export([new/1, format/1, format/2]).
-export_type([opts/0, engine/0]).
-type engine() :: fezzik | pe | pc.
-opaque opts() :: #lfmt_opts{}.
%% new/1: build + validate an opts handle. This is a constructor — it returns the
%% handle directly and *raises* on bad input (bad input is a programmer error):
%% default engine -> fezzik
%% reserved engine (pe|pc) -> error({engine_not_available, E})
%% unknown engine -> error({unknown_engine, E})
%% unknown option key -> error({unknown_option, K}) (no option is dropped)
-spec new(map()) -> opts().
new(Map) when is_map(Map) ->
validate(maps:fold(fun set_opt/3, #lfmt_opts{}, Map)).
set_opt(engine, E, Opts) -> Opts#lfmt_opts{engine = E};
set_opt(K, _V, _Opts) -> error({unknown_option, K}).
validate(#lfmt_opts{engine = fezzik} = Opts) ->
Opts;
validate(#lfmt_opts{engine = E}) when E =:= pe; E =:= pc ->
error({engine_not_available, E});
validate(#lfmt_opts{engine = E}) ->
error({unknown_engine, E}).
%% format/2: format Source with the engine selected in the handle.
-spec format(opts(), unicode:chardata()) -> {ok, iolist()} | {error, term()}.
format(#lfmt_opts{engine = E} = Opts, Source) ->
(engine_module(E)):format(Opts, Source).
%% format/1: format Source with the default engine (fezzik).
-spec format(unicode:chardata()) -> {ok, iolist()} | {error, term()}.
format(Source) ->
format(new(#{}), Source).
%% engine_module/1: map an engine atom to its backend module. Only fezzik is
%% mapped at 0.4.0; the fallback keeps the function total over engine() (pe|pc
%% never reach here — new/1 rejects them — but the opaque boundary needs it).
-spec engine_module(engine()) -> module().
engine_module(fezzik) -> lfmt_fezzik;
engine_module(E) -> error({engine_not_available, E}).