package analyze

import (
	"strings"

	"chirami/internal/dict"
	"chirami/internal/jp"
	"chirami/internal/morph"
)

// furigana segments one morpheme, preferring JmdictFurigana's per-kanji
// readings: for the surface itself, else for its dictionary form with the
// inflected ending swapped in, else the kana-matching aligner below.
func (a *Analyzer) furigana(t *morph.Token, reading string) []Seg {
	if a.Dict == nil || reading == "" || !hasKanji(t.Surface) {
		return Furigana(t.Surface, reading)
	}
	if rs, ok := a.Dict.Furigana(t.Surface, reading); ok {
		return segs(rs)
	}
	base := t.Base
	if br := baseReading(t.Surface, base, reading); br != "" && base != t.Surface {
		if rs, ok := a.Dict.Furigana(base, br); ok {
			if s, ok := inflect(rs, t.Surface, reading); ok {
				return s
			}
		}
	}
	return Furigana(t.Surface, reading)
}

// inflect reuses a dictionary form's segments for an inflected surface: the
// kanji segments are a shared prefix, the rest is plain kana. The result must
// spell out reading, or it is rejected.
func inflect(base []dict.Ruby, surface, reading string) ([]Seg, bool) {
	var out []Seg
	rest, spelled := surface, ""
	for _, r := range base {
		if !strings.HasPrefix(rest, r.Text) {
			if r.Ruby != "" {
				return nil, false
			}
			break
		}
		out = append(out, Seg{r.Text, r.Ruby})
		rest = rest[len(r.Text):]
		spelled += cmpRuby(r)
	}
	if rest != "" {
		out = append(out, Seg{Text: rest})
		spelled += jp.ToHiragana(rest)
	}
	return appendSegs(nil, out), spelled == reading
}

func cmpRuby(r dict.Ruby) string {
	if r.Ruby != "" {
		return r.Ruby
	}
	return jp.ToHiragana(r.Text)
}

func segs(rs []dict.Ruby) []Seg {
	out := make([]Seg, len(rs))
	for i, r := range rs {
		out[i] = Seg{r.Text, r.Ruby}
	}
	return appendSegs(nil, out)
}

// Furigana splits surface into runs and puts reading only over the kanji
// runs, matching the kana runs literally: 食べる/たべる -> 食(た) べる.
// When the kana do not line up it puts the whole reading over the whole
// surface.
func Furigana(surface, reading string) []Seg {
	if reading == "" || !hasKanji(surface) {
		return []Seg{{Text: surface}}
	}
	runs := splitRuns(surface)
	if rubies, ok := align(runs, reading); ok {
		segs := make([]Seg, len(runs))
		for i, r := range runs {
			segs[i] = Seg{Text: r.text}
			if r.kanji {
				segs[i].Ruby = rubies[i]
			}
		}
		return segs
	}
	return []Seg{{Text: surface, Ruby: reading}}
}

type run struct {
	text  string
	kanji bool
}

func hasKanji(s string) bool { return strings.IndexFunc(s, jp.IsKanji) >= 0 }

func splitRuns(s string) []run {
	var runs []run
	for _, r := range s {
		k := jp.IsKanji(r)
		if n := len(runs); n > 0 && runs[n-1].kanji == k {
			runs[n-1].text += string(r)
			continue
		}
		runs = append(runs, run{string(r), k})
	}
	return runs
}

// align matches kana runs against reading and gives each kanji run the
// reading between them, backtracking over where each kanji run ends.
func align(runs []run, reading string) ([]string, bool) {
	out := make([]string, len(runs))
	var walk func(i int, rest string) bool
	walk = func(i int, rest string) bool {
		if i == len(runs) {
			return rest == ""
		}
		r := runs[i]
		if !r.kanji {
			k := jp.ToHiragana(r.text)
			return strings.HasPrefix(rest, k) && walk(i+1, rest[len(k):])
		}
		// a kanji run reads as at least one kana
		for j, c := range rest {
			end := j + len(string(c))
			out[i] = rest[:end]
			if walk(i+1, rest[end:]) {
				return true
			}
		}
		return false
	}
	return out, walk(0, reading)
}
