package gophermap

import (
	"bufio"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"slices"
	"strings"

	"tildegit.org/tjp/sliderule/gopher"
	"tildegit.org/tjp/sliderule/internal/types"
)

// ListDir builds a gopher menu representing the contents of a directory.
func ListDir(dir string, location *url.URL, settings FileSystemSettings) (gopher.MapDocument, error) {
	return listDir(dir, location, settings, nil, nil)
}

func listDir(dir string, location *url.URL, settings FileSystemSettings, hidden map[string]struct{}, extensions map[string]types.Status) (gopher.MapDocument, error) {
	contents, err := os.ReadDir(dir)
	if err != nil {
		return nil, err
	}

	doc := gopher.MapDocument{}

	for _, entry := range contents {
		name := entry.Name()

		inf, err := entry.Info()
		if err != nil {
			return nil, err
		}
		if inf.Mode()&4 == 0 {
			continue
		}

		if _, ok := hidden[name]; ok || slices.Contains(settings.DirMaps, name) {
			continue
		}

		var code types.Status

		if entry.IsDir() {
			code = gopher.MenuType
		} else {
			ext := strings.TrimPrefix(filepath.Ext(name), ".")
			if c, ok := extensions[ext]; ok {
				code = c
			} else if c, ok := extensions[name]; ok {
				code = c
			} else {
				code = gopher.GuessItemType(name)
			}
		}

		doc = append(doc, gopher.MapItem{
			Type:     code,
			Display:  displayName(dir, entry, settings),
			Selector: path.Join(path.Dir(location.Path), name),
			Hostname: location.Hostname(),
			Port:     location.Port(),
		})
	}

	return doc, nil
}

func displayName(dir string, entry os.DirEntry, settings FileSystemSettings) string {
	fname := entry.Name()
	fullpath := filepath.Join(dir, fname)

	if entry.Type().IsRegular() && settings.ParseExtended && (strings.HasSuffix(fname, ".gophermap") || slices.Contains(settings.DirMaps, fname)) {
		if title := gophermapTitle(fullpath); title != "" {
			return title
		}
	}

	if entry.IsDir() {
		if settings.DirTag != "" {
			if tag := tagTitle(filepath.Join(fullpath, settings.DirTag)); tag != "" {
				return tag
			}
		}

		if settings.ParseExtended {
			for _, mapname := range settings.DirMaps {
				if title := gophermapTitle(filepath.Join(fullpath, mapname)); title != "" {
					return title
				}
			}
		}
	}

	return fname
}

func gophermapTitle(path string) string {
	file, err := os.Open(path)
	if err != nil {
		return ""
	}
	defer func() {
		_ = file.Close()
	}()

	rdr := bufio.NewReader(file)
	line, err := rdr.ReadString('\n')
	if err != nil {
		return ""
	}

	if !strings.HasPrefix(line, "!") {
		return ""
	}
	return strings.TrimRight(line[1:], "\r\n")
}

func tagTitle(path string) string {
	file, err := os.Open(path)
	if err != nil {
		return ""
	}
	defer func() {
		_ = file.Close()
	}()

	stat, err := file.Stat()
	if err != nil || stat.IsDir() {
		return ""
	}

	rdr := bufio.NewReader(file)
	line, err := rdr.ReadString('\n')
	if err != nil {
		return ""
	}
	return strings.TrimRight(line, "\r\n")
}