# Copyright 2026 Marimo. All rights reserved.
from __future__ import annotations

import os
from dataclasses import asdict, dataclass, field
from typing import Any

from marimo import _loggers
from marimo._config.config import ExportType, SqlOutputType, WidthType
from marimo._utils.env import env_to_value

LOGGER = _loggers.marimo_logger()


@dataclass
class _AppConfig:
    """Program-specific configuration.

    Configuration for frontends or runtimes that is specific to
    a single marimo program.
    """

    # NB. The addition of non-constant types here maybe have to be reflected in
    # marimo._ast.parse since these values are read statically.

    width: WidthType = "compact"

    app_title: str | None = None

    # The file path of the layout file, relative to the app file.
    layout_file: str | None = None

    # CSS file, relative to the app file
    css_file: str | None = None

    # HTML head file, relative to the app file
    html_head_file: str | None = None

    # Whether to automatically download the app as HTML and Markdown
    auto_download: list[ExportType] = field(default_factory=list)

    # The type of SQL output to display
    sql_output: SqlOutputType = "auto"

    @staticmethod
    def sanitize(updates: dict[str, Any]) -> dict[str, Any]:
        """Return only recognized config keys, silently dropping others."""
        return {k: v for k, v in updates.items() if hasattr(_AppConfig, k)}

    @staticmethod
    def from_untrusted_dict(
        updates: dict[str, Any], silent: bool = False
    ) -> _AppConfig:
        # Certain flags are useful to pass to App for construction, but
        # shouldn't make it into the config. (e.g. the _filename flag is
        # internal)
        other_allowed = {"_filename"}
        config = _AppConfig()
        for key in updates:
            if hasattr(config, key):
                config.__setattr__(key, updates[key])
            elif key not in other_allowed and not silent:
                LOGGER.warning(
                    f"Unrecognized key '{key}' in app config. Ignoring."
                )
        return config

    def asdict(self) -> dict[str, Any]:
        # Used for experimental hooks which start with _
        return {
            k: v for (k, v) in asdict(self).items() if not k.startswith("_")
        }

    def update(self, updates: dict[str, Any]) -> _AppConfig:
        config_dict = asdict(self)
        for key in updates:
            if key in config_dict:
                self.__setattr__(key, updates[key])

        return self

    def asdict_difference(self) -> dict[str, Any]:
        default_config = _AppConfig().asdict()
        updates = self.asdict()
        for key in default_config:
            if updates[key] == default_config[key]:
                updates.pop(key)
        return updates


def overloads_from_env() -> _AppConfig:
    """Return a dictionary of overloads from environment variables."""
    overloads: dict[str, Any] = {}
    prefix = "_MARIMO_APP_OVERLOAD_"
    for key in os.environ:
        if key.startswith(prefix):
            new_key = key[len(prefix) :].lower()
            value = env_to_value(key)
            if isinstance(value, tuple):
                overloads[new_key] = value[0]
    return _AppConfig.from_untrusted_dict(overloads, silent=True)
