parent user
parent properties
object help_user

var root name 'help_user
var help_user inited 1
var help_user current_help $help_root

method init
    arg ancestors;

    pass(ancestors);
    if (definer() in ancestors)
        current_help = $help_root;
.

eval
    .initialize();
    .add_property('help_dict, #[["*", $help_root], ["help", $help_help], ["**", $user_help]]);
    .set_vr_name("Clueless");
    .add_command("help *", 'help_cmd);
    .add_shortcut("?*", 'help_cmd, ["help", 1]);
.

method help_dict
    return .get_property('help_dict);
.

method match_help_path
    arg string;
    var current, node;

    current = current_help;
    for node in (explode(string))
        current = (> .match_help_node(node, current) <);
    return current;
.

method match_help_node
    arg string, current;
    var node, sib, i;

    switch (string) {
        case ">":
            node = current;
            while (1) {
                if (node == $help_root)
                    throw(~keynf, current.name() + " has no successor.");
                sib = node.upnode().subnodes();
                i = node in sib;
                if (i && i < listlen(sib))
                    return sib[i+1];
                node = node.upnode();
            }
        case "<":
            node = current;
            while (1) {
                if (node == $help_root)
                    throw(~keynf, current.name() + " has no predecessor.");
                sib = node.upnode().subnodes();
                i = node in sib;
                if (i > 1)
                    return sib[i-1];
                node = node.upnode();
            }
        case "..":
            if (current == $help_root)
                throw(~keynf, "Top node has no supernode.");
            return current.upnode();
        case "?":
            if (!current.subnodes())
                return (> .match_help_node(">", current) <);
            return current.subnodes()[1];
    }
    catch ~keynf {
        return .get_property('help_dict)[string];
    }
    return (> current.match_menu(string) <);
.

method help_cmd
    arg string, [rest];

    if (rest)
        string = rest[1];
    else if (string == "help")
        string = "";
    catch ~range {
        sender().tell("[:" + string + ":] " + current_help.footnote(toint(string)));
        return;
    }
    catch ~keynf {
        current_help = .match_help_path(string);
    } with handler {
        sender().tell("No help found on " + toliteral(string) + ".");
        return;
    }
    sender().tell(current_help.text());
    if (current_help.subnodes())
        sender().tell("-----[[ `??' to read " + current_help.subnodes()[1].brief() + " (" + current_help.subnodes()[1].name() + ") ]]");
    else catch ~keynf {
        string = .match_help_node("?", current_help);
        sender().tell("-----[[ `??' to read " + string.brief() + " (" + $list.to_string($list.map(string.path(), 'name), " ") + " " + string.name() + ") ]]");
    } with handler {
        sender().tell("-----[[ Last node. `?*' to return to the top. ]]");
    }
.