"""ESP-IDF direct build generator for ESPHome."""

import json
from pathlib import Path

from esphome.components.esp32 import get_esp32_variant
from esphome.core import CORE
from esphome.helpers import mkdir_p, write_file_if_changed


def get_available_components() -> list[str] | None:
    """Get list of available ESP-IDF components from project_description.json.

    Returns only internal ESP-IDF components, excluding external/managed
    components (from idf_component.yml).
    """
    project_desc = Path(CORE.build_path) / "build" / "project_description.json"
    if not project_desc.exists():
        return None

    try:
        with open(project_desc, encoding="utf-8") as f:
            data = json.load(f)

        component_info = data.get("build_component_info", {})

        result = []
        for name, info in component_info.items():
            # Exclude our own src component
            if name == "src":
                continue

            # Exclude managed/external components
            comp_dir = info.get("dir", "")
            if "managed_components" in comp_dir:
                continue

            result.append(name)

        return result
    except (json.JSONDecodeError, OSError):
        return None


def has_discovered_components() -> bool:
    """Check if we have discovered components from a previous configure."""
    return get_available_components() is not None


def get_project_cmakelists() -> str:
    """Generate the top-level CMakeLists.txt for ESP-IDF project."""
    # Get IDF target from ESP32 variant (e.g., ESP32S3 -> esp32s3)
    variant = get_esp32_variant()
    idf_target = variant.lower().replace("-", "")

    # Extract compile definitions from build flags (-DXXX -> XXX)
    compile_defs = [flag for flag in CORE.build_flags if flag.startswith("-D")]
    extra_compile_options = "\n".join(
        f'idf_build_set_property(COMPILE_OPTIONS "{compile_def}" APPEND)'
        for compile_def in compile_defs
    )

    return f"""\
# Auto-generated by ESPHome
cmake_minimum_required(VERSION 3.16)

set(IDF_TARGET {idf_target})
set(EXTRA_COMPONENT_DIRS ${{CMAKE_SOURCE_DIR}}/src)

include($ENV{{IDF_PATH}}/tools/cmake/project.cmake)

{extra_compile_options}

project({CORE.name})
"""


def get_component_cmakelists(minimal: bool = False) -> str:
    """Generate the main component CMakeLists.txt."""
    idf_requires = [] if minimal else (get_available_components() or [])
    requires_str = " ".join(idf_requires)

    # Extract compile options (-W flags, excluding linker flags)
    compile_opts = [
        flag
        for flag in CORE.build_flags
        if flag.startswith("-W") and not flag.startswith("-Wl,")
    ]
    compile_opts_str = "\n    ".join(sorted(compile_opts)) if compile_opts else ""

    # Extract linker options (-Wl, flags)
    link_opts = [flag for flag in CORE.build_flags if flag.startswith("-Wl,")]
    link_opts_str = "\n    ".join(sorted(link_opts)) if link_opts else ""

    return f"""\
# Auto-generated by ESPHome
file(GLOB_RECURSE app_sources
    "${{CMAKE_CURRENT_SOURCE_DIR}}/*.cpp"
    "${{CMAKE_CURRENT_SOURCE_DIR}}/*.c"
    "${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cpp"
    "${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.c"
)

idf_component_register(
    SRCS ${{app_sources}}
    INCLUDE_DIRS "." "esphome"
    REQUIRES {requires_str}
)

# Apply C++ standard
target_compile_features(${{COMPONENT_LIB}} PUBLIC cxx_std_20)

# ESPHome compile options
target_compile_options(${{COMPONENT_LIB}} PUBLIC
    {compile_opts_str}
)

# ESPHome linker options
target_link_options(${{COMPONENT_LIB}} PUBLIC
    {link_opts_str}
)
"""


def write_project(minimal: bool = False) -> None:
    """Write ESP-IDF project files."""
    mkdir_p(CORE.build_path)
    mkdir_p(CORE.relative_src_path())

    # Write top-level CMakeLists.txt
    write_file_if_changed(
        CORE.relative_build_path("CMakeLists.txt"),
        get_project_cmakelists(),
    )

    # Write component CMakeLists.txt in src/
    write_file_if_changed(
        CORE.relative_src_path("CMakeLists.txt"),
        get_component_cmakelists(minimal=minimal),
    )
