### ----------------------------------------------------
### -- LibXML::Class
### -- Licenses: Artistic-2.0
### -- Authors: Vadim Belman
### -- File: lib/LibXML/Class.rakumod
### ----------------------------------------------------
### -- Chunk 2 of 3
code --> Mu) is raw {
        LibXML::Class::X::Deserialize::NoCtx.new(:type(self.WHAT)) without $!xml-dctx;
        my $*LIBXML-CLASS-CTX = $!xml-dctx;
        my $*LIBXML-CLASS-CONFIG = $!xml-dctx.document.config;
        # Where there is no more lazies to deserialize we don't need the context object anymore.
        LEAVE {
            unless self.xml-has-lazies {
                $!xml-dctx.check-up;
                $!xml-dctx = Nil;
            }
        }
        &code()
    }

    method !xml-post-deserialize-attr(Str:D $attr-name) {
        # note "%%% POST-DESERIALIZING $attr-name // ", $*LIBXML-CLASS-CTX.WHICH;
        $!xml-lazies.DELETE-KEY($attr-name);
        for $*LIBXML-CLASS-CTX.take-out-deserializations<> -> (:key($node), :$value) {
            # note "%%% + deser for ", brief-node-str($node), " -> ", $value.raku;
            self.xml-add-deserialization($node, $value);
        }
    }

    # This method is to be used as the builder for lazy attributes.
    proto method xml-deserialize-attr(::?CLASS:D: |) {*}

    # This candidate would basically serve as the entry point because $attribute is always passed in by AttrX::Mooish
    multi method xml-deserialize-attr(::?CLASS:D: Str:D :$attribute! --> Mu) is raw {
        self.xml-deserialize-attr($attribute, self.^xml-get-attr($attribute))
    }

    multi method xml-deserialize-attr( ::?CLASS:D:
                                       Str:D $attr-name,
                                       LibXML::Class::Attr::XMLPositional:D $attr
                                       --> Mu ) is raw
    {
        with $!xml-lazies{$attr-name} -> \initializer {
            return self.xml-lazy-deserialize-context: {
                # If succeeded then there is no more need to hold the source element.
                KEEP self!xml-post-deserialize-attr($attr-name);
                # Map creates a lazy Seq where map's code is invoked under another stack frame than this one thus
                # effectively loosing the dynamic context and all $*LIBXML-CLASS variables. .eager forces it to be
                # executed in place.
                # note "? . will coerce into ", $attr.name;
                initializer.map({ self.xml-coerce-into-attr($attr, $_) }).eager
            }
        }
        Empty
    }

    multi method xml-deserialize-attr( ::?CLASS:D:
                                       Str:D $attr-name,
                                       LibXML::Class::Attr::XMLTextNode:D $attr
                                       --> Mu ) is raw
    {
        with $!xml-lazies{$attr-name} -> \initializer {
            return self.xml-lazy-deserialize-context: {
                my Mu $value;

                KEEP {
                    $!xml-lazies.DELETE-KEY{$attr-name};
                    self.xml-add-deserialization($_, $value) for initializer<>;
                }

                my $text-content = initializer.map(*.data).join;
                $text-content .= trim if $attr.trim;
                $value := self.xml-coerce-into-attr($attr, $text-content)
            }
        }
        Nil
    }

    multi method xml-deserialize-attr( ::?CLASS:D:
                                       Str:D $attr-name,
                                       LibXML::Class::Attr::XMLish:D $attr
                                       --> Mu ) is raw
    {
        # note "%%% DESERIALIZING $attr-name";
        with $!xml-lazies{$attr-name} -> \initializer {
            return self.xml-lazy-deserialize-context: {
                # If all succeeded then there is no more need to hold the source element.
                KEEP self!xml-post-deserialize-attr($attr-name);
                self.xml-coerce-into-attr($attr, initializer)
            }
        }
        Nil
    }

    proto method xml-deserialize-node(::?CLASS:D: | --> Mu) {*}

    multi method xml-deserialize-node(::?CLASS:D: LibXML::Attr:D $xml-attr --> Mu) is raw {
        # No context means no lazies to deserialize
        fail LibXML::Class::X::Deserialize::NoCtx.new(:node($xml-attr), :type(self.WHAT))
            without $!xml-dctx;

        with $!xml-dctx.attr-for-prop($xml-attr) -> LibXML::Class::Attr::XMLAttribute $attr {
            self.xml-deserialize-attr($attr.name, $attr);
            return self.xml-deserialization($xml-attr)
                // LibXML::Class::X::Deserialize::ForeignNode.new(
                    :type(self.WHAT),
                    :node($.xml-backing),
                    :expected($xml-attr)).Failure
        }

        fail LibXML::Class::X::Deserialize::NoDescriptor.new(:type(self.WHAT), :node($xml-attr))
    }

    multi method xml-deserialize-node(::?CLASS:D: LibXML::Text:D $text --> Mu) is raw {
        # No context means no lazies to deserialize
        fail LibXML::Class::X::Deserialize::NoCtx.new(:node($text), :type(self.WHAT))
            without $!xml-dctx;

        with $!xml-dctx.xml-text -> LibXML::Class::Attr::XMLTextNode $attr {
            self.xml-deserialize-attr($attr.name, $attr);
            return self.xml-deserialization($text)
                // LibXML::Class::X::Deserialize::ForeignNode.new(
                    :type(self.WHAT),
                    :node($.xml-backing),
                    :expected($text)).Failure
        }

        fail LibXML::Class::X::Deserialize::NoDescriptor.new(:type(self.WHAT), :node($text))
    }

    multi method xml-deserialize-node(::?CLASS:D: LibXML::Element:D $node --> Mu) is raw {
        # No context means no lazies to deserialize
        fail LibXML::Class::X::Deserialize::NoCtx.new(:$node, :type(self.WHAT))
            without $!xml-dctx;

        with $!xml-dctx.desc-for-elem($node) -> $desc {
            return self.xml-deserialize-element($node, $desc)
        }

        fail LibXML::Class::X::Deserialize::NoDescriptor.new(:type(self.WHAT), :node($node))
    }

    multi method xml-deserialize-element( ::?CLASS:D:
                                          LibXML::Element:D $node,
                                          LibXML::Class::Attr::XMLPositional:D $desc
                                          --> Mu ) is raw
    {
        # First we need to find out what is element's index
        with $!xml-lazies{$desc.name} {
            my $unique-key = $node.unique-key;
            for .kv -> $idx, $cand {
                if $cand.unique-key eq $unique-key {
                    return $desc.attr.get_value(self)[$idx]
                }
            }
        }

        nextwith($node, $desc, :no-match)
    }

    multi method xml-deserialize-element( ::?CLASS:D:
                                          LibXML::Element:D $node,
                                          LibXML::Class::Attr::XMLValueElement:D $desc
                                          --> Mu ) is raw
    {
        if $desc.attr.is-set(self) {
            my $deserialization := $desc.attr.get_value(self);
            return $deserialization if $deserialization.xml-unique-key eq $node.unique-key;
            fail $desc.gist.tc
                ~ " is already deserialized but its unique key "
                ~ $deserialization.xml-unique-key ~ " doesn't match "
                ~ brief-node-str($node) ~ " key " ~ $node.unique-key;
        }
        with $!xml-lazies{$desc.name} {
            return $desc.attr.get_value(self) if .unique-key eq $node.unique-key
        }

        nextwith($node, $desc, :no-match)
    }

    multi method xml-deserialize-element( ::?CLASS:D:
                                          LibXML::Element:D $node,
                                          LibXML::Class::Attr::XMLish:D $desc,
                                          Bool:D :$no-match! )
    {
        die "Proposed element " ~ brief-node-str($node) ~ " for attribute " ~ $desc.name
            ~ " doesn't match lazies"
    }

    multi method xml-deserialize-element(::?CLASS:D: LibXML::Element:D $node, $desc) {
        die "Can't deserialize element " ~ brief-node-str($node) ~ " with " ~ $desc.gist
    }

    method xml-decontainerize( LibXML::Element:D $node,
                               LibXML::Class::Attr::XMLContainer:D $attr,
                               Str:D $expected-ns = $node.namespaceURI // "",
                               # Should we throw away empty #text?
                               Bool :$trim
                               --> Iterable:D )
    {
        return ($node,) unless $attr.container;

        # Register the container element to be late-resolved into the final value. We must do it by registering the
        # attribute descriptor because lazy deserialization doesn't care and pay no attention to containers.
        $*LIBXML-CLASS-CTX.add-deserialization($node, $attr);

        # If we got here it means the container element has been validated already and matches NS and container name
        # of the attribute $attr
        gather {
            for $node.children -> LibXML::Node:D $child {
                if $child ~~ LibXML::Text {
                    next if $trim && !$child.data.trim;
                    take $child;
                }
                else {
                    if (my $childNS = $child.namespaceURI // "") ne $expected-ns {
                        # Mismatch of container's child element to the expected namespace is not ignorable.
                        LibXML::Class::X::NS::Mismatch.new(
                            :expected($expected-ns),
                            :got($childNS),
                            :what("child element '" ~ $child.name ~ "' of container '" ~ $node.name ~ "'")).throw
                    }
                    take $child
                }
            }
        }
    }

    # Serialize a value based on attribute meta data.
    method xml-ser-desc-value(LibXML::Class::Descriptor:D $desc, Mu $value is raw --> Str:D) {
        self.xml-try-serializer: $desc, \($value), { self.xml-type-to-str($value) }
    }

    method xml-ser-desc-val2elem(LibXML::Element:D $velem, LibXML::Class::Descriptor:D $desc, Mu $value) {
        my $*LIBXML-CLASS-ELEMENT = $velem;

        self.xml-try-serializer: $desc, \($velem, $value), {
            if $desc.value-attr -> $xml-aname {
                # Attribute value is to be kept in XML element attribute
                $velem.setAttribute($xml-aname, self.xml-ser-desc-value($desc, $value))
            }
            elsif !$desc.serializer-cando($value) && ($value ~~ XMLRepresentation || $value !~~ BasicType) {
                my $cvalue = $value;
                unless $cvalue ~~ XMLRepresentation {
                    # Turn a basic class into an XMLRepresentation with implicit flag raised
                    $cvalue = $.xml-config.xmlize($value, XMLRepresentation);
                }
                $cvalue.to-xml($velem);
            }
            else {
                $velem.appendText(self.xml-ser-desc-value($desc, $value));
            }
        }
    }

    # Take a dummy XML element and complete it with data from attribute value.
    method xml-ser-attr-val2elem( LibXML::Element:D $node,
                                  LibXML::Class::Attr::XMLish:D $desc,
                                  Mu $value )
    {
        my LibXML::Element:D $velem = $node;

        if $desc.is-any {
            my $xml-config = $.xml-config;

            # When attribute value is an xml-element then let it do all the work, but prepare an element for it first
            # so that if it refers to upstream xmlns prefixes then it will have them readily available.

            # Map through configuration's ns-map first.
            my $ns = $node.namespaceURI // "";
            without my $ns-map = $xml-config.ns-map-type($value.WHAT, :$ns) {
                $xml-config.alert:
                    LibXML::Class::X::Serialize::Impossible.new(
                        :type(self.WHAT),
                        :what($value),
                        :why( "not found in config's ns-map for attribute "
                                ~ $desc.name
                                ~ " with namespace '$ns'" ));
                return Nil
            }

            $velem = self.xml-create-child-element:
                        $node,
                        ($value ~~ XMLRepresentation ?? $value !! $desc),
                        :name($ns-map.xml-name),
                        # only take attribute's element prefix if set. Namespace would be irrelevant since it would be
                        # just borrowed in the absense of the prefix. This only works for XML:any-sourced elements.
                        |(:prefix($_) with $node.prefix);
        }

        self.xml-ser-desc-val2elem($velem, $desc, $value);
        $node
    }

    proto method xml-serialize-attr(LibXML::Element:D, LibXML::Class::Attr::XMLish:D) {*}

    multi method xml-serialize-attr(LibXML::Element:D $node, LibXML::Class::Attr::XMLAttribute:D $desc) {
        my $value := $desc.attr.get_value(self);

        return without $value;

        my $xml-attr-name = $desc.xml-name;
        my $xml-attr-value = self.xml-ser-desc-value($desc, $value);

        my ($ns, $) = $desc.compose-ns(:from($node), :resolve);

        with $ns {
            $node.setAttributeNS: $ns, $xml-attr-name, $xml-attr-value;
        }
        else {
            $node.setAttribute: $xml-attr-name, $xml-attr-value;
        }
    }

    multi method xml-serialize-attr(LibXML::Element:D $node, LibXML::Class::Attr::XMLTextNode:D $desc) {
        with $desc.get_value(self) {
            $node.appendText: self.xml-ser-desc-value($desc, $_);
        }
    }

    multi method xml-serialize-attr(LibXML::Element:D $node, LibXML::Class::Attr::XMLPositional:D $desc) {
        my \attr-values := $desc.get_value(self);

        return unless attr-values;

        # Positional containerization differs from other elements since by default their elements are direct
        # children of the parent.
        my ($namespace, $prefix) = $desc.compose-ns(:from($node));

        my LibXML::Element:D $celem =
            $desc.container
                ?? self.xml-create-child-element($node, $desc, :name($desc.container-name), :$namespace, :$prefix)
                !! $node;

        self.xml-try-serializer: $desc, \($celem, attr-values), {
            for attr-values<> -> $avalue {
                my $velem =
                    self.xml-create-child-element:
                        $celem, $desc, :name($desc.value-name($avalue)), :$namespace, :$prefix;
                self.xml-ser-attr-val2elem: $velem, $desc, $avalue;
            }
        }
    }

    multi method xml-serialize-attr(LibXML::Element:D $node, LibXML::Class::Attr::XMLAssociative:D $desc) {
        my \attr-values = $desc.get_value(self);

        return unless attr-values;

        my $document = $node.ownerDocument;

        my ($namespace, $prefix) = $desc.compose-ns(:from($node));
        my $celem = self.xml-create-child-element($node, $desc, :$namespace, :$prefix);

        $desc.xml-apply-ns($celem, :$namespace, :$prefix);

        self.xml-try-serializer: $desc, \($celem, attr-values), {
            for attr-values.sort -> (:key($vname), :$value) {
                my LibXML::Element:D $velem =
                    self.xml-create-child-element($celem, $desc, :name($vname), :$namespace, :$prefix);
                self.xml-ser-attr-val2elem: $velem, $desc, $value;
            }
        }
    }

    multi method xml-serialize-attr(LibXML::Element:D $node, LibXML::Class::Attr::XMLValueElement:D $desc) {
        my $value := $desc.get_value(self);

        return without $value;

        my $document = $node.ownerDocument;
        my $attr-type := $desc.nominal-type<>;

        my (Str $namespace, Str $prefix) = $desc.compose-ns(:from($node));

        my LibXML::Element:D $celem =
            $desc.container
                ?? self.xml-create-child-element($node, $desc, :name($desc.container-name), :$namespace, :$prefix)
                !! $node;

        my LibXML::Element:D $attr-elem =
            self.xml-create-child-element($celem, $desc, :name($desc.value-name($value)), :$namespace, :$prefix);

        self.xml-ser-attr-val2elem: $attr-elem, $desc, $value;
    }

    method xml-to-element-repr(LibXML::Element:D $node) {
        for self.^xml-attrs(:!local).values -> LibXML::Class::Attr::XMLish:D $desc {
            self.xml-serialize-attr($node, $desc)
        }
    }

    method xml-to-element( ::?CLASS:D:
                           LibXML::Element:D $node,
                           Str :ns(:xml-default-ns(:$namespace)),
                           Str :xml-default-ns-pfx(:$prefix)
        --> LibXML::Element:D )
    {
        self.xml-apply-ns( $node, :$namespace, :$prefix,
                           # Don't override default namespace and prefix if any of them is already set.
                           default => (! ($node.prefix // $node.namespaceURI).defined) );

        $.xml-unused andthen .map: -> $ast {
            given LibXML::Item.ast-to-xml($ast) {
                when LibXML::Attr {
                    $node.setAttributeNode($_);
                }
                default {
                    $node.add: $_
                }
            }
        }

        # Process all our XML attributes
        for self.xml-serialize-stages -> $stage {
            self."$stage"($node);
        }

        $node
    }

    method xml-new-dctx(*%profile) {
        DeserializingCtx.new: |%profile
    }

    method xml-from-element( LibXML::Element:D $elem,
                             LibXML::Class::Document:D $doc,
                             :%user-profile,
                             Str :$name,
                             Str :namespace(:ns(:$xml-default-ns)) is copy,
                             Str :prefix(:$xml-default-ns-pfx) is copy )
    {
        my $how = self.xml-class.HOW;

        my $ctx-outer := $*LIBXML-CLASS-CTX || Nil;

        # Merge namespace prefixes.
        my %xml-namespaces is OHash =
            |(.xml-namespaces with $ctx-outer),
            |$how.xml-namespaces,
            |("" => $_ with ($xml-default-ns // $how.xml-default-ns)),
            |(%*LIBXML-CLASS-NS-OVERRIDE || ());

        my \xml-class-how = self.xml-class.HOW;
        # When there is no explicit default namespaces we do fallback to the outer default. But not for the prefix
        # which is not inherited by a prefix-less XML entity from its parent.
        without $xml-default-ns // $xml-default-ns-pfx {
            $xml-default-ns = xml-class-how.xml-default-ns // ($ctx-outer andthen .xml-default-ns orelse Nil);
            $xml-default-ns-pfx = xml-class-how.xml-default-ns-pfx;
        }

        my $dctx =
            self.xml-new-dctx:
                :into(self.WHAT), :$elem, :document($doc),
                :%user-profile, :%xml-namespaces,
                :$xml-default-ns, :$xml-default-ns-pfx;

        {
            my $*LIBXML-CLASS-CTX = $dctx;
            my $*LIBXML-CLASS-CONFIG = $dctx.document.config;

            # Make sure first we can deserialize from this element
            my Str $default-ns = $dctx.default-ns;
            my Str:D $expect-name = $name // $.xml-name;
            unless ($elem.localName eq $expect-name) && ($elem.namespaceURI // "") ~~ $default-ns {
                $.xml-config.alert:
                    LibXML::Class::X::Deserialize::BadNode.new(
                        :type(self.WHAT),
                        :expected("<" ~ $expect-name ~ "> XML tag" ~ (" in namespace '$_'" with $default-ns)),
                        :got("<" ~ $elem.localName ~ ">" ~ (" in namespace '$_'" with $elem.namespaceURI)) );
                # If we are not in STRICT severity mode then return nothing
                return Nil;
            }

            for self.xml-profile-stages -> $stage {
                self."$stage"($dctx);
            }
        }

        {
            CATCH {
                default {
                    LibXML::Class::X::Deserialize::Constructor.new( :type(self.WHAT), :exception($_), :$elem ).throw
                }
            }
            self.xml-create: |$dctx.final-profile
        }
    }

    proto method xml-config-context(|) {*}
    multi method xml-config-context(&code, LibXML::Class::Document:D :$document, *%twiddles) is raw {
        self.xml-config-context: &code, :config($document.config), |%twiddles;
    }
    multi method xml-config-context(&code, LibXML::Class::Config:D :$config, *%twiddles) is raw {
        &code( my $*LIBXML-CLASS-CONFIG = %twiddles ?? $config.clone(|%twiddles) !! $config )
    }
    multi method xml-config-context(&code, LibXML::Class::Document :$document, :%config, *%twiddles) {
        my $config =
            ((CALLERS::<$*LIBXML-CLASS-CONFIG>:exists
                ?? CALLERS::<$*LIBXML-CLASS-CONFIG>
                !! ($document andthen .config))
            andthen (%config || %twiddles ?? .clone(|%config, |%twiddles) !! $_)
            orelse LibXML::Class::Config.clone-from( LibXML::Class::Config.global,
                                                     |self.xml-config-defaults,
                                                     |%config,
                                                     |%twiddles ));

        my $*LIBXML-CLASS-CONFIG = $config;

        &code($config)
    }
    multi method xml-config-context(:$config, |c) {
        self.xml-config-context(|c, config => %$config)
    }

    proto method from-xml(|) {*}

    multi method from-xml( IO:D $source, |c) { self.from-xml: $source.slurp(:close), |c }

    multi method from-xml( Str:D $source-xml,
                           Str :$name,
                           Str :ns(:namespace(:$xml-default-ns)),
                           Str :prefix(:$xml-default-ns-pfx),
                           :$config,
                           :%user-profile )
    {
        self.xml-config-context: :$config, {
            my LibXML::Class::Document:D $document = .document-class.parse: $source-xml, :config($_);
            self.from-xml:
                $document.libxml-document.documentElement,
                $document,
                :$name,
                :$xml-default-ns,
                :$xml-default-ns-pfx,
                :%user-profile
        }
    }

    multi method from-xml( Str:D $source-xml,
                           LibXML::Class::Document:D :$document!,
                           Str :$name,
                           Str :ns(:namespace(:$xml-default-ns)),
                           Str :prefix(:$xml-default-ns-pfx),
                           :%user-profile )
    {
        $document.parse(string => $source-xml);
        self.from-xml:
            $document.libxml-document.documentElement,
            $document,
            :$name,
            :$xml-default-ns,
            :$xml-default-ns-pfx,
            :%user-profile
    }

    multi method from-xml( LibXML::Document:D $libxml-document,
                           LibXML::Class::Config:D :$config!,
                           Str :$name,
                           Str :ns(:namespace(:$xml-default-ns)),
                           Str :prefix(:$xml-default-ns-pfx),
                           :%user-profile )
    {
        my LibXML::Class::Document:D $new-doc = $config.document-class.new: :$config, :$libxml-document;
        self.from-xml:
            $libxml-document.documentElement,
            $new-doc,
            :$name,
            :$xml-default-ns,
            :$xml-default-ns-pfx,
            :%user-profile
    }

    multi method from-xml( LibXML::Element:D $elem,
                           LibXML::Class::Document:D $document,
                           Str :$name,
                           Str :ns(:namespace(:$xml-default-ns)),
                           Str :prefix(:$xml-default-ns-pfx),
                           :%user-profile )
    {
        self.xml-config-context: :$document, {
            self.xml-from-element($elem, $document, :%user-profile, :$name, :$xml-default-ns, :$xml-default-ns-pfx)
        }
    }

    multi method from-xml( LibXML::Element:D $elem,
                           LibXML::Class::Document:U $?,
                           Str :$name,
                           Str :ns(:namespace(:$xml-default-ns)),
                           Str :prefix(:$xml-default-ns-pfx),
                           :$config,
                           :%user-profile )
    {
        my LibXML::Document:D $libxml-document = $elem.ownerDocument;
        self.xml-config-context: :$config, :libxml-config($libxml-document.config), {
            self.xml-from-element:
                $elem,
                .document-class.new(:$libxml-document, :config($_)),
                :%user-profile,
                :$name,
                :$xml-default-ns,
                :$xml-default-ns-pfx;
        }
    }

    proto method to-xml(::?CLASS:D: |) {*}

    multi method to-xml( ::?CLASS:D:
                         Str :$name,
                         Str :ns(:namespace(:$xml-default-ns)),
                         Str :prefix(:$xml-default-ns-pfx),
                         :$config )
    {
        self.xml-config-context: :$config, {
            my LibXML::Document:D $doc = .libxml-config.class-from(LibXML::Document).new(:config(.libxml-config));
            $doc.documentElement = samewith($doc, :$name, :$xml-default-ns, :$xml-default-ns-pfx);
            $doc
        }
    }

    multi method to-xml( ::?CLASS:D:
                         LibXML::Document:D $doc,
                         Str :$name,
                         Str :ns(:namespace(:$xml-default-ns)),
                         Str :prefix(:$xml-default-ns-pfx),
                         :$config )
    {
        self.xml-config-context: :$config, {
            my $elem = $doc.createElement($name // $.xml-name);
            self.xml-to-element($elem, :$xml-default-ns, :$xml-default-ns-pfx)
        }
    }

    multi method to-xml( ::?CLASS:D:
                         LibXML::Element:D $elem,
                         Str :ns(:namespace(:$xml-default-ns)),
                         Str :prefix(:$xml-default-ns-pfx),
                         :$config )
    {
        self.xml-config-context: :$config, {
            self.xml-to-element($elem, :$xml-default-ns, :$xml-default-ns-pfx)
        }
    }
}

our role XMLRepresentation is XMLObject {
    method xml-build-name {
        (::?CLASS.^xml-name if ::?CLASS.HOW ~~ LibXML::Class::HOW::Element) // ::?CLASS.^shortname
    }

    proto method xml-name {*}
    multi method xml-name(::?CLASS:U:) { self.xml-build-name }
    multi method xml-name(::?CLASS:D:) { nextsame }

    method xml-config-defaults {
        ::?CLASS.^xml-config-defaults
    }

    # Since the actual xml-element class can be subclassed this method is fast and reliable way to know the innermost
    # xml-element parent of the subclass which will be in charge of serializing the object.
    method xml-class { ::?CLASS }

    method xml-from-element-repr(DeserializingCtx:D $dctx) {
        callsame();

        my $config = $dctx.config;
        my $lazy-class = ::?CLASS.^xml-is-lazy;
        my $force-eager = $config.eager;

        for $dctx.unclaimed-attrs -> LibXML::Attr:D $xml-attr {
            with $dctx.attr-for-prop($xml-attr) -> LibXML::Class::Attr::XMLAttribute:D $desc {
                if !$force-eager && ($desc.lazy // $lazy-class) {
                    $dctx.add-lazy($desc, $xml-attr);
                }
                else {
                    $dctx.to-profile:
                        $desc,
                        self.xml-coerce-into-attr($desc, $xml-attr),
                        :node($xml-attr);
                }

                $dctx.claim-attr($xml-attr);
            }
        }

        # When eager and there is text content for the element then this is where we collect content of all text nodes
        # before coercing it into xml-text attribute target type.
        my @element-text-nodes;
        my $xml-text-desc = $dctx.xml-text;
        my $is-text-lazy = ? ($xml-text-desc && !$force-eager && ($dctx.xml-text.lazy // $lazy-class));

        for $dctx.unclaimed-children -> LibXML::Node:D $node {
            given $node {
                when LibXML::Element {
                    with $dctx.desc-for-elem($node) -> LibXML::Class::Attr::XMLish:D $attr {
                        my $value-elems := self.xml-decontainerize($node, $attr, :trim).cache;

                        # Validate xml:any by making sure the number of XML elements matches attribute declaration.
                        if $attr.is-any {
                            if $attr.sigil ne '@' {
                                $config.alarm:
                                    LibXML::Class::X::Deserialize::BadNode.new(
                                        :expected("single element for xml:any attribute " ~ $attr.name),
                                        :got($value-elems.elems))
                                    if $value-elems.elems > 1;
                            }
                            else {
                                for $value-elems.List -> $velem {
                                    if $velem.elements.elems > 1 {
                                        $config.alarm:
                                            LibXML::Class::X::Deserialize::BadNode.new(
                                                :type(self.WHAT),
                                                :expected("single child under xml:any element '" ~ $velem.name ~ "'"),
                                                :got($velem.elements.elems))
                                    }
                                }
                            }
                        }

                        if !$force-eager && ($attr.lazy // $lazy-class) {
                            # Lazy xml-element attribute
                            for $value-elems {
                                $dctx.add-lazy($attr, $_);
                            }
                        }
                        else {
                            for $value-elems {
                                if $_ ~~ LibXML::Element {
                                    $dctx.to-profile: $attr, self.xml-coerce-into-attr($attr, $_), :$node;
                                }
                                else {
                                    $dctx.config.alert:
                                        LibXML::Class::X::Deserialize::BadNode.new(
                                            :type(self.WHAT)
                                            :expected('an element'),
                                            :got('a ' ~ .^name ~ ' node'));
                                }
                            }
                        }

                        $dctx.claim-child($node);
                    }
                }
                when LibXML::Text {
                    with $xml-text-desc {
                        if $is-text-lazy {
                            $dctx.add-lazy($_, $node);
                        }
                        else {
                            @element-text-nodes.push: $node;
                        }

                        $dctx.claim-child($node);
                    }
                }
            }
        }

        # We have a non-lazy xml-text attribute
        if $dctx.xml-text && @element-text-nodes {
            my $text-content = $dctx.elem.textContent;
            $text-content .= trim if $xml-text-desc.trim;

            my \value = self.xml-coerce-into-attr($xml-text-desc, $text-content);
            $dctx.to-profile: $xml-text-desc, value;

            # We now know the final value of xml-text and can register all text nodes that contributed to it.
            $dctx.add-deserialization($_, value) for @element-text-nodes;
        }
    }
}

# XMLSequence class is an implementation of xsd:sequence
class XMLSequence does Positional does Iterable {
    # Un-deserialized yet elements
    has @!xml-seq-elems is built;
    # Deserialized or user-installed items.
    has @!xml-items handles <ASSIGN-POS push append of>;

    # Whether we, or any of our parent class, or any role is an xml-any sequence. In other words, would we expect items
    # of a non-xml-element type?
    has Bool:D $.xml-is-any = self.xml-seq-either-any;

    multi method new(*@items, *%profile) {
        given self.bless(|%profile) {
            .append: @items if @items;
            $_
        }
    }

    submethod TWEAK {
        @!xml-items := self.xml-seq-array-type.new;
        # Now we need to setup a container descriptor with name because by default a typed Array use no name causing
        # unclear type check error messages. This could be done with Metamodel, but nqp is faster because we delegate
        # the work of locating attributes to the backend.
        nqp::bindattr(@!xml-items, Array, '$!descriptor',
                      ContainerDescriptor.new(:name('item of <' ~ self.xml-name ~ '>'), :of(@!xml-items.of),
                                              :default(Any)));
    }

    method xml-has-lazies(::?CLASS:D:) {
        ?@!xml-seq-elems || nextsame()
    }

    method xml-serialize-stages is raw {
        <xml-to-element-repr xml-to-element-seq>
    }
    method xml-profile-stages is raw {
        <xml-from-element-seq xml-from-element-repr>
    }

    method xml-from-element-seq(DeserializingCtx:D $dctx) {
        my $xml-config = $dctx.config;
        my $of-type := self.of;

        for $dctx.unclaimed-children.grep(LibXML::Element) -> LibXML::Element:D $node {
            if $dctx.desc-for-elem($node) ~~ LibXML::Class::ItemDescriptor:D
                or (self.xml-seq-either-any
                    # If xml:any then tag must be in the namespace map and match an allowed item type
                    && (my \any-type = $xml-config.ns-map($node)) !=== Nil
                    && any-type ~~ $of-type)
            {
                $dctx.to-profile('xml-seq-elems', $node, :positional);
                $dctx.claim-child($node);
            }
        }
    }

    method !xml-ser-guess-descriptor(LibXML::Element:D $node, Mu $item) {
        my @desc = |self.xml-seq-desc-for-type($item);
        my Str $elem-ns = $node.namespaceURI;

        if @desc > 1 {
            @desc = @desc.grep({ $elem-ns ~~ $^desc.ns });
        }

        my $xml-config = $.xml-config;

        if !@desc {
            $xml-config.alert:
                LibXML::Class::X::Serialize::Impossible.new(
                    :type(self.WHAT),
                    :what($item),
                    :why('the type is not registered with <' ~ $node.localName ~ '>'));
            return Nil;
        }
        elsif @desc > 1 {
            $xml-config.alert:
                LibXML::Class::X::Serialize::Impossible.new(
                    :type(self.WHAT),
                    :what($item),
                    :why('too many declarations found for the type registered with <' ~ $node.localName ~ '>'));
            return Nil
        }

        my LibXML::Class::ItemDescriptor:D $desc = @desc.head;

        return $desc if $desc.xml-name;

        # This object cannot carry instances of types not registered with specific XML names.
        unless self.xml-seq-either-any {
            $xml-config.alert:
                LibXML::Class::X::Serialize::Impossible.new(
                    :type(self.WHAT),
                    :what($item),
                    :why('<' ~ $node.localName ~ '> is not xml-any, but the type has no associated name'));
            return Nil
        }

        # If the descriptor found doesn't have xml-name then it was a bare type registered with XML:any. We'd
        # need to pull the name with config's .ns-map.
        my $ns = $desc.xml-guess-default-ns(:resolve($node)) // $node.namespaceURI // "";
        without my $ns-map = $xml-config.ns-map-type($item.W