"""Multi-file project output for ladder_to_pyrung_project().

Generates a Python project layout::

    main.py
    tags.py
    subroutines/
        __init__.py
        startup.py
        alarm_handler.py
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from pyrung.click.codegen.collector import _scan_file_refs
from pyrung.click.codegen.emitter import (
    _emit_plain_block_declarations,
    _emit_rung_sequence,
    _emit_structure_declarations,
    _emit_tag_declarations,
    _emit_tag_map,
    _is_trailing_return,
    _prescan_expr_funcs,
)
from pyrung.click.codegen.models import (
    _AnalyzedRung,
    _FileRefs,
    _OperandCollection,
    _SubroutineInfo,
)
from pyrung.click.codegen.utils import _build_sub_name_map, _slugify

if TYPE_CHECKING:
    from pyrung.click.tag_map import TagMap


_GENERATED_HEADER = "# Generated by pyrung from Click ladder CSV"


# ---------------------------------------------------------------------------
# Public entry point
# ---------------------------------------------------------------------------


def _generate_project(
    rungs: list[_AnalyzedRung],
    collection: _OperandCollection,
    nicknames: dict[str, str] | None,
    subroutines: list[_SubroutineInfo],
    *,
    structured_map: TagMap | None = None,
) -> dict[str, str]:
    """Generate a multi-file pyrung project.

    Returns ``{relative_path: content}`` — ready to write to disk or inspect.
    """
    # Pre-scan expression functions so imports are complete.
    _prescan_expr_funcs(rungs, collection, subroutines or None)

    sub_name_map = _build_sub_name_map(subroutines)
    # call_func_map: subroutine display name → Python function identifier
    call_func_map = sub_name_map

    files: dict[str, str] = {}

    # tags.py
    files["tags.py"] = _generate_tags_file(collection)

    # subroutines/
    if subroutines:
        files["subroutines/__init__.py"] = ""
        for sub in subroutines:
            func_name = sub_name_map[sub.name]
            files[f"subroutines/{func_name}.py"] = _generate_subroutine_file(
                sub,
                collection,
                nicknames,
                structured_map=structured_map,
                call_func_map=call_func_map,
            )

    # main.py
    files["main.py"] = _generate_main_file(
        rungs,
        collection,
        nicknames,
        subroutines,
        structured_map=structured_map,
        call_func_map=call_func_map,
    )

    # run.py
    files["run.py"] = _generate_run_file()

    # project_to_csv.py
    files["project_to_csv.py"] = _generate_export_file()

    return files


# ---------------------------------------------------------------------------
# run.py
# ---------------------------------------------------------------------------


def _generate_run_file() -> str:
    """Generate run.py: instantiate PLCRunner and step the logic."""
    return """\
from pyrung import PLCRunner
from main import logic

