/* Input — `.l-input` on a native text-like `<input>` (text, search, number,
   password, email, url, tel, date, time, …).

   Like `.l-checkbox`, the class is the canonical primitive and works anywhere.
   Two contexts auto-style a bare text input with no class needed:
   - `:where(l-form-field:not([unstyled])) > input` (type-filtered to text-like
     types so `range`/`color`/`file`/buttons keep their native look);
   - `:where(l-input-group) > input` — see the group block below.

   Adornments (leading icon, trailing unit, buttons) can't live inside a
   replaced `<input>`, so wrap the control in `<l-input-group>`: the group owns
   the border and the inner input becomes borderless. Layout is pure CSS (DOM
   order = visual order); the element's JS only adds behavior on top
   (`password-toggle`, click-to-focus).

   Native date/time pickers and the search clear button are restyled in place:
   the `::-webkit-calendar-picker-indicator` and `::-webkit-search-cancel-button`
   are recolored and masked with a Lucide glyph (overridable via
   `--calendar-icon` / `--clock-icon` / `--clear-icon`). This is CSS-only —
   Firefox simply keeps its default native controls. */

@layer components {
  .l-input,
  :where(l-form-field:not([unstyled]))
    > input:where(
      :not([type]),
      [type='text'],
      [type='search'],
      [type='email'],
      [type='url'],
      [type='tel'],
      [type='password'],
      [type='number'],
      [type='date'],
      [type='time'],
      [type='datetime-local'],
      [type='month'],
      [type='week']
    ),
  :where(l-input-group) > input {
    /* Public knobs */
    --height: var(--l-form-control-height);
    --border-radius: var(--l-form-control-border-radius);
    /* https://lucide.dev/icons/calendar */
    --calendar-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/></svg>');
    /* https://lucide.dev/icons/clock */
    --clock-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>');
    /* https://lucide.dev/icons/circle-x */
    --clear-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>');

    box-sizing: border-box;
    inline-size: 100%;
    block-size: var(--height);
    margin: 0;
    padding-block: 0;
    padding-inline: var(--l-form-control-padding-inline);
    appearance: none;
    border: var(--l-form-control-border-width) solid var(--l-form-control-border-color);
    border-radius: var(--border-radius);
    background-color: var(--l-form-control-background-color);
    color: var(--l-form-control-value-color);
    font: inherit;
    line-height: normal;
    transition-property: border-color, box-shadow, background-color;
    transition-duration: 150ms;

    &::placeholder {
      color: var(--l-form-control-placeholder-color);
      opacity: 1; /* Firefox dims placeholders by default */
    }

    /* `:where()` keeps the disabled guard at zero specificity so the focus and
       invalid rules below (equal specificity, later in source) win over hover. */
    @media (hover: hover) {
      &:hover:where(:not(:disabled)) {
        border-color: var(--l-form-control-border-color-hover);
      }
    }

    /* Focus: the border takes the focus-ring color. The soft halo lives in a
       separate rule below (kept off the group's inner input). The transparent
       outline keeps a visible indicator under forced colors. */
    &:focus-visible {
      border-color: var(--l-focus-ring);
      outline: 2px solid transparent;
      outline-offset: 2px;
    }

    /* Invalid: red border + a faint danger wash. After interaction
       (`:user-invalid`) or forced via `aria-invalid` (set by `l-form-field` /
       server-side validation). Ordered after `:focus-visible` so the red border
       wins when a field is both focused and invalid. */
    &:user-invalid,
    &[aria-invalid='true'] {
      border-color: var(--l-form-control-border-color-invalid);
      background-color: color-mix(
        in oklab,
        var(--l-form-control-border-color-invalid) 6%,
        var(--l-form-control-background-color)
      );
    }

    /* Disabled: a solid greyed-out fill (shadcn-style) rather than fading the
       whole control with opacity. `-webkit-text-fill-color` overrides WebKit's
       forced grey on disabled inputs so the token color actually applies. */
    &:disabled {
      cursor: not-allowed;
      border-color: var(--l-form-control-disabled-border);
      background-color: var(--l-form-control-disabled-background);
      color: var(--l-form-control-disabled-color);
      -webkit-text-fill-color: var(--l-form-control-disabled-color);
    }

    &:disabled::placeholder {
      color: var(--l-form-control-disabled-color);
    }
  }

  /* Soft focus halo — on the standalone `.l-input` and a field-styled input
     only. NOT on a group's inner input: nesting `&` inside the base selector
     inflates its specificity (via `:is(.l-input, …)`), which would beat the
     group's reset and paint a halo around the inner input. The group renders
     its own halo on the wrapper instead. */
  :is(.l-input, :where(l-form-field:not([unstyled])) > input):focus-visible {
    box-shadow: 0 0 0 3px color-mix(in oklab, var(--l-focus-ring) 22%, transparent);
  }

  :is(.l-input, :where(l-form-field:not([unstyled])) > input):is(
      :user-invalid,
      [aria-invalid='true']
    ):focus-visible {
    box-shadow: 0 0 0 3px
      color-mix(in oklab, var(--l-form-control-border-color-invalid) 22%, transparent);
  }

  /* Size — `data-size` maps the control height to the shared `--l-size-control-*`
     scale (default `md`). Only the height changes; the label and hint/error are
     unaffected. The group's inner input is excluded — `l-input-group[size]` sizes
     the wrapper instead. */
  :is(.l-input, :where(l-form-field:not([unstyled])) > input) {
    &[data-size='xs'] {
      --height: var(--l-size-control-xs);
    }
    &[data-size='sm'] {
      --height: var(--l-size-control-sm);
    }
    &[data-size='md'] {
      --height: var(--l-size-control-md);
    }
    &[data-size='lg'] {
      --height: var(--l-size-control-lg);
    }
    &[data-size='xl'] {
      --height: var(--l-size-control-xl);
    }
  }

  /*
   * Native picker / clear icons — recolored + masked. Overridable glyphs; the
   * color comes from `background-color: currentColor`, not the image. Applied
   * in every text-control context (class, field, group).
   */
  :is(
    .l-input,
    :where(l-form-field:not([unstyled]), l-input-group) > input
  )::-webkit-calendar-picker-indicator {
    inline-size: 1.15em;
    block-size: 1.15em;
    padding: 0;
    color: var(--l-form-control-placeholder-color);
    background-color: currentColor;
    background-image: none;
    cursor: pointer;
    /* 1em mask-size (not `contain`, which would fill the 1.15em hit area) keeps
       the glyph the same visual size as a 1em `<l-icon>` affix. */
    mask: var(--calendar-icon) center / 1em no-repeat;
  }

  :is(
      .l-input,
      :where(l-form-field:not([unstyled]), l-input-group) > input
    )[type='time']::-webkit-calendar-picker-indicator {
    mask-image: var(--clock-icon);
  }

  @media (hover: hover) {
    :is(
        .l-input,
        :where(l-form-field:not([unstyled]), l-input-group) > input
      )::-webkit-calendar-picker-indicator:hover {
      color: var(--l-form-control-value-color);
    }
  }

  /* Keep the editable date/time segments in the value color. */
  :is(.l-input, :where(l-form-field:not([unstyled]), l-input-group) > input):is(
      [type='date'],
      [type='time'],
      [type='datetime-local'],
      [type='month'],
      [type='week']
    )::-webkit-datetime-edit {
    color: var(--l-form-control-value-color);
  }

  :is(
      .l-input,
      :where(l-form-field:not([unstyled]), l-input-group) > input
    ):disabled::-webkit-datetime-edit {
    color: var(--l-color-text-disabled);
  }

  /* Native search clear — clears the field with zero JS (WebKit/Blink; Firefox
     has no cancel button but Escape clears the field natively). */
  :is(
      .l-input,
      :where(l-form-field:not([unstyled]), l-input-group) > input
    )[type='search']::-webkit-search-cancel-button {
    appearance: none;
    inline-size: 1.15em;
    block-size: 1.15em;
    color: var(--l-form-control-placeholder-color);
    background-color: currentColor;
    cursor: pointer;
    mask: var(--clear-icon) center / 1em no-repeat;
  }

  @media (hover: hover) {
    :is(
        .l-input,
        :where(l-form-field:not([unstyled]), l-input-group) > input
      )[type='search']::-webkit-search-cancel-button:hover {
      color: var(--l-form-control-value-color);
    }
  }

  /*
   * Input group — `<l-input-group>` wraps one text input plus leading/trailing
   * adornments. The group owns the border + chrome; the inner input is reset to
   * a bare field. Layout is DOM order: a child before the input is a leading
   * adornment, after is trailing. Any non-input child (an `<l-icon>`, a unit
   * `<span>`, a `<button>`) is styled as an adornment — no class needed.
   */
  l-input-group {
    /* Public knobs (mirror the input) */
    --height: var(--l-form-control-height);
    --border-radius: var(--l-form-control-border-radius);

    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    gap: 0.5ch;
    inline-size: 100%;
    block-size: var(--height);
    padding-inline: var(--l-form-control-padding-inline);
    border: var(--l-form-control-border-width) solid var(--l-form-control-border-color);
    border-radius: var(--border-radius);
    background-color: var(--l-form-control-background-color);
    color: var(--l-form-control-value-color);
    transition-property: border-color, box-shadow, background-color;
    transition-duration: 150ms;

    /* Size — `size` (reflected) maps the height to the shared scale, like the
       other custom controls (`l-input-stepper`, `l-input-otp`). */
    &[size='xs'] {
      --height: var(--l-size-control-xs);
    }
    &[size='sm'] {
      --height: var(--l-size-control-sm);
    }
    &[size='md'] {
      --height: var(--l-size-control-md);
    }
    &[size='lg'] {
      --height: var(--l-size-control-lg);
    }
    &[size='xl'] {
      --height: var(--l-size-control-xl);
    }

    /* The control inside drops its own chrome — the group provides it.
       `:is(input, .l-input)` outranks the `.l-input` base rule. */
    & > :is(input, .l-input) {
      flex: 1 1 auto;
      min-inline-size: 0;
      inline-size: auto;
      block-size: 100%;
      padding-inline: 0;
      border: 0;
      border-radius: 0;
      background: transparent;
    }

    /* The group owns the state visuals (focus halo, invalid wash); keep the
       inner input neutral so they don't render twice. */
    & > :is(input, .l-input):focus-visible {
      outline: 0;
      box-shadow: none;
    }
    & > :is(input, .l-input):is(:user-invalid, [aria-invalid='true'], :disabled) {
      background: transparent;
    }

    /* Stepping UI inside a group is the adornments' job (`l-input-stepper`
       exists for that) — native spinners would sit between value and unit. */
    & > input[type='number'] {
      appearance: textfield;
    }

    /* Adornment — any non-input child: icon, unit text, or button. */
    & > :not(input) {
      flex: 0 0 auto;
      display: inline-flex;
      align-items: center;
      color: var(--l-form-control-placeholder-color);
      white-space: nowrap;
      -webkit-user-select: none;
              user-select: none;
    }

    /* Interactive adornment (authored or the injected password toggle): a
       full-height 1:1 square hit area with a hover wash, bleeding into the
       group's inline padding so it sits flush against the edge — mirrors the
       stepper's buttons. */
    & > button {
      align-self: stretch;
      aspect-ratio: 1;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      margin: 0;
      padding: 0;
      appearance: none;
      border: 0;
      border-radius: calc(var(--border-radius) - var(--l-form-control-border-width));
      background: none;
      color: var(--l-form-control-placeholder-color);
      font: inherit;
      cursor: pointer;
      transition-property: background-color, color;
      transition-duration: 150ms;

      /* Bleed over the group padding and square off the inner corners so the
         hover wash follows the group's rounded edge. */
      &:first-child {
        margin-inline-start: calc(var(--l-form-control-padding-inline) * -1);
        border-start-end-radius: 0;
        border-end-end-radius: 0;
      }
      &:last-child {
        margin-inline-end: calc(var(--l-form-control-padding-inline) * -1);
        border-start-start-radius: 0;
        border-end-start-radius: 0;
      }

      @media (hover: hover) {
        &:hover:not(:disabled) {
          color: var(--l-form-control-value-color);
          background-color: var(--l-color-bg-fill-neutral-soft);
        }
      }

      &:active:not(:disabled) {
        background-color: var(--l-color-bg-fill-neutral-subtle);
      }

      &:focus-visible {
        outline: 2px solid var(--l-focus-ring);
        outline-offset: -2px;
        z-index: 1;
      }

      &:disabled {
        cursor: not-allowed;
        opacity: 0.4;
      }
    }

    @media (hover: hover) {
      &:hover:where(:not(:has(> input:disabled))) {
        border-color: var(--l-form-control-border-color-hover);
      }
    }

    /* Focus: the group border takes the focus-ring color + soft halo.
       `:has(> input:focus-visible)`, not `:focus-within` — a focused adornment
       button shows its own ring, not the group's. */
    &:has(> input:focus-visible) {
      border-color: var(--l-focus-ring);
      outline: 2px solid transparent;
      outline-offset: 2px;
      box-shadow: 0 0 0 3px color-mix(in oklab, var(--l-focus-ring) 22%, transparent);
    }

    /* Invalid: red border + faint danger wash. Ordered after focus so the red
       border wins when the field is both focused and invalid. */
    &:has(> input:user-invalid),
    &:has(> input[aria-invalid='true']) {
      border-color: var(--l-form-control-border-color-invalid);
      background-color: color-mix(
        in oklab,
        var(--l-form-control-border-color-invalid) 6%,
        var(--l-form-control-background-color)
      );
    }

    &:has(> input:user-invalid):has(> input:focus-visible),
    &:has(> input[aria-invalid='true']):has(> input:focus-visible) {
      box-shadow: 0 0 0 3px
        color-mix(in oklab, var(--l-form-control-border-color-invalid) 22%, transparent);
    }

    &:has(> input:disabled) {
      cursor: not-allowed;
      border-color: var(--l-form-control-disabled-border);
      background-color: var(--l-form-control-disabled-background);
      color: var(--l-form-control-disabled-color);
    }
  }

  /* Hide native spinners / Edge's reveal inside a group — kept top-level
     because Safari ignores some `::-webkit-*` pseudos in nested rules. */
  l-input-group > input[type='number']::-webkit-inner-spin-button,
  l-input-group > input[type='number']::-webkit-outer-spin-button {
    appearance: none;
    display: none;
    margin: 0;
  }

  /* Edge's native password reveal would duplicate the injected toggle. */
  l-input-group > input::-ms-reveal,
  l-input-group > input::-ms-clear {
    display: none;
  }

  @media (prefers-reduced-motion: reduce) {
    .l-input,
    :where(l-form-field:not([unstyled]), l-input-group) > input,
    l-input-group,
    l-input-group > button {
      transition-duration: 0ms;
    }
  }
}
