package wand

import (
	"fmt"
	"io"
	"sort"
	"strings"
)

func printer(out io.Writer) (printf func(f string, args ...any), println func(args ...any), finish func() error) {
	var err error
	printf = func(f string, args ...any) {
		if err != nil {
			return
		}

		_, err = fmt.Fprintf(out, f, args...)
	}

	println = func(args ...any) {
		if err != nil {
			return
		}

		_, err = fmt.Fprintln(out, args...)
	}

	finish = func() error {
		return err
	}

	return
}

func paragraphs(s string) string {
	var (
		paragraph  []string
		paragraphs [][]string
	)

	l := strings.Split(s, "\n")
	for i := range l {
		l[i] = strings.TrimSpace(l[i])
		if l[i] == "" {
			if len(paragraph) > 0 {
				paragraphs, paragraph = append(paragraphs, paragraph), nil
			}

			continue
		}

		paragraph = append(paragraph, l[i])
	}

	if len(paragraph) > 0 {
		paragraphs = append(paragraphs, paragraph)
	}

	var cparagraphs []string
	for _, p := range paragraphs {
		cparagraphs = append(cparagraphs, strings.Join(p, " "))
	}

	return strings.Join(cparagraphs, "\n\n")
}

func lines(s string) string {
	p := paragraphs(s)
	pp := strings.Split(p, "\n\n")
	return strings.Join(pp, "\n")
}

func manParagraphs(s string) string {
	p := paragraphs(s)
	pp := strings.Split(p, "\n\n")
	for i := range pp {
		pp[i] = fmt.Sprintf(".PP\n%s", pp[i])
	}

	return strings.Join(pp, "\n")
}

func manLines(s string) string {
	l := lines(s)
	ll := strings.Split(l, "\n")
	for i := range ll {
		ll[i] = fmt.Sprintf(".br\n%s", ll[i])
	}

	return strings.Join(ll, "\n")
}

func escapeTeletype(s string) string {
	r := []rune(s)
	for i := range r {
		if r[i] >= 0x00 && r[i] <= 0x1f && r[i] != '\n' && r[i] != '\t' {
			r[i] = 0xb7
		}

		if r[i] >= 0x7f && r[i] <= 0x9f {
			r[i] = 0xb7
		}
	}

	return string(r)
}

func escapeRoff(s string) string {
	var (
		rr          []rune
		lastNewline bool
	)

	r := []rune(s)
	for _, ri := range r {
		switch ri {
		case '\\', '-', '"':
			rr = append(rr, '\\', ri)
		case '.', '\'':
			if lastNewline {
				rr = append(rr, '\\')
			}

			rr = append(rr, ri)
		case 0x2013:
			rr = append(rr, []rune("\\(en")...)
		case 0x2014:
			rr = append(rr, []rune("\\(em")...)
		case 0x201c:
			rr = append(rr, []rune("\\(lq")...)
		case 0x201d:
			rr = append(rr, []rune("\\(rq")...)
		case 0x2018:
			rr = append(rr, []rune("\\(oq")...)
		case 0x2019:
			rr = append(rr, []rune("\\(cq")...)
		default:
			rr = append(rr, ri)
		}

		lastNewline = ri == '\n'
	}

	return string(rr)
}

func escapeMD(s string) string {
	var (
		rr        []rune
		lastDigit bool
	)

	r := []rune(s)
	for _, ri := range r {
		switch ri {
		case '*', '_', '#', '-', '+', '[', ']', '`', '<', '>', '|', '\\':
			rr = append(rr, '\\', ri)
		case '.':
			if lastDigit {
				rr = append(rr, '\\')
			}

			rr = append(rr, ri)
		default:
			rr = append(rr, ri)
		}

		lastDigit = ri >= 0 && ri <= 9
	}

	return string(rr)
}

func prepareOptions(o []docOption) (names []string, descriptions map[string]string) {
	for _, oi := range o {
		ons := []string{fmt.Sprintf("--%s", oi.name)}
		for _, sn := range oi.shortNames {
			ons = append(ons, fmt.Sprintf("-%s", sn))
		}

		n := strings.Join(ons, ", ")
		if oi.isBool {
			n = fmt.Sprintf("%s [b]", n)
		} else {
			n = fmt.Sprintf("%s %s", n, oi.typ)
		}

		if oi.acceptsMultiple {
			n = fmt.Sprintf("%s [*]", n)
		}

		names = append(names, n)
		if descriptions == nil {
			descriptions = make(map[string]string)
		}

		descriptions[n] = oi.description
	}

	sort.Strings(names)
	return
}
