package choose_files

import (
	"archive/tar"
	"compress/bzip2"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"strings"

	"github.com/klauspost/compress/gzip"
	"github.com/klauspost/compress/zip"
	"github.com/klauspost/compress/zstd"
	"github.com/nwaples/rardecode/v2"
	"github.com/ulikunitz/xz"

	"github.com/kovidgoyal/kitty/tools/utils"
)

var _ = fmt.Print

func IsSupportedArchiveFile(abspath string) bool {
	name := strings.ToLower(filepath.Base(abspath))
	ext := filepath.Ext(name)
	switch ext {
	case ".zip", ".tgz", ".tbz2", ".tzst", ".txz", ".rar":
		return true
	case ".gz", ".bz2", ".zst", ".xz":
		name = name[:len(name)-len(ext)]
		ext = filepath.Ext(name)
		return ext == ".tar"
	default:
		return false
	}
}

type archive_preview struct {
	path             string
	metadata         fs.FileInfo
	WakeupMainThread func() bool
	ch               chan *MessagePreview
	mp               *MessagePreview
}

func displayFilename(s string) string {
	if isPrintableUTF8(s) {
		return s
	}
	return fmt.Sprintf("%X", utils.UnsafeStringToBytes(s))
}

func isPrintableUTF8(s string) bool {
	for _, r := range s {
		if r == '\uFFFD' { // replacement char
			return false
		}
	}
	return true
}

func (p *archive_preview) render() {
	name := strings.ToLower(filepath.Base(p.path))
	ext := filepath.Ext(name)
	names := []string{""}
	populate_tar := func(r io.Reader) {
		tr := tar.NewReader(r)
		for len(names) < 500 {
			hdr, err := tr.Next()
			if err != nil {
				break
			}
			names = append(names, displayFilename(hdr.Name))
		}
	}
	switch ext {
	case ".zip":
		r, err := zip.OpenReader(p.path)
		if err == nil || errors.Is(err, zip.ErrInsecurePath) {
			defer r.Close()
			for _, f := range r.File {
				if f.NonUTF8 {
					names = append(names, displayFilename(f.Name))
				} else {
					names = append(names, f.Name)
				}

			}
		}
	case ".rar":
		r, err := rardecode.OpenReader(p.path, rardecode.SkipCheck)
		if err == nil {
			defer r.Close()
			for len(names) < 500 {
				hdr, err := r.Next()
				if err != nil {
					break
				}
				names = append(names, displayFilename(hdr.Name))
			}
		}
	case ".gz", ".tgz":
		if f, err := os.Open(p.path); err == nil {
			defer f.Close()
			if gz, err := gzip.NewReader(f); err == nil {
				defer gz.Close()
				populate_tar(gz)
			}
		}
	case ".xz", ".txz":
		if f, err := os.Open(p.path); err == nil {
			defer f.Close()
			if gz, err := xz.NewReader(f); err == nil {
				populate_tar(gz)
			}
		}
	case ".bz2", ".tbz2":
		if f, err := os.Open(p.path); err == nil {
			defer f.Close()
			populate_tar(bzip2.NewReader(f))
		}
	case ".zst", ".tzst":
		if f, err := os.Open(p.path); err == nil {
			defer f.Close()
			if gz, err := zstd.NewReader(f); err == nil {
				defer gz.Close()
				populate_tar(gz)
			}
		}
	}
	mp := *p.mp
	mp.trailers = append(mp.trailers, names...)
	p.ch <- &mp
	p.WakeupMainThread()
}

func (p *archive_preview) IsReady() bool                         { return true }
func (p *archive_preview) Unload()                               {}
func (p *archive_preview) IsValidForColorScheme(light bool) bool { return true }
func (p *archive_preview) String() string                        { return fmt.Sprintf("ArchivePreview{%s}", p.path) }

func (p *archive_preview) Render(h *Handler, x, y, width, height int) {
	if p.ch != nil {
		select {
		case mp := <-p.ch:
			p.mp = mp
			close(p.ch)
			p.ch = nil
		default:
		}
	}
	p.mp.Render(h, x, y, width, height)
}

func NewArchivePeview(
	abspath string, metadata fs.FileInfo, opts Settings, WakeupMainThread func() bool,
) Preview {
	mp := NewFileMetadataPreview(abspath, metadata)
	ans := &archive_preview{
		path: abspath, metadata: metadata, WakeupMainThread: WakeupMainThread, ch: make(chan *MessagePreview, 1), mp: mp,
	}
	go ans.render()
	return ans
}
