// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package httpcommon

import (
	
	
	
	
	
	
)

var errConcurrentRead = errors.New("http: concurrent read on response body")

// incomparable is a zero-width, non-comparable type. Adding it to a struct
// makes that struct also non-comparable, and generally doesn't add
// any size (as long as it's first).
type incomparable [0]func()

// GzipReader wraps a response body so it can lazily
// get gzip.Reader from the pool on the first call to Read.
// After Close is called it puts gzip.Reader to the pool immediately
// if there is no Read in progress or later when Read completes.
type GzipReader struct {
	_    incomparable
	Body io.ReadCloser // underlying Response.Body
	mu   sync.Mutex    // guards zr and zerr
	zr   *gzip.Reader  // stores gzip reader from the pool between reads
	zerr error         // sticky gzip reader init error or sentinel value to detect concurrent read and read after close
}

type eofReader struct{}

func (eofReader) ([]byte) (int, error) { return 0, io.EOF }
func (eofReader) () (byte, error)  { return 0, io.EOF }

var gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }}

// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r.
func ( io.Reader) (*gzip.Reader, error) {
	 := gzipPool.Get().(*gzip.Reader)
	if  := .Reset();  != nil {
		gzipPoolPut()
		return nil, 
	}
	return , nil
}

// gzipPoolPut puts a gzip.Reader back into the pool.
func ( *gzip.Reader) {
	// Reset will allocate bufio.Reader if we pass it anything
	// other than a flate.Reader, so ensure that it's getting one.
	var  flate.Reader = eofReader{}
	.Reset()
	gzipPool.Put()
}

// acquire returns a gzip.Reader for reading response body.
// The reader must be released after use.
func ( *GzipReader) () (*gzip.Reader, error) {
	.mu.Lock()
	defer .mu.Unlock()
	if .zerr != nil {
		return nil, .zerr
	}
	if .zr == nil {
		// gzipPoolGet might block indefinitely since it reads the gzip header.
		// Therefore, drop mu temporarily when using gzipPoolGet.
		// We set zerr to errConcurrentRead to prevent concurrent read
		// even when mu is temporarily dropped.
		.zerr = errConcurrentRead
		.mu.Unlock()
		,  := gzipPoolGet(.Body)
		.mu.Lock()
		// Guard against Close being called while gzipPoolGet is running.
		if .zerr != errConcurrentRead {
			if  != nil {
				gzipPoolPut()
			}
			return nil, .zerr
		}
		.zr, .zerr = , 
		if .zerr != nil {
			return nil, .zerr
		}
	}
	 := .zr
	.zr, .zerr = nil, errConcurrentRead
	return , nil
}

// release returns the gzip.Reader to the pool if Close was called during Read.
func ( *GzipReader) ( *gzip.Reader) {
	.mu.Lock()
	defer .mu.Unlock()
	if .zerr == errConcurrentRead {
		.zr, .zerr = , nil
	} else { // fs.ErrClosed
		gzipPoolPut()
	}
}

// close returns the gzip.Reader to the pool immediately or
// signals release to do so after Read completes.
func ( *GzipReader) () {
	.mu.Lock()
	defer .mu.Unlock()
	if .zerr == nil && .zr != nil {
		gzipPoolPut(.zr)
		.zr = nil
	}
	.zerr = fs.ErrClosed
}

func ( *GzipReader) ( []byte) ( int,  error) {
	,  := .acquire()
	if  != nil {
		return 0, 
	}
	defer .release()

	return .Read()
}

func ( *GzipReader) () error {
	.close()

	return .Body.Close()
}