# mypy: disable-error-code="return,var-annotated"
from datatailr.excel import Addin, Queue  # type: ignore[attr-defined]
import time

addin = Addin("Test Addin", "Functions to exercise Excel type mappings")

# -------------------------
# NUMBER (int/float)
# -------------------------


@addin.expose(description="Add two numbers - float", help="Returns a + b")
def num_add_float(a: float, b: float) -> float:
    return a + b


@addin.expose(description="Add two numbers - float", help="Returns a + b")
def add_optional(a: float, b: float = 3.14) -> float:
    return a + b


@addin.expose(description="Add two numbers - int", help="Returns a + b")
def num_add_int(a: int, b: int) -> int:
    return a + b


@addin.expose(description="Add two numbers - int", help="Returns a + b")
def num_add_int_optional(a: int, b: int = 1) -> int:
    return a + b


@addin.expose(
    description="Confirms type float",
    help="Returns 1 if the python type is float, 0 otherwise",
    volatile=True,
)
def float_confirm_type(a: float) -> int:
    return int(isinstance(a, float))


@addin.expose(
    description="Confirms type int",
    help="Returns 1 if the python type is int, 0 otherwise",
    volatile=True,
)
def int_confirm_type(a: int) -> int:
    return int(isinstance(a, int))


@addin.expose(
    description="Echos type, expects int", help="Echo type int", volatile=True
)
def int_echo_type(a: int) -> str:
    return type(a).__name__


@addin.expose(
    description="Echos type, expects float", help="Echo type float", volatile=True
)
def float_echo_type(a: float) -> str:
    return type(a).__name__


# Accept number, return number (identity)
@addin.expose(description="Number identity", help="Returns the same number you pass in")
def num_identity(x: float) -> float:
    return x


# Streaming number example (pushes an increasing value)
@addin.expose(
    description="Simple numeric ticker",
    help="Streams an increasing number",
    streaming=True,
)
def float_ticker(queue: Queue, start: float) -> float:
    value = float(start)
    while True:
        queue.push(value)
        value += 0.1
        time.sleep(1)


# Streaming number example (pushes an increasing value)
@addin.expose(
    description="Simple numeric ticker",
    help="Streams an increasing number",
    streaming=True,
)
def int_ticker(queue: Queue, start: int) -> int:
    value = float(start)
    while True:
        queue.push(value)
        value += 1
        time.sleep(1)


@addin.expose(
    description="Simple numeric ticker",
    help="Streams an increasing number",
    streaming=True,
)
def float_ticker_N(queue: Queue, start: float, N: int) -> float:
    value = float(start)
    for i in range(N):
        queue.push(value)
        value += 0.1
        time.sleep(1)


@addin.expose(
    description="Simple numeric ticker",
    help="Streams an increasing number",
    streaming=True,
)
def int_ticker_N(queue: Queue, start: int, N: int) -> int:
    value = float(start)
    for i in range(N):
        queue.push(value)
        value += 1
        time.sleep(1)


# -------------------------
# STRING
# -------------------------


@addin.expose(description="Echo string", help="Returns the same text you pass in")
def str_echo(s: str) -> str:
    return s


@addin.expose(description="Echo string", help="Returns the same text you pass in")
def str_echo_optional(s: str = "Optional text") -> str:
    return s


@addin.expose(description="Hello string", help="Returns a constant string")
def str_hello() -> str:
    return "Hello from Python"


@addin.expose(
    description="Confirms type str",
    help="Returns 1 if the python type is str, 0 otherwise",
)
def str_confirm_type(a: str) -> int:
    return int(isinstance(a, str))


@addin.expose(description="Echoes type, expects str", help="Echo type str")
def type_echo_str(x: str) -> str:
    return type(x).__name__


# -------------------------
# BOOLEAN
# -------------------------


@addin.expose(description="Negate", help="Returns NOT(flag)")
def bool_not(flag: bool) -> bool:
    return not flag


@addin.expose(description="Negate", help="Returns NOT(flag)")
def bool_not_optional(flag: bool = False) -> bool:
    return not flag


@addin.expose(description="Always TRUE", help="Returns TRUE")
def bool_true() -> bool:
    return True


@addin.expose(
    description="Confirms type bool",
    help="Returns 1 if the python type is bool, 0 otherwise",
)
def bool_confirm_type(a: bool) -> int:
    return int(isinstance(a, str))


