import { type ElementStyle, type DrawableStyle, Element, type TextStyle, type Color, type BaseStyle, type UIElementBase } from "./UINode";

type InputStyle = {
  placeholder?: string,
  placeholderColor?: Color,
  /**
   * Keyboard hint, NOT a validator — `number` does not block pasted letters; validate in your own
   * onChange. The phone-pad value is `phone` (LeCodes has no `tel`).
   *
   * `date` / `time` are PICKER kinds, not keyboards: the host opens its native picker in the
   * keyboard slot (iOS wheels / browser calendar) and free typing is disabled. `value` is always
   * canonical — `YYYY-MM-DD` for `date`, `HH:MM` (24h) for `time` — while the field displays a
   * localized string; set/compare values in the canonical form only.
   */
  type?: "text" | "password" | "search" | "phone" | "email" | "number" | "decimal" | "url"
    | "date" | "time",
  /**
   * What the layout does while the keyboard is up (decided by the FOCUSED input). `true` (default):
   * the layout viewport shrinks to the area above the keyboard — one relayout. `false`: the keyboard
   * overlays the UI with NO layout recalculation — for chat composers that manage their own inset
   * (pair with `app.keyboardHeight` / the app `"keyboard"` event) and full-screen canvases where a
   * reflow is worse than overlap.
   */
  keyboardShrink?: boolean,
  /**
   * Whether a tap on a non-interactive area dismisses the keyboard while THIS input is focused
   * (like `keyboardShrink`, the policy of the focused input). `true` (default): tapping dead
   * space blurs — but a tap on a button or another input NEVER dismisses; the control acts and
   * focus stays. `false`: taps never dismiss — the chat-composer policy. `blur()`, the return
   * key (per `enterKey`), and the platform's own gestures (iOS drag-down over the keyboard)
   * still work in both modes. Scrolling never dismisses the keyboard on any setting.
   */
  keyboardDismiss?: boolean,
  /** Hard cap on the text length — enforced by the host while typing/pasting. */
  maxLength?: number,
  /** Auto-capitalization hint; omitted = platform default. */
  autocapitalize?: "none" | "words" | "sentences" | "characters",
  /** Enable/disable the platform autocorrection & suggestions; omitted = platform default. */
  autocorrect?: boolean,
}
export type UIInputStyle = ElementStyle & DrawableStyle & TextStyle & InputStyle

/** UIInput-only (a UITextArea's Enter is a newline, period): the return-key label. */
type SubmitStyle = {
  /**
   * Return-key label. On submit the keyboard dismisses UNLESS `enterKey` is `"next"` — then the
   * app is expected to call `otherInput.focus()` from `onSubmit`, so the keyboard never blinks.
   */
  enterKey?: "done" | "go" | "next" | "search" | "send",
}

/** A single-line text input. Keyboard/picker kind via the `type` style, return-key behavior via
 *  `enterKey` + `onSubmit`; keyboard policy on `keyboardShrink`/`keyboardDismiss`. */
export interface UIInput extends UIElementBase<UIInputStyle & SubmitStyle & { onFocused?: UIInputStyle & { duration?: number } }, DrawableStyle & BaseStyle> {
  readonly type: "input",

  /**
   * The field's text. Writable at any time — set it BEFORE the screen opens to pre-fill a form
   * (settings, edit-profile); the value is stashed on the node and applied when the host builds
   * it. Reads return the live text while the screen is open, and the last value you set while it
   * isn't. The user's typing is not preserved across close/open (use `keepAlive()` for that).
   */
  value: string

  /** The text changed — fires on every user edit. */
  onChange(callback: (value: string) => void): UIInput
  /** Gained focus (keyboard opens). */
  onFocus(callback: () => void): UIInput
  /** Lost focus. */
  onBlur(callback: () => void): UIInput
  /** The keyboard's return key was pressed. `value` is the current text. See `enterKey`. */
  onSubmit(callback: (value: string) => void): UIInput

  /** Focus the input (opens the keyboard). No-op before mount. */
  focus(): void
  /** Release focus (dismisses the keyboard when this input held it). */
  blur(): void
}

