-module(bootstrap).
-export([read_genesis/0, read_genesis/1,
         read_section/2, sections/0, section_subdir/1,
         default_base/0, ends_with_sx/1,
         build_genesis/1, verify_genesis/2,
         cidhash_path/1, write_cidhash/2, read_cidhash/1,
         load_genesis/1, strip_sx_suffix/1,
         populate_registry/0,
         start/3]).

%% Genesis bundle reader per design §12.2.
%%
%% read_genesis/0,1 walks the seven canonical section subdirectories
%% under `next/genesis/`, filters .sx files, reads each file into a
%% binary, and returns a structured snapshot:
%%
%%   {ok, [{Section :: atom,
%%          [{FileName :: binary, FileBytes :: binary}, ...]},
%%         ...]}
%%
%% Step 4d will compute the bundle CID by hashing the assembled
%% byte string across all entries; Step 4e will register the parsed
%% definitions in the kernel registry.
%%
%% Port note: this module does NOT parse the .sx contents. The
%% Erlang-on-SX port has no in-Erlang path from binary bytes to SX
%% structured terms (same substrate gap that parked Step 3b); the
%% bundle CID needs only the raw bytes, and registry registration
%% will happen via an SX-side helper that the kernel hands the
%% binary contents to. read_genesis/1 ignores its arg in v1 except
%% to swap the BasePath — `default_base/0` is "next/genesis".
%%
%% Port note 2: string-literal binary segments `<<"abc">>` truncate
%% to one byte in this port, so all path constants are hand-spelled
%% as integer-segment binaries.

%% ── Public API ──────────────────────────────────────────────────

%% "next/genesis"
default_base() ->
    <<110,101,120,116,47,103,101,110,101,115,105,115>>.

read_genesis() ->
    read_genesis(default_base()).

read_genesis(BasePath) ->
    {ok, lists:map(
           fun (S) -> {S, read_section(BasePath, S)} end,
           sections())}.

sections() ->
    [activity_types, object_types, projections,
     validators, codecs, sig_suites, audience].

%% "activity-types"
section_subdir(activity_types) ->
    <<97,99,116,105,118,105,116,121,45,116,121,112,101,115>>;
%% "object-types"
section_subdir(object_types) ->
    <<111,98,106,101,99,116,45,116,121,112,101,115>>;
%% "projections"
section_subdir(projections) ->
    <<112,114,111,106,101,99,116,105,111,110,115>>;
%% "validators"
section_subdir(validators) ->
    <<118,97,108,105,100,97,116,111,114,115>>;
%% "codecs"
section_subdir(codecs) ->
    <<99,111,100,101,99,115>>;
%% "sig-suites"
section_subdir(sig_suites) ->
    <<115,105,103,45,115,117,105,116,101,115>>;
%% "audience"
section_subdir(audience) ->
    <<97,117,100,105,101,110,99,101>>.

read_section(BasePath, Section) ->
    SubDir = section_subdir(Section),
    %% 47 = '/'
    Path = <<BasePath/binary, 47, SubDir/binary>>,
    case file:list_dir(Path) of
        {ok, Names} ->
            SxNames = lists:filter(fun (N) -> ends_with_sx(N) end, Names),
            lists:map(fun (Name) -> read_one(Path, Name) end, SxNames);
        {error, _} ->
            []
    end.

%% Suffix check on the .sx extension. 46='.' 115='s' 120='x'.
ends_with_sx(<<46, 115, 120>>) -> true;
ends_with_sx(<<>>) -> false;
ends_with_sx(<<_, Rest/binary>>) -> ends_with_sx(Rest).

%% ── Internal ────────────────────────────────────────────────────

read_one(DirPath, Name) ->
    Full = <<DirPath/binary, 47, Name/binary>>,
    case file:read_file(Full) of
        {ok, Bytes} -> {Name, Bytes};
        {error, R}  -> {Name, {error, R}}
    end.

%% ── Step 4d: bundle CID compute + verify ────────────────────────
%%
%% The bundle CID is the canonical content-address of everything in
%% read_genesis/0's result. We delegate to the host `cid:to_string/1`
%% BIF (Step 1b substrate): it walks the term via `er-format-value`,
%% feeds the deterministic textual form into `cid-from-sx`, returns
%% a CIDv1 (raw codec, sha2-256 multihash) as a binary.
%%
%% Design §12.3: at startup the kernel computes this CID and
%% compares against a hardcoded value (here: a sibling `.cidhash`
%% file). A mismatch is a hard refuse-to-start.

