Skip to main content

src/greenwood@zipper.erl

-module(greenwood@zipper).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/greenwood/zipper.gleam").
-export([zip/1, up/1, unzip/1, down_where/2, down/1, down_last_where/2, down_last/1, up_n/2, up_until/2, right_where/2, right/1, left_where/2, left/1, right_n/2, nth_child/2, left_n/2, right_n_where/3, left_n_where/3, right_until/3, left_until/3, right_n_until/4, left_n_until/4, find_descendant/2, set_focus/2, map_focus/2, insert_left/2, insert_right/2, insert_down/2, delete/1]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(
    " Zipper (cursor) operations for Greenwood syntax trees.\n"
    "\n"
    " This module provides all navigation, mutation, and query operations for\n"
    " the `Zipper` type. The zipper implements a Huet zipper providing a focused\n"
    " view into the tree with O(1) local moves and edits.\n"
).

-file("src/greenwood/zipper.gleam", 17).
?DOC(
    " Create a zipper focused on the root node.\n"
    "\n"
    " `cursor`\n"
).
-spec zip(greenwood:node_(EVB)) -> greenwood:zipper(EVB).
zip(Root) ->
    {zipper, Root, []}.

-file("src/greenwood/zipper.gleam", 68).
?DOC(
    " Move focus back up to the parent.\n"
    "\n"
    " `cursor`\n"
).
-spec up(greenwood:zipper(EVZ)) -> gleam@option:option(greenwood:zipper(EVZ)).
up(Zipper) ->
    case erlang:element(3, Zipper) of
        [] ->
            none;

        [Crumb | Rest] ->
            Children = lists:append(
                lists:reverse(erlang:element(4, Crumb)),
                [{node_element, erlang:element(2, Zipper)} |
                    erlang:element(5, Crumb)]
            ),
            Parent = {node,
                erlang:element(2, Crumb),
                Children,
                erlang:element(3, Crumb)},
            {some, {zipper, Parent, Rest}}
    end.

-file("src/greenwood/zipper.gleam", 24).
?DOC(
    " Reconstruct the full tree from a zipper by moving up to the root.\n"
    "\n"
    " `cursor`\n"
).
-spec unzip(greenwood:zipper(EVE)) -> greenwood:node_(EVE).
unzip(Zipper) ->
    case up(Zipper) of
        {some, Parent_zipper} ->
            unzip(Parent_zipper);

        none ->
            erlang:element(2, Zipper)
    end.

-file("src/greenwood/zipper.gleam", 630).
-spec always(greenwood:node_(any())) -> boolean().
always(_) ->
    true.

-file("src/greenwood/zipper.gleam", 508).
-spec split_at_node(
    list(greenwood:element(FAU)),
    fun((greenwood:node_(FAU)) -> boolean()),
    list(greenwood:element(FAU))
) -> gleam@option:option({list(greenwood:element(FAU)),
    greenwood:node_(FAU),
    list(greenwood:element(FAU))}).
split_at_node(Elements, Predicate, Left) ->
    case Elements of
        [] ->
            none;

        [{node_element, N} | Rest] ->
            gleam@bool:guard(
                Predicate(N),
                {some, {Left, N, Rest}},
                fun() ->
                    split_at_node(Rest, Predicate, [{node_element, N} | Left])
                end
            );

        [Other | Rest@1] ->
            split_at_node(Rest@1, Predicate, [Other | Left])
    end.

-file("src/greenwood/zipper.gleam", 473).
-spec do_down(
    greenwood:node_(FAE),
    fun((greenwood:node_(FAE)) -> boolean()),
    list(greenwood:crumb(FAE)),
    list(greenwood:element(FAE))
) -> gleam@option:option(greenwood:zipper(FAE)).
do_down(Parent, Predicate, Crumbs, Left) ->
    case split_at_node(erlang:element(3, Parent), Predicate, Left) of
        {some, {New_left, Child, New_right}} ->
            Crumb = {crumb,
                erlang:element(2, Parent),
                erlang:element(4, Parent),
                New_left,
                New_right},
            {some, {zipper, Child, [Crumb | Crumbs]}};

        none ->
            none
    end.

-file("src/greenwood/zipper.gleam", 41).
?DOC(
    " Move focus to the first child Node matching a predicate.\n"
    "\n"
    " `cursor`\n"
).
-spec down_where(
    greenwood:zipper(EVL),
    fun((greenwood:node_(EVL)) -> boolean())
) -> gleam@option:option(greenwood:zipper(EVL)).
down_where(Zipper, Predicate) ->
    do_down(erlang:element(2, Zipper), Predicate, erlang:element(3, Zipper), []).

-file("src/greenwood/zipper.gleam", 34).
?DOC(
    " Move focus to the first child that is a Node.\n"
    "\n"
    " `cursor`\n"
).
-spec down(greenwood:zipper(EVH)) -> gleam@option:option(greenwood:zipper(EVH)).
down(Zipper) ->
    down_where(Zipper, fun always/1).

-file("src/greenwood/zipper.gleam", 530).
-spec do_split_at_last_node(
    list(greenwood:element(FBQ)),
    fun((greenwood:node_(FBQ)) -> boolean()),
    list(greenwood:element(FBQ)),
    gleam@option:option({list(greenwood:element(FBQ)),
        greenwood:node_(FBQ),
        list(greenwood:element(FBQ))})
) -> gleam@option:option({list(greenwood:element(FBQ)),
    greenwood:node_(FBQ),
    list(greenwood:element(FBQ))}).
do_split_at_last_node(Elements, Predicate, Left, Best) ->
    case Elements of
        [] ->
            Best;

        [{node_element, N} | Rest] ->
            case Predicate(N) of
                true ->
                    do_split_at_last_node(
                        Rest,
                        Predicate,
                        [{node_element, N} | Left],
                        {some, {Left, N, Rest}}
                    );

                false ->
                    do_split_at_last_node(
                        Rest,
                        Predicate,
                        [{node_element, N} | Left],
                        Best
                    )
            end;

        [Other | Rest@1] ->
            do_split_at_last_node(Rest@1, Predicate, [Other | Left], Best)
    end.

-file("src/greenwood/zipper.gleam", 523).
-spec split_at_last_node(
    list(greenwood:element(FBG)),
    fun((greenwood:node_(FBG)) -> boolean())
) -> gleam@option:option({list(greenwood:element(FBG)),
    greenwood:node_(FBG),
    list(greenwood:element(FBG))}).
split_at_last_node(Elements, Predicate) ->
    do_split_at_last_node(Elements, Predicate, [], none).

-file("src/greenwood/zipper.gleam", 494).
-spec do_down_last(
    greenwood:node_(FAN),
    fun((greenwood:node_(FAN)) -> boolean()),
    list(greenwood:crumb(FAN))
) -> gleam@option:option(greenwood:zipper(FAN)).
do_down_last(Parent, Predicate, Crumbs) ->
    case split_at_last_node(erlang:element(3, Parent), Predicate) of
        {some, {Left, Child, Right}} ->
            Crumb = {crumb,
                erlang:element(2, Parent),
                erlang:element(4, Parent),
                Left,
                Right},
            {some, {zipper, Child, [Crumb | Crumbs]}};

        none ->
            none
    end.

-file("src/greenwood/zipper.gleam", 58).
?DOC(
    " Move focus to the last child Node matching a predicate.\n"
    "\n"
    " `cursor`\n"
).
-spec down_last_where(
    greenwood:zipper(EVU),
    fun((greenwood:node_(EVU)) -> boolean())
) -> gleam@option:option(greenwood:zipper(EVU)).
down_last_where(Zipper, Predicate) ->
    do_down_last(
        erlang:element(2, Zipper),
        Predicate,
        erlang:element(3, Zipper)
    ).

-file("src/greenwood/zipper.gleam", 51).
?DOC(
    " Move focus to the last child that is a Node.\n"
    "\n"
    " `cursor`\n"
).
-spec down_last(greenwood:zipper(EVQ)) -> gleam@option:option(greenwood:zipper(EVQ)).
down_last(Zipper) ->
    down_last_where(Zipper, fun always/1).

-file("src/greenwood/zipper.gleam", 585).
-spec repeat_move(
    greenwood:zipper(FDG),
    integer(),
    fun((greenwood:zipper(FDG)) -> gleam@option:option(greenwood:zipper(FDG)))
) -> gleam@option:option(greenwood:zipper(FDG)).
repeat_move(Zipper, N, Move) ->
    case N of
        _ when N < 0 ->
            none;

        0 ->
            {some, Zipper};

        _ ->
            case Move(Zipper) of
                {some, Z} ->
                    repeat_move(Z, N - 1, Move);

                none ->
                    none
            end
    end.

-file("src/greenwood/zipper.gleam", 90).
?DOC(
    " Move focus up `n` parents. Returns `None` if `n` is negative or would move\n"
    " above the root.\n"
    "\n"
    " `up_n(z, by: 0)` returns `Some(z)`.\n"
    "\n"
    " `cursor`\n"
).
-spec up_n(greenwood:zipper(EWD), integer()) -> gleam@option:option(greenwood:zipper(EWD)).
up_n(Zipper, N) ->
    repeat_move(Zipper, N, fun up/1).

-file("src/greenwood/zipper.gleam", 99).
?DOC(
    " Ascend until the focused node's parent matches a predicate.\n"
    " Returns the zipper focused on the first ancestor matching `predicate`,\n"
    " or `None` if the root is reached without a match.\n"
    "\n"
    " `cursor`\n"
).
-spec up_until(greenwood:zipper(EWH), fun((greenwood:node_(EWH)) -> boolean())) -> gleam@option:option(greenwood:zipper(EWH)).
up_until(Zipper, Predicate) ->
    case up(Zipper) of
        none ->
            none;

        {some, Z} ->
            gleam@bool:guard(
                Predicate(erlang:element(2, Z)),
                {some, Z},
                fun() -> up_until(Z, Predicate) end
            )
    end.

-file("src/greenwood/zipper.gleam", 570).
-spec scan_right(
    list(greenwood:element(FCU)),
    fun((greenwood:node_(FCU)) -> boolean()),
    list(greenwood:element(FCU))
) -> gleam@option:option({list(greenwood:element(FCU)),
    greenwood:node_(FCU),
    list(greenwood:element(FCU))}).
scan_right(Elements, Predicate, Left) ->
    case Elements of
        [] ->
            none;

        [{node_element, N} | Rest] ->
            gleam@bool:guard(
                Predicate(N),
                {some, {Left, N, Rest}},
                fun() ->
                    scan_right(Rest, Predicate, [{node_element, N} | Left])
                end
            );

        [Other | Rest@1] ->
            scan_right(Rest@1, Predicate, [Other | Left])
    end.

-file("src/greenwood/zipper.gleam", 176).
?DOC(
    " Move focus to the nearest sibling Node to the right matching a predicate.\n"
    "\n"
    " `cursor`\n"
).
-spec right_where(
    greenwood:zipper(EXD),
    fun((greenwood:node_(EXD)) -> boolean())
) -> gleam@option:option(greenwood:zipper(EXD)).
right_where(Zipper, Predicate) ->
    case erlang:element(3, Zipper) of
        [] ->
            none;

        [Crumb | Rest] ->
            case scan_right(
                erlang:element(5, Crumb),
                Predicate,
                [{node_element, erlang:element(2, Zipper)} |
                    erlang:element(4, Crumb)]
            ) of
                {some, {New_left, New_focus, New_right}} ->
                    New_crumb = {crumb,
                        erlang:element(2, Crumb),
                        erlang:element(3, Crumb),
                        New_left,
                        New_right},
                    {some, {zipper, New_focus, [New_crumb | Rest]}};

                none ->
                    none
            end
    end.

-file("src/greenwood/zipper.gleam", 169).
?DOC(
    " Move focus to the nearest sibling Node to the right.\n"
    "\n"
    " `cursor`\n"
).
-spec right(greenwood:zipper(EWZ)) -> gleam@option:option(greenwood:zipper(EWZ)).
right(Zipper) ->
    right_where(Zipper, fun always/1).

-file("src/greenwood/zipper.gleam", 555).
-spec scan_left(
    list(greenwood:element(FCI)),
    fun((greenwood:node_(FCI)) -> boolean()),
    list(greenwood:element(FCI))
) -> gleam@option:option({list(greenwood:element(FCI)),
    greenwood:node_(FCI),
    list(greenwood:element(FCI))}).
scan_left(Elements, Predicate, Right) ->
    case Elements of
        [] ->
            none;

        [{node_element, N} | Rest] ->
            gleam@bool:guard(
                Predicate(N),
                {some, {Rest, N, Right}},
                fun() ->
                    scan_left(Rest, Predicate, [{node_element, N} | Right])
                end
            );

        [Other | Rest@1] ->
            scan_left(Rest@1, Predicate, [Other | Right])
    end.

-file("src/greenwood/zipper.gleam", 138).
?DOC(
    " Move focus to the nearest sibling Node to the left matching a predicate.\n"
    "\n"
    " `cursor`\n"
).
-spec left_where(
    greenwood:zipper(EWU),
    fun((greenwood:node_(EWU)) -> boolean())
) -> gleam@option:option(greenwood:zipper(EWU)).
left_where(Zipper, Predicate) ->
    case erlang:element(3, Zipper) of
        [] ->
            none;

        [Crumb | Rest] ->
            case scan_left(
                erlang:element(4, Crumb),
                Predicate,
                [{node_element, erlang:element(2, Zipper)} |
                    erlang:element(5, Crumb)]
            ) of
                {some, {New_left, New_focus, New_right}} ->
                    New_crumb = {crumb,
                        erlang:element(2, Crumb),
                        erlang:element(3, Crumb),
                        New_left,
                        New_right},
                    {some, {zipper, New_focus, [New_crumb | Rest]}};

                none ->
                    none
            end
    end.

-file("src/greenwood/zipper.gleam", 131).
?DOC(
    " Move focus to the nearest sibling Node to the left.\n"
    "\n"
    " `cursor`\n"
).
-spec left(greenwood:zipper(EWQ)) -> gleam@option:option(greenwood:zipper(EWQ)).
left(Zipper) ->
    left_where(Zipper, fun always/1).

-file("src/greenwood/zipper.gleam", 235).
?DOC(
    " Move focus `n` sibling Nodes to the right. Negative `n` flips direction.\n"
    "\n"
    " `cursor`\n"
).
-spec right_n(greenwood:zipper(EXR), integer()) -> gleam@option:option(greenwood:zipper(EXR)).
right_n(Zipper, N) ->
    gleam@bool:guard(
        N < 0,
        repeat_move(Zipper, - N, fun left/1),
        fun() -> repeat_move(Zipper, N, fun right/1) end
    ).

-file("src/greenwood/zipper.gleam", 116).
?DOC(
    " Descend to the nth child Node (1-indexed). Returns `None` if fewer than `n`\n"
    " Node children exist.\n"
    "\n"
    " `cursor`\n"
).
-spec nth_child(greenwood:zipper(EWM), integer()) -> gleam@option:option(greenwood:zipper(EWM)).
nth_child(Zipper, N) ->
    gleam@bool:guard(N < 1, none, fun() -> case down(Zipper) of
                none ->
                    none;

                {some, Z} when N =:= 1 ->
                    {some, Z};

                {some, Z@1} ->
                    right_n(Z@1, N - 1)
            end end).

-file("src/greenwood/zipper.gleam", 207).
?DOC(
    " Move focus `n` sibling Nodes to the left. Negative `n` flips direction.\n"
    "\n"
    " `cursor`\n"
).
-spec left_n(greenwood:zipper(EXI), integer()) -> gleam@option:option(greenwood:zipper(EXI)).
left_n(Zipper, N) ->
    gleam@bool:guard(
        N < 0,
        repeat_move(Zipper, - N, fun right/1),
        fun() -> repeat_move(Zipper, N, fun left/1) end
    ).

-file("src/greenwood/zipper.gleam", 244).
?DOC(
    " Move focus `n` sibling Nodes to the right matching a predicate.\n"
    " Negative `n` flips direction.\n"
    "\n"
    " `cursor`\n"
).
-spec right_n_where(
    greenwood:zipper(EXV),
    integer(),
    fun((greenwood:node_(EXV)) -> boolean())
) -> gleam@option:option(greenwood:zipper(EXV)).
right_n_where(Zipper, N, Predicate) ->
    case N of
        0 ->
            {some, Zipper};

        _ when N < 0 ->
            left_n_where(Zipper, - N, Predicate);

        _ ->
            case right_where(Zipper, Predicate) of
                {some, Z} ->
                    right_n_where(Z, N - 1, Predicate);

                none ->
                    none
            end
    end.

-file("src/greenwood/zipper.gleam", 216).
?DOC(
    " Move focus `n` sibling Nodes to the left matching a predicate.\n"
    " Negative `n` flips direction.\n"
    "\n"
    " `cursor`\n"
).
-spec left_n_where(
    greenwood:zipper(EXM),
    integer(),
    fun((greenwood:node_(EXM)) -> boolean())
) -> gleam@option:option(greenwood:zipper(EXM)).
left_n_where(Zipper, N, Predicate) ->
    case N of
        0 ->
            {some, Zipper};

        _ when N < 0 ->
            right_n_where(Zipper, - N, Predicate);

        _ ->
            case left_where(Zipper, Predicate) of
                {some, Z} ->
                    left_n_where(Z, N - 1, Predicate);

                none ->
                    none
            end
    end.

-file("src/greenwood/zipper.gleam", 264).
?DOC(
    " Scan right siblings for `target`, aborting early if `stop` matches first.\n"
    " Returns `None` if siblings are exhausted or `stop` fires.\n"
    "\n"
    " `cursor`\n"
).
-spec right_until(
    greenwood:zipper(EYA),
    fun((greenwood:node_(EYA)) -> boolean()),
    fun((greenwood:node_(EYA)) -> boolean())
) -> gleam@option:option(greenwood:zipper(EYA)).
right_until(Zipper, Target, Stop) ->
    case right(Zipper) of
        none ->
            none;

        {some, Next} ->
            gleam@bool:guard(
                Stop(erlang:element(2, Next)),
                none,
                fun() ->
                    gleam@bool:guard(
                        Target(erlang:element(2, Next)),
                        {some, Next},
                        fun() -> right_until(Next, Target, Stop) end
                    )
                end
            )
    end.

-file("src/greenwood/zipper.gleam", 282).
?DOC(
    " Scan left siblings for `target`, aborting early if `stop` matches first.\n"
    "\n"
    " `cursor`\n"
).
-spec left_until(
    greenwood:zipper(EYG),
    fun((greenwood:node_(EYG)) -> boolean()),
    fun((greenwood:node_(EYG)) -> boolean())
) -> gleam@option:option(greenwood:zipper(EYG)).
left_until(Zipper, Target, Stop) ->
    case left(Zipper) of
        none ->
            none;

        {some, Next} ->
            gleam@bool:guard(
                Stop(erlang:element(2, Next)),
                none,
                fun() ->
                    gleam@bool:guard(
                        Target(erlang:element(2, Next)),
                        {some, Next},
                        fun() -> left_until(Next, Target, Stop) end
                    )
                end
            )
    end.

-file("src/greenwood/zipper.gleam", 301).
?DOC(
    " Scan `n` target-matching right siblings, stopping early if `stop` fires.\n"
    " `right_n_until(zipper:, n: 0, ..)` returns `Some(zipper)`.\n"
    "\n"
    " `cursor`\n"
).
-spec right_n_until(
    greenwood:zipper(EYM),
    integer(),
    fun((greenwood:node_(EYM)) -> boolean()),
    fun((greenwood:node_(EYM)) -> boolean())
) -> gleam@option:option(greenwood:zipper(EYM)).
right_n_until(Zipper, N, Target, Stop) ->
    case N of
        0 ->
            {some, Zipper};

        _ ->
            case right_until(Zipper, Target, Stop) of
                none ->
                    none;

                {some, Next} ->
                    right_n_until(Next, N - 1, Target, Stop)
            end
    end.

-file("src/greenwood/zipper.gleam", 321).
?DOC(
    " Scan `n` target-matching left siblings, stopping early if `stop` fires.\n"
    " `left_n_until(zipper:, n: 0, ..)` returns `Some(zipper)`.\n"
    "\n"
    " `cursor`\n"
).
-spec left_n_until(
    greenwood:zipper(EYS),
    integer(),
    fun((greenwood:node_(EYS)) -> boolean()),
    fun((greenwood:node_(EYS)) -> boolean())
) -> gleam@option:option(greenwood:zipper(EYS)).
left_n_until(Zipper, N, Target, Stop) ->
    case N of
        0 ->
            {some, Zipper};

        _ ->
            case left_until(Zipper, Target, Stop) of
                none ->
                    none;

                {some, Next} ->
                    left_n_until(Next, N - 1, Target, Stop)
            end
    end.

-file("src/greenwood/zipper.gleam", 612).
-spec search_siblings_and_below(
    greenwood:zipper(FDS),
    fun((greenwood:node_(FDS)) -> boolean())
) -> gleam@option:option(greenwood:zipper(FDS)).
search_siblings_and_below(Zipper, Predicate) ->
    gleam@bool:guard(
        Predicate(erlang:element(2, Zipper)),
        {some, Zipper},
        fun() -> case do_find_descendant(Zipper, Predicate) of
                {some, _} = Found ->
                    Found;

                none ->
                    case right(Zipper) of
                        none ->
                            none;

                        {some, Next} ->
                            search_siblings_and_below(Next, Predicate)
                    end
            end end
    ).

-file("src/greenwood/zipper.gleam", 601).
-spec do_find_descendant(
    greenwood:zipper(FDN),
    fun((greenwood:node_(FDN)) -> boolean())
) -> gleam@option:option(greenwood:zipper(FDN)).
do_find_descendant(Zipper, Predicate) ->
    case down(Zipper) of
        none ->
            none;

        {some, Child_z} ->
            search_siblings_and_below(Child_z, Predicate)
    end.

-file("src/greenwood/zipper.gleam", 341).
?DOC(
    " Depth-first search below the focus for a descendant matching a predicate.\n"
    " Returns a zipper focused on the match, or `None`.\n"
    "\n"
    " `cursor`\n"
).
-spec find_descendant(
    greenwood:zipper(EYY),
    fun((greenwood:node_(EYY)) -> boolean())
) -> gleam@option:option(greenwood:zipper(EYY)).
find_descendant(Zipper, Predicate) ->
    do_find_descendant(Zipper, Predicate).

-file("src/greenwood/zipper.gleam", 351).
?DOC(
    " Replace the focused node.\n"
    "\n"
    " `cursor`\n"
).
-spec set_focus(greenwood:zipper(EZD), greenwood:node_(EZD)) -> greenwood:zipper(EZD).
set_focus(Zipper, Node) ->
    {zipper, Node, erlang:element(3, Zipper)}.

-file("src/greenwood/zipper.gleam", 361).
?DOC(
    " Apply a transform to the focused node.\n"
    "\n"
    " `cursor`\n"
).
-spec map_focus(
    greenwood:zipper(EZH),
    fun((greenwood:node_(EZH)) -> greenwood:node_(EZH))
) -> greenwood:zipper(EZH).
map_focus(Zipper, F) ->
    {zipper, F(erlang:element(2, Zipper)), erlang:element(3, Zipper)}.

-file("src/greenwood/zipper.gleam", 372).
?DOC(
    " Insert an element to the left of the focus. Focus does not move.\n"
    " Returns `None` if the focus is the root (no parent to hold the sibling).\n"
    "\n"
    " `cursor`\n"
).
-spec insert_left(greenwood:zipper(EZM), greenwood:element(EZM)) -> gleam@option:option(greenwood:zipper(EZM)).
insert_left(Zipper, Element) ->
    case erlang:element(3, Zipper) of
        [] ->
            none;

        [Crumb | Rest] ->
            New_crumb = {crumb,
                erlang:element(2, Crumb),
                erlang:element(3, Crumb),
                [Element | erlang:element(4, Crumb)],
                erlang:element(5, Crumb)},
            {some, {zipper, erlang:element(2, Zipper), [New_crumb | Rest]}}
    end.

-file("src/greenwood/zipper.gleam", 389).
?DOC(
    " Insert an element to the right of the focus. Focus does not move.\n"
    " Returns `None` if the focus is the root.\n"
    "\n"
    " `cursor`\n"
).
-spec insert_right(greenwood:zipper(EZR), greenwood:element(EZR)) -> gleam@option:option(greenwood:zipper(EZR)).
insert_right(Zipper, Element) ->
    case erlang:element(3, Zipper) of
        [] ->
            none;

        [Crumb | Rest] ->
            New_crumb = {crumb,
                erlang:element(2, Crumb),
                erlang:element(3, Crumb),
                erlang:element(4, Crumb),
                [Element | erlang:element(5, Crumb)]},
            {some, {zipper, erlang:element(2, Zipper), [New_crumb | Rest]}}
    end.

-file("src/greenwood/zipper.gleam", 407).
?DOC(
    " Insert a child as the first child of the focus and move focus to it.\n"
    " Returns `None` if the focus has no children list (impossible for Node, but\n"
    " kept as Option for API consistency).\n"
    "\n"
    " `cursor`\n"
).
-spec insert_down(greenwood:zipper(EZW), greenwood:node_(EZW)) -> greenwood:zipper(EZW).
insert_down(Zipper, Child) ->
    Crumb = {crumb,
        erlang:element(2, erlang:element(2, Zipper)),
        erlang:element(4, erlang:element(2, Zipper)),
        [],
        erlang:element(3, erlang:element(2, Zipper))},
    {zipper, Child, [Crumb | erlang:element(3, Zipper)]}.

-file("src/greenwood/zipper.gleam", 429).
?DOC(
    " Delete the focused node. Focus shifts to:\n"
    " 1. Right sibling (if exists)\n"
    " 2. Left sibling (if exists)\n"
    " 3. Parent with focus removed from children\n"
    "\n"
    " Returns `None` if the focus is the root.\n"
    "\n"
    " `cursor`\n"
).
-spec delete(greenwood:zipper(FAA)) -> gleam@option:option(greenwood:zipper(FAA)).
delete(Zipper) ->
    case erlang:element(3, Zipper) of
        [] ->
            none;

        [Crumb | Rest] ->
            case scan_right(
                erlang:element(5, Crumb),
                fun always/1,
                erlang:element(4, Crumb)
            ) of
                {some, {New_left, New_focus, New_right}} ->
                    New_crumb = {crumb,
                        erlang:element(2, Crumb),
                        erlang:element(3, Crumb),
                        New_left,
                        New_right},
                    {some, {zipper, New_focus, [New_crumb | Rest]}};

                none ->
                    case scan_left(
                        erlang:element(4, Crumb),
                        fun always/1,
                        erlang:element(5, Crumb)
                    ) of
                        {some, {New_left@1, New_focus@1, New_right@1}} ->
                            New_crumb@1 = {crumb,
                                erlang:element(2, Crumb),
                                erlang:element(3, Crumb),
                                New_left@1,
                                New_right@1},
                            {some, {zipper, New_focus@1, [New_crumb@1 | Rest]}};

                        none ->
                            Children = lists:append(
                                lists:reverse(erlang:element(4, Crumb)),
                                erlang:element(5, Crumb)
                            ),
                            Parent = {node,
                                erlang:element(2, Crumb),
                                Children,
                                erlang:element(3, Crumb)},
                            {some, {zipper, Parent, Rest}}
                    end
            end
    end.