package main

// Human-readable printing and the export formats.

import (
	"encoding/csv"
	"fmt"
	"io"
	"sort"
	"strconv"
	"strings"
)

func printConflicts(w io.Writer, in Inputs, cs []Conflict, strict bool) {
	fmt.Fprintf(w, "WindowDeck conflict report\n")
	if in.BaseFile != "" {
		fmt.Fprintf(w, "base     : %s (set %q)\n", in.BaseFile, in.BaseSet)
	} else {
		fmt.Fprintf(w, "base     : none - pass --base <file> to check against a team standard\n")
	}
	fmt.Fprintf(w, "sets     : %d (%s)\n", len(in.Sets), strings.Join(in.setNames(), ", "))
	fmt.Fprintf(w, "bindings : %d\n", len(in.Bindings))
	errs, warns := countSeverities(cs)
	fmt.Fprintf(w, "conflicts: %d (%d error, %d warning)\n", len(cs), errs, warns)
	if strict {
		fmt.Fprintf(w, "mode     : strict - warnings are failures too\n")
	}
	fmt.Fprintln(w)
	if len(cs) == 0 {
		fmt.Fprintln(w, "no conflicts found.")
		return
	}
	for i, c := range cs {
		fmt.Fprintf(w, "#%d  [%s] %s\n", i+1, c.Severity, c.Kind)
		if c.Chord != "" {
			fmt.Fprintf(w, "    chord   : %s\n", c.Chord)
		}
		if c.Action != "" {
			fmt.Fprintf(w, "    action  : %s\n", c.Action)
		}
		fmt.Fprintf(w, "    detail  : %s\n", c.Detail)
		if c.OS != "" {
			fmt.Fprintf(w, "    os      : %s - %s\n", osLabel(c.OS), c.OSDoes)
			fmt.Fprintf(w, "    source  : %s\n", c.OSSource)
		}
		for _, p := range c.Involved {
			fmt.Fprintf(w, "    involved: %-14s %-28s %-24s (written %q in %s)\n", p.Set, p.Action, p.Chord, p.Raw, p.File)
		}
		if len(c.Suggestions) > 0 {
			fmt.Fprintf(w, "    free    : %s\n", strings.Join(c.Suggestions, ", "))
		}
		fmt.Fprintln(w)
	}
}

func countSeverities(cs []Conflict) (errs, warns int) {
	for _, c := range cs {
		if c.Severity == SevError {
			errs++
		} else {
			warns++
		}
	}
	return
}

func printDeck(w io.Writer, d Deck, out string) {
	fmt.Fprintf(w, "WindowDeck merge\n")
	if d.Base != "" {
		fmt.Fprintf(w, "base     : %s\n", d.Base)
	} else {
		fmt.Fprintf(w, "base     : none\n")
	}
	fmt.Fprintf(w, "sets     : %s\n", strings.Join(d.Sets, ", "))
	kept, droppedN, dup := 0, 0, 0
	for _, r := range d.Resolutions {
		switch r.Decision {
		case "kept":
			kept++
		case "dropped":
			droppedN++
		case "duplicate":
			dup++
		}
	}
	fmt.Fprintf(w, "deck     : %d bindings (%d kept, %d dropped, %d identical duplicates)\n", len(d.Bindings), kept, droppedN, dup)
	if out != "" {
		fmt.Fprintf(w, "written  : %s\n", out)
	}
	fmt.Fprintln(w)

	fmt.Fprintln(w, "MERGED DECK")
	for _, e := range d.Bindings {
		flag := ""
		if len(e.Reserved) > 0 {
			flag = "  [OS-RESERVED]"
		}
		fmt.Fprintln(w, strings.TrimRight(fmt.Sprintf("  %-30s %-26s %-14s%s", e.Action, e.Chord, e.Set, flag), " "))
	}
	fmt.Fprintln(w)

	fmt.Fprintln(w, "RESOLUTIONS")
	for _, r := range d.Resolutions {
		fmt.Fprintf(w, "  [%s] %s\n", r.Decision, r.Explanation)
	}
	if len(d.Warnings) > 0 {
		fmt.Fprintln(w)
		fmt.Fprintln(w, "WARNINGS")
		for _, s := range d.Warnings {
			fmt.Fprintf(w, "  %s\n", s)
		}
	}
	if len(d.Suggestions) > 0 {
		fmt.Fprintln(w)
		fmt.Fprintln(w, "FREE CHORDS FOR THE DROPPED BINDINGS")
		for _, s := range d.Suggestions {
			free := "none found in the searched space"
			if len(s.Free) > 0 {
				free = strings.Join(s.Free, ", ")
			}
			fmt.Fprintf(w, "  %s (%s, wanted %s): %s\n", s.Action, s.Set, s.Chord, free)
		}
	}
}

