package git_pages

import (
	"archive/tar"
	"context"
	"fmt"
	"io"
)

type Flusher interface {
	Flush() error
}

// Inverse of `ExtractTar`.
func CollectTar(
	context context.Context, writer io.Writer, manifest *Manifest, metadata ManifestMetadata,
) (
	err error,
) {
	archive := tar.NewWriter(writer)

	appendFile := func(header *tar.Header, data []byte, transform Transform) (err error) {
		switch transform {
		case Transform_Identity:
		case Transform_Zstd:
			data, err = zstdDecoder.DecodeAll(data, []byte{})
			if err != nil {
				return fmt.Errorf("zstd: %s: %w", header.Name, err)
			}
		default:
			return fmt.Errorf("%s: unexpected transform", header.Name)
		}
		header.Size = int64(len(data))

		err = archive.WriteHeader(header)
		if err != nil {
			return fmt.Errorf("tar: %w", err)
		}
		_, err = archive.Write(data)
		if err != nil {
			return fmt.Errorf("tar: %w", err)
		}
		return
	}

	for fileName, entry := range manifest.Contents {
		var header tar.Header
		if fileName == "" {
			continue
		}
		header.Name = fileName

		switch entry.GetType() {
		case Type_Directory:
			header.Typeflag = tar.TypeDir
			header.Mode = 0755
			header.ModTime = metadata.LastModified
			err = appendFile(&header, nil, Transform_Identity)

		case Type_InlineFile:
			header.Typeflag = tar.TypeReg
			header.Mode = 0644
			header.ModTime = metadata.LastModified
			err = appendFile(&header, entry.GetData(), entry.GetTransform())

		case Type_ExternalFile:
			var blobReader io.Reader
			var blobMetadata BlobMetadata
			var blobData []byte
			blobReader, blobMetadata, err = backend.GetBlob(context, string(entry.Data))
			if err != nil {
				return
			}
			blobData, err = io.ReadAll(blobReader)
			if err != nil {
				return
			}
			header.Typeflag = tar.TypeReg
			header.Mode = 0644
			header.ModTime = blobMetadata.LastModified
			err = appendFile(&header, blobData, entry.GetTransform())

		case Type_Symlink:
			header.Typeflag = tar.TypeSymlink
			header.Mode = 0644
			header.ModTime = metadata.LastModified
			err = appendFile(&header, entry.GetData(), Transform_Identity)

		default:
			panic(fmt.Errorf("CollectTar encountered invalid entry: %v, %v",
				entry.GetType(), entry.GetTransform()))
		}
		if err != nil {
			return err
		}
	}

	if redirects := CollectRedirectsFile(manifest); redirects != "" {
		err = appendFile(&tar.Header{
			Name:     RedirectsFileName,
			Typeflag: tar.TypeReg,
			Mode:     0644,
			ModTime:  metadata.LastModified,
		}, []byte(redirects), Transform_Identity)
		if err != nil {
			return err
		}
	}

	if headers := CollectHeadersFile(manifest); headers != "" {
		err = appendFile(&tar.Header{
			Name:     HeadersFileName,
			Typeflag: tar.TypeReg,
			Mode:     0644,
			ModTime:  metadata.LastModified,
		}, []byte(headers), Transform_Identity)
		if err != nil {
			return err
		}
	}

	err = archive.Flush()
	if err != nil {
		return fmt.Errorf("tar: %w", err)
	}

	flusher, ok := writer.(Flusher)
	if ok {
		err = flusher.Flush()
	}
	return err
}
