//! HIR WidgetNode → Wear OS Tiles Kotlin emitter
//!
//! Emits Compose for Wear OS Tiles Kotlin code from the declarative widget tree.

use perry_hir::ir::*;
use std::fmt::Write;

/// Emit the TileService Kotlin class
pub fn emit_tile_service(widget: &WidgetDecl, name: &str, package: &str) -> String {
    let mut out = String::new();
    let entry_param = &widget.entry_param_name;

    writeln!(out, "// Auto-generated by Perry compiler — do not edit").unwrap();
    writeln!(out, "package {}", package).unwrap();
    writeln!(out).unwrap();
    writeln!(out, "import android.content.Context").unwrap();
    writeln!(out, "import androidx.wear.tiles.*").unwrap();
    writeln!(out, "import androidx.wear.tiles.material.*").unwrap();
    writeln!(out, "import androidx.wear.tiles.material.layouts.*").unwrap();
    writeln!(
        out,
        "import com.google.android.horologist.annotations.ExperimentalHorologistApi"
    )
    .unwrap();
    writeln!(
        out,
        "import com.google.android.horologist.tiles.SuspendingTileService"
    )
    .unwrap();
    writeln!(out).unwrap();

    // Data class for entry
    write!(out, "data class {}Entry(", name).unwrap();
    let field_strs: Vec<String> = widget
        .entry_fields
        .iter()
        .map(|(fname, ftype)| format!("val {}: {}", fname, kotlin_type_for_field(ftype)))
        .collect();
    write!(out, "{}", field_strs.join(", ")).unwrap();
    writeln!(out, ")").unwrap();
    writeln!(out).unwrap();

    writeln!(out, "@OptIn(ExperimentalHorologistApi::class)").unwrap();
    writeln!(
        out,
        "class {}TileService : SuspendingTileService() {{",
        name
    )
    .unwrap();
    writeln!(out).unwrap();

    // resourcesRequest
    writeln!(out, "    override suspend fun resourcesRequest(requestParams: RequestBuilders.ResourcesRequest): ResourceBuilders.Resources {{").unwrap();
    writeln!(out, "        return ResourceBuilders.Resources.Builder()").unwrap();
    writeln!(out, "            .setVersion(\"1\")").unwrap();
    writeln!(out, "            .build()").unwrap();
    writeln!(out, "    }}").unwrap();
    writeln!(out).unwrap();

    // tileRequest
    writeln!(out, "    override suspend fun tileRequest(requestParams: RequestBuilders.TileRequest): TileBuilders.Tile {{").unwrap();

    if widget.provider_func_name.is_some() {
        writeln!(
            out,
            "        val {} = {}TileBridge.fetchEntry(this)",
            entry_param, name
        )
        .unwrap();
    } else {
        write!(out, "        val {} = {}Entry(", entry_param, name).unwrap();
        let defaults: Vec<String> = widget
            .entry_fields
            .iter()
            .map(|(_, ftype)| kotlin_default_value(ftype))
            .collect();
        write!(out, "{}", defaults.join(", ")).unwrap();
        writeln!(out, ")").unwrap();
    }
    writeln!(out).unwrap();

    let reload_ms = widget
        .reload_after_seconds
        .map(|s| s as u64 * 1000)
        .unwrap_or(3600000);

    writeln!(
        out,
        "        val layout = LayoutElementBuilders.Column.Builder()"
    )
    .unwrap();

    // Emit the tile layout
    if widget.render_body.is_empty() {
        writeln!(out, "            .addContent(").unwrap();
        writeln!(out, "                LayoutElementBuilders.Text.Builder()").unwrap();
        writeln!(out, "                    .setText(\"Empty tile\")").unwrap();
        writeln!(out, "                    .build()").unwrap();
        writeln!(out, "            )").unwrap();
    } else {
        for node in &widget.render_body {
            out.push_str(&emit_node(node, entry_param, 3));
        }
    }

    writeln!(out, "            .build()").unwrap();
    writeln!(out).unwrap();
    writeln!(out, "        return TileBuilders.Tile.Builder()").unwrap();
    writeln!(out, "            .setResourcesVersion(\"1\")").unwrap();
    writeln!(
        out,
        "            .setFreshnessIntervalMillis({})",
        reload_ms
    )
    .unwrap();
    writeln!(out, "            .setTileTimeline(").unwrap();
    writeln!(out, "                TimelineBuilders.Timeline.Builder()").unwrap();
    writeln!(out, "                    .addTimelineEntry(").unwrap();
    writeln!(
        out,
        "                        TimelineBuilders.TimelineEntry.Builder()"
    )
    .unwrap();
    writeln!(out, "                            .setLayout(").unwrap();
    writeln!(
        out,
        "                                LayoutElementBuilders.Layout.Builder()"
    )
    .unwrap();
    writeln!(out, "                                    .setRoot(layout)").unwrap();
    writeln!(out, "                                    .build()").unwrap();
    writeln!(out, "                            )").unwrap();
    writeln!(out, "                            .build()").unwrap();
    writeln!(out, "                    )").unwrap();
    writeln!(out, "                    .build()").unwrap();
    writeln!(out, "            )").unwrap();
    writeln!(out, "            .build()").unwrap();
    writeln!(out, "    }}").unwrap();
    writeln!(out, "}}").unwrap();
    out
}

