### ----------------------------------------------------
### -- Text::CSV
### -- Licenses: Artistic-2.0
### -- Authors: H.Merijn Brand (Tux)
### -- File: lib/Text/CSV.rakumod
### ----------------------------------------------------
### -- Chunk 2 of 3
11ranges ($range);
        $!rrange;
        }

    multi method rowrange () returns RangeSet {
        $!rrange;
        }

    method version () returns Str {
        $VERSION;
        }

    method status () returns Bool {
        !?$!errno;
        }

    method error_input () returns Str {
        $!errno ?? $!error_input !! Str
        }

    method error_diag () returns CSV::Diag {
        CSV::Diag.new (
            error   => $!errno,
            message => $!error_message,
            pos     => $!error_pos,
            field   => $!error_field,
            record  => $!record_number,
            buffer  => $!error_input // "", # // for 2012
            );
        }

    method is_quoted  (Int:D $i) returns Bool {
        $i >= 0 && $i < $!csv-row.fields.elems && $!csv-row.fields[$i].is_quoted;
        }

    method is_binary  (Int:D $i) returns Bool {
        $i >= 0 && $i < $!csv-row.fields.elems && $!csv-row.fields[$i].is_binary;
        }

    method is_utf8    (Int:D $i) returns Bool {
        $i >= 0 && $i < $!csv-row.fields.elems && $!csv-row.fields[$i].is_utf8;
        }

    method is_missing (Int:D $i) returns Bool {
        $i >= 0 && $i < $!csv-row.fields.elems && $!csv-row.fields[$i].is_missing;
        }

    method !accept-field (CSV::Field $f) returns Bool {
        $!csv-row.push: $f;
        True;
        }

    # combine : direction = 0
    # parse   : direction = 1
    method !ready (Int:D $direction, CSV::Field $f) returns Bool {

        if ($f.undefined) {
            if ($direction) {
                $!blank_is_undef || $!empty_is_undef or $f.add ("");
                }
            #return self!accept-field ($f);
            }

        elsif ($f.Str eq "") {
            $!empty_is_undef and $f.text = Str;
            #return self!accept-field ($f);
            }

        elsif ($!formula ne "none" && $f.is-formula) {
            given $!formula {
                when "die"   { die "Formulas are forbidden"; }
                when "croak" { die "Formulas are forbidden"; }
                when "empty" { $f.text = "";                 }
                when "undef" { $f.text = Str;                }
                when "diag"  {
                    my $fnum = $!csv-row.fields.elems + 1;
                    @!cnames.elems and $fnum ~= " (column: '@!cnames[$fnum - 1]')";
                    my $recn = $direction ?? " in record $!record_number" !! "";
                    warn "Field $fnum$recn contains formula '$f'\n";
                    }
                }
            $f.analyse (True);
            }

        # Postpone all other field attributes like is_binary and is_utf8
        # till it is actually asked for unless it is required right now
        # to fail
        elsif (!$!binary and $f.Str ~~ m{ <[ \x00..\x08 \x0A..\x1F ]> }) {
            $!error_pos     = $/.from + 1 + $f.is_quoted;
            $!errno         = $f.is_quoted ??
                 $f.Str ~~ m{<[ \r ]>} ?? 2022 !!
                 $f.Str ~~ m{<[ \n ]>} ?? 2021 !!  2026 !! 2037;
            $!error_field   = $!csv-row.fields.elems + 1;
            $!error_message = %errors{$!errno};
            $!error_input   = $f.Str;
            $!auto_diag and self.error_diag;
            return False;
            }

        self!accept-field ($f);
        } # ready

    method fields () {
        @!crange ?? ($!csv-row.fields[@!crange]:v) !! $!csv-row.fields;
        }

    method strings () {
        self.fields».Str.Array;
        }

    multi method string () returns Str {
        #progress (0, $!csv-row.fields);
        self.string ($!csv-row.fields);
        }

    multi method string (CSV::Field:D @fld) returns Str {

        %!callbacks<before_print>.defined and
            %!callbacks<before_print>.($!csv-row);

        @fld or return Str;
        my Str $s = $!sep;
        my Str $q = $!quo;
        my Str $e = $!esc;
        my Str @f;
        for @fld -> $f {
            if (!$f.defined || $f.undefined) {
                @f.push: "";
                next;
                }
            my Str $t = $f.Str;
            if ($t eq "") {
                @f.push: $!always_quote || $!quote_empty ?? "$!quo$!quo" !! "";
                next;
                }
            $t.subst-mutate (/( $q | $e )/, { "$e$0" }, :g);
            $t.subst-mutate (/ \x[0] /,     { $e ~ 0 }, :g) if $!escape_null;
            $!always_quote
            ||                    $t ~~ / $e  | $s | \r | \n /
            || ($!quote_space  && $t ~~ / " " | \t /)
            || ($!quote_binary && $t ~~ / <[ \x00..\x08 \x0a..\x1f \x7f..\xa0 ]> /)
                and $t = "$q$t$q";
            @f.push: $t;
            }
        #progress (0, @f);
        #progress (1, my $x =
        defined ($!eol)
          ?? join ( $!sep, @f ) ~ $!eol
          !! join ( $!sep, @f )
        #); $x
        } # string

    multi method combine (Capture $c) returns Bool {
        self.combine ($c.list);
        }
    multi method combine (*@f) returns Bool {
        self.combine (@f);
        }
    multi method combine (@f) returns Bool {
        $!csv-row.fields = ();
        my int $i = 0;
        for flat @f -> $f {
            $i = $i + 1;
            my CSV::Field $cf;
            if ($f.isa (CSV::Field)) {
                $cf = $f;
                }
            else {
                $cf = CSV::Field.new;
                defined $f and $cf.add ($f.Str);
                }
            unless (self!ready (0, $cf)) {
                $!errno       = 2110;
                $!error_input = $f.Str;
                $!error_field = $i;
                return False;
                }
            }
        True;
        }

    method parse (Str $buffer) returns Bool {

        my int $skip = 0;
        my int $pos  = 0;
        my int $ppos = 0;

        $!errno = 0;
        my sub parse_error (Int $errno, Int :$fpos) {
            $!errno         = $errno;
            $!error_pos     = $fpos // $pos;
            $!error_field   = $!csv-row.fields.elems + 1;
            $!error_message = %errors{$errno};
            $!error_input   = $buffer;
            $!eof           = $errno == 2012;
            $!auto_diag && !($!io && $!eof) and self.error_diag;
            False;
            }

        my sub chunks (Str $str, @re) {
            $str.defined
              ?? $str.chars
                ?? $str.split (@re, :v, :skip-empty) # :ignoremark
                !! ("")
              !! ()
            }

        $!record_number = $!record_number + 1;
        $opt_v > 4 and progress ($!record_number, $buffer.raku);

        my CSV::Field $f   = CSV::Field.new;
        my            $eol = $!eol // rx{ \r\n || \r || \n };
        my Str        $sep = $!sep;
        my Str        $quo = $!quo;
        my Str        $esc = $!esc;
        my Bool       $noe = !$esc.defined || ($quo.defined && $esc eq $quo);
        my            @eox = $!eol.defined ?? $eol !! ("\r\n", "\r", "\n");
        my            @chx = @eox;
        defined $sep and @chx.push: $sep;
        defined $quo and @chx.push: $quo;
        $noe         or  @chx.push: $esc;

        $!csv-row.fields = ();

        my sub keep () returns Bool {
            self!ready (1, $f) ?? !($f = CSV::Field.new) !! False;
            } # add

        my sub parse_done () {
            self!ready (1, $f) or return False;
            .($!csv-row) with %!callbacks<after_parse>;
            if ($!strict) {
                $!strict_n //= $!csv-row.fields.elems;
                $!strict_n  == $!csv-row.fields.elems or
                    return parse_error (2014);
                }
            True;
            }

        my @ch;
        $!io and @ch = @!ahead;
        @!ahead = ();
        if ($buffer.defined) {
            $!comment_str && $!csv-row.fields.elems == 0
                && $buffer.starts-with ($!comment_str) and
                    return self.parse ($!io.get // Str);
            @ch.append: chunks ($buffer, @chx);
            }
        @ch or return parse_error (2012);

        $opt_v > 2 and progress (0, @ch.raku);

        if ($!skip_empty_rows and
                @ch.elems == 0 ||
               (@ch.elems == 1 && @ch[0] eq @eox.any)) {
            $!skip_empty_rows == 1 and return self.parse ($!io.get // Str);
            $!skip_empty_rows == 2 and return False;
            $!skip_empty_rows == 3 and die "Empty row";
            $!skip_empty_rows == 4 and die "Empty row";
            $!skip_empty_rows == 5 and return parse_error (2015);
            $!skip_empty_rows == 6 and @ch = chunks ($!skip_empty_rows_cb.(self), @chx);
            }

        @ch.elems or return parse_done ();       # An empty line

        loop {
            # faster than @ch.kv, faster than my int $i
            loop (my Int $i = 0; $i < @ch.elems; $i = $i + 1) {
                my Str $chunk := @ch[$i];
                $ppos += $chunk.chars;

                if ($skip) {
                    $skip--;
                    next;
                    }

                $pos = $ppos;

                $opt_v > 8 and progress ($i, "###", $chunk.raku~"\t", $f.gist);

                if ($chunk eq $sep) {
                    $opt_v > 5 and progress ($i, "SEP - " ~ $f.gist);

                    # ,1,"foo, 3",,bar,
                    # ^           ^
                    if ($f.undefined) {
                        $!blank_is_undef || $!empty_is_undef or
                            $f.add ("");
                        keep () or return False;
                        next;
                        }

                    # ,1,"foo, 3",,bar,
                    #        ^
                    if ($f.is_quoted) {
                        $opt_v > 9 and progress ($i, "    inside quoted field ", @ch[$i..*-1].raku);
                        $f.add ($chunk);
                        next;
                        }

                    # ,1 ,"foo, 3"  ,,bar ,
                    #    ^          ^     ^
                    $!allow_whitespace && !$f.undefined and $f.text ~~ s{ <[\ \t]>+ $} = "";

                    # ,1,"foo, 3",,bar,
                    #   ^        ^    ^
                    keep () or return False;
                    next;
                    }

                if ($quo.defined and $chunk eq $quo) {
                    $opt_v > 5 and progress ($i, "QUO -" ~ $f.gist);

                    # ,1,"foo, 3",,bar,\r\n
                    #    ^
                    if ($f.undefined) {
                        $opt_v > 9 and progress ($i, "    initial quote");
                        $f.set_quoted;
                        next;
                        }

                    if ($f.is_quoted) {

                        $opt_v > 9 and progress ($i, "    inside quoted field ", @ch[$i..*-1].raku);
                        # ,1,"foo, 3"
                        #           ^
                        $i == @ch - 1 and return parse_done ();

                        my Str $next   = @ch[$i + 1];
                        my int $omit   = 1;
                        my int $quoesc = 0;

                        # , 1 , "foo, 3" , , bar , "" \r\n
                        #               ^            ^
                        if ($!allow_whitespace && $next ~~ /^ <[\ \t]>+ $/) {
                            $i == @ch - 2 and return parse_done ();
                            $next = @ch[$i + 2];
                            $omit = $omit + 1;
                            }

                        $opt_v > 8 and progress ($i, "QUO", "next = $next");

                        # ,1,"foo, 3",,bar,\r\n
                        #           ^
                        if ($next eq $sep) {
                            $opt_v > 7 and progress ($i, "SEP");
                            $skip = $omit;
                            keep () or return False;
                            next;
                            }

                        # ,1,"foo, 3"\r\n
                        #           ^
                        # $next ~~ /^ $eol $/ and return parse_done ();
                        if ($!eol.defined
                                ?? $next eq $!eol
                                !! ( $next eq "\r\n"
                                  || $next eq "\n"
                                  || $next eq "\r")) {
                            return parse_done ();
                            }

                        if (defined $esc and $esc eq $quo) {
                            $opt_v > 7 and progress ($i, "ESC", "($next)");

                            $quoesc = 1;

                            # ,1,"foo, 3"056",,bar,\r\n
                            #            ^
                            if ($next.starts-with ("0")) {
                                @ch[$i + 1] ~~ s{^ "0"} = "";
                                $ppos = $ppos + 1;
                                $opt_v > 8 and progress ($i, "Add NIL");
                                $f.add ("\c0");
                                next;
                                }

                            # ,1,"foo, 3""56",,bar,\r\n
                            #            ^
                            if ($next eq $quo) {
                                $skip = $omit;
                                $f.add ($chunk);
                                next;
                                }

                            if ($!allow_loose_escapes) {
                                # ,1,"foo, 3"56",,bar,\r\n
                                #            ^
                                next;
                                }
                            }

                        # No need to special-case \r

                        if ($quoesc == 1) {
                            # 1,"foo" ",3
                            #        ^
                            return parse_error (2023);
                            }

                        if ($!allow_loose_quotes) {
                            # ,1,"foo, 3"456",,bar,\r\n
                            #            ^
                            $f.add ($chunk);
                            next;
                            }

                        # Keep rest of @ch for hooks?
                        return parse_error (2011);
                        }

                    # 1,foo "boo" d'uh,1
                    #       ^
                    if ($!allow_loose_quotes) {
                        $f.add ($chunk);
                        next;
                        }
                    return parse_error (2034);
                    }

                if ($esc.defined and $chunk eq $esc) {
                    $opt_v > 5 and progress ($i, "ESC - " ~ $f.gist);

                    if ($i >= $@ch.elems - 1) {
                        if ($!allow_loose_escapes) {
                            $f.add ($chunk);
                            next;
                            }
                        return parse_error ($f.is_quoted ?? 2024 !! 2035);
                        }

                    my $next := @ch[$i + 1];

                    # ,1,"foo, 3\056",,bar,\r\n
                    #            ^
                    if ($next.starts-with ("0")) {
                        @ch[$i + 1] ~~ s{^ "0"} = "";
                        $ppos = $ppos + 1;
                        $opt_v > 8 and progress ($i, "Add NIL");
                        $f.add ("\c0");
                        next;
                        }

                    # ,1,"foo, 3\"56",,bar,\r\n
                    #            ^
                    if ($next eq $quo) {
                        $skip = 1;
                        $f.add ($quo);
                        next;
                        }

                    # ,1,"foo, 3\\56",,bar,\r\n
                    #            ^
                    if ($next eq $esc) {
                        $skip = 1;
                        $f.add ($esc);
                        next;
                        }

                    if ($!allow_loose_escapes) {
                        # ,1,"foo, 3\56",,bar,\r\n
                        #            ^
                        next;
                        }

                    return parse_error (2025);
                    }

                #if ($chunk ~~ rx{^ $eol $}) {
                if ($!eol.defined
                        ??  $chunk eq $!eol
                        !! ($chunk eq "\r\n" || $chunk eq "\n" || $chunk eq "\r")) {
                    $opt_v > 5 and progress ($i, "EOL - " ~ $f.gist);
                    if ($f.is_quoted) {     # 1,"2\n3"
                        $!binary or
                            return parse_error (
                                $!eol.defined       ?? 2021 !!
                                $chunk ~~ m{<[\r]>} ?? 2022 !!
                                $chunk ~~ m{<[\n]>} ?? 2021 !!  2026);

                        $f.add ($chunk);

                        if ($i == @ch.elems - 1 && $!io.defined) {
                            my $str := $!io.get or return parse_error (2012);
                            @ch.append: chunks ($str, @chx);
                            }

                        next;
                        }

                    # ,1,"foo, 3",,bar
                    #                    ^
                    $!allow_whitespace && !$f.undefined and $f.text ~~ s{ <[\ \t]>+ $} = "";

                    $!io.defined and @!ahead = @ch[($i + 1) .. *];

                    # sep=;
                    if ($!record_number == 1 && $!io.defined && $!csv-row.fields.elems == 0 &&
                            !$f.undefined && $f.Str ~~ /^ "sep=" (.*) /) {
                        $!sep = $0.Str;
                        $!record_number = 0;
                        return self.parse ($!io.get // Str);
                        }

                    return parse_done ();
                    }

                # 1,foo,  bar,4
                #       ^
                if ($!allow_whitespace) {
                    $f.undefined        and $chunk ~~ s{^ <[\ \t]>+  } = "";
                    $i + 1 == @ch.elems and $chunk ~~ s{  <[\ \t]>+ $} = "";
                    }

                unless ($!binary) {
                    $opt_v > 5 and progress ($i, "data - check binary");
                    my $fpos = $pos - $chunk.chars + 1;
                    if ($f.is_quoted) {
                        $chunk ~~ m/  \r / and return parse_error (2022, fpos => $fpos + $/.from);
                        $chunk ~~ m/  \n / and return parse_error (2021, fpos => $fpos + $/.from);
                        }
                    else {
                        $chunk ~~ m/^ \r / and return parse_error (2031, fpos => $fpos);
                        $chunk ~~ m/  \r / and return parse_error (2032, fpos => $fpos + $/.from);
                        }
                    }
                $chunk ne "" and $f.add ($chunk);
                }

            $f.is_quoted       or last;
            $!io.defined       or return parse_error (2027);

            my $str = $!io.get;
            unless ($str.defined) {
                parse_error (2027);
                $!eof = True;
                return False;
                }

            @ch = chunks ($str, @chx);

            $i = 0;
            };

#       !$!binary && $f.is_binary and
#           return parse_error ($f.is_quoted ?? 2026 !! 2037);

        parse_done ();
        } # parse

    method row returns CSV::Row { $!csv-row; }

    method !row_hr (@row) {
        my @cn  = (@!crange ?? @!cnames[@!crange] !! @!cnames);
        hash @cn Z=> @row;
        }

    multi method getline_hr (Str $str, Bool :$meta = $!keep_meta) {
        @!cnames.elems or self!fail (3002);
        self!row_hr (self.getline ($str, :$meta));
        } # getline_hr

    multi method getline_hr (IO::Handle:D $io, Bool :$meta = $!keep_meta) {
        @!cnames.elems or self!fail (3002);
        self!row_hr (self.getline ($io,  :$meta));
        } # getline_hr

    multi method getline (Str $str, Bool :$meta = $!keep_meta) {
        self.parse ($str)
          ?? $meta
            ?? self.fields
            !! self.strings
          !! Nil;
        } # getline

    multi method getline (IO::Handle:D $io, Bool :$meta = $!keep_meta) {
        my Bool $chomped = $io.chomp;
        my $nl    = $io.nl-in;
        $!eol.defined and $io.nl-in = $!eol;
        $io.chomp = False;
        $!io      = $io;
        my Bool $status  = self.parse ($io.get // Str);
        $!io      = IO::Handle;
        $io.nl-in = $nl;
        $io.chomp = $chomped;
        $status ?? $meta ?? self.fields !! self.strings !! ();
        } # getline

    method getline_hr_all (IO::Handle:D  $io,
                           Int   $offset =  0,
                           Int   $length = -1,
                           Bool :$meta   = False) {
        @!cnames.elems or self!fail (3002);
        self.getline_all ($io, $offset, $length, :$meta, :hr);
        }

    method !row (Bool:D $meta, Bool:D $hr) {
        my @row = $meta ?? self.fields !! self.strings;
        $hr or return $[ @row ];

        my @cn = (@!crange ?? @!cnames[@!crange] !! @!cnames);
        my %hash = @cn Z=> @row;
        $%hash;
        }

    # @a = $csv.getline_all ($io);
    # @a = $csv.getline_all ($io, $offset);
    # @a = $csv.getline_all ($io, $offset, $length);
    method getline_all (IO::Handle:D  $io,
                        Int           $offset is copy =  0,
                        Int           $length is copy = -1,
                        Bool         :$meta = $!keep_meta,
                        Bool         :$hr   = False) {
        my Bool $chomped = $io.chomp;
        my $nl           = $io.nl-in;
        $!eol.defined  and $io.nl-in = $!eol;
        $io.chomp        = False;
        $!io             = $io;
        $!record_number  = 0;

        my @lines;
        if ($offset >= 0) {
            while (self.parse ($io.get // Str)) {
                !$!rrange || $!rrange.in ($!record_number - 1) or next;

                $offset--  > 0 and next;
                $length-- == 0 and last;

                !%!callbacks<filter>.defined ||
                    %!callbacks<filter>.($!csv-row) or next;

                @lines.push: self!row ($meta, $hr);
                }
            }
        else {
            $offset = -$offset;
            while (self.parse ($io.get // Str)) {
                !%!callbacks<filter>.defined ||
                    %!callbacks<filter>.($!csv-row) or next;

                @lines.elems == $offset and @lines.shift;
                @lines.push: self!row ($meta, $hr);
                }
            $length >= 0 && @lines.elems > $length and @lines.splice ($length);
            }

        $!io      = IO::Handle;
        $io.nl-in = $nl;
        $io.chomp = $chomped;
        @lines;
        }

    method fragment (IO::Handle:D $io, Str $spec is copy, Bool :$meta = $!keep_meta) {

        $spec.defined && $spec.chars or self!fail (2013);

        self.rowrange (Str);
        self.colrange (Str);

        if ($spec ~~ s:i{^ "row=" } = "") {
            self.rowrange ($spec);
            return @!cnames.elems ?? self.getline_hr_all ($io, :$meta)
                                  !! self.getline_all    ($io, :$meta);
            }

        if ($spec ~~ s:i{^ "col=" } = "") {
            self.colrange ($spec);
            return @!cnames.elems ?? self.getline_hr_all ($io, :$meta)
                                  !! self.getline_all    ($io, :$meta);
            }

        $spec ~~ s:i{^ "cell=" } = "" or self!fail (2013);

        my CellSet $cs = CellSet.new;
        # cell=1,1-2,2;3,3-4,4;1,4;4,1
        for $spec.split (";") -> $r {
            $r ~~ /^     (<[0..9]>+)         ","  (<[0..9]>+)
                   ["-" [(<[0..9]>+)||("*")] "," [(<[0..9]>+)||("*") ]]?
                   $/ or self!fail (2013);
            my Int $tlr = +$0;
                   $tlr ==  0  and self!fail (2013);
            my Int $tlc = +$1;
                   $tlc ==  0  and self!fail (2013);
            my Str $Brr = ($2 // $tlr).Str;
            my Str $Brc = ($3 // $tlc).Str;
            my Num $brr = $Brr eq "*" ?? Inf !! (+$Brr).Num;
            my Num $brc = $Brc eq "*" ?? Inf !! (+$Brc).Num;
            $tlr <= $brr && --$tlc <= --$brc or self!fail (2013);
            $cs.add ($tlr, $tlc, $brr, $brc);
            }

        my Bool $chomped = $io.chomp;
        my $nl           = $io.nl-in;
        $!eol.defined  and $io.nl-in = $!eol;
        $io.chomp        = False;
        $!io             = $io;
        $!record_number  = 0;

        my @lines;
        while (self.parse ($io.get // Str)) {

            my CSV::Field @f = $!csv-row.fields[
                (^($!csv-row.fields.elems)).grep ({
                    $cs.in ($!record_number, $_) })] or next;

            !%!callbacks<filter>.defined ||
                %!callbacks<filter>.(CSV::Row.new (csv => self, @f)) or next;

            my @row = $meta ?? @f !! @f.map ( -> \x --> Str { x.Str });
            if (@!cnames.elems) {
                my %h = @!cnames Z=> @row;
                @lines.push: $%h;
                next;
                }
            @lines.push: $[ @row ];
            }

        $!io      = IO::Handle;
        $io.nl-in = $nl;
        $io.chomp = $chomped;
        @lines;
        }

    multi method print (IO::Handle:D $io, *%p) returns Bool {
        @!cnames.elems or self!fail (3009);
        self.print ($io, %p{@!cnames});
        }

    multi method print (IO::Handle:D $io,  %c) returns Bool {
        @!cnames.elems or self!fail (3009);
        self.print ($io, [ %c{@!cnames} ]);
        }

    multi method print (IO::Handle:D $io, Capture $c) returns Bool {
        self.print ($io, $c.list);
        }

    multi method print (IO::Handle:D $io,  @fld) returns Bool {
        self.combine (@fld) or return False;
        my $nl = $io.nl-out;
        $!eol.defined and $io.nl-out = $!eol;
        $io.print (self.string);
        $io.nl-out = $nl;
        True;
        }

    multi method print (IO::Handle:D $io, *@fld) returns Bool {
        # Temp guard ... I expected *%p sig to be called
        if (@fld[0] ~~ Pair) {
            @!cnames.elems or self!fail (3009);
            my %hash;
            for @fld -> $kv {
                %hash{$kv.key} = $kv.value;
                }
            return self.print ($io, [ %hash{@!cnames} ]);
            }
        self.print ($io, [@fld]);
        }

    method say (IO::Handle:D $io, |f) returns Bool {
        my $eol = $!eol;
        $!eol ||= "\r\n";
        my Bool $state = self.print ($io, |f);
        $!eol = $eol;
        $state;
        }

    # Only as a method, both in and out are required
    method CSV ( Any    :$in!,
                 Any    :$out!     is copy,
                 Any    :$headers  is copy,
                 Str    :$encoding is copy,
                 Str    :$fragment is copy,
                 Bool   :$strict = False,
                 Bool   :$meta   = False,
                        :@kh,
                 *%args) {

        # Aliasses
        #   frag   fragment
        #   enc    encoding
        %args<frag>.defined and $fragment ||= %args<frag> :delete;
        %args<enc >.defined and $encoding ||= %args<enc>  :delete;
        %args<enc >.defined and $encoding ||= %args<enc>  :delete;
        $fragment //= "";

        if (@kh.VAR.name ne "element") { # @kh = "@kh" or [] = "element"
            # https://github.com/rakudo/rakudo/issues/2483
            $out     //= Hash if $out === Any;
            $headers //= "auto";
            }

        # Indirect options to invoke headers
        if ($encoding.defined && $encoding eq "auto") {
            %args<detect-bom> = True;
            $encoding = Str;
            }
        if ($headers ~~ Bool && ?$headers) {
            %args<detect-bom> //= True;
            $headers = Any;
            }

        my $skip = %args<skip> :delete || 0 and
            self.rowrange (++$skip ~ "-*");

        # Check csv-only args
        # Hooks
        #   after_in    after-in    after_parse  after-parse
        #   before_out  before-out
        #   filter
        #   error
        #   on_in       on-in
        my Callable $on-in;
        my Callable $before-out;
        my %hooks;
        my %hdrargs;
        if (my $c = %args<callbacks> :delete) {
            if ($c.defined) {
                $c ~~ Hash or self!fail (1004);
                %args{$c.keys} = $c.values;
                }
            else {
                self.callbacks ($c);
                }
            }
        for (%args.keys) -> $k {
            if ($k.lc ~~ m{^ "on"     <[-_]>   "in"             $}) {
                $on-in               = %args{$k} :delete;
                next;
                }
            if ($k.lc ~~ m{^ "before" <[-_]>   "out"            $}) {
                $before-out          = %args{$k} :delete;
                next;
                }
            if ($k.lc ~~ m{^ "after"  <[-_]> ( "parse" | "in" ) $}) {
                %hooks<after_parse>  = %args{$k} :delete;
                next;
                }
            if ($k.lc ~~ m{^ "before" <[-_]>   "print"          $}) {
                %hooks<before_print> = %args{$k} :delete;
                next;
                }
            if ($k.lc eq "filter") {
                %hooks<filter>       = %args{$k} :delete;
                if (%hooks<filter> &&
                    %hooks<filter>.signature.params.head.sigil eq "%") {
                    $out     //= Hash if $out === Any;
                    $headers //= "auto";
                    }
                next;
                }
            if ($k.lc eq "error") {
                %hooks<error>        = %args{$k} :delete;
                next;
                }

            if ($k.lc ~~ m{^ [ "detect" <[-_]> ]? "bom"         $}) {
                %hdrargs<detect-bom>         = %args{$k} :delete;
                next;
                }
            if ($k.lc ~~ m{^ "sep" [ <[-_]> "set" || "s" ]      $}) {
                %hdrargs<sep-set>            = %args{$k} :delete;
                next;
                }
            if ($k.lc eq "munge") {
                %hdrargs<munge-column-names> = %args{$k} :delete;
                next;
                }
            if ($k.lc ~~ m{^ "munge" [ <[-_]> "column" <[-_]> "names" ]? $}) {
                %hdrargs<munge-column-names> = %args{$k} :delete;
                next;
                }
            if ($k.lc ~~ m{^ "set" <[-_]> "column" <[-_]> "names" $}) {
                %hdrargs<set-column-names>   = %args{$k} :delete;
                next;
                }
            }
        if ($on-in && !%hooks<after_parse>) {
            %hooks<after_parse> = $on-in;
            $on-in = Callable;
            }
        %hooks.keys and self.callbacks (|%hooks.list);

        my $key = %args<key> :delete;
        if ($key.defined) {
            $out     //= Hash if $out === Any;
            $headers //= "auto";
            }

        # Rest is for Text::CSV
        %args<strict>    = $strict;
        %args<keep_meta> = $meta;
        self!set-attributes (%args);

        my @in; # Either AoA or AoH

        my IO::Handle $io-in;
        # in
        #   parse
        #     "file.csv"                       Str
        #     $io                              IO::Handle
       