package pipeline

import (
	"encoding/json"
	"fmt"
	"maps"
	"os"
	"slices"
	"sort"
	"strings"
	"unicode"

	"gopkg.in/yaml.v3"

	"textmachine/backend/internal/store"
)

// memseed.go: the DETERMINISTIC seeding of the glossary from its two milestone inputs —
// a manual seed file (curated approved/draft terms) and the captured ruby readings
// (classified into auto candidates). No LLM (autopopulation/adjudication are a later
// milestone). The output is a []store.GlossaryEntry that seedGlossary REPLACES into the
// book (idempotent full-replace: same inputs → same rows, an edit converges every column).

// --- manual seed file (YAML) ----------------------------------------------------

type seedFile struct {
	Terms []seedTerm `yaml:"terms"`
}

type seedTerm struct {
	Src                 string      `yaml:"src"`
	Dst                 string      `yaml:"dst"`
	Type                string      `yaml:"type"`
	Sense               string      `yaml:"sense"`
	Gender              string      `yaml:"gender"`
	Speech              string      `yaml:"speech"`
	Decl                *seedDecl   `yaml:"decl"`
	TranslitPolicy      string      `yaml:"translit_policy"`
	FirstPerson         string      `yaml:"first_person"`
	NicknameTranslation string      `yaml:"nickname_translation"`
	SinceCh             int         `yaml:"since_ch"`
	UntilCh             int         `yaml:"until_ch"`
	Status              string      `yaml:"status"`
	AllowShort          bool        `yaml:"allow_short"`
	Note                string      `yaml:"note"`
	Aliases             []seedAlias `yaml:"aliases"`
}

type seedDecl struct {
	Invariant bool     `yaml:"invariant"`
	Forms     []string `yaml:"forms"`
}

type seedAlias struct {
	Alias string `yaml:"alias"`
	Type  string `yaml:"type"`
}

