# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

import math
from collections.abc import Sequence
from dataclasses import dataclass
from datetime import date, datetime, time, timedelta, timezone, tzinfo
from numbers import Integral, Real
from textwrap import dedent
from typing import (
    TYPE_CHECKING,
    Any,
    Final,
    TypeAlias,
    TypedDict,
    TypeVar,
    cast,
    overload,
)

from streamlit.elements.lib.form_utils import current_form_id
from streamlit.elements.lib.js_number import JSNumber, JSNumberBoundsException
from streamlit.elements.lib.layout_utils import create_layout_config
from streamlit.elements.lib.policies import (
    check_widget_policies,
    maybe_raise_label_warnings,
)
from streamlit.elements.lib.utils import (
    Key,
    LabelVisibility,
    compute_and_register_element_id,
    get_label_visibility_proto_value,
    to_key,
)
from streamlit.errors import (
    StreamlitAPIException,
    StreamlitValueAboveMaxError,
    StreamlitValueBelowMinError,
)
from streamlit.proto.Slider_pb2 import Slider as SliderProto
from streamlit.runtime.metrics_util import gather_metrics
from streamlit.runtime.scriptrunner import ScriptRunContext, get_script_run_ctx
from streamlit.runtime.state import (
    BindOption,
    PersistStateOption,
    WidgetArgs,
    WidgetCallback,
    WidgetKwargs,
    get_session_state,
    register_widget,
)

if TYPE_CHECKING:
    from streamlit.delta_generator import DeltaGenerator
    from streamlit.elements.lib.column_types import DateTimeFormat, NumberFormat
    from streamlit.elements.lib.layout_utils import WidthWithoutContent

SliderNumericT = TypeVar("SliderNumericT", int, float)
SliderDatelikeT = TypeVar("SliderDatelikeT", date, time, datetime)

SliderNumericSpanT: TypeAlias = (
    list[SliderNumericT]
    | tuple[()]
    | tuple[SliderNumericT]
    | tuple[SliderNumericT, SliderNumericT]
)
SliderDatelikeSpanT: TypeAlias = (
    list[SliderDatelikeT]
    | tuple[()]
    | tuple[SliderDatelikeT]
    | tuple[SliderDatelikeT, SliderDatelikeT]
)

StepNumericT: TypeAlias = SliderNumericT
StepDatelikeT: TypeAlias = timedelta

SliderStep: TypeAlias = int | float | timedelta
SliderScalar: TypeAlias = int | float | date | time | datetime
SliderValueT = TypeVar("SliderValueT", int, float, date, time, datetime)
SliderValueGeneric: TypeAlias = SliderValueT | Sequence[SliderValueT]
SliderValue: TypeAlias = (
    SliderValueGeneric[int]
    | SliderValueGeneric[float]
    | SliderValueGeneric[date]
    | SliderValueGeneric[time]
    | SliderValueGeneric[datetime]
)
SliderReturnGeneric: TypeAlias = (
    SliderValueT | tuple[SliderValueT] | tuple[SliderValueT, SliderValueT]
)
SliderReturn: TypeAlias = (
    SliderReturnGeneric[int]
    | SliderReturnGeneric[float]
    | SliderReturnGeneric[date]
    | SliderReturnGeneric[time]
    | SliderReturnGeneric[datetime]
)

SECONDS_TO_MICROS: Final = 1000 * 1000
DAYS_TO_MICROS: Final = 24 * 60 * 60 * SECONDS_TO_MICROS

UTC_EPOCH: Final = datetime(1970, 1, 1, tzinfo=timezone.utc)

SUPPORTED_TYPES: Final = {
    Integral: SliderProto.INT,
    Real: SliderProto.FLOAT,
    datetime: SliderProto.DATETIME,
    date: SliderProto.DATE,
    time: SliderProto.TIME,
}
TIMELIKE_TYPES: Final = (SliderProto.DATETIME, SliderProto.TIME, SliderProto.DATE)

# Widest span of whole days the frontend can represent exactly. Timelike values are
# serialized as microseconds since the epoch, and the frontend holds that in a
# JavaScript number, which is only exact up to ``JSNumber.MAX_SAFE_INTEGER``.
#
# That limit lands mid-day -- 00:12 on the first day, 23:47 on the last -- so these
# round *inward* to the first and last day that is usable at any time of day. Naming
# the raw limit days instead would recommend values the check below then rejects.
_SAFE_TIMELIKE_SPAN: Final = timedelta(microseconds=JSNumber.MAX_SAFE_INTEGER)
_MIN_SAFE_DAY: Final = (datetime(1970, 1, 1) - _SAFE_TIMELIKE_SPAN).date() + timedelta(
    days=1
)
_MAX_SAFE_DAY: Final = (datetime(1970, 1, 1) + _SAFE_TIMELIKE_SPAN).date() - timedelta(
    days=1
)


def _time_to_datetime(time_: time) -> datetime:
    # Note, here we pick an arbitrary date well after Unix epoch.
    # This prevents pre-epoch timezone issues (https://bugs.python.org/issue36759)
    # We're dropping the date from datetime later, anyway.
    # If this base date changes, also update _TIME_BASE_DATE in
    # streamlit/runtime/state/query_params.py.
    return datetime.combine(date(2000, 1, 1), time_)


def _date_to_datetime(date_: date) -> datetime:
    return datetime.combine(date_, time())