runner = PLCRunner(logic)
runner.step()
print(f"Scan {runner.current_state.scan_id}: OK")
"""


def _generate_export_file() -> str:
    """Generate project_to_csv.py: convert the pyrung project back to Click CSV."""
    return '''\
"""Export this pyrung project back to Click ladder CSV files."""

from pathlib import Path

from pyrung.click import pyrung_to_ladder
from main import logic
from tags import mapping

here = Path(__file__).parent
output_dir = here / "csv_output"
bundle = pyrung_to_ladder(logic, mapping)
bundle.write(output_dir)

mapping.to_nickname_file(output_dir / "nicknames.csv")

print(f"Wrote CSV to {output_dir}")
'''


# ---------------------------------------------------------------------------
# tags.py
# ---------------------------------------------------------------------------


def _generate_tags_file(collection: _OperandCollection) -> str:
    """Generate tags.py: declarations + TagMap."""
    lines: list[str] = [_GENERATED_HEADER, ""]

    # Imports
    _emit_tags_imports(lines, collection)
    lines.append("")

    # Tag declarations
    has_flat_tags = any(op not in collection.semantic_operands for op in collection.tags)
    if has_flat_tags:
        lines.append("# --- Tags ---")
        _emit_tag_declarations(lines, collection, suppress_comments=True)
        lines.append("")

    if collection.plain_blocks:
        lines.append("# --- Blocks ---")
        _emit_plain_block_declarations(lines, collection)
        lines.append("")

    # Structure declarations
    if collection.structures:
        lines.append("# --- Structures ---")
        _emit_structure_declarations(lines, collection)
        lines.append("")

    # TagMap
    lines.append("# --- Tag Map ---")
    _emit_tag_map(lines, collection)
    lines.append("")

    return "\n".join(lines) + "\n"


def _emit_tags_imports(lines: list[str], collection: _OperandCollection) -> None:
    """Emit imports for tags.py (types, blocks, TagMap)."""
    core: list[str] = []

    # Block/TagType for reconstructed plain named blocks
    if collection.plain_blocks:
        core.append("Block")
        core.append("TagType")

    # Structure decorators
    has_named_array = any(s.structure_type == "named_array" for s in collection.structures)
    has_udt = any(s.structure_type == "udt" for s in collection.structures)
    has_retentive = any(any(v for v in s.field_retentive.values()) for s in collection.structures)
    if has_named_array:
        core.append("named_array")
    if has_udt:
        core.append("udt")
    if has_retentive:
        core.append("Field")

    # Tag types
    for tt in sorted(collection.used_types):
        if tt not in core:
            core.append(tt)

    if core:
        lines.append(f"from pyrung import {', '.join(core)}")

    # Click imports
    click: list[str] = ["TagMap"]
    for bv in sorted(collection.used_blocks):
        click.append(bv)
    lines.append(f"from pyrung.click import {', '.join(click)}")

    # Expression functions
    if collection.used_expr_funcs:
        expr = sorted(collection.used_expr_funcs)
        lines.append(f"from pyrung.core.expression import {', '.join(expr)}")


# ---------------------------------------------------------------------------
# subroutines/<name>.py
# ---------------------------------------------------------------------------


def _generate_subroutine_file(
    sub: _SubroutineInfo,
    collection: _OperandCollection,
    nicknames: dict[str, str] | None,
    *,
    structured_map: TagMap | None = None,
    call_func_map: dict[str, str] | None = None,
) -> str:
    """Generate a single subroutine file with @subroutine decorator."""
    func_name = call_func_map[sub.name] if call_func_map else _slugify(sub.name)

    sub_rungs = sub.analyzed
    if sub_rungs and _is_trailing_return(sub_rungs[-1]):
        sub_rungs = sub_rungs[:-1]

    refs = _scan_file_refs(sub_rungs, collection, nicknames, call_func_map=call_func_map)

    lines: list[str] = [_GENERATED_HEADER, ""]

    # pyrung imports
    _emit_logic_imports(lines, refs, is_subroutine=True)

    # from tags import ...
    _emit_tags_import_line(lines, refs)

    # Cross-subroutine imports
    for cross_func in sorted(refs.subroutine_func_names):
        lines.append(f"from subroutines.{cross_func} import {cross_func}")

    lines.append("")
    lines.append("")

    # @subroutine("name") decorator + function
    lines.append(f'@subroutine("{sub.name}")')
    lines.append(f"def {func_name}():")
    if sub_rungs:
        _emit_rung_sequence(
            lines,
            sub_rungs,
            collection,
            nicknames,
            indent=1,
            structured_map=structured_map,
            call_func_map=call_func_map,
        )
    else:
        lines.append("    pass")
    lines.append("")

    return "\n".join(lines) + "\n"


# ---------------------------------------------------------------------------
# main.py
# ---------------------------------------------------------------------------


def _generate_main_file(
    rungs: list[_AnalyzedRung],
    collection: _OperandCollection,
    nicknames: dict[str, str] | None,
    subroutines: list[_SubroutineInfo],
    *,
    structured_map: TagMap | None = None,
    call_func_map: dict[str, str] | None = None,
) -> str:
    """Generate main.py: Program with main rungs + call() statements."""
    refs = _scan_file_refs(rungs, collection, nicknames, call_func_map=call_func_map)

    lines: list[str] = [_GENERATED_HEADER, ""]

    # pyrung imports
    has_calls = bool(subroutines)
    _emit_logic_imports(lines, refs, is_main=True, has_calls=has_calls)

    # from tags import mapping, ...
    refs_with_mapping = _FileRefs(
        tag_var_names=set(refs.tag_var_names),
        block_var_names=set(refs.block_var_names),
        range_var_names=set(refs.range_var_names),
        structure_names=set(refs.structure_names),
    )
    _emit_tags_import_line(lines, refs_with_mapping, include_mapping=True)

    # from subroutines.X import X
    if subroutines:
        for sub in subroutines:
            func_name = call_func_map[sub.name] if call_func_map else _slugify(sub.name)
            lines.append(f"from subroutines.{func_name} import {func_name}")

    lines.append("")

    # Program body (main rungs only, no subroutine definitions)
    lines.append("with Program() as logic:")
    if rungs:
        _emit_rung_sequence(
            lines,
            rungs,
            collection,
            nicknames,
            indent=1,
            structured_map=structured_map,
            call_func_map=call_func_map,
        )
    else:
        lines.append("    pass")
    lines.append("")

    return "\n".join(lines) + "\n"


# ---------------------------------------------------------------------------
# Shared import helpers
# ---------------------------------------------------------------------------


def _emit_logic_imports(
    lines: list[str],
    refs: _FileRefs,
    *,
    is_main: bool = False,
    is_subroutine: bool = False,
    has_calls: bool = False,
) -> None:
    """Emit ``from pyrung import ...`` for a logic file (main or subroutine)."""
    parts: list[str] = []

    if is_main:
        parts.append("Program")
    parts.append("Rung")

    if has_calls or refs.subroutine_func_names:
        parts.append("call")
    if is_subroutine:
        parts.append("subroutine")

    # Conditions
    if refs.has_any_of:
        parts.append("any_of")
    if refs.has_all_of:
        parts.append("all_of")
    for cw in sorted(refs.used_conditions):
        parts.append(cw)

    # Instructions
    _INSTR_MAP = {
        "out": "out",
        "latch": "latch",
        "reset": "reset",
        "copy": "copy",
        "blockcopy": "blockcopy",
        "fill": "fill",
        "math": "calc",
        "on_delay": "on_delay",
        "off_delay": "off_delay",
        "count_up": "count_up",
        "count_down": "count_down",
        "shift": "shift",
        "search": "search",
        "pack_bits": "pack_bits",
        "pack_words": "pack_words",
        "pack_text": "pack_text",
        "unpack_to_bits": "unpack_to_bits",
        "unpack_to_words": "unpack_to_words",
        "event_drum": "event_drum",
        "time_drum": "time_drum",
        "return": "return_early",
    }
    for instr_name in sorted(refs.used_instructions):
        import_name = _INSTR_MAP.get(instr_name)
        if import_name and import_name not in parts:
            parts.append(import_name)

    if refs.has_branch:
        parts.append("branch")
    if refs.has_comment:
        parts.append("comment")
    if refs.has_forloop:
        parts.append("forloop")

    # Time units
    for tu in sorted(refs.used_time_units):
        parts.append(tu)

    # Copy converters
    for cc in sorted(refs.used_copy_converters):
        parts.append(cc)

    lines.append(f"from pyrung import {', '.join(parts)}")

    # Click imports (only if needed)
    click_parts: list[str] = []
    for bv in sorted(refs.used_click_blocks):
        click_parts.append(bv)
    if refs.has_modbus_target:
        click_parts.append("ModbusTcpTarget")
    if refs.has_modbus_rtu_target:
        click_parts.append("ModbusRtuTarget")
    if refs.has_modbus_address:
        click_parts.append("ModbusAddress")
    if "send" in refs.used_instructions:
        click_parts.append("send")
    if "receive" in refs.used_instructions:
        click_parts.append("receive")
    if click_parts:
        lines.append(f"from pyrung.click import {', '.join(click_parts)}")

    # Expression functions
    if refs.used_expr_funcs:
        expr = sorted(refs.used_expr_funcs)
        lines.append(f"from pyrung.core.expression import {', '.join(expr)}")

    if refs.has_system_import:
        lines.append("from pyrung.core.system_points import system")


def _emit_tags_import_line(
    lines: list[str],
    refs: _FileRefs,
    *,
    include_mapping: bool = False,
) -> None:
    """Emit ``from tags import ...`` for the symbols this file references."""
    names: list[str] = []
    if include_mapping:
        names.append("mapping")
    names.extend(sorted(refs.tag_var_names))
    names.extend(sorted(refs.block_var_names))
    names.extend(sorted(refs.range_var_names))
    names.extend(sorted(refs.structure_names))

    if names:
        lines.append(f"from tags import {', '.join(names)}")