// loadGlossarySeed parses a glossary seed YAML into store entries. A manual seed is
// curated, so an empty status defaults to "approved" (the author downgrades explicitly
// with status: draft|auto). Validation is fail-loud: a term without src, or with an
// unknown status, is a config error (a silently-dropped term is exactly the A-class
// hole this bank exists to close).
func loadGlossarySeed(path string) ([]store.GlossaryEntry, error) {
	raw, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("pipeline: read glossary seed %s: %w", path, err)
	}
	var sf seedFile
	if err := yaml.Unmarshal(raw, &sf); err != nil {
		return nil, fmt.Errorf("pipeline: parse glossary seed %s: %w", path, err)
	}
	var out []store.GlossaryEntry
	var problems []string
	termKeys := map[[4]string]bool{} // (src,sense,since,until) → detect a duplicate term key
	for i, t := range sf.Terms {
		// Trim surrounding whitespace on the KEYING surfaces before anything reads them.
		// normalizeSourceKey does not trim, so a raw " 強敵" would key as " 強敵" and never
		// match "強敵" — a silently-inert approved term (the A-class hole this bank closes).
		// A trailing space on sense also makes two identical senses look distinct, slipping
		// past BOTH the duplicate-key and the same-sense contradiction guards below.
		// Surrounding whitespace is never intentional; trim it (a forgiving fix, like the
		// alias dedup). Internal spacing of a multi-word surface is preserved.
		t.Src = strings.TrimSpace(t.Src)
		t.Sense = strings.TrimSpace(t.Sense)
		t.Dst = strings.TrimSpace(t.Dst)
		if t.Src == "" {
			problems = append(problems, fmt.Sprintf("term %d: src is required", i))
			continue
		}
		// A duplicate (src, sense, window) would crash ReplaceGlossary on the store's
		// UNIQUE constraint mid-run (aborting the whole book) — catch it here as a clear
		// config error instead (self-review #5, sibling of the alias case below).
		tk := [4]string{t.Src, t.Sense, fmt.Sprint(t.SinceCh), fmt.Sprint(t.UntilCh)}
		if termKeys[tk] {
			problems = append(problems, fmt.Sprintf("term %q: duplicate (src, sense=%q, since_ch=%d, until_ch=%d) — a term may appear once per spoiler window", t.Src, t.Sense, t.SinceCh, t.UntilCh))
			continue
		}
		termKeys[tk] = true
		status := t.Status
		if status == "" {
			status = "approved"
		}
		switch status {
		case "auto", "draft", "approved":
		default:
			problems = append(problems, fmt.Sprintf("term %q: status must be auto|draft|approved, got %q", t.Src, status))
			continue
		}
		// An approved OR draft term with no dst is silently inert (not matchable — it renders
		// nothing) yet reads as an intended rendering: fail loud (external-review minor #5).
		// Only status=auto (a raw candidate — e.g. a ruby reading awaiting a dst) may be empty.
		if (status == "approved" || status == "draft") && t.Dst == "" {
			problems = append(problems, fmt.Sprintf("term %q: a %s term must have a non-empty dst (an empty one is silently inert; only status=auto may lack a dst)", t.Src, status))
			continue
		}
		decl := ""
		if t.Decl != nil {
			b, mErr := json.Marshal(declInfo{Invariant: t.Decl.Invariant, Forms: t.Decl.Forms})
			if mErr != nil {
				return nil, mErr
			}
			decl = string(b)
		}
		e := store.GlossaryEntry{
			BookID: "", Src: t.Src, Dst: t.Dst, Type: t.Type, Sense: t.Sense,
			Gender: t.Gender, Speech: t.Speech, Decl: decl,
			TranslitPolicy: t.TranslitPolicy, FirstPerson: t.FirstPerson,
			NicknameTranslation: t.NicknameTranslation,
			SinceCh:             t.SinceCh, UntilCh: t.UntilCh, Status: status,
			AllowShort: t.AllowShort, Source: "seed", Note: t.Note,
		}
		// Dedup aliases within a term by surface (keep the first type). A repeated alias
		// surface — a copy-paste, or the same name annotated with two types — would
		// otherwise crash ReplaceGlossary on UNIQUE(book_id,term_id,alias) and abort the
		// whole book run (self-review #5). Deduping is the forgiving fix: the surface is
		// what matters for matching, and a redundant alias is a harmless authoring slip.
		seenAlias := map[string]bool{}
		for _, a := range t.Aliases {
			a.Alias = strings.TrimSpace(a.Alias) // same silent-inert risk as src (an alias is a match key)
			if a.Alias == "" {
				problems = append(problems, fmt.Sprintf("term %q: an alias is empty", t.Src))
				continue
			}
			if seenAlias[a.Alias] {
				continue
			}
			seenAlias[a.Alias] = true
			e.Aliases = append(e.Aliases, store.GlossaryAlias{Alias: a.Alias, AliasType: a.Type})
		}
		out = append(out, e)
	}
	// Overlapping spoiler windows for the SAME src inject CONTRADICTORY renderings at a
	// chapter in the overlap — silently (external-review minor #4; D16.1). The deterministic
	// matcher keys ONLY on src (it cannot disambiguate sense within a chunk), so BOTH rows
	// fire together (keyOwners maps the shared key to both indices) and the post-check is
	// guaranteed to miss one — with the gate ON, a deterministic per-chunk livelock. Fail
	// loud. UNIQUE(src,sense,window) already blocks identical (src,sense,window), so this
	// catches partial overlap and the different-SENSE case the old same-sense-only guard let
	// through. Iterate `out` in order (deterministic message).
	for i := range out {
		for j := 0; j < i; j++ {
			a, b := out[i], out[j]
			if a.Src != b.Src || a.Dst == b.Dst || !windowsOverlap(a.SinceCh, a.UntilCh, b.SinceCh, b.UntilCh) {
				continue
			}
			if a.Sense == b.Sense {
				problems = append(problems, fmt.Sprintf("term %q (sense %q): spoiler windows [%d,%d] and [%d,%d] overlap with different dst (%q vs %q) — a term has ONE rendering per chapter",
					a.Src, a.Sense, b.SinceCh, b.UntilCh, a.SinceCh, a.UntilCh, b.Dst, a.Dst))
				continue
			}
			// Different sense, overlapping windows, different dst (D16.1 polysemy livelock).
			// Scope to a MATCHABLE src: an A3-banned single-char src (道 — significantLen below
			// the min-key floor, never in the automaton) is inert and cannot livelock, so its
			// polysemy stays allowed. Both rows must be matchable for the contradiction to fire.
			if srcKeyFires(a.Src, a.AllowShort) && srcKeyFires(b.Src, b.AllowShort) {
				problems = append(problems, fmt.Sprintf("term %q: senses %q→%q and %q→%q have OVERLAPPING spoiler windows [%d,%d] and [%d,%d] — the deterministic matcher keys only on src and cannot pick a sense, so both inject as authoritative and the post-check is guaranteed to miss one (give them non-overlapping windows, merge the sense, or mark one status: auto)",
					a.Src, b.Sense, b.Dst, a.Sense, a.Dst, b.SinceCh, b.UntilCh, a.SinceCh, a.UntilCh))
			}
		}
	}
	if len(problems) > 0 {
		return nil, fmt.Errorf("glossary seed %s:\n  - %s", path, strings.Join(problems, "\n  - "))
	}
	return out, nil
}

