"""
Native Python Swift code generator.

Produces the exact same Swift output as the TypeScript generator in
`src/core/generator.ts`. This eliminates the Node.js dependency — the
Python SDK can now compile Python intent files to Swift without shelling
out to `@axint/compiler`.

The generator is deliberately kept in lock-step with the TS version:
same struct layout, same decorator style, same return-type signatures.
If you change one, change the other.
"""

from __future__ import annotations

from typing import Any

from .ir import (
    AppIR,
    AppSceneIR,
    EntityIR,
    IntentIR,
    IntentParameter,
    ParamType,
    ViewIR,
    ViewStateIR,
    WidgetIR,
)

# ── Swift Type Mapping ──────────────────────────────────────────────

SWIFT_TYPE_MAP: dict[str, str] = {
    "string": "String",
    "int": "Int",
    "double": "Double",
    "float": "Float",
    "number": "Int",  # legacy alias
    "boolean": "Bool",
    "date": "Date",
    "duration": "Measurement<UnitDuration>",
    "url": "URL",
}


def param_type_to_swift(param_type: ParamType | str) -> str:
    """Convert a ParamType to its Swift representation."""
    if isinstance(param_type, str):
        return SWIFT_TYPE_MAP.get(param_type, "String")
    return SWIFT_TYPE_MAP.get(str(param_type), "String")

DEFAULT_LITERALS: dict[str, str] = {
    "string": '""',
    "int": "0",
    "double": "0.0",
    "float": "Float(0)",
    "number": "0",
    "boolean": "false",
    "date": "Date()",
    "duration": "Measurement<UnitDuration>(value: 0, unit: .seconds)",
    "url": 'URL(string: "about:blank")!',
}


# ── String Escaping ─────────────────────────────────────────────────

def escape_swift_string(s: str) -> str:
    """Escape a string for safe interpolation into Swift string literals."""
    return (
        s.replace("\\", "\\\\")
        .replace('"', '\\"')
        .replace("\n", "\\n")
        .replace("\r", "\\r")
        .replace("\t", "\\t")
    )


def escape_xml(s: str) -> str:
    """Escape a string for safe interpolation into XML (plist)."""
    return (
        s.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace('"', "&quot;")
        .replace("'", "&apos;")
    )


# ── Primary Swift Generator ─────────────────────────────────────────

def generate_swift(intent: IntentIR) -> str:
    """Generate a Swift App Intent source file from an IntentIR."""
    lines: list[str] = []

    # File header
    lines.append(f"// {intent.name}Intent.swift")
    lines.append("// Generated by Axint — https://github.com/agenticempire/axint")
    lines.append("// Do not edit manually. Re-run `axint compile` to regenerate.")
    lines.append("")
    lines.append("import AppIntents")
    lines.append("import Foundation")
    lines.append("")

    # Struct declaration
    lines.append(f"struct {intent.name}Intent: AppIntent {{")

    # Static metadata
    lines.append(
        f'    static let title: LocalizedStringResource = "{escape_swift_string(intent.title)}"'
    )
    lines.append(
        f'    static let description: IntentDescription = IntentDescription("{escape_swift_string(intent.description)}")'
    )
    lines.append(f"    static let isDiscoverable: Bool = {str(intent.is_discoverable).lower()}")
    lines.append("")

    # Parameters
    for param in intent.parameters:
        lines.append(_generate_parameter(param))

    if intent.parameters:
        lines.append("")

    # Perform function
    return_type = intent.return_type or "string"
    swift_return = SWIFT_TYPE_MAP.get(return_type, "String")
    return_sig = f"some IntentResult & ReturnsValue<{swift_return}>"

    lines.append(f"    func perform() async throws -> {return_sig} {{")
    lines.append("        // TODO: Implement your intent logic here.")

    if intent.parameters:
        param_list = ", ".join(f"\\({p.name})" for p in intent.parameters)
        lines.append(f"        // Parameters available: {param_list}")

    default_literal = DEFAULT_LITERALS.get(return_type, '""')
    lines.append(f"        return .result(value: {default_literal})")
    lines.append("    }")
    lines.append("}")
    lines.append("")

    return "\n".join(lines)