/** A multi-line text field — see the {@link UITextArea} factory for the auto-grow contract. */
export interface UITextArea extends UIElementBase<UIInputStyle & { onFocused?: UIInputStyle & { duration?: number } }, DrawableStyle & BaseStyle> {
  readonly type: "textarea",

  /** The field's text — same contract as {@link UIInput.value}: settable before mount to pre-fill. */
  value: string

  /** The text changed — fires on every user edit. */
  onChange(callback: (value: string) => void): UITextArea
  /** Gained focus (keyboard opens). */
  onFocus(callback: () => void): UITextArea
  /** Lost focus. */
  onBlur(callback: () => void): UITextArea

  /** Focus the text area (opens the keyboard). No-op before mount. */
  focus(): void
  /** Release focus (dismisses the keyboard when this text area held it). */
  blur(): void
}

class InputElement<T extends "input" | "textarea"> extends Element<T> {

  readonly cl: Function[] = []
  readonly fl: Function[] = []
  readonly bl: Function[] = []
  readonly sl: Function[] = []

  /** @internal The value as last set by CODE. Hosts read it at node creation (after the initial
   *  style, so a `date` field's canonical parse and `maxLength` are already in place), which is
   *  what makes a value assigned before mount — a pre-filled settings form — land on the field.
   *  Same pre-mount stash as UIImage's `_sourceRect`. Typing does NOT sync back here: while
   *  mounted the HOST owns the text (IME composition, paste, the maxLength clamp, date/time
   *  canonicalization) and the getter reads it over the bridge. So on a remount the field comes
   *  back with the last code-set value, not what the user typed — screens that must keep live
   *  editing state across close/open use `keepAlive()`, the mechanism built for it. */
  _value = ""

  get value() {
    // Mounted: the host is authoritative. Unmounted: what code last set (0 => never mounted).
    return this._id !== 0 ? _creatorUI.getTextValue(this._id) : this._value
  }
  set value(value: string) {
    this._value = value
    if (this._id !== 0) {
      _creatorUI.updateNode(this._id, "value", value)
    }
  }

  // `phone` is SDK-surface only — hosts (and the web's verbatim `el.type`) keep HTML's `tel` as
  // the wire format, so the rename never reaches them.
  _mapStyle(style: any): void {
    if (style.type === "phone") style.type = "tel"
  }

  onChange (callback: (value: string) => void): this {
    this.cl.push(callback)
    return this
  }
  onFocus(callback: () => void): this {
    this.fl.push(callback)
    return this
  }
  onBlur(callback: () => void): this {
    this.bl.push(callback)
    return this
  }
  onSubmit(callback: (value: string) => void): this {
    this.sl.push(callback)
    return this
  }

  // Focus rides the existing value channel (updateNode "focus") — no new ABI method.
  focus(): void {
    if (this._id !== 0) {
      _creatorUI.updateNode(this._id, "focus", true)
    }
  }
  blur(): void {
    if (this._id !== 0) {
      _creatorUI.updateNode(this._id, "focus", false)
    }
  }
}

/** Create a single-line text input. Read/write the text via `.value`, listen with
 *  `onChange`/`onSubmit`; style keys `placeholder`, `type`, `enterKey`, … configure it. */
export function UIInput(): UIInput;
export function UIInput(style: UIInputStyle): UIInput;
export function UIInput(style?: UIInputStyle): UIInput {
  // One text line as the floor — an unstyled input must never collapse to 0 height.
  return new InputElement("input", { minHeight: "1.2em", ...style })
}

/**
 * Multi-line text field. Enter is a newline (submit lives on UIInput / your own button).
 *
 * Auto-grow: with no explicit `height` the field measures its own content and grows line by
 * line — clamp the range with `minHeight` / `maxHeight` (past `maxHeight` the text scrolls
 * inside). A fixed `height` opts out and scrolls internally from the start. Chat composers are
 * the canonical use: a bare textarea in a padded pill, `maxHeight` at ~5 lines.
 */
export function UITextArea(): UITextArea;
export function UITextArea(style: UIInputStyle): UITextArea;
export function UITextArea(style?: UIInputStyle): UITextArea {
  return new InputElement("textarea", { minHeight: "1.2em", ...style })
}