// approvedSharedKeyCollisions detects the alias-generalization of the D16.1 polysemy livelock
// (Task-6 re-audit, confirmed by execution): a firing KEY (normalized src OR alias) shared by
// two approved terms with DIFFERENT dst and OVERLAPPING spoiler windows. The matcher keys only
// on the surface (keyOwners maps the shared key to BOTH terms), so both inject as authoritative
// and the post-check is guaranteed to miss one — a deterministic chunk livelock with the gate
// ON, exactly like same-src polysemy but reached via a shared прозвище (老赵 for two 赵). It does
// NOT skip same-src pairs: loadGlossarySeed's D16.1 check scopes its fail-loud to a MATCHABLE
// src (srcKeyFires), so a same-src pair whose src is single-key-BANNED (道) but that shares an
// ELIGIBLE alias (大道) slips D16.1 entirely — this catches it via the shared alias key (self-
// review finding #1). In the common case (an eligible shared src) loadGlossarySeed fails loud
// FIRST and seedGlossary never reaches here, so there is no double report. Returns human
// messages (deterministic order); seedGlossary fails loud on any. A shared key with the SAME
// dst (a legitimate merge) or NON-overlapping windows (a spoiler handoff) is not a contradiction.
func approvedSharedKeyCollisions(entries []store.GlossaryEntry) []string {
	owners := map[string][]int{} // normalized firing key → indices of approved terms that own it
	for i, e := range entries {
		if e.Status != "approved" || strings.TrimSpace(e.Dst) == "" {
			continue
		}
		for _, k := range entryFiringKeys(e) {
			owners[k] = append(owners[k], i)
		}
	}
	keys := slices.Sorted(maps.Keys(owners))
	var out []string
	seenPair := map[[2]int]bool{}
	for _, k := range keys {
		idxs := owners[k]
		for a := 0; a < len(idxs); a++ {
			for b := a + 1; b < len(idxs); b++ {
				i, j := idxs[a], idxs[b]
				ei, ej := entries[i], entries[j]
				if ei.Dst == ej.Dst {
					continue // same dst → both render identically, no contradiction (incl. an alias of one entry)
				}
				if !windowsOverlap(ei.SinceCh, ei.UntilCh, ej.SinceCh, ej.UntilCh) {
					continue
				}
				pair := [2]int{i, j}
				if seenPair[pair] {
					continue
				}
				seenPair[pair] = true
				out = append(out, fmt.Sprintf("firing key %q is shared by different terms %q→%q and %q→%q with overlapping spoiler windows — both inject as authoritative and the deterministic matcher cannot pick one (livelock; give non-overlapping windows, a more specific alias, or drop the shared surface)",
					k, ei.Src, ei.Dst, ej.Src, ej.Dst))
			}
		}
	}
	return out
}

// entryFiringKeys returns the normalized keys of a term ELIGIBLE to enter the automaton (the
// same eligibility materializeMemory applies: src + aliases, each passing the per-language
// single-key floor or allow_short). Mirrors the matcher so the collision check sees exactly the
// keys that would fire.
func entryFiringKeys(e store.GlossaryEntry) []string {
	surfaces := []string{e.Src}
	for _, a := range e.Aliases {
		surfaces = append(surfaces, a.Alias)
	}
	var keys []string
	seen := map[string]bool{}
	for _, s := range surfaces {
		nk := normalizeSourceKey(s)
		if nk == "" || seen[nk] {
			continue
		}
		if significantLen(nk) >= minKeyLenFor(nk) || e.AllowShort {
			seen[nk] = true
			keys = append(keys, nk)
		}
	}
	return keys
}

