package main

import (
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/gdamore/tcell/v3"
)

type iconDef struct {
	icon     string
	hasStyle bool
	style    tcell.Style
}

type iconMap struct {
	icons         map[string]iconDef
	useLinkTarget bool
}

func iconWithoutStyle(icon string) iconDef {
	return iconDef{icon, false, tcell.StyleDefault}
}

func iconWithStyle(icon string, style tcell.Style) iconDef {
	return iconDef{icon, true, style}
}

func parseIcons() iconMap {
	im := iconMap{
		icons:         make(map[string]iconDef),
		useLinkTarget: false,
	}

	defaultIcons := []string{
		"ln=l",
		"or=l",
		"tw=t",
		"ow=d",
		"st=t",
		"di=d",
		"pi=p",
		"so=s",
		"bd=b",
		"cd=c",
		"su=u",
		"sg=g",
		"ex=x",
		"fi=-",
	}

	im.parseEnv(strings.Join(defaultIcons, ":"))

	if env := os.Getenv("LF_ICONS"); env != "" {
		im.parseEnv(env)
	}

	for _, path := range gIconsPaths {
		if _, err := os.Stat(path); !os.IsNotExist(err) {
			im.parseFile(path)
		}
	}

	return im
}

func (im *iconMap) parseFile(path string) {
	log.Printf("reading file: %s", path)

	f, err := os.Open(path)
	if err != nil {
		log.Printf("opening icons file: %s", err)
		return
	}
	defer f.Close()

	arrs, err := readArrays(f, 1, 3)
	if err != nil {
		log.Printf("reading icons file: %s", err)
		return
	}

	for _, arr := range arrs {
		im.parseArray(arr)
	}
}

func (im *iconMap) parseEnv(env string) {
	for entry := range strings.SplitSeq(env, ":") {
		if entry == "" {
			continue
		}

		pair := strings.Split(entry, "=")

		if len(pair) != 2 {
			log.Printf("invalid $LF_ICONS entry: %s", entry)
			return
		}

		im.parseArray(pair)
	}
}

func (im *iconMap) parseArray(arr []string) {
	key := replaceTilde(arr[0])

	if filepath.IsAbs(key) {
		key = filepath.Clean(key)
	}

	switch len(arr) {
	case 1:
		delete(im.icons, key)
	case 2:
		icon := arr[1]
		if key == "ln" && icon == "target" {
			im.useLinkTarget = true
		} else {
			im.icons[key] = iconWithoutStyle(icon)
		}
	case 3:
		icon, color := arr[1], arr[2]
		im.icons[key] = iconWithStyle(icon, applySGR(color, tcell.StyleDefault))
	}
}

func (im iconMap) get(f *file) iconDef {
	if val, ok := im.icons[f.path]; ok {
		return val
	}

	if f.IsDir() {
		if val, ok := im.icons[f.Name()+"/"]; ok {
			return val
		}
	}

	var key string

	switch {
	case f.linkState == working && !im.useLinkTarget:
		key = "ln"
	case f.linkState == broken:
		key = "or"
	case f.IsDir() && f.Mode()&os.ModeSticky != 0 && f.Mode()&0o002 != 0:
		key = "tw"
	case f.IsDir() && f.Mode()&0o002 != 0:
		key = "ow"
	case f.IsDir() && f.Mode()&os.ModeSticky != 0:
		key = "st"
	case f.IsDir():
		key = "di"
	case f.Mode()&os.ModeNamedPipe != 0:
		key = "pi"
	case f.Mode()&os.ModeSocket != 0:
		key = "so"
	case f.Mode()&os.ModeCharDevice != 0:
		key = "cd"
	case f.Mode()&os.ModeDevice != 0:
		key = "bd"
	case f.Mode()&os.ModeSetuid != 0:
		key = "su"
	case f.Mode()&os.ModeSetgid != 0:
		key = "sg"
	case isExecutable(f.FileInfo):
		key = "ex"
	}

	if val, ok := im.icons[key]; ok {
		return val
	}

	if val, ok := im.icons[f.Name()+"*"]; ok {
		return val
	}

	if val, ok := im.icons["*"+f.Name()]; ok {
		return val
	}

	if val, ok := im.icons[filepath.Base(f.Name())+".*"]; ok {
		return val
	}

	if val, ok := im.icons["*"+strings.ToLower(f.ext)]; ok {
		return val
	}

	if val, ok := im.icons["fi"]; ok {
		return val
	}

	return iconWithoutStyle(" ")
}