/// Emit AndroidManifest service snippet for Wear OS tile
pub fn emit_manifest_snippet(name: &str, package: &str) -> String {
    let mut out = String::new();
    writeln!(
        out,
        "<!-- Add to AndroidManifest.xml inside <application> -->"
    )
    .unwrap();
    writeln!(out, "<service").unwrap();
    writeln!(out, "    android:name=\"{}.{}TileService\"", package, name).unwrap();
    writeln!(out, "    android:exported=\"true\"").unwrap();
    writeln!(
        out,
        "    android:permission=\"com.google.android.wearable.permission.BIND_TILE_PROVIDER\">"
    )
    .unwrap();
    writeln!(out, "    <intent-filter>").unwrap();
    writeln!(
        out,
        "        <action android:name=\"androidx.wear.tiles.action.BIND_TILE_PROVIDER\" />"
    )
    .unwrap();
    writeln!(out, "    </intent-filter>").unwrap();
    writeln!(out, "    <meta-data").unwrap();
    writeln!(out, "        android:name=\"androidx.wear.tiles.PREVIEW\"").unwrap();
    writeln!(
        out,
        "        android:resource=\"@drawable/{}_preview\" />",
        name.to_lowercase()
    )
    .unwrap();
    writeln!(out, "</service>").unwrap();
    out
}