// srcKeyFires reports whether src normalizes to a key eligible to ENTER the matcher's
// automaton — it passes the per-language single-key floor (minKeyLenFor), or allow_short
// overrides it. Mirrors materializeMemory's eligibility test. An ineligible src (a single-
// char Han like 道, banned by A3) never fires on the hot path, so a polysemy contradiction on
// it is inert and must not fail the seed load (D16.1 scoping).
func srcKeyFires(src string, allowShort bool) bool {
	nk := normalizeSourceKey(src)
	if nk == "" {
		return false
	}
	return allowShort || significantLen(nk) >= minKeyLenFor(nk)
}

// windowsOverlap reports whether two spoiler windows share any chapter. since_ch 0 means
// "from chapter 1"; until_ch 0 means "no end".
func windowsOverlap(since1, until1, since2, until2 int) bool {
	const inf = 1 << 62
	s1, s2 := since1, since2
	if s1 == 0 {
		s1 = 1
	}
	if s2 == 0 {
		s2 = 1
	}
	u1, u2 := until1, until2
	if u1 == 0 {
		u1 = inf
	}
	if u2 == 0 {
		u2 = inf
	}
	lo, hi := s1, u1
	if s2 > lo {
		lo = s2
	}
	if u2 < hi {
		hi = u2
	}
	return lo <= hi
}

// --- ruby → auto candidates -----------------------------------------------------

// rubyToCandidates classifies the captured ruby readings into auto glossary candidates
// (registry §Ruby-seed / §8). One candidate per BASE (the dominant reading by occurrence,
// ties broken lexically) so variant readings never collide on UNIQUE(src,sense,window)
// — the (base,reading) source of truth stays in ruby_readings. A base already covered by
// the manual seed is skipped (the curated entry wins). Every candidate is status=auto
// with NO dst: the reading is the Polivanov bridge (B6, Phase 2), not a dst — so nothing
// is auto-injected, and a human/batch-judge promotes it (never a blind name-lock, which
// would be a mistranslation). The ruby_class is a deterministic HINT for that promotion.
func rubyToCandidates(readings []store.RubyReading, manualSrcs map[string]bool) []store.GlossaryEntry {
	// Group by base → the dominant (base,reading).
	type best struct {
		reading string
		occ     int
		firstCh int
	}
	byBase := map[string]best{}
	order := []string{}
	for _, rr := range readings {
		if manualSrcs[rr.Base] {
			continue // the curated seed already owns this src
		}
		cur, ok := byBase[rr.Base]
		if !ok {
			byBase[rr.Base] = best{rr.Reading, rr.Occurrences, rr.FirstChapter}
			order = append(order, rr.Base)
			continue
		}
		// Dominant reading: higher occurrences wins; tie → lexically smaller reading.
		if rr.Occurrences > cur.occ || (rr.Occurrences == cur.occ && rr.Reading < cur.reading) {
			cur.reading, cur.occ = rr.Reading, rr.Occurrences
		}
		if rr.FirstChapter < cur.firstCh { // earliest appearance across readings
			cur.firstCh = rr.FirstChapter
		}
		byBase[rr.Base] = cur
	}
	sort.Strings(order) // deterministic output regardless of input order
	var out []store.GlossaryEntry
	for _, base := range order {
		b := byBase[base]
		class := classifyRubyReading(base, b.reading)
		typ := ""
		if class == rubyClassName {
			typ = "name"
		}
		out = append(out, store.GlossaryEntry{
			Src: base, Dst: "", Type: typ, Status: "auto", Source: "ruby",
			RubyReading: b.reading, RubyClass: class, SinceCh: b.firstCh,
			Confidence: b.occ,
			Note:       "ruby auto-candidate (" + class + "); reading is the Polivanov bridge, not a dst — promote before use",
		})
	}
	return out
}