func printExplanation(w io.Writer, ex Explanation) {
	fmt.Fprintf(w, "WindowDeck explain\n")
	fmt.Fprintf(w, "query    : %q\n", ex.Query)
	fmt.Fprintf(w, "canonical: %s\n", ex.Chord)
	fmt.Fprintln(w)
	fmt.Fprintln(w, "BOUND BY")
	if len(ex.BoundBy) == 0 {
		fmt.Fprintln(w, "  (nobody - this chord is free in the loaded sets)")
	}
	for _, p := range ex.BoundBy {
		fmt.Fprintf(w, "  %-14s %-28s written %q in %s\n", p.Set, p.Action, p.Raw, p.File)
	}
	fmt.Fprintln(w)
	fmt.Fprintln(w, "OS RESERVATIONS")
	if len(ex.Reserved) == 0 {
		fmt.Fprintln(w, "  (none in the curated Windows and macOS tables)")
	}
	for _, h := range ex.Reserved {
		fmt.Fprintf(w, "  %-8s chord %d: %s\n", osLabel(h.OS), h.Position, h.Does)
		fmt.Fprintf(w, "           source: %s\n", h.Source)
	}
	if len(ex.PrefixOf) > 0 {
		fmt.Fprintln(w)
		fmt.Fprintln(w, "IS A STRICT PREFIX OF")
		for _, p := range ex.PrefixOf {
			fmt.Fprintf(w, "  %-14s %-28s %s\n", p.Set, p.Action, p.Chord)
		}
	}
	if len(ex.PrefixedBy) > 0 {
		fmt.Fprintln(w)
		fmt.Fprintln(w, "IS PREFIXED BY")
		for _, p := range ex.PrefixedBy {
			fmt.Fprintf(w, "  %-14s %-28s %s\n", p.Set, p.Action, p.Chord)
		}
	}
	fmt.Fprintln(w)
	fmt.Fprintln(w, "IN THE MERGED DECK")
	if ex.InDeck == nil {
		fmt.Fprintln(w, "  (not present)")
	} else {
		fmt.Fprintf(w, "  %s -> %s from %s (%s)\n", ex.InDeck.Action, ex.InDeck.Chord, ex.InDeck.Set, ex.InDeck.Origin)
	}
}

// ---------------------------------------------------------------------------
// export formats
// ---------------------------------------------------------------------------

func reservedSummary(hits []ReservedHit) string {
	if len(hits) == 0 {
		return ""
	}
	parts := make([]string, 0, len(hits))
	for _, h := range hits {
		parts = append(parts, fmt.Sprintf("%s: %s", osLabel(h.OS), h.Does))
	}
	sort.Strings(parts)
	return strings.Join(parts, "; ")
}

func exportCSV(w io.Writer, d Deck) error {
	cw := csv.NewWriter(w)
	if err := cw.Write([]string{"action", "chord", "set", "origin", "override", "note", "reserved", "file"}); err != nil {
		return err
	}
	for _, e := range d.Bindings {
		rec := []string{e.Action, e.Chord, e.Set, e.Origin, strconv.FormatBool(e.Override), e.Note, reservedSummary(e.Reserved), e.File}
		if err := cw.Write(rec); err != nil {
			return err
		}
	}
	cw.Flush()
	return cw.Error()
}

func mdEscape(s string) string {
	return strings.ReplaceAll(s, "|", "\\|")
}

func exportMarkdown(w io.Writer, d Deck) error {
	base := d.Base
	if base == "" {
		base = "(none)"
	}
	fmt.Fprintf(w, "# WindowDeck merged deck\n\n")
	fmt.Fprintf(w, "- Base (team standard): %s\n", mdEscape(base))
	fmt.Fprintf(w, "- Sets merged: %s\n", mdEscape(strings.Join(d.Sets, ", ")))
	fmt.Fprintf(w, "- Bindings in deck: %d\n\n", len(d.Bindings))

	fmt.Fprintf(w, "## Bindings\n\n")
	fmt.Fprintf(w, "| Action | Chord | From | Origin | OS-reserved |\n")
	fmt.Fprintf(w, "| --- | --- | --- | --- | --- |\n")
	for _, e := range d.Bindings {
		res := reservedSummary(e.Reserved)
		if res == "" {
			res = "-"
		}
		fmt.Fprintf(w, "| %s | `%s` | %s | %s | %s |\n",
			mdEscape(e.Action), mdEscape(e.Chord), mdEscape(e.Set), mdEscape(e.Origin), mdEscape(res))
	}

	fmt.Fprintf(w, "\n## Resolutions\n\n")
	for _, r := range d.Resolutions {
		fmt.Fprintf(w, "- **%s** - %s\n", r.Decision, mdEscape(r.Explanation))
	}
	if len(d.Warnings) > 0 {
		fmt.Fprintf(w, "\n## Warnings\n\n")
		for _, s := range d.Warnings {
			fmt.Fprintf(w, "- %s\n", mdEscape(s))
		}
	}
	if len(d.Suggestions) > 0 {
		fmt.Fprintf(w, "\n## Free chords for dropped bindings\n\n")
		for _, s := range d.Suggestions {
			free := "(none found)"
			if len(s.Free) > 0 {
				free = "`" + strings.Join(s.Free, "`, `") + "`"
			}
			fmt.Fprintf(w, "- %s (%s, wanted `%s`): %s\n", mdEscape(s.Action), mdEscape(s.Set), mdEscape(s.Chord), free)
		}
	}
	return nil
}
