1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//! Fallback title-extraction strategies.
//!
//! When the primary positional extraction in [`super::extract_title`] yields
//! an empty cleaned string, control passes to the **fallback ladder** defined
//! here. Each strategy is a small implementation of [`TitleStrategy`]; they
//! are tried in [`FALLBACK_STRATEGIES`] order and the first non-`None` answer
//! wins.
//!
//! ## Why a trait?
//!
//! Before this module, the ladder was an inline if-let chain inside
//! `extract_title` calling four free functions with bespoke signatures.
//! Each function duplicated the "scan the filename, pick a byte range,
//! clean it, build a [`MatchSpan`]" skeleton. That worked at four; D10
//! (the post-#127 architecture-review tripwire) flagged a 6th strategy as
//! the threshold for refactor-first. Trait + registry hits that threshold
//! preemptively: adding a 5th strategy is now appending one line to
//! [`FALLBACK_STRATEGIES`], not editing the orchestrator.
//!
//! ## How to add a new strategy
//!
//! 1. Create `strategies/your_thing.rs` with a unit struct implementing
//! [`TitleStrategy`].
//! 2. `mod your_thing;` here.
//! 3. Add `&your_thing::YourThing` to [`FALLBACK_STRATEGIES`] at the
//! correct ordinal (the ladder is tried in order, so place it where
//! it should win against existing strategies).
//!
//! That's the entire surface area.
use crateMatchSpan;
use trace;
// Strategy structs are re-exported for the rare callers in `super::mod` that
// need to invoke a SPECIFIC strategy in isolation (the parent-dir casing
// fallback in the main path, and the empty-title-zone recovery). Adding
// the strategy to the ladder is the common case; ad-hoc invocation is the
// exception.
pub use AfterBracketGroup;
pub use CjkBracket;
pub use ParentDir;
pub use UnclaimedBracket;
/// How much the pipeline should trust an extracted title.
///
/// The pipeline orchestrates between three title sources:
/// 1. The file's own pass2 extraction (this enum).
/// 2. Cross-file invariance (sibling consensus).
/// 3. Ancestor fallback (a parent directory's cached title).
///
/// Confidence determines who wins when multiple sources disagree. The
/// general rule: **a structurally-marked self-description beats any
/// external hint, because the file is asserting its own identity.**
///
/// Replaces the previous `filename_has_bracket` heuristic with a
/// per-strategy declaration of strength.
/// A title extraction result paired with its confidence.
/// Inputs every strategy needs. Bundled into a struct so adding a new
/// piece of context (e.g. `zone_map`) is a one-line change to every
/// strategy signature — not N.
pub
/// One fallback title extractor.
///
/// Strategies are stateless unit structs; behavior lives entirely in
/// [`try_extract`](Self::try_extract).
pub
/// The fallback ladder, in priority order.
///
/// Order rationale (do not shuffle without thought):
///
/// 1. **CjkBracket** \u2014 most specific (requires `[Group][Title][Ep]` shape +
/// an Episode match). Cheap to reject when it doesn't apply.
/// 2. **AfterBracketGroup** \u2014 anime `[Group] Title - Ep [tags]`. Runs
/// before the all-bracket fallback because some files satisfy both
/// patterns and this one is more accurate when applicable.
/// 3. **UnclaimedBracket** \u2014 broader all-bracket fallback for files like
/// `[a][b][title][c][d].mkv` where one bracket isn't claimed by any
/// matcher.
/// 4. **ParentDir** \u2014 last resort: walk up the directory tree.
pub static FALLBACK_STRATEGIES: & = &;
/// Run the ladder; return the first hit paired with that strategy's
/// declared confidence. The pipeline uses the confidence to decide
/// whether cross-file context (invariance, ancestor fallback) should
/// override this title.
pub