// attachRubyAliasesToManual makes the kana READING of a manually-seeded name matchable
// (D16.4): a manual term 鈴木 that appears in a kana-written chunk as すずき would otherwise
// miss — rubyToCandidates SKIPS manual bases, so the reading is discarded, a "тихо пусто"
// recall hole on the ja acceptance book. For each name-shape reading (all-Han base +
// all-kana reading — the furigana-name form classifyRubyReading calls rubyClassName) whose
// base is a manual src, it appends the reading as an ALIAS of that manual entry IN PLACE,
// deduped. The reading then fires with the ordinary alias disposition — AMBIGUOUS when
// short/collision-prone (post-checked), so a possible double-reading (強敵→とも "friend") is
// never blindly trusted; full offline disambiguation is B6. Auto-candidates with no dst stay
// unmatchable (deliberate). Deterministic: readings are processed sorted, and GlossaryForBook
// re-sorts aliases on read, so the materialized bank is stable regardless of input order.
//
// A ruby reading is auto-derived (not curated), so a reading that would give a FIRING key to
// two seeded terms with different dst (homophone names 高橋/高梁 both read たかはし) must NOT be
// attached to either — the reading is genuinely ambiguous between them. External-review MAJOR
// (homophone first-wins): the previous pass attached-then-blocked, so the alphabetically-first
// base won the reading as an ALIAS while the other was skipped; at ≥4 kana that alias is not
// collision-prone → it fired CONFIRMED → every kana occurrence injected as an authoritative
// (and wrong) rendering for whichever base sorted first (class A2). The key must therefore be
// decided BEFORE any attach, for ALL its owners at once: a contested firing key (≥2 would-fire
// owners with different dst over overlapping spoiler windows) skips EVERY proposed attach and
// logs it (not silent) — the kana form stays unmatchable until the seed disambiguates it with a
// curated alias (whose own collisions DO fail loud, as an authoring error). Attaching would also
// trip approvedSharedKeyCollisions loud on an alias the operator cannot edit out of the seed.
//
// Window/dst-eligibility awareness (review minor): only a dst-bearing owner fires (materializeMemory
// builds no surface for a no-dst row), and two owners with the SAME dst (a merge) or NON-overlapping
// windows (a spoiler handoff) do not contradict — so those are no longer falsely skipped.
func attachRubyAliasesToManual(entries []store.GlossaryEntry, readings []store.RubyReading) (skipped []string) {
	idx := map[string][]int{}
	for i := range entries {
		idx[entries[i].Src] = append(idx[entries[i].Src], i)
	}
	dstBearing := func(e *store.GlossaryEntry) bool { return strings.TrimSpace(e.Dst) != "" }

	// wouldOwn[rk] = set of term indices that would own firing key rk AFTER the proposed
	// attaches: the pre-existing dst-bearing owners (src + eligible aliases already on the seed)
	// plus every deferred (fires) proposal. Only dst-bearing entries fire, so only they can
	// collide. addOwner keeps a set (an index is never paired with itself in contested()).
	wouldOwn := map[string]map[int]bool{}
	addOwner := func(rk string, ti int) {
		if wouldOwn[rk] == nil {
			wouldOwn[rk] = map[int]bool{}
		}
		wouldOwn[rk][ti] = true
	}
	for i := range entries {
		if !dstBearing(&entries[i]) {
			continue
		}
		for _, k := range entryFiringKeys(entries[i]) {
			addOwner(k, i)
		}
	}

	sorted := append([]store.RubyReading(nil), readings...)
	sort.Slice(sorted, func(i, j int) bool {
		if sorted[i].Base != sorted[j].Base {
			return sorted[i].Base < sorted[j].Base
		}
		return sorted[i].Reading < sorted[j].Reading
	})

	// Pass 1 — gather proposals. A proposal that would actually FIRE (dst-bearing target + a key
	// past the per-language floor / allow_short) is DEFERRED to the contested-key decision so a
	// homophone key is resolved for all its owners together, never first-wins. An inert attach (no
	// dst, or a sub-floor key) can never fire → attach it immediately; it round-trips the reading
	// but cannot collide.
	type prop struct {
		rk, reading, base string
		ti                int
	}
	var deferred []prop
	for _, rr := range sorted {
		targets, ok := idx[rr.Base]
		if !ok {
			continue // the reading's base is not a manual term
		}
		if classifyRubyReading(rr.Base, rr.Reading) != rubyClassName {
			continue // only the all-kana furigana-NAME shape is a match surface; a kanji-bearing gloss/double-reading is a footnote, not an alias
		}
		rk := normalizeSourceKey(rr.Reading)
		for _, ti := range targets {
			e := &entries[ti]
			if rr.Reading == e.Src || hasAliasSurface(e.Aliases, rr.Reading) {
				continue
			}
			fires := dstBearing(e) && rk != "" && (significantLen(rk) >= minKeyLenFor(rk) || e.AllowShort)
			if !fires {
				e.Aliases = append(e.Aliases, store.GlossaryAlias{Alias: rr.Reading, AliasType: "чтение"})
				continue
			}
			deferred = append(deferred, prop{rk: rk, reading: rr.Reading, base: rr.Base, ti: ti})
			addOwner(rk, ti)
		}
	}

	// contested(rk): among ALL would-fire owners of rk, two render DIFFERENT dst over OVERLAPPING
	// spoiler windows — attaching for any of them would inject a wrong authoritative rendering (and
	// trip the loud shared-key check). Deterministic (owner set → sorted index pairs).
	contested := func(rk string) bool {
		is := make([]int, 0, len(wouldOwn[rk]))
		for i := range wouldOwn[rk] {
			is = append(is, i)
		}
		sort.Ints(is)
		for a := 0; a < len(is); a++ {
			for b := a + 1; b < len(is); b++ {
				ei, ej := entries[is[a]], entries[is[b]]
				if ei.Dst != ej.Dst && windowsOverlap(ei.SinceCh, ei.UntilCh, ej.SinceCh, ej.UntilCh) {
					return true
				}
			}
		}
		return false
	}

	// Pass 2 — attach every deferred reading whose key is uncontested; skip+log the contested ones
	// (for ALL owners symmetrically, closing the first-wins hole).
	for _, p := range deferred {
		if contested(p.rk) {
			skipped = append(skipped, fmt.Sprintf("%q reading %q (homophone of another seeded term — kana left unmatchable; disambiguate in the seed if needed)", p.base, p.reading))
			continue
		}
		e := &entries[p.ti]
		if hasAliasSurface(e.Aliases, p.reading) { // a same-key sibling proposal already attached this exact surface
			continue
		}
		e.Aliases = append(e.Aliases, store.GlossaryAlias{Alias: p.reading, AliasType: "чтение"})
	}
	return skipped
}

