unit role LibXML::_Options[%OPTS];

multi sub neg($_ where .starts-with('no-')) { .substr(3) }
multi sub neg($_) { 'no-' ~ $_ }

method get-flag(UInt $flags, Str:D $k is copy) {
    $k .= subst("_", "-", :g);
    with %OPTS{$k} {
        ($flags +& $_) == $_
            unless $_ ~~ Bool;
    }
    else {
        with %OPTS{neg($k)} {
            # mask - negated
            ! (($flags +& $_) == $_)
                unless $_ ~~ Bool;
        }
        else {
            warn "unknown parser flag: $k";
            Bool;
        }
    }
}

multi method set-flag(UInt $flags is rw, 'flags', UInt $v, $?) {
    $flags +|= $v;
}
multi method set-flag(UInt $flags is rw, Str:D $k is copy, Bool() $v, Bool $lax?) {
    $k .= subst("_", "-", :g);
    $flags //= 0;
    with %OPTS{$k} {
        # mask
        when Bool { }
        when $v {
            $flags +|= $_;
        }
        default {
            $flags = $flags +& (0xffffffff +^ $_)
            if $flags +& $_;
        }
    }
    else {
        my $kn := neg($k);
        with %OPTS{$kn} {
            # mask - negated
            $.set-flag($flags, $kn, ! $v);
        }
        else {
            warn "unknown parser flag: $k"
                unless $lax;
        }
    }
    $v;
}

method set-flags($flags is rw, Bool :$lax, *%opts) {
    $flags //= 0;
    for %opts.pairs.sort {
        self.set-flag($flags, .key, .value, $lax);
    }
    $flags;
}

method option-exists(Str:D $k is copy) {
    $k .= subst("_", "-", :g);
    (%OPTS{$k} // %OPTS{neg($k)}).defined;
}

multi method get-option(Str:D $k where .starts-with("no-")) {
    ! $.get-option: $k.substr(3);
}
multi method get-option(Str:D $k) {
    self.can($k)
        ?? self."$k"()
        !! $.get-flag($.flags, $k);
}

multi method set-option(Str:D $k where .starts-with("no-"), $v) {
    $.set-option($k.substr(3), !$v);
}
multi method set-option(Str:D $k, $v) {
    self.can($k)
        ?? (self."$k"() = $v)
        !! $.set-flag($.flags, $k, $v);
}
multi method set-option(*%opt) { $.set-options(|%opt); }

method set-options(*%opt) {
    my $rv := $.set-option(.key, .value) for %opt.sort;
    $rv;
}

multi method option(Str:D $key) is rw {
    Proxy.new(
        FETCH => { $.get-option($key) },
        STORE => -> $, Bool() $val {
            $.set-option($key, $val);
        });
}

multi method option(Str:D $key, Bool() $val) {
    $.set-option($key, $val);
}

method options(LibXML::_Options:D) {
    my % = %OPTS.keys.sort.map: -> $k {
        with .get-option($k) {
            $k => $_;
        }
        else {
            Empty;
        }
    }
}
