package gliffy2drawio

import (
	"bufio"
	"encoding/json"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
)

const DefaultTranslationPath = "java/gliffy/importer/gliffyTranslation.properties"

type StencilTranslator struct {
	once         sync.Once
	path         string
	translations map[string]string
	customFile   string
}

func NewStencilTranslator(path string) *StencilTranslator {
	return &StencilTranslator{path: path}
}

// WithCustomMapping allows specifying an optional JSON file (map[string]string) for extra translations.
func (s *StencilTranslator) WithCustomMapping(jsonPath string) *StencilTranslator {
	s.customFile = jsonPath
	return s
}

func (s *StencilTranslator) Translate(uid, tid string) string {
	s.once.Do(s.load)
	if s.translations == nil {
		return ""
	}
	if v, ok := s.translations[uid]; ok {
		return v
	}
	if tid != "" {
		if v, ok := s.translations[tid]; ok {
			return v
		}
	}
	return ""
}

func (s *StencilTranslator) load() {
	path := s.path
	if path == "" {
		path = DefaultTranslationPath
	}
	file, err := os.Open(filepath.Clean(path))
	if err != nil {
		return
	}
	defer file.Close()

	s.translations = make(map[string]string)
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		parts := strings.SplitN(line, "=", 2)
		if len(parts) != 2 {
			continue
		}
		s.translations[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
	}
	// Hard-coded additions for shapes absent from properties.
	for k, v := range map[string]string{
		"gshape.aws_v3.database.Amazon_RDS_PostgreSQL_instance_light_bg":   "mxgraph.aws4.rds_postgresql_instance;fillColor=#3334B9",
		"gstencil.aws_v3.database.Amazon_RDS_PostgreSQL_instance_light_bg": "mxgraph.aws4.rds_postgresql_instance;fillColor=#3334B9",
	} {
		if _, ok := s.translations[k]; !ok {
			s.translations[k] = v
		}
	}

	// Load optional custom JSON mapping file if provided.
	if s.customFile != "" {
		if data, err := os.ReadFile(filepath.Clean(s.customFile)); err == nil {
			var extra map[string]string
			if err := json.Unmarshal(data, &extra); err == nil {
				for k, v := range extra {
					if _, exists := s.translations[k]; !exists {
						s.translations[k] = v
					}
				}
			}
		}
	}
}

func DashStyleMapping(value string, width int) string {
	if value == "" {
		return ""
	}
	parts := strings.Split(value, ",")
	for i, p := range parts {
		if f, err := strconv.ParseFloat(strings.TrimSpace(p), 64); err == nil {
			parts[i] = floatToString(f * float64(width))
		}
	}
	return "dashed=1;fixDash=1;dashPattern=" + strings.Join(parts, " ") + ";"
}

var lineMapping = map[string]string{
	"linear":     "",
	"orthogonal": "edgeStyle=orthogonal;",
	"quadratic":  "curved=1;edgeStyle=orthogonalEdgeStyle;",
}

func LineMapping(style string) string {
	return lineMapping[style]
}

type ArrowStyle struct {
	Name    string
	Fill    bool
	Size    int
	Spacing int
}

func (a ArrowStyle) ToString(start bool) string {
	fill := 0
	if a.Fill {
		fill = 1
	}
	if start {
		return "startArrow=" + a.Name + ";startFill=" + strconv.Itoa(fill) + ";startSize=" + strconv.Itoa(a.Size) + optionalSpacing("sourcePerimeterSpacing", a.Spacing) + ";"
	}
	return "endArrow=" + a.Name + ";endFill=" + strconv.Itoa(fill) + ";endSize=" + strconv.Itoa(a.Size) + optionalSpacing("targetPerimeterSpacing", a.Spacing) + ";"
}

func optionalSpacing(key string, v int) string {
	if v == 0 {
		return ""
	}
	return ";" + key + "=" + strconv.Itoa(v) + ";"
}

var arrowMapping = map[int]ArrowStyle{
	0:  {Name: "none", Fill: false, Size: 6},
	1:  {Name: "open", Fill: false, Size: 6},
	2:  {Name: "block", Fill: true, Size: 6},
	3:  {Name: "block", Fill: false, Size: 6},
	4:  {Name: "block", Fill: false, Size: 10},
	5:  {Name: "diamond", Fill: false, Size: 12},
	6:  {Name: "open", Fill: false, Size: 10},
	7:  {Name: "diamond", Fill: true, Size: 12},
	8:  {Name: "classic", Fill: true, Size: 6},
	9:  {Name: "ERzeroToMany", Fill: true, Size: 10},
	10: {Name: "ERoneToMany", Fill: true, Size: 10},
	11: {Name: "ERmandOne", Fill: true, Size: 10},
	12: {Name: "ERone", Fill: true, Size: 10},
	13: {Name: "ERzeroToOne", Fill: true, Size: 10},
	14: {Name: "ERmany", Fill: true, Size: 10},
	15: {Name: "oval", Fill: false, Size: 10, Spacing: 6},
	16: {Name: "dash", Fill: false, Size: 6},
	17: {Name: "block", Fill: true, Size: 6},
	18: {Name: "classic", Fill: true, Size: 6},
	19: {Name: "openAsync", Fill: false, Size: 10},
}

func ArrowMapping(id *int) ArrowStyle {
	if id == nil {
		return arrowMapping[0]
	}
	if a, ok := arrowMapping[*id]; ok {
		return a
	}
	return arrowMapping[0]
}