// hasAliasSurface reports whether an alias with the given raw surface already exists on a term.
func hasAliasSurface(aliases []store.GlossaryAlias, surface string) bool {
	for _, a := range aliases {
		if a.Alias == surface {
			return true
		}
	}
	return false
}

// ruby classifier classes (deterministic HINTS for human promotion).
const (
	rubyClassName      = "name_candidate"  // all-Han base + all-kana reading: the furigana-name SHAPE
	rubyClassGloss     = "gloss_candidate" // reading carries Han: likely an author double-reading → footnote, NOT a lock
	rubyClassAmbiguous = "ambiguous"       // anything else → flag to a human
)

// classifyRubyReading is the deterministic clear-case rule (§8). It NEVER auto-locks:
// the all-Han+all-kana shape is a name-lock CANDIDATE (it could still be a double-reading
// like 強敵→とも "friend", indistinguishable without a yomi dictionary — Phase 2), so it is
// flagged for human confirmation, not locked. A reading that carries kanji is a clear
// author's double-reading/gloss → a footnote candidate. Over-flagging is safe; a blind
// lock is a mistranslation. Pure and deterministic.
func classifyRubyReading(base, reading string) string {
	if runesAnyHan(reading) {
		return rubyClassGloss // the "reading" is itself a word (semantic gloss / double-reading)
	}
	if runesAllHan(base) && runesAllKana(reading) {
		return rubyClassName
	}
	return rubyClassAmbiguous
}

func runesAllHan(s string) bool {
	if s == "" {
		return false
	}
	for _, r := range s {
		if !unicode.Is(unicode.Han, r) {
			return false
		}
	}
	return true
}

func runesAnyHan(s string) bool {
	for _, r := range s {
		if unicode.Is(unicode.Han, r) {
			return true
		}
	}
	return false
}

func runesAllKana(s string) bool {
	if s == "" {
		return false
	}
	for _, r := range s {
		if r == 'ー' || r == '・' { // prolonged-sound mark, middle dot — allowed within kana readings
			continue
		}
		if !unicode.Is(unicode.Hiragana, r) && !unicode.Is(unicode.Katakana, r) {
			return false
		}
	}
	return true
}
