use Cro::MediaType;
use Cro::MessageWithBody;
use Cro::HTTP::Header;

role Cro::HTTP::Message does Cro::MessageWithBody {
    has Str $.http-version is rw;
    has Int $.http2-stream-id is rw;
    has Cro::HTTP::Header @!headers;

    method headers() {
        @!headers.List
    }

    multi method append-header(Cro::HTTP::Header $header --> Nil) {
        @!headers.push($header);
    }

    multi method append-header(Str $header --> Nil) {
        @!headers.push(Cro::HTTP::Header.parse($header));
    }

    multi method append-header(Str $name, Str(Cool) $value --> Nil) {
        @!headers.push(Cro::HTTP::Header.new(:$name, :$value));
    }
    multi method append-header(Pair $header --> Nil) {
        @!headers.push(Cro::HTTP::Header.new(name => $header.key, value => $header.value));
    }

    multi method remove-header(Str $name --> Int) {
        my $folded = $name.fc;
        my $removed = 0;
        @!headers .= grep({ not .name.fc eq $folded && ++$removed });
        $removed
    }

    multi method remove-header(&predicate --> Int) {
        my $removed = 0;
        @!headers .= grep({ not predicate($_) && ++$removed });
        $removed
    }

    multi method remove-header(Cro::HTTP::Header $header --> Int) {
        my $removed = 0;
        @!headers .= grep({ not $_ === $header && ++$removed });
        $removed
    }

    method has-header(Str $header-name --> Bool) {
        my $folded = $header-name.fc;
        so @!headers.first(*.name.fc eq $folded)
    }

    method header(Str $header-name) {
        my $folded = $header-name.fc;
        my @matching := @!headers.grep(*.name.fc eq $folded).list;
        @matching == 1
            ?? @matching[0].value
            !! @matching == 0
                ?? Nil
                !! @matching.map(*.value).join(',')
    }

    method header-list(Str $header-name) {
        my $folded = $header-name.fc;
        @!headers.grep(*.name.fc eq $folded).map(*.value).list
    }

    method !headers-str() {
        @!headers.map({ .name ~ ": " ~ .value ~ "\r\n" }).join
    }

    method content-type() {
        with self.header('content-type') {
            Cro::MediaType.parse($_)
        }
        else {
            Nil
        }
    }

    method body-text-encoding(Blob $blob) {
        my $encoding;
        with self.content-type {
            with .parameters.first(*.key.fc eq 'charset') {
                $encoding = .value;
            }
        }
        without $encoding {
            # Decoder drops the BOM by itself, if it exists, so just use
            # it for identification here.
            if $blob[0] == 0xEF && $blob[1] == 0xBB && $blob[2] == 0xBF {
                $encoding = 'utf-8';
            }
            elsif $blob[0] == 0xFF && $blob[1] == 0xFE {
                $encoding = 'utf-16';
            }
            elsif $blob[0] == 0xFE && $blob[1] == 0xFF {
                $encoding = 'utf-16';
            }
        }
        $encoding // ('utf-8', 'latin-1')
    }
}
