-module(albedo_extension_settings).
-include_lib("kernel/include/file.hrl").
-export([home/0, read/1, set_enabled/3, set_entry/4, remove_entry/3]).

-define(MAX_BYTES, 1048576).

home() ->
    unicode:characters_to_binary(case os:getenv("ALBEDO_HOME") of
        false ->
            case os:getenv("HOME") of
                false -> ".albedo";
                Home -> filename:join(Home, ".albedo")
            end;
        Home -> Home
    end).

read(Home) ->
    Path = filename:join(Home, <<"extensions.json">>),
    case file:read_file_info(Path) of
        {error, enoent} -> {ok, <<"{}">>};
        {ok, #file_info{type = regular, size = Size}} when Size =< ?MAX_BYTES ->
            case file:read_file(Path) of
                {ok, Bytes} when byte_size(Bytes) =< ?MAX_BYTES -> {ok, Bytes};
                {ok, _} -> {error, <<"extensions.json exceeds 1 MiB">>};
                _ -> {error, <<"could not read extensions.json">>}
            end;
        {ok, #file_info{type = regular}} -> {error, <<"extensions.json exceeds 1 MiB">>};
        _ -> {error, <<"extensions.json is not a readable regular file">>}
    end.

%% Records a global extension default in the `enabled` section.
set_enabled(Home, Name, Enabled) ->
    set_entry(Home, <<"enabled">>, Name, Enabled).

remove_entry(Home, Section, Key) ->
    set_entry(Home, Section, Key, null).

%% Sets one key of one section, keeping every other section and key as
%% written; `null` removes the key. The rename is the commit, so readers never
%% see a partly written file.
set_entry(Home, Section, Key, Value) ->
    albedo_settings_lock:with_lock(Home,
        fun() -> set_entry_locked(Home, Section, Key, Value) end,
        fun() -> {error, <<"settings store is busy">>} end).

set_entry_locked(Home, Section, Key, Value) ->
    try
        {ok, Bytes} = read(Home),
        Sections = case Bytes of <<>> -> #{}; _ -> json:decode(Bytes) end,
        true = is_map(Sections),
        Entries = case maps:get(Section, Sections, #{}) of Map when is_map(Map) -> Map; _ -> erlang:error(invalid_section) end,
        Updated = case Value of
            null -> Sections#{Section => maps:remove(Key, Entries)};
            _ -> Sections#{Section => Entries#{Key => Value}}
        end,
        case albedo_credentials:write(filename:join(Home, <<"extensions.json">>), Updated) of
            ok -> {ok, nil};
            _ -> {error, <<"could not save extensions.json">>}
        end
    catch
        _:_ -> {error, <<"extensions.json is not a valid settings object">>}
    end.