def _generate_parameter(param: IntentParameter) -> str:
    """Generate a single @Parameter property declaration."""
    # Handle entity types
    if param.type == "entity" and param.entity_name:
        swift_type = param.entity_name
        if param.optional:
            swift_type = f"{swift_type}?"
        lines: list[str] = []
        attrs: list[str] = [f'title: "{escape_swift_string(param.description)}"']
        decorator = f"    @Parameter({', '.join(attrs)})"
        lines.append(decorator)
        lines.append(f"    var {param.name}: {swift_type}")
        lines.append("")
        return "\n".join(lines)

    # Handle enum types
    if param.type == "enum" and param.enum_cases:
        # Generate enum type inline or reference it
        swift_type = _generate_enum_type_name(param.name)
        if param.optional:
            swift_type = f"{swift_type}?"
        enum_lines: list[str] = []
        enum_attrs: list[str] = [f'title: "{escape_swift_string(param.description)}"']
        decorator = f"    @Parameter({', '.join(enum_attrs)})"
        enum_lines.append(decorator)
        if param.default is not None:
            default_str = _format_swift_default(param.default, param.type)
            enum_lines.append(f"    var {param.name}: {swift_type} = {default_str}")
        else:
            enum_lines.append(f"    var {param.name}: {swift_type}")
        enum_lines.append("")
        return "\n".join(enum_lines)

    # Standard primitive types
    swift_type = SWIFT_TYPE_MAP.get(param.type, "String")
    if param.optional:
        swift_type = f"{swift_type}?"

    prim_lines: list[str] = []

    # Build @Parameter decorator
    prim_attrs: list[str] = [f'title: "{escape_swift_string(param.description)}"']
    decorator = f"    @Parameter({', '.join(prim_attrs)})"
    prim_lines.append(decorator)

    # Property declaration
    if param.default is not None:
        default_str = _format_swift_default(param.default, param.type)
        prim_lines.append(f"    var {param.name}: {swift_type} = {default_str}")
    else:
        prim_lines.append(f"    var {param.name}: {swift_type}")

    prim_lines.append("")

    return "\n".join(prim_lines)


def _generate_enum_type_name(param_name: str) -> str:
    """Generate a PascalCase enum type name from parameter name."""
    # Convert snake_case to PascalCase
    parts = param_name.split("_")
    return "".join(p.capitalize() for p in parts) + "Option"


def _format_swift_default(value: object, param_type: str) -> str:
    """Format a Python default value as a Swift literal."""
    if isinstance(value, str):
        return f'"{escape_swift_string(value)}"'
    if isinstance(value, bool):
        return "true" if value else "false"
    if isinstance(value, (int, float)):
        return str(value)
    return f'"{escape_swift_string(str(value))}"'


# ── Info.plist Fragment Generator ───────────────────────────────────

def generate_info_plist_fragment(intent: IntentIR) -> str | None:
    """Generate an Info.plist XML fragment, or None if no keys declared."""
    if not intent.info_plist_keys:
        return None

    lines: list[str] = [
        '<?xml version="1.0" encoding="UTF-8"?>',
        f"<!-- Info.plist fragment generated by Axint for {intent.name}Intent -->",
        "<!-- Merge these keys into your app's Info.plist. -->",
        '<plist version="1.0">',
        "<dict>",
    ]
    for key, value in intent.info_plist_keys:
        lines.append(f"    <key>{escape_xml(key)}</key>")
        description = value
        if description == key:
            description = f"TODO: Add description for {key}"
        lines.append(f"    <string>{escape_xml(description)}</string>")
    lines.append("</dict>")
    lines.append("</plist>")
    lines.append("")

    return "\n".join(lines)


# ── Entitlements Fragment Generator ─────────────────────────────────

def generate_entitlements_fragment(intent: IntentIR) -> str | None:
    """Generate a .entitlements XML fragment, or None if no entitlements declared."""
    if not intent.entitlements:
        return None

    lines: list[str] = [
        '<?xml version="1.0" encoding="UTF-8"?>',
        f"<!-- Entitlements fragment generated by Axint for {intent.name}Intent -->",
        "<!-- Merge these into your target's .entitlements file. -->",
        "<!-- Note: entitlements requiring typed values (string/array) -->",
        "<!-- need manual adjustment — defaults below are boolean true. -->",
        '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
        '<plist version="1.0">',
        "<dict>",
    ]
    for ent in intent.entitlements:
        lines.append(f"    <key>{escape_xml(ent)}</key>")
        lines.append("    <true/>")
    lines.append("</dict>")
    lines.append("</plist>")
    lines.append("")

    return "\n".join(lines)


