"""Production-quality Wireshark Lua dissector generation.

This module generates idiomatic Lua dissectors for Wireshark from inferred protocol
message schemas, supporting:

- Smart field naming from context and patterns
- Enum detection and value_string tables
- Nested field hierarchies and subtrees
- Expert info for validation (checksums, reserved fields)
- Clean, documented Lua code following Wireshark best practices

Requirements addressed: Protocol Export, Wireshark Integration

Example:
    >>> from oscura.inference.message_format import infer_format
    >>> from oscura.exporters.wireshark import generate_dissector
    >>> messages = [b'\\x01\\x00\\x05Hello', b'\\x02\\x00\\x05World']
    >>> schema = infer_format(messages)
    >>> lua_code = generate_dissector(schema, protocol_name="custom")
    >>> with open("custom.lua", "w") as f:
    ...     f.write(lua_code)

References:
    Wireshark Lua API: https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Proto.html
    Wireshark dissector best practices
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from oscura.inference.message_format import InferredField, MessageSchema

# Version should come from package metadata
_OSCURA_VERSION = "0.9.0"


def generate_dissector(
    schema: MessageSchema,
    protocol_name: str = "unknown",
    protocol_description: str | None = None,
    author: str = "Oscura Framework",
    *,
    include_expert_info: bool = True,
    detect_enums: bool = True,
    enum_threshold: int = 10,
    add_comments: bool = True,
) -> str:
    """Generate production-quality Wireshark Lua dissector from message schema.

    Creates idiomatic Lua code with:
    - Smart field naming based on field types and patterns
    - Enum detection and value_string tables with semantic labels
    - Expert info for checksums, reserved fields, and validation
    - Proper indentation and comments
    - Protocol metadata (author, version, description)
    - Best practices for Wireshark dissectors

    Args:
        schema: Inferred message schema from message_format.infer_format()
        protocol_name: Short protocol name (e.g., "uart", "custom_proto")
        protocol_description: Human-readable description (default: auto-generated)
        author: Dissector author name
        include_expert_info: Add expert info for validation (default: True)
        detect_enums: Detect enum fields from value sets (default: True)
        enum_threshold: Max unique values to treat as enum (default: 10)
        add_comments: Add inline comments for clarity (default: True)

    Returns:
        Complete Lua dissector code ready for Wireshark

    Example:
        >>> from oscura.inference.message_format import infer_format
        >>> messages = [b'\\xAA\\x01\\x00\\x05DATA1', b'\\xAA\\x02\\x00\\x05DATA2']
        >>> schema = infer_format(messages)
        >>> lua = generate_dissector(schema, "custom", "Custom Protocol")
        >>> print(lua[:100])
        -- Auto-generated Wireshark dissector for Custom Protocol
        -- Generated by Oscura v0.9.0
    """
    if protocol_description is None:
        protocol_description = f"{protocol_name.upper()} Protocol"

    # Detect enums if requested
    enum_fields = {}
    if detect_enums:
        enum_fields = _detect_enum_fields(schema, enum_threshold)

    # Generate smart field names with enhanced context
    field_names = _generate_smart_field_names(schema, enum_fields)

    # Build Lua code sections
    header = _generate_header(protocol_name, protocol_description, author)
    proto_declaration = _generate_proto_declaration(protocol_name, protocol_description)
    value_strings = _generate_value_strings(enum_fields, field_names, protocol_name, schema)
    field_definitions = _generate_field_definitions(
        schema, field_names, protocol_name, enum_fields, add_comments
    )
    dissector_function = _generate_dissector_function(
        schema,
        field_names,
        protocol_name,
        enum_fields,
        include_expert_info,
        add_comments,
    )
    registration = _generate_registration(protocol_name)

    # Combine all sections
    sections = [
        header,
        proto_declaration,
        value_strings,
        field_definitions,
        dissector_function,
        registration,
    ]
    return "\n\n".join(s for s in sections if s)


def _generate_header(protocol_name: str, protocol_description: str, author: str) -> str:
    """Generate Lua file header with metadata and installation instructions."""
    safe_name = protocol_name.lower().replace("-", "_").replace(" ", "_")
    return f"""-- Auto-generated Wireshark dissector for {protocol_description}
