/*
 * Input
 *
 * Measured from Figma `Input` (80:275) and `Form Field` (80:330).
 * Three sizes x seven states, plus the label/helper wrapper.
 *
 * GEOMETRY COMES FROM THE SPACING SCALE, NOT FROM A CONTROL RECIPE
 *
 * 1. Every measurement below is a rung on `spacing/*` (or the `icon-size`
 *    ladder), picked by this component. There is no shared "control" token
 *    prescribing what a medium field's padding should be — that group existed,
 *    was reverse-engineered from Button's numbers, and was bound by 3 of 26
 *    components because nothing whose measurements differed could use it.
 *
 *    So Input picking 12 where Button picks 16 is not a divergence to explain
 *    away. It is a component fact, recorded here, which is the only place that
 *    knows it. The scale is the shared contract; the rung is not.
 *
 * 2. Figma draws its 1px stroke INSIDE the frame, so padding is measured from
 *    the outer edge and the stroke overlaps it. A CSS border sits outside the
 *    padding box, so `padding-x: 12px` would put the text at 13px. The border
 *    width is subtracted back out — the same correction Badge and Tabs needed.
 *
 *    The subtraction is against the LIVE border width, which used to matter:
 *    focus was a 2px inside stroke that ate a further 1px of padding, and
 *    subtracting it kept the text from shifting. Every state is 1px as of
 *    5 Sep 2026, so the correction is now constant — but it stays written this
 *    way, because it is correct either way and the next state to need a
 *    different width gets it for free.
 */
.ion-input {
  /*
   * Size is a set of slots defaulting to Medium, so a size modifier reassigns
   * six variables rather than restating six declarations — same shape as
   * Button, and it keeps the icon rule size-agnostic.
   */
  --ion-input-height: var(--spacing-40);
  --ion-input-padding-x: var(--spacing-12);
  --ion-input-gap: var(--spacing-8);
  --ion-input-icon-size: var(--icon-size-md);
  --ion-input-radius: var(--radius-md);
  --ion-input-font-size: var(--type-body);
  --ion-input-line-height: var(--type-body-line-height);
  --ion-input-border-width: var(--border-width-default);

  display: flex;
  align-items: center;
  gap: var(--ion-input-gap);

  /*
   * Figma pins each size to a fixed height (32/40/48) with no vertical padding.
   * `min-height` reproduces that while still growing if the field is ever given
   * a taller control inside it.
   */
  min-height: var(--ion-input-height);
  padding-inline: calc(
    var(--ion-input-padding-x) - var(--ion-input-border-width)
  );
  background-color: var(--surface-default);
  border-style: solid;
  border-width: var(--ion-input-border-width);
  border-color: var(--border-default);
  border-radius: var(--ion-input-radius);
  font-family: var(--font-family-sans);
  font-size: var(--ion-input-font-size);
  line-height: var(--ion-input-line-height);
  transition:
    border-color var(--ion-duration-base) var(--ion-ease-out),
    background-color var(--ion-duration-base) var(--ion-ease-out);
}

/*
 * Small shares Medium's padding. It was 10px and unbound in Figma — the only
 * untokenised measurement in either component, and off a scale that runs
 * 8, 12, 16, 20. Resolved in Figma by moving to 12; height, gap, radius and the
 * type ramp still separate the two sizes.
 */
.ion-input--sm {
  --ion-input-height: var(--spacing-32);
  --ion-input-padding-x: var(--spacing-12);
  --ion-input-gap: var(--spacing-6);
  --ion-input-icon-size: var(--icon-size-sm);
  --ion-input-radius: var(--radius-sm);
  --ion-input-font-size: var(--type-body-sm);
  --ion-input-line-height: var(--type-body-sm-line-height);
}

/* Large keeps Medium's radius, icon size and type ramp — only height and padding grow. */
.ion-input--lg {
  --ion-input-height: var(--spacing-48);
  --ion-input-padding-x: var(--spacing-16);
}

/*
 * A leading addon and the control, sharing one outline — Figma `Input/Phone`
 * (80:372).
 *
 * `align-items: stretch` rather than a height here on purpose: the addon takes
 * its height from the control, so the size ladder stays in exactly one place
 * and the two halves cannot drift. The addon supplies its own left radii; this
 * rule only has to square off the edge they meet on.
 */
.ion-input-group {
  display: flex;
  align-items: stretch;
}

.ion-input-group > .ion-input {
  border-start-start-radius: 0;
  border-end-start-radius: 0;
}

/*
 * The field itself carries no box of its own — the wrapper owns the border,
 * background and padding so the icons sit inside it. Everything here is either
 * inherited or reset.
 */
.ion-input__field {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
  background-color: transparent;
  border: none;
  outline: none;
  color: var(--text-secondary);
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  font-weight: var(--font-weight-regular);
}

/*
 * Figma's Filled state differs from Default only in the value colour
 * (text/tertiary -> text/secondary). That is exactly the placeholder
 * distinction, so it needs no variant: the field is text/secondary and the
 * placeholder pseudo-element is text/tertiary.
 */
.ion-input__field::placeholder {
  color: var(--text-tertiary);
  opacity: 1;
}