def _window_around(anchor: date, window: timedelta) -> tuple[date, date]:
    """Return ``anchor`` -/+ ``window``, clamped to the representable range.

    Used for the default ``min_value``/``max_value``, which are computed even when
    the caller passed both explicitly, so this has to hold for any ``anchor``.

    Clamping covers two failure modes:

    - Arithmetic that overflows near ``date``/``datetime``'s own limits.
    - A window that avoids overflow but still falls outside the frontend's
      representable range, which would reject a bound the caller never set --
      including for an ``anchor`` this module's own error message recommends.

    A bound is never moved past ``anchor``, so an unrepresentable ``anchor`` still
    reaches the range check below as itself rather than as an inverted window.
    """
    floor: date
    ceiling: date
    if isinstance(anchor, datetime):
        # The limits carry ``anchor``'s tzinfo so the comparisons below are
        # like-for-like, and ``_datetime_to_micros`` later reads wall-clock fields as
        # UTC rather than converting -- so wall-clock is what has to land in range. For
        # a zone whose offset varies by date the two can disagree, but only by that
        # offset, and the day of slack these constants keep from the true limit
        # (~23h48m at each end) is wider than any real-world offset.
        floor = datetime.combine(_MIN_SAFE_DAY, time.min, tzinfo=anchor.tzinfo)
        ceiling = datetime.combine(_MAX_SAFE_DAY, time.max, tzinfo=anchor.tzinfo)
    else:
        floor, ceiling = _MIN_SAFE_DAY, _MAX_SAFE_DAY

    try:
        low = min(anchor, max(anchor - window, floor))
    except OverflowError:
        low = anchor
    try:
        high = max(anchor, min(anchor + window, ceiling))
    except OverflowError:
        high = anchor
    return low, high


def _delta_to_micros(delta: timedelta) -> int:
    return (
        delta.microseconds
        + delta.seconds * SECONDS_TO_MICROS
        + delta.days * DAYS_TO_MICROS
    )


def _datetime_to_micros(dt: datetime) -> int:
    # The frontend is not aware of timezones and only expects a UTC-based
    # timestamp (in microseconds). Since we want to show the date/time exactly
    # as it is in the given datetime object, we just set the tzinfo to UTC and
    # do not do any timezone conversions. Only the backend knows about
    # original timezone and will replace the UTC timestamp in the deserialization.
    utc_dt = dt.replace(tzinfo=timezone.utc)
    return _delta_to_micros(utc_dt - UTC_EPOCH)


def _micros_to_datetime(micros: int, orig_tz: tzinfo | None) -> datetime:
    """Restore times/datetimes to original timezone (dates are always naive)."""
    utc_dt = UTC_EPOCH + timedelta(microseconds=micros)
    # Add the original timezone. No conversion is required here,
    # since in the serialization, we also just replace the timestamp with UTC.
    return utc_dt.replace(tzinfo=orig_tz)


class SliderDefaultValues(TypedDict):
    min_value: SliderScalar
    max_value: SliderScalar
    step: SliderStep
    format: str


@dataclass
class SliderSerde:
    value: list[float]
    data_type: int
    single_value: bool
    orig_tz: tzinfo | None
    min_value: float
    max_value: float

    def deserialize_single_value(self, value: float) -> SliderScalar:
        if self.data_type == SliderProto.INT:
            return int(value)
        if self.data_type == SliderProto.DATETIME:
            return _micros_to_datetime(int(value), self.orig_tz)
        if self.data_type == SliderProto.DATE:
            return _micros_to_datetime(int(value), self.orig_tz).date()
        if self.data_type == SliderProto.TIME:
            return (
                _micros_to_datetime(int(value), self.orig_tz)
                .time()
                .replace(tzinfo=self.orig_tz)
            )
        return value

    def deserialize(self, ui_value: list[float] | None) -> Any:
        if ui_value is not None:
            val = ui_value
            expected_len = 1 if self.single_value else 2
            if len(val) != expected_len:
                # Wrong number of values (e.g. single URL param for a range
                # slider); fall back to default so the URL param is cleared.
                val = self.value
            else:
                # Reset to default if any value is outside [min_value, max_value].
                # This rejects out-of-range values seeded from URL query params;
                # a no-op for frontend values since the UI enforces bounds.
                # TODO(query-params): URL values that pass bounds checking but
                # don't align to the step (e.g., ?val=0.15 with step=0.1) are
                # accepted as-is. Consider snapping to the nearest valid step
                # for consistency with the UI.
                for v in val:
                    if not math.isfinite(v) or v < self.min_value or v > self.max_value:
                        val = self.value
                        break
        else:
            # Widget has not been used; fallback to the original value,
            val = self.value

        # The widget always returns a float array, so fix the return type if necessary
        deserialized_values = [self.deserialize_single_value(v) for v in val]
        return (
            deserialized_values[0] if self.single_value else tuple(deserialized_values)
        )

    def serialize(self, v: Any) -> list[Any]:
        range_value = isinstance(v, (list, tuple))
        # Convert to list to handle tuples
        processed_value = list(v) if range_value else [v]

        if self.data_type == SliderProto.DATE:
            return [
                _datetime_to_micros(_date_to_datetime(val)) for val in processed_value
            ]
        if self.data_type == SliderProto.TIME:
            return [
                _datetime_to_micros(_time_to_datetime(val)) for val in processed_value
            ]
        if self.data_type == SliderProto.DATETIME:
            return [_datetime_to_micros(val) for val in processed_value]
        # For numeric types, ensure they are floats if not already
        return [float(val) for val in processed_value]


