// Package grammar provides Japanese morphological deinflection ported from
// Yomitan (github.com/yomidevs/yomitan, GPL-3.0-or-later) at commit
// e7334a96106a6c74e6fa75bd33a5ea1219d92712.
//
// Source files ported:
//   - ext/js/language/ja/japanese-transforms.js
//   - ext/js/language/language-transformer.js
//   - ext/js/language/language-transforms.js
//
// # Suffix Splitting & Sound Changes
//
// The Suffix of each Step is designed to let callers split the surface text into
// constituent parts:
//
//	Base stem + Step[0].Suffix + Step[1].Suffix + ... + Step[k-1].Suffix
//
// spells the original surface word where regular inflectional boundaries apply.
//
// In Japanese morphology, auxiliary inflections frequently rewrite earlier text
// through euphonic sound changes (音便 onbin / 音韻変化):
//
//  1. Geminate / Sokuon (促音便) on Adjectives:
//     When past -た, conditional -たら, or representative -たり attaches to an
//     i-adjective or negative form ending in い (e.g. ない -> なかった), the
//     Yomitan transform rule matches かった -> い. Here, the geminate かっ
//     belongs to the inflected stem of the preceding step (e.g. なかっ), while
//     た / たら / たり is the suffix of the inflection step itself.
//
//  2. Continuative (連用形) Ku-change before Negative or -て:
//     When negative ない or -て attaches to an i-adjective (e.g. 見たい -> 見たくない),
//     the Yomitan rule matches くない -> い or くて -> い. The continuative く
//     is attributed to the preceding stem (e.g. たく), while ない or て is the
//     suffix of the negative or conjunctive step.
//
//  3. Conditional (仮定形) Ba-change:
//     When -ば attaches to an i-adjective (e.g. ない -> なければ), the Yomitan rule
//     matches ければ -> い. The けれ is attributed to the preceding stem (なけれ),
//     while ば is attributed to -ば.
//
//  4. Overlapping Auxiliary Prefixes:
//     In progressive or auxiliary verbs like -いる (-te iru) matching でいる or
//     ている, the preceding で or て is preserved with the -て step, and いる
//     is attributed to the -いる step (e.g. 読 + んで + いる).
//
//  5. Slang / Contraction Transforms:
//     When slang or colloquial contractions rewrite across morpheme boundaries
//     (e.g. -ゃ contracting ければ -> きゃ in しなきゃ, or n-slang contracting
//     られない -> らんない in 食べらんない, or Kansai-ben onbin like こうて from
//     かって), intermediate steps that are completely subsumed by the contraction
//     have Suffix set to "", and the contraction step carries the fused surface
//     morpheme.
package grammar

import (
	"strings"
)

// Step is one inflectional transformation applied to a base word.
type Step struct {
	Name        string // yomitan transform name, e.g. "passive", "-nai", "-ta"
	Description string // yomitan's description text
	Suffix      string // the inflected text this step added on the surface, e.g. "られ"; "" if not attributable
}

// Result is one deinflected candidate produced by the deinflector.
type Result struct {
	Base           string   // deinflected dictionary form, e.g. "食べる"
	Conditions     []string // yomitan condition names of Base, e.g. ["v1"]
	Steps          []Step   // from the dictionary form outward: causative, passive, negative, past
	conditionFlags uint32   // condition bitmask for hierarchy checks
	baseStem       string   // stem retained from Base
}

// MatchesCondition reports whether the result satisfies the given Yomitan
// condition name (e.g. "v1", "v5", "adj-i") according to Yomitan's condition
// inheritance hierarchy.
func (r Result) MatchesCondition(condition string) bool {
	f, ok := ConditionFlags[condition]
	if !ok {
		return false
	}
	return r.conditionFlags == 0 || (r.conditionFlags&f) != 0
}

// BaseStem returns the stem of Base that precedes the first inflectional suffix.
// For example, for Base="食べる" with causative suffix "させ", BaseStem is "食べ".
// When concatenated with Steps in order, BaseStem + Step[0].Suffix + ... reproduces
// the surface word where inflectional boundaries permit.
func (r Result) BaseStem() string {
	if len(r.Steps) == 0 {
		return r.Base
	}
	if r.baseStem != "" {
		return r.baseStem
	}
	return computeBaseStem(r.Base, r.Steps)
}

// frame represents one step in the deinflection trace.
// In Yomitan, trace frames are prepended, so trace[0] is the outermost deinflection
// step applied, but when reversed it represents outward steps from Base.
type traceFrame struct {
	transformID int
	ruleIndex   int
	textBefore  string // text before this deinflection step was applied
}

type queueItem struct {
	text         string
	conditions   uint32
	condNamesOut []string
	trace        []traceFrame // trace[0] is most recent deinflection step (innermost in inflection)
}