/// Emit a WidgetNode as Wear Tiles LayoutElementBuilders Kotlin
fn emit_node(node: &WidgetNode, entry_param: &str, indent: usize) -> String {
    let mut out = String::new();
    let pad = "    ".repeat(indent);

    match node {
        WidgetNode::Text { content, modifiers } => {
            let text_val = emit_text_content(content, entry_param);
            writeln!(out, "{}.addContent(", pad).unwrap();
            writeln!(out, "{}    LayoutElementBuilders.Text.Builder()", pad).unwrap();
            writeln!(out, "{}        .setText({})", pad, text_val).unwrap();
            let _ = modifiers;
            writeln!(out, "{}        .build()", pad).unwrap();
            writeln!(out, "{})", pad).unwrap();
        }
        WidgetNode::Stack {
            kind,
            children,
            modifiers,
            spacing: _,
        } => {
            let builder = match kind {
                WidgetStackKind::VStack => "Column",
                WidgetStackKind::HStack => "Row",
                WidgetStackKind::ZStack => "Box",
            };
            let _ = modifiers;
            writeln!(out, "{}.addContent(", pad).unwrap();
            writeln!(
                out,
                "{}    LayoutElementBuilders.{}.Builder()",
                pad, builder
            )
            .unwrap();
            for child in children {
                out.push_str(&emit_node(child, entry_param, indent + 2));
            }
            writeln!(out, "{}        .build()", pad).unwrap();
            writeln!(out, "{})", pad).unwrap();
        }
        WidgetNode::Spacer => {
            writeln!(out, "{}.addContent(", pad).unwrap();
            writeln!(out, "{}    LayoutElementBuilders.Spacer.Builder()", pad).unwrap();
            writeln!(out, "{}        .setHeight(DimensionBuilders.dp(8f))", pad).unwrap();
            writeln!(out, "{}        .build()", pad).unwrap();
            writeln!(out, "{})", pad).unwrap();
        }
        WidgetNode::Divider => {
            writeln!(out, "{}.addContent(", pad).unwrap();
            writeln!(out, "{}    LayoutElementBuilders.Spacer.Builder()", pad).unwrap();
            writeln!(out, "{}        .setHeight(DimensionBuilders.dp(1f))", pad).unwrap();
            writeln!(out, "{}        .setWidth(DimensionBuilders.expand())", pad).unwrap();
            writeln!(out, "{}        .build()", pad).unwrap();
            writeln!(out, "{})", pad).unwrap();
        }
        WidgetNode::Gauge {
            value_expr,
            label,
            style,
            modifiers,
        } => {
            let _ = modifiers;
            match style {
                GaugeStyle::Circular => {
                    writeln!(out, "{}.addContent(", pad).unwrap();
                    writeln!(out, "{}    LayoutElementBuilders.Arc.Builder()", pad).unwrap();
                    writeln!(out, "{}        .addContent(", pad).unwrap();
                    writeln!(
                        out,
                        "{}            LayoutElementBuilders.ArcLine.Builder()",
                        pad
                    )
                    .unwrap();
                    writeln!(out, "{}                .setLength(DimensionBuilders.degrees({}.{}.toFloat() * 360f))", pad, entry_param, value_expr).unwrap();
                    writeln!(out, "{}                .build()", pad).unwrap();
                    writeln!(out, "{}        )", pad).unwrap();
                    writeln!(out, "{}        .build()", pad).unwrap();
                    writeln!(out, "{})", pad).unwrap();
                    writeln!(out, "{}// Gauge label: {}", pad, label).unwrap();
                }
                GaugeStyle::LinearCapacity => {
                    writeln!(
                        out,
                        "{}// Linear gauge for: {} ({})",
                        pad, label, value_expr
                    )
                    .unwrap();
                    writeln!(out, "{}.addContent(", pad).unwrap();
                    writeln!(out, "{}    LayoutElementBuilders.Text.Builder()", pad).unwrap();
                    writeln!(
                        out,
                        "{}        .setText(\"{}: ${{{}.{}}}\")",
                        pad, label, entry_param, value_expr
                    )
                    .unwrap();
                    writeln!(out, "{}        .build()", pad).unwrap();
                    writeln!(out, "{})", pad).unwrap();
                }
            }
        }
        WidgetNode::Image {
            system_name,
            modifiers,
        } => {
            let _ = modifiers;
            writeln!(out, "{}// Image: {} (provide resource)", pad, system_name).unwrap();
        }
        WidgetNode::Conditional {
            field,
            op,
            value,
            then_node,
            else_node,
        } => {
            let cond = emit_kotlin_condition(field, op, value, entry_param);
            writeln!(out, "{}// Conditional: {}", pad, cond).unwrap();
            out.push_str(&emit_node(then_node, entry_param, indent));
            if let Some(else_n) = else_node {
                out.push_str(&emit_node(else_n, entry_param, indent));
            }
        }
        WidgetNode::ForEach {
            collection_field,
            item_param,
            body,
        } => {
            writeln!(
                out,
                "{}// ForEach {}.{}",
                pad, entry_param, collection_field
            )
            .unwrap();
            let _ = (item_param, body);
        }
        WidgetNode::Label {
            text,
            system_image,
            modifiers,
        } => {
            let text_val = emit_text_content(text, entry_param);
            let _ = (system_image, modifiers);
            writeln!(out, "{}.addContent(", pad).unwrap();
            writeln!(out, "{}    LayoutElementBuilders.Text.Builder()", pad).unwrap();
            writeln!(out, "{}        .setText({})", pad, text_val).unwrap();
            writeln!(out, "{}        .build()", pad).unwrap();
            writeln!(out, "{})", pad).unwrap();
        }
        WidgetNode::FamilySwitch { cases, default } => {
            writeln!(
                out,
                "{}// Family switch — Wear OS tiles are always full-screen",
                pad
            )
            .unwrap();
            // Emit the first case or default
            if let Some((_, node)) = cases.first() {
                out.push_str(&emit_node(node, entry_param, indent));
            } else if let Some(default_node) = default {
                out.push_str(&emit_node(default_node, entry_param, indent));
            }
        }
    }

    out
}

/// Emit text content as Kotlin string expression
fn emit_text_content(content: &WidgetTextContent, entry_param: &str) -> String {
    match content {
        WidgetTextContent::Literal(s) => format!("\"{}\"", escape_kotlin_string(s)),
        WidgetTextContent::Field(field) => format!("\"${{{}.{}}}\"", entry_param, field),
        WidgetTextContent::Template(parts) => {
            let mut s = String::from("\"");
            for part in parts {
                match part {
                    WidgetTemplatePart::Literal(lit) => s.push_str(&escape_kotlin_string(lit)),
                    WidgetTemplatePart::Field(field) => {
                        write!(s, "${{{}.{}}}", entry_param, field).unwrap()
                    }
                    WidgetTemplatePart::Formatted(expr) => {
                        write!(s, "${{{}}}", emit_format_kotlin(expr, entry_param)).unwrap()
                    }
                }
            }
            s.push('"');
            s
        }
        WidgetTextContent::Formatted(expr) => {
            format!("\"${{{}}}\"", emit_format_kotlin(expr, entry_param))
        }
    }
}

/// Issue #1179 follow-up: emit a Kotlin expression for one of the
/// whitelisted formatter calls. Result is a String-typed expression.
fn emit_format_kotlin(expr: &WidgetFormatExpr, entry_param: &str) -> String {
    let value = emit_format_arg_kotlin(&expr.arg, entry_param);
    match expr.call {
        WidgetFormatCall::StringCast | WidgetFormatCall::ToString => {
            format!("{}.toString()", value)
        }
        WidgetFormatCall::NumberCast => format!("{}.toDouble()", value),
        WidgetFormatCall::Round => format!("kotlin.math.round({}).toInt()", value),
        WidgetFormatCall::Floor => format!("kotlin.math.floor({}).toInt()", value),
        WidgetFormatCall::Ceil => format!("kotlin.math.ceil({}).toInt()", value),
        WidgetFormatCall::ToFixed { digits } => {
            format!("\"%.{}f\".format({})", digits, value)
        }
    }
}

