# 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 datetime import date, timedelta
from typing import Literal, overload

from streamlit.errors import StreamlitAPIException, StreamlitBadTimeStringError


def adjust_years(input_date: date, years: int) -> date:
    """Add or subtract years from a date, clamping at ``date.min`` / ``date.max``.

    A target year outside 1-9999 saturates at the calendar endpoint
    (``0001-01-01`` / ``9999-12-31``) rather than raising.
    """
    target_year = input_date.year + years
    if target_year > date.max.year:
        return date.max
    if target_year < date.min.year:
        return date.min
    try:
        return input_date.replace(year=target_year)
    except ValueError as err:
        # Handle case for leap year date (February 29) that doesn't exist in the target year
        # by moving the date to February 28
        if input_date.month == 2 and input_date.day == 29:
            return input_date.replace(year=target_year, month=2, day=28)

        raise StreamlitAPIException(  # pragma: no cover - defensive
            f"Date {input_date} does not exist in the target year {target_year}. "
            "This should never happen. Please report this bug.",
            error_id="date-does-not-exist-in-year",
        ) from err


@overload
def time_to_seconds(
    t: float | timedelta | str | None, *, coerce_none_to_inf: Literal[False]
) -> float | None: ...


@overload
def time_to_seconds(t: float | timedelta | str | None) -> float: ...


def time_to_seconds(
    t: float | timedelta | str | None, *, coerce_none_to_inf: bool = True
) -> float | None:
    """Convert a time string value to a float representing "number of seconds"."""
    if coerce_none_to_inf and t is None:
        return math.inf
    if isinstance(t, timedelta):
        return t.total_seconds()
    if isinstance(t, str):
        import numpy as np
        import pandas as pd

        try:
            seconds: float = pd.Timedelta(t).total_seconds()

            if np.isnan(seconds):
                raise StreamlitBadTimeStringError(t)

            return seconds
        except ValueError as ex:
            raise StreamlitBadTimeStringError(t) from ex

    return t