-- Generated by Oscura v{_OSCURA_VERSION}
-- Author: {author}
--
-- This dissector was automatically generated from inferred protocol structure.
-- It includes smart field naming, enum detection, and validation checks.
--
-- Installation:
--   1. Copy this file to your Wireshark plugins directory:
--      - Windows: %APPDATA%\\Wireshark\\plugins
--      - Linux: ~/.local/lib/wireshark/plugins
--      - macOS: ~/.config/wireshark/plugins
--   2. Restart Wireshark or reload Lua plugins (Ctrl+Shift+L)
--   3. Protocol will appear as "{protocol_name.upper()}" in packet list
--
-- Usage:
--   - For UDP traffic: DissectorTable.get("udp.port"):add(PORT_NUM, {safe_name}_proto)
--   - For TCP traffic: DissectorTable.get("tcp.port"):add(PORT_NUM, {safe_name}_proto)
--   - Currently registered as postdissector (processes all packets)"""


def _generate_proto_declaration(protocol_name: str, protocol_description: str) -> str:
    """Generate protocol object declaration with safe naming."""
    safe_name = protocol_name.lower().replace("-", "_").replace(" ", "_")
    return f"""-- Protocol declaration
local {safe_name}_proto = Proto("{safe_name}", "{protocol_description}")"""


def _generate_value_strings(
    enum_fields: dict[str, dict[int, str]],
    field_names: dict[int, str],
    protocol_name: str,
    schema: MessageSchema,
) -> str:
    """Generate value_string tables for enum fields with semantic labels."""
    if not enum_fields:
        return ""

    safe_name = protocol_name.lower().replace("-", "_").replace(" ", "_")
    lines = ["-- Value string tables for enum fields"]

    for field_idx_str, value_map in enum_fields.items():
        field_idx = int(field_idx_str)
        field_name = field_names[field_idx]
        vs_name = f"{safe_name}_{field_name}_vals"

        # Build value string entries with better formatting
        entries = []
        for value, label in sorted(value_map.items()):
            # Escape quotes in labels
            safe_label = label.replace('"', '\\"')
            entries.append(f'    [{value}] = "{safe_label}",')

        lines.append(f"local {vs_name} = {{")
        lines.extend(entries)
        lines.append("}")

    return "\n".join(lines)


def _generate_field_definitions(
    schema: MessageSchema,
    field_names: dict[int, str],
    protocol_name: str,
    enum_fields: dict[str, dict[int, str]],
    add_comments: bool,
) -> str:
    """Generate ProtoField definitions with enhanced descriptions."""
    safe_name = protocol_name.lower().replace("-", "_").replace(" ", "_")
    lines = ["-- Field definitions"]
    field_vars = []

    for idx, field in enumerate(schema.fields):
        field_name = field_names[idx]
        var_name = f"f_{field_name}"
        field_vars.append(var_name)

        fqn = f"{safe_name}.{field_name}"  # Fully qualified name
        label = _generate_field_label(field, field_name)
        base, field_type = _get_field_type_and_base(field, str(idx) in enum_fields)

        # Add inline comment for field context
        if add_comments:
            comment = f"  -- {field.field_type} at offset {field.offset}"
            if field.confidence < 0.8:
                comment += f" (confidence: {field.confidence:.2f})"
        else:
            comment = ""

        # Add value_string for enums
        vs_suffix = ""
        if str(idx) in enum_fields:
            vs_name = f"{safe_name}_{field_name}_vals"
            vs_suffix = f", {vs_name}"

        lines.append(
            f'local {var_name} = ProtoField.{field_type}("{fqn}", "{label}", '
            f"{base}{vs_suffix}){comment}"
        )

    # Register fields with protocol
    lines.append("")
    lines.append(f"{safe_name}_proto.fields = {{{', '.join(field_vars)}}}")

    return "\n".join(lines)


def _generate_dissector_function(
    schema: MessageSchema,
    field_names: dict[int, str],
    protocol_name: str,
    enum_fields: dict[str, dict[int, str]],
    include_expert_info: bool,
    add_comments: bool,
) -> str:
    """Generate main dissector function with enhanced validation."""
    safe_name = protocol_name.lower().replace("-", "_").replace(" ", "_")

    lines = [
        "-- Dissector function",
        f"function {safe_name}_proto.dissector(buffer, pinfo, tree)",
        "    local pkt_len = buffer:len()",
        f"    if pkt_len < {schema.total_size} then",
        "        return 0  -- Not enough data for minimum message size",
        "    end",
        "",
        "    -- Set protocol column",
        f'    pinfo.cols.protocol = "{protocol_name.upper()}"',
        "",
    ]

    # Add info column with dynamic content if we have identifiable fields
    info_parts = _generate_info_column_content(schema, field_names)
    if info_parts:
        lines.append("    -- Set info column with message details")
        lines.append(f'    local info = "{protocol_name.upper()}"')
        for part in info_parts:
            lines.append(f"    {part}")
        lines.append("    pinfo.cols.info = info")
        lines.append("")

    lines.extend(
        [
            "    -- Create protocol tree",
            f"    local subtree = tree:add({safe_name}_proto, buffer(), "
            f'"{protocol_name.upper()} Message")',
            "",
        ]
    )

    # Add fields to tree with enhanced structure
    for idx, field in enumerate(schema.fields):
        field_name = field_names[idx]
        var_name = f"f_{field_name}"
        offset = field.offset
        size = field.size

        # Add section comment for field groups
        if add_comments and _is_field_group_boundary(idx, schema):
            group_name = _get_field_group_name(idx, schema)
            lines.append(f"    -- {group_name}")

        # Add field to tree
        field_tree_var = f"field_{idx}_tree"
        if add_comments:
            lines.append(
                f"    -- {field.field_type.upper()}: {field_name} (offset={offset}, size={size})"
            )

        # For multi-byte fields, use little-endian by default (most common)
        if size > 1 and field.field_type not in ["data", "checksum"]:
            endian_comment = "  -- Little-endian" if add_comments else ""
            lines.append(
                f"    local {field_tree_var} = subtree:add_le({var_name}, "
                f"buffer({offset}, {size})){endian_comment}"
            )
        else:
            lines.append(
                f"    local {field_tree_var} = subtree:add({var_name}, buffer({offset}, {size}))"
            )

        # Add expert info for special fields
        if include_expert_info:
            expert_lines = _generate_expert_info(field, field_name, offset, size, idx, enum_fields)
            if expert_lines:
                lines.extend(f"    {line}" for line in expert_lines)

        lines.append("")

    lines.append(f"    return {schema.total_size}")
    lines.append("end")

    return "\n".join(lines)


def _generate_info_column_content(schema: MessageSchema, field_names: dict[int, str]) -> list[str]:
    """Generate code to populate info column with message details.

    Args:
        schema: Message schema
        field_names: Field name mapping

    Returns:
        List of Lua code lines to build info string
    """
    parts = []

    # Look for counter or sequence number
    for field in schema.fields:
        if field.field_type == "counter" and field.size <= 2:
            parts.append(f'info = info .. " Seq=" .. buffer({field.offset}, {field.size}):uint()')
            break

    # Look for length field
    for field in schema.fields:
        if field.field_type == "length" and field.size <= 2:
            parts.append(f'info = info .. " Len=" .. buffer({field.offset}, {field.size}):uint()')
            break

    return parts


def _is_field_group_boundary(idx: int, schema: MessageSchema) -> bool:
    """Check if field is at a logical group boundary (e.g., header->payload)."""
    if idx == 0:
        return True
    field = schema.fields[idx]
    prev_field = schema.fields[idx - 1]

    # Header to payload transition
    if prev_field.offset < schema.header_size <= field.offset:
        return True

    # Type changes
    return prev_field.field_type != field.field_type


def _get_field_group_name(idx: int, schema: MessageSchema) -> str:
    """Get name for field group at given index."""
    field = schema.fields[idx]

    if field.offset < schema.header_size:
        return "Header Fields"
    elif field.field_type == "checksum":
        return "Checksum/Validation"
    elif field.field_type == "data":
        return "Payload Data"
    else:
        return f"{field.field_type.title()} Fields"


def _generate_expert_info(
    field: InferredField,
    field_name: str,
    offset: int,
    size: int,
    field_idx: int,
    enum_fields: dict[str, dict[int, str]],
) -> list[str]:
    """Generate expert info annotations for field validation."""
    lines = []
    field_tree_var = f"field_{field_idx}_tree"

    # Checksum validation placeholder with more detailed instructions
    if field.field_type == "checksum":
        lines.append("")
        lines.append("-- TODO: Implement checksum validation")
        lines.append("-- Steps:")
        lines.append("--   1. Determine checksum algorithm (CRC16, CRC32, etc.)")
        lines.append("--   2. Calculate checksum over appropriate data range")
        lines.append("--   3. Compare with field value")
        lines.append("--   4. Uncomment validation code below")
        lines.append("--")
        lines.append("-- local calculated_checksum = calculate_checksum(buffer, ...)")
        lines.append(f"-- local expected_checksum = buffer({offset}, {size}):uint()")
        lines.append("-- if calculated_checksum ~= expected_checksum then")
        lines.append(
            f'--     {field_tree_var}:add_expert_info(PI_CHECKSUM, PI_ERROR, "Invalid checksum")'
        )
        lines.append("-- end")

    # Reserved field warnings with value display
    elif field.field_type == "reserved":
        lines.append(f"local {field_name}_value = buffer({offset}, {size}):uint()")
        lines.append(f"if {field_name}_value ~= 0 then")
        lines.append(
            f"    {field_tree_var}:add_expert_info(PI_PROTOCOL, PI_WARN, "
            f'"Reserved field has non-zero value: 0x" .. '
            f'string.format("%0{size * 2}X", {field_name}_value))'
        )
        lines.append("end")

    # Enum value validation with detailed error messages
    elif str(field_idx) in enum_fields:
        value_map = enum_fields[str(field_idx)]
        valid_values = sorted(value_map.keys())
        lines.append(f"local {field_name}_value = buffer({offset}, {size}):uint()")

        # More efficient validation using Lua table lookup
        lines.append("local valid_values = {")
        for v in valid_values:
            lines.append(f"    [{v}] = true,")
        lines.append("}")

        lines.append(f"if not valid_values[{field_name}_value] then")
        lines.append(
            f"    {field_tree_var}:add_expert_info(PI_MALFORMED, PI_WARN, "
            f'"Unexpected {field_name} value: 0x" .. '
            f'string.format("%0{size * 2}X", {field_name}_value) .. '
            f'" (valid: {", ".join(f"0x{v:X}" for v in valid_values)}")")'
        )
        lines.append("end")

    # Length field validation
    elif field.field_type == "length":
        lines.append(f"local {field_name}_value = buffer({offset}, {size}):uint()")
        lines.append(f"if {field_name}_value > buffer:len() then")
        lines.append(
            f"    {field_tree_var}:add_expert_info(PI_MALFORMED, PI_ERROR, "
            f'"Length field exceeds packet size")'
        )
        lines.append("end")

    return lines


def _generate_registration(protocol_name: str) -> str:
    """Generate protocol registration code with usage examples."""
    safe_name = protocol_name.lower().replace("-", "_").replace(" ", "_")
    return f"""-- Protocol registration