# ── Entity Generator ────────────────────────────────────────────────
#
# Kept byte-for-byte aligned with `generateEntity` / `generateEntityQuery` in
# src/core/generator.ts. A Python-authored entity compiled here must produce
# the same Swift source as one authored in TypeScript.

_COMPARABLE_SWIFT_TYPES = {"Int", "Double", "Float", "Date"}


def generate_entity(entity: EntityIR) -> str:
    """Generate an AppEntity Swift struct from an EntityIR."""
    property_names = {p.name for p in entity.properties}
    lines: list[str] = [
        f"struct {entity.name}: AppEntity {{",
        f"    static var defaultQuery = {entity.name}Query()",
        "",
    ]

    # Apple requires AppEntity to declare an `id`. If the user didn't, we add it.
    if "id" not in property_names:
        lines.append("    var id: String")

    for prop in entity.properties:
        if prop.name == "id":
            lines.append(f"    var id: {param_type_to_swift(prop.type)}")
            continue
        if prop.description:
            lines.append(
                f'    @Property(title: "{escape_swift_string(prop.description)}")'
            )
        lines.append(f"    var {prop.name}: {param_type_to_swift(prop.type)}")

    lines.append("")
    lines.append(
        "    static let typeDisplayRepresentation: TypeDisplayRepresentation = TypeDisplayRepresentation("
    )
    lines.append(
        f'        name: LocalizedStringResource("{escape_swift_string(entity.name)}")'
    )
    lines.append("    )")
    lines.append("")

    # Display representation references property names via Swift interpolation —
    # we emit `\(propName)` which Swift resolves against the entity instance.
    display = entity.display_representation
    lines.append("    var displayRepresentation: DisplayRepresentation {")
    lines.append("        DisplayRepresentation(")

    has_subtitle = display.subtitle is not None
    has_image = display.image is not None
    title_trailing = "," if has_subtitle or has_image else ""
    lines.append(f'            title: "\\({display.title})"{title_trailing}')

    if has_subtitle:
        subtitle_trailing = "," if has_image else ""
        lines.append(f'            subtitle: "\\({display.subtitle})"{subtitle_trailing}')

    if has_image:
        lines.append(
            f'            image: .init(systemName: "{escape_swift_string(display.image or "")}")'
        )

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

    return "\n".join(lines)


def generate_entity_query(entity: EntityIR) -> str:
    """Generate the EntityQuery conformance matching the entity's query_type."""
    if entity.query_type == "all":
        protocol = "EnumerableEntityQuery"
    elif entity.query_type == "string":
        protocol = "EntityStringQuery"
    elif entity.query_type == "property":
        protocol = "EntityPropertyQuery"
    else:
        protocol = "EntityQuery"

    lines: list[str] = [f"struct {entity.name}Query: {protocol} {{"]
    if entity.query_type in {"all", "property"}:
        lines.append(
            f'    static var findIntentDescription: IntentDescription = IntentDescription("Find {escape_swift_string(entity.name)}")'
        )
        lines.append("")
    lines.extend(
        [
            f"    func entities(for identifiers: [{entity.name}.ID]) async throws -> [{entity.name}] {{",
            "        // TODO: Fetch entities by IDs",
            "        return []",
            "    }",
            "",
            f"    func suggestedEntities() async throws -> [{entity.name}] {{",
            "        // TODO: Return suggested entities for pickers and shortcuts",
            "        return []",
            "    }",
            "",
        ]
    )

    if entity.query_type == "all":
        lines.append(f"    func allEntities() async throws -> [{entity.name}] {{")
        lines.append("        // TODO: Return all entities")
        lines.append("        return []")
        lines.append("    }")
    elif entity.query_type == "id":
        lines.append("    // ID-based query is provided by the entities(for:) method above")
    elif entity.query_type == "string":
        lines.append(
            f"    func entities(matching string: String) async throws -> [{entity.name}] {{"
        )
        lines.append("        // TODO: Search entities by string")
        lines.append("        return []")
        lines.append("    }")
    elif entity.query_type == "property":
        primitives = [p for p in entity.properties if isinstance(p.type, str) and p.name != "id"]

        lines.append("    static var properties = QueryProperties {")
        for prop in primitives:
            swift_type = param_type_to_swift(prop.type)
            lines.append(f"        Property(\\.${prop.name}) {{")
            lines.append("            EqualToComparator()")
            if swift_type == "String":
                lines.append("            ContainsComparator()")
            if swift_type in _COMPARABLE_SWIFT_TYPES:
                lines.append("            LessThanComparator()")
                lines.append("            GreaterThanComparator()")
            lines.append("        }")
        lines.append("    }")
        lines.append("")
        lines.append("    static var sortingOptions = SortingOptions {")
        for prop in primitives:
            lines.append(f"        SortableBy(\\.${prop.name})")
        lines.append("    }")
        lines.append("")
        lines.append(
            f"    func entities(matching comparators: [{entity.name}Comparator], "
            f"mode: ComparatorMode, sortedBy: [{entity.name}Sort], limit: Int?) "
            f"async throws -> [{entity.name}] {{"
        )
        lines.append("        // TODO: Filter and sort entities using the comparators")
        lines.append("        return []")
        lines.append("    }")

    lines.append("}")

    return "\n".join(lines)