.ion-input__icon-start,
.ion-input__icon-end {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

/* One rule for every size — the slot is reassigned by the size modifier. */
.ion-input__icon-start svg,
.ion-input__icon-end svg {
  width: var(--ion-input-icon-size);
  height: var(--ion-input-icon-size);
}

/*
 * The two icons carry different tokens. The leading icon is decorative
 * (icon/tertiary); the trailing one is a control — clear, reveal, calendar —
 * and sits at icon/interactive. v2 splits text and icon precisely so this can
 * differ from the value colour beside it.
 */
.ion-input__icon-start {
  color: var(--icon-tertiary);
}

.ion-input__icon-end {
  color: var(--icon-interactive);
}

/*
 * State order below is deliberate and equal-specificity, so later rules win:
 * hover -> focus -> invalid -> read-only -> disabled.
 *
 * Invalid sits after focus and overrides the colour only, never the width. A
 * focused invalid field therefore shows the error colour rather than the focus
 * colour — both states are true at once and the more urgent one is shown. That
 * ordering mattered more when the two differed in width as well; it is still
 * the behaviour, and still deliberate.
 */

/*
 * Hover is declared twice, as it is on Button. Native `:hover` latches on touch
 * until you tap elsewhere; `[data-hovered]` comes from React Aria's useHover,
 * which is pointer-aware. The media query keeps this working without React.
 */
@media (hover: hover) {
  .ion-input:hover {
    border-color: var(--border-strong);
  }
}

.ion-input[data-hovered='true'] {
  border-color: var(--border-strong);
}

/*
 * 1px, not the 2px this used to carry. See the note on --invalid below: both
 * states were thinned on the same design call, and the trade is the same one.
 */
.ion-input:focus-within,
.ion-input[data-focused='true'] {
  border-color: var(--border-focus);
}

/*
 * 1px. THIS COMMENT USED TO ARGUE FOR 2px AND THE ARGUMENT WAS SOUND, so read
 * it before changing anything back:
 *
 *   "2px, matching Focus and matching Select. At 1px the invalid state differed
 *    from default only in hue, which is the exact failure mode WCAG 1.4.1 is
 *    about — error has to be perceivable without relying on colour alone.
 *    Reconciled in Figma; Input was the one that was wrong."
 *
 * That defect was found once and fixed once, and thinning the border reopens
 * it: an invalid field now differs from a default field by hue alone. It was
 * changed on an explicit design call on 5 Sep 2026, applied to Input and
 * Textarea and to their Figma sets, and it is recorded here rather than left to
 * look like drift.
 *
 * Select, Checkbox, Radio, Tabs and Menu still carry border-width/thick, so
 * these two are deliberately out of step with the rest of the system.
 *
 * IF THE ERROR STATE NEEDS A NON-COLOUR CUE AGAIN, this is the decision to
 * revisit — and an icon or a message would satisfy 1.4.1 without the border,
 * which is the better fix than putting the 2px back.
 */
.ion-input--invalid,
.ion-input[data-invalid='true'] {
  border-color: var(--border-error-strong);
}

/* Read-only keeps the value legible but drops the field out of the editable
 * palette entirely — page surface, subtle border. */
.ion-input--readonly,
.ion-input[data-readonly='true'] {
  background-color: var(--surface-page);
  border-color: var(--border-subtle);
}

.ion-input--disabled,
.ion-input[data-disabled='true'] {
  background-color: var(--surface-disabled);
  border-color: var(--border-disabled);
  cursor: not-allowed;
}

.ion-input--disabled .ion-input__field,
.ion-input[data-disabled='true'] .ion-input__field {
  color: var(--text-disabled);
  cursor: not-allowed;
}

.ion-input--disabled .ion-input__field::placeholder,
.ion-input[data-disabled='true'] .ion-input__field::placeholder {
  color: var(--text-disabled);
}

.ion-input--disabled .ion-input__icon-start,
.ion-input--disabled .ion-input__icon-end,
.ion-input[data-disabled='true'] .ion-input__icon-start,
.ion-input[data-disabled='true'] .ion-input__icon-end {
  color: var(--icon-disabled);
}

/*
 * Form Field — Figma `Form Field` (80:330).
 *
 * Label + control + helper stacked at 6px. Named `ion-field` rather than
 * `ion-input__field-wrapper` because Select uses it unchanged: the wrapper
 * knows nothing about what control sits in the middle.
 *
 * Only the Error variant differs from Default. Figma's Disabled variant leaves
 * both the label and the helper on their default tokens, so it needs no rule —
 * the control inside carries the whole disabled treatment.
 */
.ion-field {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-6);
}

.ion-field__label {
  color: var(--text-secondary);
  font-family: var(--font-family-sans);
  font-size: var(--type-body-sm);
  line-height: var(--type-body-sm-line-height);
  font-weight: var(--font-weight-medium);
}

.ion-field__helper {
  color: var(--text-tertiary);
  font-family: var(--font-family-sans);
  font-size: var(--type-body-sm);
  line-height: var(--type-body-sm-line-height);
  font-weight: var(--font-weight-regular);
}

.ion-field--error .ion-field__helper {
  color: var(--text-error);
}
