use crate::{Choice, Curve, ThemeChoice, Tone};
// Names this module's prose links to, resolved for rustdoc.
#[allow(unused_imports)]
use crate::{Awaiting, Contrast, Fill, ThemeVariant};
/// What kind of value a form field takes.
///
/// The union of the two vocabularies that diverged, which is what triggered
/// this crate. They have since converged on their own: both apps now have a
/// `renderFormField` emitting the same anatomy, and what is left differing is
/// the kind set, the error shape, and whether the return is a string or a node.
///
/// Validation is deliberately absent. Neither app has a shared story (goingson
/// validates after collecting the form data, with per-field transform hooks;
/// Balanced Breakfast has `required` and nothing else), and a schema that
/// describes fields but not constraints acquires a constraint layer per app,
/// which is exactly how the current divergence started. Naming it absent is a
/// decision; leaving it unmentioned would not be.
/// `#[non_exhaustive]` for the reason [`Fill`] is: renderers match on this and
/// the set keeps growing, so growth must not be a lockstep event.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FieldKind {
/// A single line of text.
Text,
/// A single line of text that must never be echoed, logged or round-tripped
/// through anything that might persist it.
Secret,
/// A number.
Number,
/// A number inside bounds the user drags across, where the range being
/// visible is the point.
///
/// Not [`Number`](Self::Number) with [`min`](Field::min) and
/// [`max`](Field::max), which is the reading to resist and is the same
/// resistance [`Radio`](Self::Radio) needed against `Select`. A bounded
/// number and a validated number are different *questions*. A validated
/// number is typed and can be wrong: the bounds are a rule the answer is
/// checked against, and being told "must be at least 1" afterwards is the
/// normal course of it. A range cannot be out of range at all, because the
/// bounds are the control's extent rather than a rule, and the two ends are
/// what the question means — audiofiles asks for a classifier threshold
/// between 0 and 1, where 0 is never and 1 is only-on-certainty, and a typed
/// 0.72 says nothing without both ends on screen beside it.
///
/// A renderer cannot infer which one is meant from `min`/`max` alone, which
/// is why this is a kind and not an inference: goingson's `min="1"` duration
/// is a validated number and would become a slider.
///
/// The membership test passes without stretching: a webview emits
/// ``, egui has `Slider`, a terminal draws a bar and
/// takes arrow keys, a CLI takes a bounded argument.
///
/// # It owes its bounds
///
/// [`min`](Field::min) and [`max`](Field::max) are `Option` for every other
/// kind and are **required** here, in the sense the description can require
/// anything: [`Field::bounded`] is the check, and a range missing one has no
/// extent for a renderer to draw. What a renderer does with an unbounded
/// range is its own call and both answers are honest — fall back to a typed
/// number, or pick a host default — so this is stated rather than enforced,
/// the way every other constraint here is.
///
/// [`Field::step`] is the third fact and is genuinely optional: absent, the
/// host's own granularity stands.
Range,
/// One question with two ends: a lower value and an upper one, submitted
/// under two names.
///
/// "Show me samples between 90 and 130 BPM" has a single answer with two
/// ends, and the ends constrain each other: a minimum above the maximum is
/// not a wrong value, it is an empty result nobody asked for. Described as
/// two [`Number`](Self::Number) fields that is unsayable — nothing says they
/// are one question, so a renderer draws two controls with two labels and no
/// relationship, and [`Field::error`] can only be attached to one side of a
/// fault that belongs to both.
///
/// Not [`Range`](Self::Range), which was the reading to resist and the
/// resistance is the same one `Range` itself needed against `Number`. A
/// range describes *one* value inside an extent; this describes two, and the
/// extent is a bound on each rather than the question's meaning. The two
/// come apart in the answer: a range has a value, an interval has a pair,
/// and either end may be absent while the other stands.
///
/// # It states both names
///
/// [`Field::name`] is the lower end and [`Field::upper_name`] is the upper
/// one, stated rather than derived. One member instead of a naming
/// convention this crate would then own forever.
///
/// Direction is carried by which member the name sits in, so nothing
/// separate says which end is which.
///
/// # What it does not enforce
///
/// The crossing rule. A lower end above the upper one is describable here
/// and always was, exactly as an out-of-[`min`](Field::min) number is: this
/// crate carries constraints and never checks them, and deciding a value is
/// wrong stays with whoever validated. What the description buys is that the
/// fault now has one place to be reported rather than two.
///
/// # Both ends take the same facts
///
/// [`min`](Field::min), [`max`](Field::max), [`step`](Field::step) and
/// [`unit`](Field::unit) describe the axis rather than one end of it, so
/// they are read once and applied to both. Six of audiofiles' filter axes
/// are exactly this: one extent, one unit, one granularity, two ends.
///
/// The bounds are optional here, unlike `Range`. They are a rule the answer
/// is checked against rather than the control's extent, which is
/// [`Number`](Self::Number)'s arrangement and not a slider's.
Interval,
/// An email address.
///
/// Distinct from [`Text`](Self::Text) because the distinction is not
/// decoration: a webview renderer emits `type="email"`, which on a touch
/// device changes the keyboard that appears and turns on the platform's own
/// validation. goingson ships to iOS, so collapsing this into text costs a
/// keyboard with no `@` on it.
Email,
/// A URL. Same reasoning as [`Email`](Self::Email).
Url,
/// A telephone number. Same reasoning as [`Email`](Self::Email), and the
/// clearest case of it: the keyboard is a numeric pad rather than letters.
Tel,
/// A calendar day, with no time of day in it.
///
/// [`Email`](Self::Email)'s argument, and it carries further: a webview
/// emits `type="date"`, which is a native picker, the platform's own
/// validation, and on a touch device the date keyboard. Described as
/// [`Text`](Self::Text) with a hint reading "YYYY-MM-DD", all three are
/// lost and the hint is doing the platform's job in prose.
///
/// The membership test passes on every host without stretching: a webview
/// and a Tauri app emit the input, egui has a date picker, a terminal
/// prompts for a day and can validate it, a CLI takes an argument.
///
/// # The value is ISO 8601, `YYYY-MM-DD`
///
/// Named here rather than left to each host, because a host that picks
/// differently sends a server something it parses differently, and the
/// failure is silent and per-host. It is ``'s own wire
/// format, so the webview renderer owes nothing to honour it and the other
/// hosts have one spelling to meet. [`DATE_FORMAT`] is the constant, and a
/// test asserts this doc and that constant agree.
Date,
/// A calendar day and a time of day together.
///
/// Apart from [`Date`](Self::Date) because the question is different rather
/// than more precise: "which day does this expire" and "at what moment does
/// this publish" are asked by different screens and answered by different
/// controls. A webview emits `type="datetime-local"` for one and
/// `type="date"` for the other, and a host that collapsed them would ask
/// half the tree for a precision it does not want.
///
/// Both arrived together on measurement rather than on symmetry: 13 sites
/// of each across the MNW server and goingson, and **zero** of `time`,
/// `month` or `week`, which is why those are not here. A member added for a
/// case nobody has is a member designed against nothing, which is
/// [`File`](Self::File)'s reasoning about `accept` applied to a whole
/// member.
///
/// # The value is `YYYY-MM-DDTHH:MM`, local, with no zone
///
/// ``'s own format, and the "local" is the
/// load-bearing half: the value carries no offset and no `Z`, so the moment
/// it names is only fixed once something supplies a zone. That is the app's
/// business and not the description's. Seconds are absent, which is the
/// browser's own default and is left as the rule rather than restated as a
/// constraint. [`DATETIME_FORMAT`] is the constant.
///
/// [`Field::min`] and [`Field::max`] already take "the host's own spelling
/// of a bound", so a floor of *not in the past* needs nothing new here: it
/// is a string in this same format.
DateTime,
/// Several lines of text.
Textarea,
/// Several lines of text the user writes markdown in.
///
/// The editing counterpart of prose a description carries as markdown
/// source, and the reason it can exist at all is the same one that lets the
/// source be carried: editing markdown is editing text, so a terminal, an
/// immediate-mode host and a webview all have an honest answer, and none of
/// them has to refuse. A kind that meant "rich text" in the WYSIWYG sense
/// would have been a document model, and two of the three hosts would have
/// had to draw something they cannot.
///
/// What the mark buys over [`Textarea`](Self::Textarea) is that a renderer
/// may offer the affordances markdown has and plain text does not — a
/// preview, a syntax pass, a monospaced face for the source — and that a
/// host reading the value back knows what it is holding. A renderer with
/// none of that draws a textarea, which is why this is additive rather than
/// a second control.
///
/// It says nothing about **when** the value is saved. Autosave is a clock,
/// clocks are not described here, and the four MNW editors this was measured
/// against each keep their own.
///
/// Sanitising stays where it already is for markdown that is only displayed:
/// with the renderer, at the point markup is produced. Being described is
/// not a safety property, and a host with its own sanitiser and its own
/// content-security posture still owns both.
Rich,
/// One of a fixed set, offered behind a control that shows one at a time.
Select,
/// One of a fixed set, with every option on screen at once.
///
/// Not a presentation of [`Select`](Self::Select), which is the reading to
/// resist: what differs is a property of the *question*. A choice that is
/// consequential or irreversible has to be readable without opening
/// anything, because a closed control shows one option and hides the rest,
/// and the one it shows is whichever was current before the user had read
/// the alternatives. audiofiles asks whether a library copies samples into
/// its store or references them where they lie — which cannot be changed
/// afterwards — and had already promoted that out of a checkbox by hand,
/// with a comment giving this reason, before the description could say it.
///
/// Everything here is an ``, a `