# ── SwiftUI View Generator ──────────────────────────────────────────

def generate_swift_view(view: ViewIR) -> str:
    """Generate a SwiftUI view source file from a ViewIR."""
    lines: list[str] = []

    lines.append(f"// {view.name}.swift")
    lines.append("// Generated by Axint — https://github.com/agenticempire/axint")
    lines.append("// Do not edit manually. Re-run `axint compile` to regenerate.")
    lines.append("")
    lines.append("import SwiftUI")
    lines.append("")

    lines.append(f"struct {view.name}: View {{")

    for prop in view.props:
        swift_type = param_type_to_swift(prop.type)
        if prop.optional:
            swift_type = f"{swift_type}?"
        if prop.default is not None:
            default_str = _format_swift_literal(prop.default, prop.type)
            lines.append(f"    var {prop.name}: {swift_type} = {default_str}")
        else:
            lines.append(f"    var {prop.name}: {swift_type}")

    if view.props and view.state:
        lines.append("")

    for state_prop in view.state:
        lines.append(_generate_view_state_property(state_prop))

    if view.props or view.state:
        lines.append("")

    lines.append("    var body: some View {")
    for node in view.body:
        node_lines = _generate_body_node(node, 2)
        lines.extend(node_lines)
    lines.append("    }")

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

    # Preview
    lines.append("#Preview {")
    if view.props:
        preview_args = []
        for prop in view.props:
            if not prop.optional and prop.default is None:
                preview_args.append(f"{prop.name}: {_preview_default(prop.type)}")
        if preview_args:
            lines.append(f"    {view.name}({', '.join(preview_args)})")
        else:
            lines.append(f"    {view.name}()")
    else:
        lines.append(f"    {view.name}()")
    lines.append("}")
    lines.append("")

    return "\n".join(lines)


def _generate_view_state_property(state: ViewStateIR) -> str:
    """Generate a state property declaration."""
    swift_type = param_type_to_swift(state.type)

    if state.kind == "binding":
        return f"    @Binding var {state.name}: {swift_type}"
    if state.kind == "environment":
        key_str = state.environment_key or f".{state.name}"
        return f"    @Environment({key_str}) var {state.name}"
    if state.kind == "observed":
        return f"    @ObservedObject var {state.name}: {swift_type}"
    if state.default is not None:
        default_str = _format_swift_literal(state.default, state.type)
        return f"    @State private var {state.name}: {swift_type} = {default_str}"
    return f"    @State private var {state.name}: {swift_type}"