--
-- By default, registered as a postdissector (processes all packets).
-- To register for specific ports/protocols, use one of:
--
-- UDP port:
--   DissectorTable.get("udp.port"):add(12345, {safe_name}_proto)
--
-- TCP port:
--   DissectorTable.get("tcp.port"):add(12345, {safe_name}_proto)
--
-- Heuristic dissector (auto-detect based on packet content):
--   function {safe_name}_proto.heuristic_checker(buffer, pinfo, tree)
--       -- Check for protocol signature/magic bytes
--       if buffer:len() < 4 then return false end
--       -- Add your detection logic here
--       -- if buffer(0, 2):uint() == 0xAABB then
--       --     {safe_name}_proto.dissector(buffer, pinfo, tree)
--       --     return true
--       -- end
--       return false
--   end
--   {safe_name}_proto:register_heuristic("udp", {safe_name}_proto.heuristic_checker)

register_postdissector({safe_name}_proto)"""


def _generate_smart_field_names(
    schema: MessageSchema, enum_fields: dict[str, dict[int, str]]
) -> dict[int, str]:
    """Generate semantic field names based on field types, positions, and patterns.

    Enhanced naming that considers:
    - Field type and characteristics
    - Position in message (header vs payload)
    - Relationships to other fields
    - Enum detection results
    - Common protocol patterns

    Args:
        schema: Message schema with inferred fields
        enum_fields: Detected enum fields

    Returns:
        Dictionary mapping field index to smart name
    """
    field_names = {}
    type_counters: dict[str, int] = {}

    for idx, field in enumerate(schema.fields):
        field_type = field.field_type

        # Special naming for specific field types with enhanced heuristics
        if field_type == "constant":
            # Check if this looks like a magic/sync byte or protocol version
            if idx == 0 and field.offset == 0:
                if field.size == 1:
                    name = "sync_byte"
                elif field.size == 2:
                    name = "magic"
                else:
                    name = "protocol_signature"
            elif field.offset < schema.header_size and field.size == 1:
                # Could be protocol version or flags
                if field.entropy < 0.5:  # Very low entropy suggests version
                    name = "version"
                else:
                    name = "flags"
            else:
                type_counters[field_type] = type_counters.get(field_type, 0) + 1
                name = f"constant_{type_counters[field_type]}"

        elif field_type == "counter":
            type_counters[field_type] = type_counters.get(field_type, 0) + 1
            if type_counters[field_type] == 1:
                # First counter is usually sequence number
                name = "sequence_num"
            elif field.offset < schema.header_size:
                name = "msg_counter"
            else:
                name = f"counter_{type_counters[field_type]}"

        elif field_type == "checksum":
            # Name by size and position
            if field.offset + field.size == schema.total_size:
                # Checksum at end of message
                suffix = "trailer"
            elif field.offset < schema.header_size:
                suffix = "header"
            else:
                suffix = "payload"

            if field.size == 1:
                name = f"checksum_{suffix}"
            elif field.size == 2:
                name = f"crc16_{suffix}"
            elif field.size == 4:
                name = f"crc32_{suffix}"
            else:
                name = f"checksum_{field.size}b"

        elif field_type == "length":
            type_counters[field_type] = type_counters.get(field_type, 0) + 1
            if type_counters[field_type] == 1:
                if field.offset < 4:  # Early in message
                    name = "msg_length"
                else:
                    name = "payload_length"
            else:
                name = f"length_{type_counters[field_type]}"

        elif field_type == "timestamp":
            type_counters[field_type] = type_counters.get(field_type, 0) + 1
            if field.size == 4:
                name = "timestamp_sec" if type_counters[field_type] == 1 else "timestamp"
            elif field.size == 8:
                name = "timestamp_usec" if type_counters[field_type] == 1 else "timestamp"
            else:
                name = f"timestamp_{type_counters[field_type]}"

        elif field_type == "data":
            # Distinguish header vs payload data
            if field.offset < schema.header_size:
                type_counters["header_data"] = type_counters.get("header_data", 0) + 1
                if type_counters["header_data"] == 1:
                    name = "header_data"
                else:
                    name = f"header_data_{type_counters['header_data']}"
            else:
                # Check if this is the main payload
                remaining_fields = len(schema.fields) - idx - 1
                if remaining_fields <= 1:  # Last or second-to-last field
                    name = "payload"
                else:
                    type_counters["payload"] = type_counters.get("payload", 0) + 1
                    name = f"payload_{type_counters['payload']}"

        elif field_type == "enum":
            # Check if this might be a specific enum type
            if str(idx) in enum_fields:
                values = list(enum_fields[str(idx)].keys())
                if all(v < 256 for v in values):  # Single byte enum
                    if field.offset < 4:
                        name = "msg_type"
                    else:
                        name = "status_code"
                else:
                    name = "enum_field"
            else:
                type_counters[field_type] = type_counters.get(field_type, 0) + 1
                name = f"enum_{type_counters[field_type]}"

        elif field_type == "reserved":
            type_counters[field_type] = type_counters.get(field_type, 0) + 1
            name = f"reserved_{type_counters[field_type]}"

        elif field_type == "float":
            type_counters[field_type] = type_counters.get(field_type, 0) + 1
            if field.size == 4:
                name = f"float32_{type_counters[field_type]}"
            elif field.size == 8:
                name = f"float64_{type_counters[field_type]}"
            else:
                name = f"float_{type_counters[field_type]}"

        else:  # unknown
            name = f"field_{idx}"

        field_names[idx] = name

    return field_names


def _generate_field_label(field: InferredField, field_name: str) -> str:
    """Generate human-readable field label with enhanced context.

    Args:
        field: Inferred field object
        field_name: Generated field name

    Returns:
        Human-readable label for Wireshark UI
    """
    # Start with field name formatted nicely
    base_label = field_name.replace("_", " ").title()

    # Add size information
    size_str = f"{field.size} byte" if field.size == 1 else f"{field.size} bytes"

    # Add type hint if name doesn't make it obvious
    if field.field_type not in field_name.lower():
        type_hint = f" ({field.field_type})"
    else:
        type_hint = ""

    return f"{base_label} [{size_str}]{type_hint}"


def _get_field_type_and_base(field: InferredField, is_enum: bool) -> tuple[str, str]:
    """Determine Lua ProtoField type and base display format.

    Returns:
        Tuple of (base_format, field_type)
        e.g., ("base.HEX", "uint16") or ("base.DEC", "uint8")
    """
    # Determine field type
    if field.size == 1:
        lua_type = "uint8"
    elif field.size == 2:
        lua_type = "uint16"
    elif field.size == 4:
        lua_type = "uint32"
    elif field.size == 8:
        lua_type = "uint64"
    else:
        # Use bytes for larger fields
        return ("base.NONE", "bytes")

    # Determine base display format
    if field.field_type in ["checksum", "data"]:
        base = "base.HEX"
    elif field.field_type in ["counter", "length", "timestamp"] or is_enum:
        base = "base.DEC"
    elif field.field_type == "constant":
        base = "base.HEX"
    else:
        base = "base.DEC"

    return (base, lua_type)


def _detect_enum_fields(schema: MessageSchema, max_unique: int) -> dict[str, dict[int, str]]:
    """Detect enum fields from limited value sets with enhanced labeling.

    Args:
        schema: Message schema with inferred fields
        max_unique: Maximum unique values to treat as enum

    Returns:
        Dictionary mapping field index (as string) to {value: label} mapping
    """
    enum_fields: dict[str, dict[int, str]] = {}

    for idx, field in enumerate(schema.fields):
        # Only consider fields with sample values
        if not field.values_seen:
            continue

        # Skip non-integer fields (tuples)
        if isinstance(field.values_seen[0], tuple):
            continue

        # Check if value set is small enough to be enum
        unique_values = set(field.values_seen)
        if len(unique_values) <= max_unique and len(unique_values) > 1:
            # Generate semantic enum labels
            value_map = _generate_enum_labels(field, list(unique_values), idx, schema)
            enum_fields[str(idx)] = value_map

    return enum_fields


def _generate_enum_labels(
    field: InferredField, values: list[Any], field_idx: int, schema: MessageSchema
) -> dict[int, str]:
    """Generate human-readable labels for enum values with enhanced semantics.

    Args:
        field: Field object
        values: List of unique values
        field_idx: Field index in schema
        schema: Complete message schema for context

    Returns:
        Dictionary mapping value to semantic label
    """
    labels = {}

    for val in values:
        if not isinstance(val, int):
            continue

        # Generate label based on field type and value patterns
        if field.field_type == "counter":
            # Counters get simple count labels
            labels[val] = f"Count {val}"

        elif field.field_type == "constant":
            # Constants shown in hex
            labels[val] = f"0x{val:02X}"

        elif field.field_type == "enum":
            # Check if this looks like a message type (first few fields)
            if field_idx < 3 and field.size == 1:
                # Common message type patterns
                msg_types = {
                    0x00: "REQUEST",
                    0x01: "RESPONSE",
                    0x02: "ACK",
                    0x03: "NACK",
                    0x04: "ERROR",
                    0x10: "DATA",
                    0x20: "CONTROL",
                    0xFF: "BROADCAST",
                }
                labels[val] = msg_types.get(val, f"Type 0x{val:02X}")

            # Check if this looks like status codes
            elif field_idx >= schema.header_size and field.size == 1:
                status_codes = {
                    0x00: "OK",
                    0x01: "WARNING",
                    0x02: "ERROR",
                    0xFF: "INVALID",
                }
                labels[val] = status_codes.get(val, f"Status {val}")
            else:
                labels[val] = f"Value {val} (0x{val:02X})"

        else:
            # Generic labels with both decimal and hex
            if val < 256:
                labels[val] = f"Value {val} (0x{val:02X})"
            else:
                labels[val] = f"Value {val} (0x{val:04X})"

    return labels


def export_to_file(
    schema: MessageSchema,
    output_path: str,
    protocol_name: str = "unknown",
    **kwargs: Any,
) -> None:
    """Generate dissector and write to file.

    Convenience function to generate Lua dissector and save to disk.

    Args:
        schema: Message schema from inference
        output_path: Path to output .lua file
        protocol_name: Protocol short name
        **kwargs: Additional arguments passed to generate_dissector()

    Example:
        >>> from oscura.inference.message_format import infer_format
        >>> from oscura.exporters.wireshark import export_to_file
        >>> messages = [b'\\xAA\\x01DATA', b'\\xAA\\x02DATA']
        >>> schema = infer_format(messages)
        >>> export_to_file(schema, "custom.lua", "custom")
    """
    lua_code = generate_dissector(schema, protocol_name, **kwargs)

    with open(output_path, "w", encoding="utf-8") as f:
        f.write(lua_code)