class SliderMixin:
    # If min/max/value/step are not provided, then we return an int.
    # if ONLY step is provided, then it must be an int and we return an int.
    @overload
    def slider(
        self,
        label: str,
        min_value: None = None,
        max_value: None = None,
        value: None = None,
        step: int | None = None,
        format: str | NumberFormat | None = None,
        key: Key | None = None,
        help: str | None = None,
        on_change: WidgetCallback | None = None,
        args: WidgetArgs | None = None,
        kwargs: WidgetKwargs | None = None,
        *,
        disabled: bool = False,
        label_visibility: LabelVisibility = "visible",
        width: WidthWithoutContent = "stretch",
        bind: BindOption = None,
        persist_state: PersistStateOption = None,
    ) -> int: ...

    # If min-value or max_value is provided and a numeric type, and value (if provided)
    #   is a singular numeric, return the same numeric type.
    @overload
    def slider(
        self,
        label: str,
        min_value: SliderNumericT | None = None,
        max_value: SliderNumericT | None = None,
        value: SliderNumericT | None = None,
        step: StepNumericT[SliderNumericT] | None = None,
        format: str | NumberFormat | None = None,
        key: Key | None = None,
        help: str | None = None,
        on_change: WidgetCallback | None = None,
        args: WidgetArgs | None = None,
        kwargs: WidgetKwargs | None = None,
        *,
        disabled: bool = False,
        label_visibility: LabelVisibility = "visible",
        width: WidthWithoutContent = "stretch",
        bind: BindOption = None,
        persist_state: PersistStateOption = None,
    ) -> SliderNumericT: ...

    # If value is provided and a sequence of numeric type,
    #   return a tuple of the same numeric type.
    @overload
    def slider(
        self,
        label: str,
        min_value: SliderNumericT | None = None,
        max_value: SliderNumericT | None = None,
        *,
        value: SliderNumericSpanT[SliderNumericT],
        step: StepNumericT[SliderNumericT] | None = None,
        format: str | NumberFormat | None = None,
        key: Key | None = None,
        help: str | None = None,
        on_change: WidgetCallback | None = None,
        args: WidgetArgs | None = None,
        kwargs: WidgetKwargs | None = None,
        disabled: bool = False,
        label_visibility: LabelVisibility = "visible",
        width: WidthWithoutContent = "stretch",
        bind: BindOption = None,
        persist_state: PersistStateOption = None,
    ) -> tuple[SliderNumericT, SliderNumericT]: ...

    # If value is provided positionally and a sequence of numeric type,
    #   return a tuple of the same numeric type.
    @overload
    def slider(
        self,
        label: str,
        min_value: SliderNumericT,
        max_value: SliderNumericT,
        value: SliderNumericSpanT[SliderNumericT],
        step: StepNumericT[SliderNumericT] | None = None,
        format: str | NumberFormat | None = None,
        key: Key | None = None,
        help: str | None = None,
        on_change: WidgetCallback | None = None,
        args: WidgetArgs | None = None,
        kwargs: WidgetKwargs | None = None,
        *,
        disabled: bool = False,
        label_visibility: LabelVisibility = "visible",
        width: WidthWithoutContent = "stretch",
        bind: BindOption = None,
        persist_state: PersistStateOption = None,
    ) -> tuple[SliderNumericT, SliderNumericT]: ...

    # If min-value is provided and a datelike type, and value (if provided)
    #   is a singular datelike, return the same datelike type.
    @overload
    def slider(
        self,
        label: str,
        min_value: SliderDatelikeT,
        max_value: SliderDatelikeT | None = None,
        value: SliderDatelikeT | None = None,
        step: StepDatelikeT | None = None,
        format: str | DateTimeFormat | None = None,
        key: Key | None = None,
        help: str | None = None,
        on_change: WidgetCallback | None = None,
        args: WidgetArgs | None = None,
        kwargs: WidgetKwargs | None = None,
        *,
        disabled: bool = False,
        label_visibility: LabelVisibility = "visible",
        width: WidthWithoutContent = "stretch",
        bind: BindOption = None,
        persist_state: PersistStateOption = None,
    ) -> SliderDatelikeT: ...

    # If max-value is provided and a datelike type, and value (if provided)
    #   is a singular datelike, return the same datelike type.
    @overload
    def slider(
        self,
        label: str,
        min_value: None = None,
        *,
        max_value: SliderDatelikeT,
        value: SliderDatelikeT | None = None,
        step: StepDatelikeT | None = None,
        format: str | DateTimeFormat | None = None,
        key: Key | None = None,
        help: str | None = None,
        on_change: WidgetCallback | None = None,
        args: WidgetArgs | None = None,
        kwargs: WidgetKwargs | None = None,
        disabled: bool = False,
        label_visibility: LabelVisibility = "visible",
        width: WidthWithoutContent = "stretch",
        bind: BindOption = None,
        persist_state: PersistStateOption = None,
    ) -> SliderDatelikeT: ...

    # If value is provided and a datelike type, return the same datelike type.
    @overload
    def slider(
        self,
        label: str,
        min_value: None = None,
        max_value: None = None,
        *,
        value: SliderDatelikeT,
        step: StepDatelikeT | None = None,
        format: str | DateTimeFormat | None = None,
        key: Key | None = None,
        help: str | None = None,
        on_change: WidgetCallback | None = None,
        args: WidgetArgs | None = None,
        kwargs: WidgetKwargs | None = None,
        disabled: bool = False,
        label_visibility: LabelVisibility = "visible",
        width: WidthWithoutContent = "stretch",
        bind: BindOption = None,
        persist_state: PersistStateOption = None,
    ) -> SliderDatelikeT: ...

    # If value is provided and a sequence of datelike type,
    #   return a tuple of the same datelike type.
    @overload
    def slider(
        self,
        label: str,
        min_value: SliderDatelikeT | None = None,
        max_value: SliderDatelikeT | None = None,
        *,
        value: list[SliderDatelikeT]
        | tuple[SliderDatelikeT]
        | tuple[SliderDatelikeT, SliderDatelikeT],
        step: StepDatelikeT | None = None,
        format: str | DateTimeFormat | None = None,
        key: Key | None = None,
        help: str | None = None,
        on_change: WidgetCallback | None = None,
        args: WidgetArgs | None = None,
        kwargs: WidgetKwargs | None = None,
        disabled: bool = False,
        label_visibility: LabelVisibility = "visible",
        width: WidthWithoutContent = "stretch",
        bind: BindOption = None,
        persist_state: PersistStateOption = None,
    ) -> tuple[SliderDatelikeT, SliderDatelikeT]: ...

    # If value is provided positionally and a sequence of datelike type,
    #   return a tuple of the same datelike type.
    @overload
    def slider(
        self,
        label: str,
        min_value: SliderDatelikeT,
        max_value: SliderDatelikeT,
        value: SliderDatelikeSpanT[SliderDatelikeT],
        /,
        step: StepDatelikeT | None = None,
        format: str | DateTimeFormat | None = None,
        key: Key | None = None,
        help: str | None = None,
        on_change: WidgetCallback | None = None,
        args: WidgetArgs | None = None,
        kwargs: WidgetKwargs | None = None,
        *,
        disabled: bool = False,
        label_visibility: LabelVisibility = "visible",
        width: WidthWithoutContent = "stretch",
        bind: BindOption = None,
        persist_state: PersistStateOption = None,
    ) -> tuple[SliderDatelikeT, SliderDatelikeT]: ...

    # https://github.com/python/mypy/issues/17614
    @gather_metrics("slider")  # type: ignore[misc]
    def slider(
        self,
        label: str,
        min_value: SliderScalar | None = None,
        max_value: SliderScalar | None = None,
        value: SliderValue | None = None,
        step: SliderStep | None = None,
        format: str | None = None,
        key: Key | None = None,
        help: str | None = None,
        on_change: WidgetCallback | None = None,
        args: WidgetArgs | None = None,
        kwargs: WidgetKwargs | None = None,
        *,  # keyword-only arguments:
        disabled: bool = False,
        label_visibility: LabelVisibility = "visible",
        width: WidthWithoutContent = "stretch",
        bind: BindOption = None,
        persist_state: PersistStateOption = None,
    ) -> Any:
        r"""Display a slider widget.

        This supports int, float, date, time, and datetime types.

        This also allows you to render a range slider by passing a two-element
        tuple or list as the ``value``.

        The difference between ``st.slider`` and ``st.select_slider`` is that
        ``slider`` only accepts numerical or date/time data and takes a range as
        input, while ``select_slider`` accepts any datatype and takes an iterable
        set of options.

        .. note::
            Integer values exceeding +/- ``(1<<53) - 1`` cannot be accurately
            stored or returned by the widget due to serialization constraints
            between the Python server and JavaScript client. You must handle
            such numbers as floats, leading to a loss in precision.

            The same constraint bounds ``date`` and ``datetime`` values, which
            are serialized as microseconds since the epoch: only values from
            1684-07-29 to 2255-06-04 can be represented exactly. Streamlit
            raises an error for a bound outside that range rather than
            silently shifting it.

        Parameters
        ----------
        label : str
            A short label explaining to the user what this slider is for.
            The label can optionally contain GitHub-flavored Markdown of the
            following types: Bold, Italics, Strikethroughs, Inline Code, Links,
            and Images. Images display like icons, with a max height equal to
            the font height.

            Unsupported Markdown elements are unwrapped so only their children
            (text contents) render. Common block-level Markdown (headings,
            lists, blockquotes) is automatically escaped and displays as
            literal text in labels.

            See the ``body`` parameter of |st.markdown|_ for additional,
            supported Markdown directives.

            For accessibility reasons, you should never set an empty label, but
            you can hide it with ``label_visibility`` if needed. In the future,
            we may disallow empty labels by raising an exception.

            .. |st.markdown| replace:: ``st.markdown``
            .. _st.markdown: https://docs.streamlit.io/develop/api-reference/text/st.markdown

        min_value : a supported type or None
            The minimum permitted value.
            If this is ``None`` (default), the minimum value depends on the
            type as follows:

            - integer: ``0``
            - float: ``0.0``
            - date or datetime: ``value - timedelta(days=14)``
            - time: ``time.min``

        max_value : a supported type or None
            The maximum permitted value.
            If this is ``None`` (default), the maximum value depends on the
            type as follows:

            - integer: ``100``
            - float: ``1.0``
            - date or datetime: ``value + timedelta(days=14)``
            - time: ``time.max``

        value : a supported type or a tuple/list of supported types or None
            The value of the slider when it first renders. If a tuple/list
            of two values is passed here, then a range slider with those lower
            and upper bounds is rendered. For example, if set to `(1, 10)` the
            slider will have a selectable range between 1 and 10.
            This defaults to ``min_value``. If the type is not otherwise
            specified in any of the numeric parameters, the widget will have an
            integer value.

        step : int, float, timedelta, or None
            The stepping interval.
            Defaults to 1 if the value is an int, 0.01 if a float,
            timedelta(days=1) if a date/datetime, timedelta(minutes=15) if a time
            (or if max_value - min_value < 1 day)

        format : str or None
            A printf-style format string or a predefined format name controlling
            how the interface should display values. This does not impact the
            return value.

            For integers and floats, you can use a printf-style format string
            or one of the following predefined formats:

            - ``"plain"``: Show the full number without formatting (e.g. ``1234.567``).
            - ``"localized"``: Show the number in the user's locale format (e.g. ``1,234.567``).
            - ``"percent"``: Show as a percentage (e.g. ``50%`` from ``0.5``).
            - ``"dollar"``: Show as US dollars (e.g. ``$1,234.57``).
            - ``"euro"``: Show as euros (e.g. ``€1,234.57``).
            - ``"yen"``: Show as Japanese yen (e.g. ``¥1,235``).
            - ``"compact"``: Show in compact notation (e.g. ``1.2K``).
            - ``"scientific"``: Show in scientific notation (e.g. ``1.235E3``).
            - ``"engineering"``: Show in engineering notation (e.g. ``1.235E3``).
            - ``"accounting"``: Show in accounting format with parentheses for negatives.
            - ``"bytes"``: Show in byte units (e.g. ``1.2KB``).

            For information about printf-style format strings, see
            `sprintf.js
            <https://github.com/alexei/sprintf.js?tab=readme-ov-file#format-specification>`_.
            For example, ``format="%0.1f"`` adjusts the displayed decimal
            precision to only show one digit after the decimal. Use ``,`` for
            thousand separators (e.g. ``"%,d"`` yields ``"1,234"``).

            For datetimes, dates, and times, you can use a momentJS format string
            or one of the following predefined formats:

            - ``"localized"``: Show in the user's locale format.
            - ``"distance"``: Show as relative time (e.g. ``"2 hours ago"``).
            - ``"calendar"``: Show as calendar time (e.g. ``"Tomorrow 12:00"``).
              Works best with datetime values. For date-only values, displays
              relative day names (e.g. ``"Yesterday"``). For time-only values,
              this format may produce unexpected results.
            - ``"iso8601"``: Show in ISO 8601 format.

            For information about momentJS format strings, see
            `momentJS <https://momentjs.com/docs/#/displaying/format/>`_.
            For example, ``format="ddd ha"`` adjusts the displayed datetime to
            show the day of the week and the hour ("Tue 8pm").

        key : str, int, or None
            An optional string or integer to use as the unique key for
            the widget. If this is ``None`` (default), a key will be
            generated for the widget based on the values of the other
            parameters. No two widgets may have the same key. Assigning
            a key stabilizes the widget's identity and preserves its
            state across reruns even when other parameters change.

            .. note::
               Changing ``min_value``, ``max_value``, or ``step`` resets
               the widget even when a key is provided, because those
               parameters constrain valid values.

            A key lets you read or update the widget's value via
            ``st.session_state[key]``. For more details, see `Widget
            behavior <https://docs.streamlit.io/develop/concepts/architecture/widget-behavior>`_.

            Additionally, if ``key`` is provided, it will be used as a
            CSS class name prefixed with ``st-key-``.

        help : str or None
            A tooltip that gets displayed next to the widget label. Streamlit
            only displays the tooltip when ``label_visibility="visible"``. If
            this is ``None`` (default), no tooltip is displayed.

            The tooltip can optionally contain GitHub-flavored Markdown,
            including the Markdown directives described in the ``body``
            parameter of ``st.markdown``.

        on_change : callable
            An optional callback invoked when this slider's value changes.

        args : list or tuple
            An optional list or tuple of args to pass to the callback.

        kwargs : dict
            An optional dict of kwargs to pass to the callback.

        disabled : bool
            An optional boolean that disables the slider if set to ``True``.
            The default is ``False``.

        label_visibility : "visible", "hidden", or "collapsed"
            The visibility of the label. The default is ``"visible"``. If this
            is ``"hidden"``, Streamlit displays an empty spacer instead of the
            label, which can help keep the widget aligned with other widgets.
            If this is ``"collapsed"``, Streamlit displays no label or spacer.

        width : "stretch" or int
            The width of the slider widget. This can be one of the
            following:

            - ``"stretch"`` (default): The width of the widget matches the
              width of the parent container.
            - An integer specifying the width in pixels: The widget has a
              fixed width. If the specified width is greater than the width of
              the parent container, the width of the widget matches the width
              of the parent container.

        bind : "query-params" or None
            Binding mode for syncing the widget's value with a URL query
            parameter. If this is ``None`` (default), the widget's value
            is not synced to the URL. When this is set to
            ``"query-params"``, changes to the widget update the URL, and
            the widget can be initialized or updated through a query
            parameter in the URL. This requires ``key`` to be set. The
            key is used as the query parameter name.

            When the widget's value equals its default, the query
            parameter is removed from the URL to keep it clean. A bound
            query parameter can't be set or deleted through
            ``st.query_params``; it can only be programmatically changed
            through ``st.session_state``.

            Invalid query parameter values are ignored and removed
            from the URL. Range sliders use repeated parameters (e.g.,
            ``?price=10&price=90``).

        persist_state : "page", "session", or None
            How long to preserve the widget's value when it isn't rendered.
            If this is ``None`` (default), the value is lost when the widget
            stops being rendered or the user switches pages. If this is
            ``"page"``, the value is preserved only while the user stays on the
            page where the widget is defined (for example, while the widget is
            conditionally hidden); it is discarded on a page switch and is not
            restored if the user returns to the page. If this is ``"session"``,
            the value is preserved for the entire session, including across
            page switches, so it returns when the user navigates back. This
            requires ``key`` to be set. If ``bind="query-params"`` is also set,
            the binding takes precedence: the value is stored in the URL, so it
            persists across page switches regardless of the ``persist_state``
            scope.

        Returns
        -------
        int/float/date/time/datetime or tuple of int/float/date/time/datetime
            The current value of the slider widget. The return type will match
            the data type of the value parameter.

        Examples
        --------
        >>> import streamlit as st
        >>>
        >>> age = st.slider("How old are you?", 0, 130, 25)
        >>> st.write("I'm ", age, "years old")

        And here's an example of a range slider:

        >>> import streamlit as st
        >>>
        >>> values = st.slider("Select a range of values", 0.0, 100.0, (25.0, 75.0))
        >>> st.write("Values:", values)

        This is a range time slider:

        >>> import streamlit as st
        >>> from datetime import time
        >>>
        >>> appointment = st.slider(
        ...     "Schedule your appointment:", value=(time(11, 30), time(12, 45))
        ... )
        >>> st.write("You're scheduled for:", appointment)

        Finally, a datetime slider:

        >>> import streamlit as st
        >>> from datetime import datetime
        >>>
        >>> start_time = st.slider(
        ...     "When do you start?",
        ...     value=datetime(2020, 1, 1, 9, 30),
        ...     format="MM/DD/YY - hh:mm",
        ... )
        >>> st.write("Start time:", start_time)

        .. output::
           https://doc-slider.streamlit.app/
           height: 300px

        """
        ctx = get_script_run_ctx()
        return self._slider(
            label=label,
            min_value=min_value,
            max_value=max_value,
            value=value,
            step=step,
            format=format,
            key=key,
            help=help,
            on_change=on_change,
            args=args,
            kwargs=kwargs,
            disabled=disabled,
            label_visibility=label_visibility,
            width=width,
            bind=bind,
            persist_state=persist_state,
            ctx=ctx,
        )

    def _slider(
        self,
        label: str,
        min_value: Any = None,
        max_value: Any = None,
        value: Any = None,
        step: Any = None,
        format: str | None = None,
        key: Key | None = None,
        help: str | None = None,
        on_change: WidgetCallback | None = None,
        args: WidgetArgs | None = None,
        kwargs: WidgetKwargs | None = None,
        *,  # keyword-only arguments:
        disabled: bool = False,
        label_visibility: LabelVisibility = "visible",
        width: WidthWithoutContent = "stretch",
        bind: BindOption = None,
        persist_state: PersistStateOption = None,
        ctx: ScriptRunContext | None = None,
    ) -> SliderReturn:
        key = to_key(key)

        check_widget_policies(
            self.dg,
            key,
            on_change,
            default_value=value,
        )
        maybe_raise_label_warnings(label, label_visibility)

        element_id = compute_and_register_element_id(
            "slider",
            user_key=key,
            # Treat the provided key as the main identity; only include
            # changes to the value-shaping arguments in the identity
            # computation as those can invalidate the current value.
            key_as_main_identity={"min_value", "max_value", "step"},
            dg=self.dg,
            label=label,
            min_value=min_value,
            max_value=max_value,
            value=value,
            step=step,
            format=format,
            help=help,
            width=width,
        )

        if value is None:
            # We need to know if this is a single or range slider, but don't have
            # a default value, so we check if session_state can tell us.
            # We already calculated the id, so there is no risk of this causing
            # the id to change.

            single_value = True

            session_state = get_session_state().filtered_state

            if key is not None and key in session_state:
                state_value = session_state[key]
                single_value = isinstance(state_value, tuple(SUPPORTED_TYPES.keys()))

            if single_value:
                value = min_value if min_value is not None else 0
            else:
                mn = min_value if min_value is not None else 0
                mx = max_value if max_value is not None else 100
                value = [mn, mx]

        # Ensure that the value is either a single value or a range of values.
        single_value = isinstance(value, tuple(SUPPORTED_TYPES.keys()))
        range_value = isinstance(value, (list, tuple)) and len(value) in {0, 1, 2}
        if not single_value and not range_value:
            raise StreamlitAPIException(
                "Slider value should either be an int/float/datetime or a list/tuple of "
                "0 to 2 ints/floats/datetimes"
            )

        # Simplify future logic by always making value a list
        prepared_value: Sequence[SliderScalar] = [value] if single_value else value  # ty: ignore[invalid-assignment]

        def value_to_generic_type(v: Any) -> SliderProto.DataType.ValueType:
            if isinstance(v, Integral):
                return SUPPORTED_TYPES[Integral]
            if isinstance(v, Real):
                return SUPPORTED_TYPES[Real]
            return SUPPORTED_TYPES[type(v)]

        def all_same_type(items: Any) -> bool:
            return len(set(map(value_to_generic_type, items))) < 2

        if not all_same_type(prepared_value):
            raise StreamlitAPIException(
                "Slider tuple/list components must be of the same type.\n"
                f"But were: {list(map(type, prepared_value))}"
            )

        data_type = (
            SliderProto.INT
            if len(prepared_value) == 0
            else value_to_generic_type(prepared_value[0])
        )

        # `date` rather than `datetime`, because the DATE path still holds plain
        # `date` values here -- they are not converted until further down.
        datetime_min: date | time = time.min
        datetime_max: date | time = time.max
        if data_type == SliderProto.TIME:
            prepared_value = cast("Sequence[time]", prepared_value)

            datetime_min = time.min.replace(tzinfo=prepared_value[0].tzinfo)
            datetime_max = time.max.replace(tzinfo=prepared_value[0].tzinfo)
        if data_type in {SliderProto.DATETIME, SliderProto.DATE}:
            prepared_value = cast("Sequence[datetime]", prepared_value)

            datetime_min, datetime_max = _window_around(
                prepared_value[0], timedelta(days=14)
            )

        defaults: Final[dict[SliderProto.DataType.ValueType, dict[str, Any]]] = {
            SliderProto.INT: {
                "min_value": 0,
                "max_value": 100,
                "step": 1,
                "format": "%d",
            },
            SliderProto.FLOAT: {
                "min_value": 0.0,
                "max_value": 1.0,
                "step": 0.01,
                "format": "%0.2f",
            },
            SliderProto.DATETIME: {
                "min_value": datetime_min,
                "max_value": datetime_max,
                "step": timedelta(days=1),
                "format": "YYYY-MM-DD",
            },
            SliderProto.DATE: {
                "min_value": datetime_min,
                "max_value": datetime_max,
                "step": timedelta(days=1),
                "format": "YYYY-MM-DD",
            },
            SliderProto.TIME: {
                "min_value": datetime_min,
                "max_value": datetime_max,
                "step": timedelta(minutes=15),
                "format": "HH:mm",
            },
        }

        if min_value is None:
            min_value = defaults[data_type]["min_value"]
        if max_value is None:
            max_value = defaults[data_type]["max_value"]
        if step is None:
            step = defaults[data_type]["step"]
            if data_type in {
                SliderProto.DATETIME,
                SliderProto.DATE,
            } and max_value - min_value < timedelta(days=1):
                step = timedelta(minutes=15)
        if format is None:
            format = cast("str", defaults[data_type]["format"])  # noqa: A001

        if step == 0:
            raise StreamlitAPIException(
                "Slider components cannot be passed a `step` of 0."
            )

        # Ensure that all arguments are of the same type.
        slider_args = [min_value, max_value, step]
        int_args = all(isinstance(a, Integral) for a in slider_args)
        float_args = all(
            isinstance(a, Real) and not isinstance(a, Integral) for a in slider_args
        )
        # When min and max_value are the same timelike, step should be a timedelta
        timelike_args = (
            data_type in TIMELIKE_TYPES
            and isinstance(step, timedelta)
            and type(min_value) is type(max_value)
        )

        if not int_args and not float_args and not timelike_args:
            msg = (
                "Slider value arguments must be of matching types."
                f"\n`min_value` has {type(min_value).__name__} type."
                f"\n`max_value` has {type(max_value).__name__} type."
                f"\n`step` has {type(step).__name__} type."
            )
            raise StreamlitAPIException(msg)

        # Ensure that the value matches arguments' types.
        all_ints = data_type == SliderProto.INT and int_args
        all_floats = data_type == SliderProto.FLOAT and float_args
        all_timelikes = data_type in TIMELIKE_TYPES and timelike_args

        if not all_ints and not all_floats and not all_timelikes:
            msg = (
                "Both value and arguments must be of the same type."
                f"\n`value` has {type(value).__name__} type."
                f"\n`min_value` has {type(min_value).__name__} type."
                f"\n`max_value` has {type(max_value).__name__} type."
            )
            raise StreamlitAPIException(msg)

        # Ensure that min <= value(s) <= max, adjusting the bounds as necessary.
        min_value = min(min_value, max_value)
        max_value = max(min_value, max_value)
        if len(prepared_value) == 1:
            min_value = min(prepared_value[0], min_value)
            max_value = max(prepared_value[0], max_value)
        elif len(prepared_value) == 2:
            start, end = prepared_value
            if start > end:  # type: ignore[operator] # ty: ignore[unsupported-operator]
                # Swap start and end, since they seem reversed
                start, end = end, start
                prepared_value = start, end
            min_value = min(start, min_value)
            max_value = max(end, max_value)
        else:
            # Empty list, so let's just use the outer bounds
            prepared_value = [min_value, max_value]

        # Bounds checks. JSNumber produces human-readable exceptions that
        # we simply re-package as StreamlitAPIExceptions.
        # (We check `min_value` and `max_value` here; `value` and `step` are
        # already known to be in the [min_value, max_value] range.)
        # Timelike bounds are checked further down instead, after the conversion to
        # microseconds -- that integer is what the frontend has to represent.
        try:
            if all_ints:
                JSNumber.validate_int_bounds(cast("int", min_value), "`min_value`")
                JSNumber.validate_int_bounds(cast("int", max_value), "`max_value`")
            elif all_floats:
                JSNumber.validate_float_bounds(cast("float", min_value), "`min_value`")
                JSNumber.validate_float_bounds(cast("float", max_value), "`max_value`")
        except JSNumberBoundsException as e:
            raise StreamlitAPIException(str(e))

        orig_tz = None
        # Convert dates or times into datetimes
        if data_type == SliderProto.TIME:
            prepared_value = cast("Sequence[time]", prepared_value)
            min_value = cast("time", min_value)
            max_value = cast("time", max_value)

            prepared_value = list(map(_time_to_datetime, prepared_value))
            min_value = _time_to_datetime(min_value)
            max_value = _time_to_datetime(max_value)

        if data_type == SliderProto.DATE:
            prepared_value = cast("Sequence[date]", prepared_value)
            min_value = cast("date", min_value)
            max_value = cast("date", max_value)

            prepared_value = list(map(_date_to_datetime, prepared_value))
            min_value = _date_to_datetime(min_value)
            max_value = _date_to_datetime(max_value)

        # The frontend will error if the values are equal, so checking here
        # lets us produce a nicer python error message and stack trace.
        if min_value == max_value:
            raise StreamlitAPIException(
                "Slider `min_value` must be less than the `max_value`."
                f"\nThe values were {min_value} and {max_value}."
            )

        # Now, convert to microseconds (so we can serialize datetime to a long)
        if data_type in TIMELIKE_TYPES:
            prepared_value = cast("Sequence[datetime]", prepared_value)
            min_value = cast("datetime", min_value)
            max_value = cast("datetime", max_value)
            step = cast("timedelta", step)

            # Restore times/datetimes to original timezone (dates are always naive)
            orig_tz = (
                prepared_value[0].tzinfo
                if data_type in {SliderProto.TIME, SliderProto.DATETIME}
                else None
            )

            prepared_value = list(map(_datetime_to_micros, prepared_value))
            min_value = _datetime_to_micros(min_value)
            max_value = _datetime_to_micros(max_value)
            step = _delta_to_micros(step)

            # Beyond MAX_SAFE_INTEGER microseconds the frontend's JavaScript number
            # cannot hold the value exactly, so the slider would silently round to a
            # different instant rather than fail.
            for name, micros in (
                ("`min_value`", min_value),
                ("`max_value`", max_value),
            ):
                if abs(micros) > JSNumber.MAX_SAFE_INTEGER:
                    raise StreamlitAPIException(
                        f"{name} is too far from 1970 for Streamlit to represent "
                        "exactly. Use a value between "
                        f"{_MIN_SAFE_DAY:%Y-%m-%d} and "
                        f"{_MAX_SAFE_DAY:%Y-%m-%d}."
                    )

        # At this point, prepared_value is expected to be a list of floats:
        prepared_value = cast("list[float]", prepared_value)

        # It would be great if we could guess the number of decimal places from
        # the `step` argument, but this would only be meaningful if step were a
        # decimal. As a possible improvement we could make this function accept
        # decimals and/or use some heuristics for floats.

        slider_proto = SliderProto()
        slider_proto.type = SliderProto.Type.SLIDER
        slider_proto.id = element_id
        slider_proto.label = label
        slider_proto.format = format
        slider_proto.default[:] = prepared_value
        slider_proto.min = cast("int | float", min_value)
        slider_proto.max = cast("int | float", max_value)
        slider_proto.step = cast("float", step)
        slider_proto.data_type = data_type
        slider_proto.options[:] = []
        slider_proto.form_id = current_form_id(self.dg)
        slider_proto.disabled = disabled
        slider_proto.label_visibility.value = get_label_visibility_proto_value(
            label_visibility
        )

        if help is not None:
            slider_proto.help = dedent(help)

        if bind and key:
            slider_proto.query_param_key = str(key)

        serde = SliderSerde(
            prepared_value,
            data_type,
            single_value,
            orig_tz,
            # Proto min/max are doubles (dates/times become microsecond floats).
            min_value=slider_proto.min,
            max_value=slider_proto.max,
        )

        widget_state = register_widget(
            slider_proto.id,
            on_change_handler=on_change,
            args=args,
            kwargs=kwargs,
            deserializer=serde.deserialize,
            serializer=serde.serialize,
            ctx=ctx,
            value_type="double_array_value",
            disabled=disabled,
            bind=bind,
            persist_state=persist_state,
            # Sliders always have a value (no empty/cleared state in the UI),
            # so disallow empty URL params (e.g., ?key=).
            clearable=False,
        )

        if widget_state.value_changed:
            # Min/Max bounds checks when the value is updated.
            serialized_values = serde.serialize(widget_state.value)
            slider_min = slider_proto.min
            slider_max = slider_proto.max
            if not isinstance(slider_min, (int, float)) or not isinstance(
                slider_max, (int, float)
            ):
                raise StreamlitAPIException("Slider bounds must be numeric.")
            for serialized_value in serialized_values:
                # Use the deserialized values for more readable error messages for dates/times
                deserialized_value = serde.deserialize_single_value(serialized_value)

                if serialized_value < slider_min:
                    raise StreamlitValueBelowMinError(
                        value=deserialized_value,
                        min_value=serde.deserialize_single_value(slider_min),
                    )
                if serialized_value > slider_max:
                    raise StreamlitValueAboveMaxError(
                        value=deserialized_value,
                        max_value=serde.deserialize_single_value(slider_max),
                    )

            slider_proto.value[:] = serialized_values
            slider_proto.set_value = True

        layout_config = create_layout_config(width=width)

        self.dg._enqueue(
            "slider",
            slider_proto,
            layout_config=layout_config,
            has_one_shot_effect=widget_state.value_changed,
        )
        return cast("SliderReturn", widget_state.value)

    @property
    def dg(self) -> DeltaGenerator:
        """The associated DeltaGenerator."""
        return cast("DeltaGenerator", self)