def _generate_body_node(node: dict[str, Any], depth: int) -> list[str]:
    """Recursively generate body node code."""
    indent = "    " * depth
    lines: list[str] = []

    node_type = node.get("type")

    if node_type == "text":
        content = node.get("content", "")
        lines.append(f'{indent}Text("{escape_swift_string(str(content))}")')

    elif node_type == "image":
        system_name = node.get("systemName")
        name = node.get("name")
        if system_name:
            lines.append(f'{indent}Image(systemName: "{escape_swift_string(str(system_name))}")')
        elif name:
            lines.append(f'{indent}Image("{escape_swift_string(str(name))}")')

    elif node_type == "button":
        label = node.get("label", "")
        action = node.get("action")
        if action:
            lines.append(f'{indent}Button("{escape_swift_string(str(label))}") {{')
            lines.append(f"{indent}    {action}")
            lines.append(f"{indent}}}")
        else:
            lines.append(f'{indent}Button("{escape_swift_string(str(label))}") {{ }}')

    elif node_type == "spacer":
        lines.append(f"{indent}Spacer()")

    elif node_type == "divider":
        lines.append(f"{indent}Divider()")

    elif node_type in ("vstack", "hstack", "zstack"):
        container = "VStack" if node_type == "vstack" else "HStack" if node_type == "hstack" else "ZStack"
        args: list[str] = []
        if node.get("alignment"):
            args.append(f"alignment: .{node['alignment']}")
        if "spacing" in node and node["spacing"] is not None:
            args.append(f"spacing: {node['spacing']}")
        arg_str = f"({', '.join(args)})" if args else ""

        lines.append(f"{indent}{container}{arg_str} {{")
        children = node.get("children", [])
        for child in children:
            child_lines = _generate_body_node(child, depth + 1)
            lines.extend(child_lines)
        lines.append(f"{indent}}}")

    elif node_type == "foreach":
        collection = node.get("collection", "")
        item_name = node.get("item", "item")
        lines.append(f'{indent}ForEach({collection}, id: \\.self) {{ {item_name} in')
        body = node.get("body", [])
        for child in body:
            child_lines = _generate_body_node(child, depth + 1)
            lines.extend(child_lines)
        lines.append(f"{indent}}}")

    elif node_type == "conditional":
        condition = node.get("condition", "")
        lines.append(f"{indent}if {condition} {{")
        then_body = node.get("then", [])
        for child in then_body:
            child_lines = _generate_body_node(child, depth + 1)
            lines.extend(child_lines)
        else_body = node.get("else", [])
        if else_body:
            lines.append(f"{indent}}} else {{")
            for child in else_body:
                child_lines = _generate_body_node(child, depth + 1)
                lines.extend(child_lines)
        lines.append(f"{indent}}}")

    elif node_type == "navigationLink":
        destination = node.get("destination", "")
        lines.append(f"{indent}NavigationLink(destination: {destination}()) {{")
        label_nodes = node.get("label", [])
        for label_node in label_nodes:
            label_lines = _generate_body_node(label_node, depth + 1)
            lines.extend(label_lines)
        lines.append(f"{indent}}}")

    elif node_type == "list":
        lines.append(f"{indent}List {{")
        children = node.get("children", [])
        for child in children:
            child_lines = _generate_body_node(child, depth + 1)
            lines.extend(child_lines)
        lines.append(f"{indent}}}")

    elif node_type == "raw":
        swift_code = node.get("swift", "")
        lines.append(f"{indent}{swift_code}")

    return lines


def _preview_default(param_type: ParamType | str) -> str:
    """Get preview default value for a parameter type."""
    ptype = str(param_type)
    defaults = {
        "string": '"Preview"',
        "int": "0",
        "double": "0.0",
        "float": "Float(0)",
        "boolean": "false",
        "date": "Date()",
        "url": 'URL(string: "https://example.com")! // TODO: Replace with your URL',
    }
    return defaults.get(ptype, '""')


# ── WidgetKit Widget Generator ──────────────────────────────────────