// Deinflect returns every deinflection candidate for word, in Yomitan's exact
// BFS transformation order.
func Deinflect(word string) []Result {
	if word == "" {
		return nil
	}

	queue := []queueItem{{
		text:         word,
		conditions:   0,
		condNamesOut: nil,
		trace:        nil,
	}}

	for i := 0; i < len(queue); i++ {
		item := queue[i]

		for tIdx := 0; tIdx < len(transforms); tIdx++ {
			t := &transforms[tIdx]

			for rIdx := 0; rIdx < len(t.rules); rIdx++ {
				r := &t.rules[rIdx]

				// Condition check
				if item.conditions != 0 && (item.conditions&r.conditionsIn) == 0 {
					continue
				}

				// Inflection match check
				if r.isSuffix {
					if !strings.HasSuffix(item.text, r.pattern) {
						continue
					}
				} else {
					if item.text != r.pattern {
						continue
					}
				}

				// Cycle check
				isCycle := false
				for _, f := range item.trace {
					if f.transformID == tIdx && f.ruleIndex == rIdx && f.textBefore == item.text {
						isCycle = true
						break
					}
				}
				if isCycle {
					continue
				}

				// Deinflect
				var newText string
				if r.isSuffix {
					newText = item.text[:len(item.text)-len(r.pattern)] + r.deinflected
				} else {
					newText = r.deinflected
				}

				// Prepend to trace (matches Yomitan's _extendTrace)
				newTrace := make([]traceFrame, len(item.trace)+1)
				newTrace[0] = traceFrame{
					transformID: tIdx,
					ruleIndex:   rIdx,
					textBefore:  item.text,
				}
				copy(newTrace[1:], item.trace)

				queue = append(queue, queueItem{
					text:         newText,
					conditions:   r.conditionsOut,
					condNamesOut: r.condNamesOut,
					trace:        newTrace,
				})
			}
		}
	}

	results := make([]Result, len(queue))
	for i, q := range queue {
		stem, steps := buildSteps(q.text, word, q.trace)
		results[i] = Result{
			Base:           q.text,
			Conditions:     q.condNamesOut,
			Steps:          steps,
			conditionFlags: q.conditions,
			baseStem:       stem,
		}
	}

	return results
}

type soundChange struct {
	deinfTarget string
	infTarget   string
	stemPart    string
	suffixPart  string
}

var soundChanges = [...]soundChange{
	{"い", "かった", "かっ", "た"},
	{"い", "かったら", "かっ", "たら"},
	{"い", "かったり", "かっ", "たり"},
	{"い", "くない", "く", "ない"},
	{"い", "くて", "く", "て"},
	{"い", "ければ", "けれ", "ば"},
	{"い", "さそう", "さ", "そう"},
	{"ます", "ました", "まし", "た"},
	{"ません", "ませんでした", "ません", "でした"},
}

// buildSteps converts Yomitan trace frames into outward Steps from the Base form,
// calculating each Step's attributable surface Suffix.
func buildSteps(base, surface string, trace []traceFrame) (string, []Step) {
	k := len(trace)
	if k == 0 {
		return base, nil
	}

	// trace in queueItem is prepended: trace[0] is the step that deinflected to Base!
	// That means trace[0] is the innermost step (closest to Base), and trace[k-1]
	// is the outermost step (which was applied on the surface).
	// This matches the outward order required for Steps: causative, passive, negative, past.

	steps := make([]Step, k)
	suffixes := make([]string, k)
	deinfs := make([]string, k)
	infs := make([]string, k)

	for i := 0; i < k; i++ {
		f := trace[i]
		t := &transforms[f.transformID]
		r := &t.rules[f.ruleIndex]

		steps[i] = Step{
			Name:        t.id,
			Description: t.description,
		}

		if r.isSuffix {
			infs[i] = r.pattern
			deinfs[i] = r.deinflected
		} else {
			infs[i] = r.pattern
			deinfs[i] = r.deinflected
		}
		suffixes[i] = infs[i]
	}

	for i := 0; i < k-1; i++ {
		dNext := deinfs[i+1]
		infNext := infs[i+1]

		matchedSC := false
		for _, sc := range soundChanges {
			if dNext == sc.deinfTarget && infNext == sc.infTarget {
				suffixes[i+1] = sc.suffixPart
				if strings.HasSuffix(suffixes[i], dNext) {
					suffixes[i] = suffixes[i][:len(suffixes[i])-len(dNext)] + sc.stemPart
				}
				matchedSC = true
				break
			}
		}

		if !matchedSC {
			if dNext != "" && strings.HasPrefix(infNext, dNext) {
				suffixes[i+1] = infNext[len(dNext):]
			} else if dNext != "" && strings.HasSuffix(suffixes[i], dNext) {
				suffixes[i] = suffixes[i][:len(suffixes[i])-len(dNext)]
			}
		}
	}

	// Calculate base stem
	d0 := deinfs[0]
	var baseStem string
	if d0 != "" && strings.HasSuffix(base, d0) {
		baseStem = base[:len(base)-len(d0)]
	} else {
		baseStem = base
	}

	// Verify surface reconstruction
	var totalSuffix strings.Builder
	for _, s := range suffixes {
		totalSuffix.WriteString(s)
	}

	spelled := baseStem + totalSuffix.String()
	if spelled != surface {
		// Fallback for single-step or special contractions
		if strings.HasPrefix(surface, baseStem) {
			if k == 1 {
				suffixes[0] = surface[len(baseStem):]
			}
		} else {
			// Stem sound change (e.g. くる -> こない)
			if strings.HasSuffix(surface, totalSuffix.String()) {
				baseStem = surface[:len(surface)-len(totalSuffix.String())]
			}
		}
	}

	for i := 0; i < k; i++ {
		steps[i].Suffix = suffixes[i]
	}

	return baseStem, steps
}

func computeBaseStem(base string, steps []Step) string {
	if len(steps) == 0 {
		return base
	}
	var totalSuffix strings.Builder
	for _, s := range steps {
		totalSuffix.WriteString(s.Suffix)
	}
	sufLen := totalSuffix.Len()
	// If base ends with a dictionary form ending that was replaced by Step[0]:
	// We can check transforms rules or common verb/adjective endings.
	for _, end := range []string{"る", "す", "く", "ぐ", "む", "ぶ", "ぬ", "つ", "う", "い", "くる", "する", "ずる"} {
		if strings.HasSuffix(base, end) {
			return base[:len(base)-len(end)]
		}
	}
	if len(base) > sufLen {
		return base[:len(base)-sufLen]
	}
	return base
}