fn emit_format_arg_kotlin(arg: &WidgetFormatArg, entry_param: &str) -> String {
    match arg {
        WidgetFormatArg::Field(field) => format!("{}.{}", entry_param, field),
        WidgetFormatArg::Number(n) => {
            if n.is_finite() && *n == n.floor() {
                format!("{:.0}", n)
            } else {
                format!("{}", n)
            }
        }
        WidgetFormatArg::String(s) => format!("\"{}\"", escape_kotlin_string(s)),
    }
}

fn emit_kotlin_condition(
    field: &str,
    op: &WidgetConditionOp,
    value: &WidgetTextContent,
    entry_param: &str,
) -> String {
    let lhs = format!("{}.{}", entry_param, field);
    match op {
        WidgetConditionOp::Truthy => lhs,
        WidgetConditionOp::GreaterThan => format!("{} > {}", lhs, emit_kotlin_value(value)),
        WidgetConditionOp::LessThan => format!("{} < {}", lhs, emit_kotlin_value(value)),
        WidgetConditionOp::Equals => format!("{} == {}", lhs, emit_kotlin_value(value)),
        WidgetConditionOp::NotEquals => format!("{} != {}", lhs, emit_kotlin_value(value)),
    }
}

fn emit_kotlin_value(value: &WidgetTextContent) -> String {
    match value {
        WidgetTextContent::Literal(s) => {
            if let Ok(n) = s.parse::<f64>() {
                if n == n.floor() {
                    format!("{:.0}", n)
                } else {
                    format!("{}", n)
                }
            } else {
                format!("\"{}\"", escape_kotlin_string(s))
            }
        }
        WidgetTextContent::Field(f) => f.clone(),
        WidgetTextContent::Template(_) => "\"\"".to_string(),
        // Conditions don't compare against formatted expressions today.
        WidgetTextContent::Formatted(_) => "\"\"".to_string(),
    }
}

fn kotlin_type_for_field(field_type: &WidgetFieldType) -> String {
    match field_type {
        WidgetFieldType::String => "String".to_string(),
        WidgetFieldType::Number => "Double".to_string(),
        WidgetFieldType::Boolean => "Boolean".to_string(),
        WidgetFieldType::Array(inner) => format!("List<{}>", kotlin_type_for_field(inner)),
        WidgetFieldType::Optional(inner) => format!("{}?", kotlin_type_for_field(inner)),
        WidgetFieldType::Object(_) => "Map<String, Any>".to_string(),
    }
}

fn kotlin_default_value(field_type: &WidgetFieldType) -> String {
    match field_type {
        WidgetFieldType::String => "\"\"".to_string(),
        WidgetFieldType::Number => "0.0".to_string(),
        WidgetFieldType::Boolean => "false".to_string(),
        WidgetFieldType::Array(_) => "emptyList()".to_string(),
        WidgetFieldType::Optional(_) => "null".to_string(),
        WidgetFieldType::Object(_) => "emptyMap()".to_string(),
    }
}

fn escape_kotlin_string(s: &str) -> String {
    s.replace('\\', "\\\\")
        .replace('"', "\\\"")
        .replace('\n', "\\n")
        .replace('\r', "\\r")
        .replace('\t', "\\t")
        .replace('$', "\\$")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_tile_service_emission() {
        let widget = WidgetDecl {
            kind: "com.test.Stats".to_string(),
            display_name: "Stats".to_string(),
            description: "Quick stats".to_string(),
            supported_families: vec!["accessoryCircular".to_string()],
            entry_fields: vec![("totalClicks".to_string(), WidgetFieldType::Number)],
            render_body: vec![WidgetNode::Text {
                content: WidgetTextContent::Field("totalClicks".to_string()),
                modifiers: vec![],
            }],
            entry_param_name: "entry".to_string(),
            config_params: vec![],
            provider_func_name: None,
            placeholder: None,
            family_param_name: None,
            app_group: None,
            reload_after_seconds: None,
        };

        let kt = emit_tile_service(&widget, "Stats", "com.test");
        assert!(kt.contains("class StatsTileService : SuspendingTileService()"));
        assert!(kt.contains("data class StatsEntry"));
    }

    #[test]
    fn test_manifest_snippet() {
        let manifest = emit_manifest_snippet("Stats", "com.test");
        assert!(manifest.contains("StatsTileService"));
        assert!(manifest.contains("BIND_TILE_PROVIDER"));
    }
}