def generate_swift_widget(widget: WidgetIR) -> str:
    """Generate a WidgetKit widget source file from a WidgetIR."""
    lines: list[str] = []

    lines.append(f"// {widget.name}Widget.swift")
    lines.append("// Generated by Axint — https://github.com/agenticempire/axint")
    lines.append("// Do not edit manually. Re-run `axint compile` to regenerate.")
    lines.append("")
    lines.append("import WidgetKit")
    lines.append("import SwiftUI")
    lines.append("")

    lines.append(f"struct {widget.name}Entry: TimelineEntry {{")
    lines.append("    let date: Date")
    for entry in widget.entry:
        swift_type = param_type_to_swift(entry.type)
        if entry.default is not None:
            default_str = _format_swift_literal(entry.default, entry.type)
            lines.append(f"    let {entry.name}: {swift_type} = {default_str}")
        else:
            lines.append(f"    let {entry.name}: {swift_type}")
    lines.append("}")
    lines.append("")

    lines.append(f"struct {widget.name}Provider: TimelineProvider {{")
    lines.append(f"    func placeholder(in context: Context) -> {widget.name}Entry {{")
    placeholder_fields = ", ".join(
        f"{e.name}: {_get_default_value(e.default, e.type)}" for e in widget.entry
    )
    if placeholder_fields:
        lines.append(f"        {widget.name}Entry(date: Date(), {placeholder_fields})")
    else:
        lines.append(f"        {widget.name}Entry(date: Date())")
    lines.append("    }")
    lines.append("")

    lines.append(f"    func getSnapshot(in context: Context, completion: @escaping ({widget.name}Entry) -> Void) {{")
    if placeholder_fields:
        lines.append(f"        let entry = {widget.name}Entry(date: Date(), {placeholder_fields})")
    else:
        lines.append(f"        let entry = {widget.name}Entry(date: Date())")
    lines.append("        completion(entry)")
    lines.append("    }")
    lines.append("")

    lines.append(f"    func getTimeline(in context: Context, completion: @escaping (Timeline<{widget.name}Entry>) -> Void) {{")
    if placeholder_fields:
        lines.append(f"        let entry = {widget.name}Entry(date: Date(), {placeholder_fields})")
    else:
        lines.append(f"        let entry = {widget.name}Entry(date: Date())")

    if widget.refresh_policy == "never":
        next_refresh = "Calendar.current.date(byAdding: .day, value: 365, to: Date())!"
    elif widget.refresh_policy == "after" and widget.refresh_interval:
        next_refresh = f"Calendar.current.date(byAdding: .minute, value: {widget.refresh_interval}, to: Date())!"
    else:
        next_refresh = "Calendar.current.date(byAdding: .hour, value: 1, to: Date())!"

    lines.append(f"        let timeline = Timeline(entries: [entry], policy: .after({next_refresh}))")
    lines.append("        completion(timeline)")
    lines.append("    }")
    lines.append("}")
    lines.append("")

    lines.append(f"struct {widget.name}EntryView: View {{")
    lines.append(f"    var entry: {widget.name}Provider.Entry")
    lines.append("")
    lines.append("    var body: some View {")
    for node in widget.body:
        node_lines = _generate_body_node(node, 2)
        lines.extend(node_lines)
    lines.append("    }")
    lines.append("}")
    lines.append("")

    lines.append(f"struct {widget.name}Widget: Widget {{")
    lines.append(f'    let kind: String = "{widget.name}"')
    lines.append("")
    lines.append("    var body: some WidgetConfiguration {")
    lines.append("        StaticConfiguration(")
    lines.append("            kind: kind,")
    lines.append(f"            provider: {widget.name}Provider()")
    lines.append("        ) { entry in")
    lines.append(f"            {widget.name}EntryView(entry: entry)")
    lines.append("        }")
    lines.append(f'        .configurationDisplayName("{escape_swift_string(widget.display_name)}")')
    lines.append(f'        .description("{escape_swift_string(widget.description)}")')
    families_str = _families_array(widget.families)
    lines.append(f"        .supportedFamilies({families_str})")
    lines.append("    }")
    lines.append("}")
    lines.append("")

    lines.append("#Preview(as: .systemSmall) {")
    lines.append(f"    {widget.name}Widget()")
    lines.append("} timeline: {")
    preview_fields = ", ".join(
        f"{e.name}: {_get_default_value(e.default, e.type)}" for e in widget.entry
    )
    if preview_fields:
        lines.append(f"    {widget.name}Entry(date: Date(), {preview_fields})")
    else:
        lines.append(f"    {widget.name}Entry(date: Date())")
    lines.append("}")
    lines.append("")

    return "\n".join(lines)


def _families_array(families: tuple[str, ...]) -> str:
    """Format widget families array."""
    mapped = [f".{f}" for f in families]
    return f"[{', '.join(mapped)}]"


def _get_default_value(value: object | None, param_type: ParamType | str) -> str:
    """Get default value for widget entry fields."""
    if value is not None:
        return _format_swift_literal(value, param_type)
    swift_type = param_type_to_swift(param_type)
    if swift_type == "String":
        return '""'
    elif swift_type == "Int":
        return "0"
    elif swift_type in ("Double", "Float"):
        return "0.0"
    elif swift_type == "Bool":
        return "false"
    elif swift_type == "Date":
        return "Date()"
    elif swift_type == "URL":
        return 'URL(string: "https://example.com")! // TODO: Replace with your URL'
    return "nil"


# ── SwiftUI App Generator ────────────────────────────────────────────

