package analyze

import (
	"strings"

	"chirami/internal/grammar"
	"chirami/internal/jp"
)

// Piece is a run of an inflected word: the stem, or what one inflection
// step added, with Yomitan's name and description for that step.
type Piece struct {
	Segs        []Seg
	Romaji      string
	Name        string // "" for the stem
	Description string
}

// Inflection splits w into its stem and the inflections Yomitan's
// deinflector finds on the way to the word's dictionary form:
// 食べ|させ|られ|なかっ|た. ok is false for words that are not inflected
// or whose inflections do not line up with the surface.
func (a *Analyzer) Inflection(w Word) ([]Piece, bool) {
	if w.Key == "" || w.Key == w.Surface {
		return nil, false
	}
	var best []grammar.Step
	stemLen := 0
	for _, r := range grammar.Deinflect(w.Surface) {
		if r.Base != w.Key || len(r.Steps) <= len(best) {
			continue
		}
		n, ok := stem(w.Surface, r.Steps)
		if !ok || !strings.HasPrefix(w.Key, w.Surface[:n]) {
			continue
		}
		best, stemLen = r.Steps, n
	}
	if best == nil {
		return nil, false
	}
	cuts := []int{stemLen}
	for _, s := range best {
		cuts = append(cuts, cuts[len(cuts)-1]+len(s.Suffix))
	}
	parts, ok := splitSegs(w.Segs, cuts)
	if !ok {
		return nil, false
	}
	pieces := []Piece{{Segs: parts[0]}}
	for i, s := range best {
		pieces = append(pieces, Piece{Segs: parts[i+1], Name: s.Name, Description: s.Description})
	}
	readings := make([]string, len(pieces))
	for i, p := range pieces {
		readings[i] = segsReading(p.Segs)
	}
	for i := range pieces {
		pieces[i].Romaji = a.pieceRomaji(readings[i:])
	}
	return pieces, true
}

// pieceRomaji romanizes the first reading in context: a trailing っ doubles
// the next piece's consonant (なかっ|た -> nakat|ta) and ん takes its
// apostrophe before a vowel, so it converts the piece with the next mora
// and cuts that mora's romaji off again.
func (a *Analyzer) pieceRomaji(readings []string) string {
	cur := readings[0]
	if len(readings) == 1 || readings[1] == "" {
		return jp.Romaji(cur, a.Style)
	}
	next := jp.Moras(readings[1])[0]
	both, tail := jp.Romaji(cur+next, a.Style), jp.Romaji(next, a.Style)
	if strings.HasSuffix(both, tail) {
		return strings.TrimSuffix(both, tail)
	}
	return jp.Romaji(cur, a.Style)
}

func segsReading(segs []Seg) string {
	var b strings.Builder
	for _, s := range segs {
		if s.Ruby != "" {
			b.WriteString(s.Ruby)
		} else {
			b.WriteString(jp.ToHiragana(s.Text))
		}
	}
	return b.String()
}

// stem returns the byte length of the stem when the steps' suffixes,
// appended in order, spell the end of surface.
func stem(surface string, steps []grammar.Step) (int, bool) {
	n := len(surface)
	for i := len(steps) - 1; i >= 0; i-- {
		sfx := steps[i].Suffix
		if sfx == "" || !strings.HasSuffix(surface[:n], sfx) {
			return 0, false
		}
		n -= len(sfx)
	}
	return n, n > 0
}

// splitSegs cuts segs at the byte offsets in cuts (the last is the total
// length). A cut inside a segment with ruby fails: a reading cannot be split.
func splitSegs(segs []Seg, cuts []int) ([][]Seg, bool) {
	out := make([][]Seg, len(cuts))
	pos, ci := 0, 0
	for _, s := range segs {
		text := s.Text
		for text != "" {
			end := cuts[ci] - pos
			if end <= 0 {
				ci++
				if ci == len(cuts) {
					return nil, false
				}
				continue
			}
			if end >= len(text) {
				out[ci] = append(out[ci], Seg{text, s.Ruby})
				pos += len(text)
				break
			}
			if s.Ruby != "" {
				return nil, false
			}
			out[ci] = append(out[ci], Seg{Text: text[:end]})
			text, pos = text[end:], pos+end
		}
	}
	return out, pos == cuts[len(cuts)-1]
}