build_genesis(ReadResult) ->
    case ReadResult of
        {ok, Sections} ->
            Cid = cid:to_string({genesis_bundle, Sections}),
            {ok, [{cid, Cid}, {sections, Sections}]};
        Other ->
            {error, {bad_read_result, Other}}
    end.

verify_genesis(ReadResult, ExpectedCid) ->
    case build_genesis(ReadResult) of
        {ok, [{cid, Cid}, _]} ->
            case Cid =:= ExpectedCid of
                true  -> ok;
                false -> {error, {cid_mismatch, Cid, ExpectedCid}}
            end;
        Err -> Err
    end.

%% Sibling-file CID storage. "/.cidhash" appended to BasePath as
%% an integer-segment binary (string-literal segments are broken).

%% "/.cidhash" — 47='/' 46='.' c i d h a s h
cidhash_path(BasePath) ->
    <<BasePath/binary, 47, 46, 99, 105, 100, 104, 97, 115, 104>>.

write_cidhash(BasePath, Cid) ->
    file:write_file(cidhash_path(BasePath), Cid).

read_cidhash(BasePath) ->
    file:read_file(cidhash_path(BasePath)).

%% ── Step 4e: load_genesis → registry ────────────────────────────
%%
%% Walks the read_genesis result and registers each file as a
%% registry entry. The section atom is the registry kind directly
%% (both name spaces are identical — see Step 4c sections/0 and
%% Step 5a registry:kinds/0). The entry Name is the filename minus
%% the `.sx` suffix, kept as a binary; the entry value is the
%% file's raw bytes.
%%
%% Returns `{ok, RegistryState}` on success. Later steps (4f / the
%% SX-parser bridge) will replace the raw bytes with parsed forms;
%% the binary stand-in is enough to prove the bridge works.

load_genesis(ReadResult) ->
    case ReadResult of
        {ok, Sections} ->
            {ok, load_sections(Sections, registry:new())};
        Other ->
            {error, {bad_read_result, Other}}
    end.

load_sections([], State) -> State;
load_sections([{Kind, Entries} | Rest], State) ->
    load_sections(Rest, load_entries(Kind, Entries, State)).

load_entries(_Kind, [], State) -> State;
load_entries(Kind, [{Name, Bytes} | Rest], State) ->
    BaseName = strip_sx_suffix(Name),
    {ok, NewState} = registry:register(Kind, BaseName, Bytes, State),
    load_entries(Kind, Rest, NewState).

%% strip_sx_suffix(Binary) — drops the trailing ".sx" if present.
%% 46='.' 115='s' 120='x'.
strip_sx_suffix(B) when is_binary(B) ->
    case ends_with_sx(B) of
        false -> B;
        true  -> take_prefix(B, byte_size(B) - 3)
    end.

take_prefix(_, 0) -> <<>>;
take_prefix(<<H, Rest/binary>>, N) when N > 0 ->
    Tail = take_prefix(Rest, N - 1),
    <<H, Tail/binary>>.

%% populate_registry/0 — load the canonical genesis bundle and
%% register every entry in the running registry gen_server. The
%% caller is expected to have started the registry (via
%% registry:start_link/0) before calling this. Returns the count
%% of entries registered across all kinds.
populate_registry() ->
    {ok, Sections} = read_genesis(),
    populate_sections(Sections, 0).

populate_sections([], Count) -> Count;
populate_sections([{Kind, Entries} | Rest], Count) ->
    populate_sections(Rest, Count + populate_entries(Kind, Entries, 0)).

populate_entries(_, [], Count) -> Count;
populate_entries(Kind, [{Name, Bytes} | Rest], Count) ->
    BaseName = strip_sx_suffix(Name),
    ok = registry:register(Kind, BaseName, Bytes),
    populate_entries(Kind, Rest, Count + 1).

%% start/3 — one-call bring-up of the kernel substrate. Starts
%% the registry gen_server, populates it from the canonical
%% genesis bundle, then starts the nx_kernel gen_server with the
%% supplied actor identity / key / state. Returns the nx_kernel
%% Pid (gen_server start_link convention in this port returns the
%% raw Pid, not {ok, Pid}).
%%
%% Tests + production bring-up share this entry point. The
%% caller is still responsible for starting any application-level
%% projections and wiring them via nx_kernel:with_projections/1.
start(ActorId, KeySpec, ActorState) ->
    registry:start_link(),
    populate_registry(),
    nx_kernel:start_link(ActorId, KeySpec, ActorState).