def generate_swift_app(app: AppIR) -> str:
    """Generate a SwiftUI App source file from an AppIR."""
    lines: list[str] = []

    lines.append(f"// {app.name}App.swift")
    lines.append("// Generated by Axint — https://github.com/agenticempire/axint")
    lines.append("// Do not edit manually. Re-run `axint compile` to regenerate.")
    lines.append("")
    lines.append("import SwiftUI")
    lines.append("")

    lines.append("@main")
    lines.append(f"struct {app.name}App: App {{")

    if app.app_storage:
        for storage in app.app_storage:
            swift_type = param_type_to_swift(storage.type)
            if storage.default is not None:
                default_str = _format_swift_literal(storage.default, storage.type)
                lines.append(
                    f'    @AppStorage("{escape_swift_string(storage.key)}") private var {storage.name}: {swift_type} = {default_str}'
                )
            else:
                lines.append(
                    f'    @AppStorage("{escape_swift_string(storage.key)}") private var {storage.name}: {swift_type}'
                )
        lines.append("")

    lines.append("    var body: some Scene {")

    ungrouped = [s for s in app.scenes if not s.platform]
    mac_only = [s for s in app.scenes if s.platform == "macOS"]
    ios_only = [s for s in app.scenes if s.platform == "iOS"]
    vision_only = [s for s in app.scenes if s.platform == "visionOS"]

    for scene in ungrouped:
        scene_lines = _generate_scene(scene, 2)
        lines.extend(scene_lines)

    if mac_only:
        lines.append("        #if os(macOS)")
        for scene in mac_only:
            scene_lines = _generate_scene(scene, 2)
            lines.extend(scene_lines)
        lines.append("        #endif")

    if ios_only:
        lines.append("        #if os(iOS)")
        for scene in ios_only:
            scene_lines = _generate_scene(scene, 2)
            lines.extend(scene_lines)
        lines.append("        #endif")

    if vision_only:
        lines.append("        #if os(visionOS)")
        for scene in vision_only:
            scene_lines = _generate_scene(scene, 2)
            lines.extend(scene_lines)
        lines.append("        #endif")

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

    return "\n".join(lines)


def _generate_scene(scene: AppSceneIR, depth: int) -> list[str]:
    """Generate a scene declaration."""
    lines: list[str] = []
    indent = "    " * depth

    kind = scene.kind
    view = scene.view
    title = scene.title
    name = scene.name

    if kind == "windowGroup":
        args: list[str] = []
        if title:
            args.append(f'"{escape_swift_string(title)}"')
        if name:
            args.append(f'id: "{escape_swift_string(name)}"')
        arg_str = f"({', '.join(args)})" if args else ""

        lines.append(f"{indent}WindowGroup{arg_str} {{")
        lines.append(f"{indent}    {view}()")
        lines.append(f"{indent}}}")

    elif kind == "window":
        window_title = title or name or "Window"
        window_id = name or view.lower()
        lines.append(
            f'{indent}Window("{escape_swift_string(window_title)}", id: "{escape_swift_string(window_id)}") {{'
        )
        lines.append(f"{indent}    {view}()")
        lines.append(f"{indent}}}")

    elif kind == "documentGroup":
        doc_type = getattr(scene, "documentType", None) or f"{view}Document"
        lines.append(f"{indent}DocumentGroup(newDocument: {{ {doc_type}() }}) {{ file in")
        lines.append(f"{indent}    {view}(document: file.$document)")
        lines.append(f"{indent}}}")

    elif kind == "settings":
        lines.append(f"{indent}Settings {{")
        lines.append(f"{indent}    {view}()")
        lines.append(f"{indent}}}")

    return lines


def _format_swift_literal(value: object, param_type: ParamType | str | object) -> str:
    """Format a Python value as a Swift literal."""
    if isinstance(value, str):
        return f'"{escape_swift_string(value)}"'
    if isinstance(value, bool):
        return "true" if value else "false"
    if isinstance(value, (int, float)):
        return str(value)
    if value is None:
        return "nil"
    if isinstance(value, list):
        elements = [_format_swift_literal(v, param_type) for v in value]
        return f"[{', '.join(elements)}]"
    if isinstance(value, dict):
        entries = [
            f'"{escape_swift_string(k)}": {_format_swift_literal(v, param_type)}'
            for k, v in value.items()
        ]
        return f"[{', '.join(entries)}]"
    return f'"{escape_swift_string(str(value))}"'