@addin.expose(description="Echoes type, expects bool", help="Echo type bool")
def type_echo_bool(x: bool) -> str:
    return type(x).__name__


# -------------------------
# MATRIX (nested lists)
# -------------------------


@addin.expose(description="Adds 2 arrays", help="Give 2 arrays for component wise add")
def add_two_arrays(a: list, b: list) -> list:
    if len(a) != len(b):
        raise ValueError("Not equal length")
    result = []
    for i in range(len(a)):
        if len(a[i]) != len(b[i]):
            raise ValueError("Not equal length")
        result.append([])
        for j in range(len(a[i])):
            result[i].append(a[i][j] + b[i][j])
    return result


@addin.expose(description="Sum of matrix", help="Sums all numbers in a 2-D range")
def mat_sum(m: list) -> float:
    total = 0.0
    for i in range(len(m)):
        for j in range(len(m[i])):
            total += float(m[i][j])
    return total


@addin.expose(
    description="Echo numeric matrix", help="Returns the same 2-D numeric range"
)
def mat_echo(m: list) -> list:
    result = []
    for i in range(len(m)):
        result.append([])
        for j in range(len(m[i])):
            result[i].append(m[i][j])
    return result


@addin.expose(
    description="Generate sequence matrix",
    help="Returns a rows×cols matrix with values 1..(rows*cols)",
)
def mat_sequence(rows: int, cols: int) -> list:
    result = []
    val = 1.0
    for i in range(rows):
        result.append([])
        for j in range(cols):
            result[i].append(val)
            val += 1.0
    return result


@addin.expose(
    description="Uppercase string matrix",
    help="Uppercases every text cell in a 2-D range",
)
def mat_upper(m: list) -> list:
    result = []
    for i in range(len(m)):
        result.append([])
        for j in range(len(m[i])):
            cell = m[i][j]
            if isinstance(cell, str):
                result[i].append(cell.upper())
            else:
                result[i].append(cell)
    return result


@addin.expose(
    description="Constant string matrix",
    help="Fills a rows×cols area with the given text",
)
def mat_fill(rows: int, cols: int, value: str) -> list:
    result = []
    for i in range(rows):
        result.append([])
        for j in range(cols):
            result[i].append(value)
    return result


@addin.expose(
    description="Constant string matrix",
    help="Fills a rows×cols area with the given text",
)
def mat_fill_optional(rows: int, cols: int, value: str = "Optional value") -> list:
    result = []
    for i in range(rows):
        result.append([])
        for j in range(cols):
            result[i].append(value)
    return result


@addin.expose(
    description="All greater than",
    help="TRUE if every number in the matrix is > threshold",
)
def mat_all_gt(m: list, threshold: float) -> bool:
    for i in range(len(m)):
        for j in range(len(m[i])):
            if m[i][j] <= threshold:
                return False
    return True


@addin.expose(description="Echoes type, expects list", help="Echo type list")
def type_echo_list(x: list) -> str:
    return type(x).__name__


@addin.expose(
    description="Sum optional numeric list",
    help="Sums a provided list of numbers or uses a default list",
)
def list_sum_optional(values: list | None = None) -> float:
    numbers = values[0] if values is not None else [1, 2, 3, 4.5]
    return float(sum(float(n) for n in numbers))


@addin.expose(
    description="Describe optional mixed list",
    help="Returns type:value strings for a quirky default list",
)
def list_mixed_optional(items: list | None = None) -> list:
    actual_items = items[0] if items is not None else [1, "two", 3.0, True, None]
    return [[f"{type(item).__name__}:{item}" for item in actual_items]]


# -------------------------
# MIXED/UTILITY
# -------------------------


# Accept string and boolean, return string (conditional)
@addin.expose(
    description="Conditional greeting", help="Uppercases greeting if shout=TRUE"
)
def str_conditional_greeting(name: str, shout: bool) -> str:
    msg = f"Hello, {name}"
    return msg.upper() if shout else msg


@addin.expose(
    description="Conditional greeting", help="Uppercases greeting if shout=TRUE"
)
def str_conditional_greeting_optional(name: str, shout: bool = False) -> str:
    msg = f"Hello, {name}"
    return msg.upper() if shout else msg


def main(port=8080, ws_port=8000):
    addin.run(port, ws_port)


if __name__ == "__main__":
    main()
