const std = @import("std");
const ast = @import("ast.zig");
const Node = ast.Node;

/// Editor metadata type model, used only by `src/sema.zig` (the language-server
/// symbol/type index) for navigation, completion, and hover. NOT the compiler's
/// source of truth: lowering, codegen, and layout use the canonical
/// `TypeId` / `TypeTable` model in `src/ir/types.zig`. Do not expand this to
/// carry new compiler semantics; the architecture endpoint (phase A8) is to
/// delete it or reduce it to display-only data derived from `TypeId`.
pub const Type = union(enum) {
    // Variable-width integers (1–64 bits)
    signed: u8,
    unsigned: u8,
    // Fixed-width floats
    f32,
    f64,
    // Other
    void_type,
    boolean,
    string_type,
    enum_type: []const u8,
    struct_type: []const u8,
    union_type: []const u8,
    array_type: ArrayTypeInfo,
    slice_type: SliceTypeInfo,
    pointer_type: PointerTypeInfo,
    many_pointer_type: ManyPointerTypeInfo,
    vector_type: VectorTypeInfo,
    function_type: FunctionTypeInfo,
    closure_type: ClosureTypeInfo,
    any_type,
    usize_type,
    isize_type,
    optional_type: OptionalTypeInfo,
    meta_type: MetaTypeInfo,
    tuple_type: TupleTypeInfo,
    /// Type resolution failed (sema couldn't infer/resolve). A dedicated
    /// sentinel — never a legitimate type — so callers can't mistake it for a
    /// real result the way a fabricated `s(64)` would be. Mirrors
    /// `ir.TypeId.unresolved`.
    unresolved,

    pub const SliceTypeInfo = struct {
        element_name: []const u8,
    };

    pub const PointerTypeInfo = struct {
        pointee_name: []const u8,
    };

    pub const ManyPointerTypeInfo = struct {
        element_name: []const u8,
    };

    pub const FunctionTypeInfo = struct {
        param_types: []const Type,
        return_type: *const Type,
    };

    pub const ClosureTypeInfo = struct {
        param_types: []const Type,
        return_type: *const Type,
    };

    pub const ArrayTypeInfo = struct {
        element_name: []const u8,
        length: u32,
    };

    pub const VectorTypeInfo = struct {
        element_name: []const u8,
        length: u32,
    };

    pub const OptionalTypeInfo = struct {
        child_name: []const u8,
    };

    pub const MetaTypeInfo = struct {
        name: []const u8,
    };

    pub const TupleTypeInfo = struct {
        field_names: ?[]const []const u8, // null for positional tuples
        field_types: []const Type,
    };

    /// Content-based equality: compares string fields by content, not pointer identity.
    pub fn eql(self: Type, other: Type) bool {
        const Tag = std.meta.Tag(Type);
        const self_tag: Tag = self;
        const other_tag: Tag = other;
        if (self_tag != other_tag) return false;
        return switch (self) {
            .signed => |w| w == other.signed,
            .unsigned => |w| w == other.unsigned,
            .f32, .f64, .void_type, .boolean, .string_type, .any_type, .usize_type, .isize_type, .unresolved => true,
            .enum_type => |n| std.mem.eql(u8, n, other.enum_type),
            .struct_type => |n| std.mem.eql(u8, n, other.struct_type),
            .union_type => |n| std.mem.eql(u8, n, other.union_type),
            .array_type => |info| info.length == other.array_type.length and
                std.mem.eql(u8, info.element_name, other.array_type.element_name),
            .slice_type => |info| std.mem.eql(u8, info.element_name, other.slice_type.element_name),
            .pointer_type => |info| std.mem.eql(u8, info.pointee_name, other.pointer_type.pointee_name),
            .many_pointer_type => |info| std.mem.eql(u8, info.element_name, other.many_pointer_type.element_name),
            .vector_type => |info| info.length == other.vector_type.length and
                std.mem.eql(u8, info.element_name, other.vector_type.element_name),
            .function_type => |info| {
                const o = other.function_type;
                if (info.param_types.len != o.param_types.len) return false;
                for (info.param_types, o.param_types) |a, b| {
                    if (!a.eql(b)) return false;
                }
                return info.return_type.eql(o.return_type.*);
            },
            .closure_type => |info| {
                const o = other.closure_type;
                if (info.param_types.len != o.param_types.len) return false;
                for (info.param_types, o.param_types) |a, b| {
                    if (!a.eql(b)) return false;
                }
                return info.return_type.eql(o.return_type.*);
            },
            .optional_type => |info| std.mem.eql(u8, info.child_name, other.optional_type.child_name),
            .meta_type => |info| std.mem.eql(u8, info.name, other.meta_type.name),
            .tuple_type => |info| {
                const o = other.tuple_type;
                if (info.field_types.len != o.field_types.len) return false;
                for (info.field_types, o.field_types) |a, b| {
                    if (!a.eql(b)) return false;
                }
                // If both have names, compare them
                if (info.field_names != null and o.field_names != null) {
                    for (info.field_names.?, o.field_names.?) |a, b| {
                        if (!std.mem.eql(u8, a, b)) return false;
                    }
                }
                return true;
            },
        };
    }

    // Convenience constructors
    pub fn s(width: u8) Type {
        return .{ .signed = width };
    }

    pub fn u(width: u8) Type {
        return .{ .unsigned = width };
    }

    pub fn fromName(name: []const u8) ?Type {
        if (name.len == 0) return null;
        return switch (name[0]) {
            's' => {
                if (std.mem.eql(u8, name, "string")) return .string_type;
                if (name.len >= 2) {
                    const width = std.fmt.parseInt(u8, name[1..], 10) catch return null;
                    if (width >= 1 and width <= 64) return Type.s(width);
                }
                return null;
            },
            'u' => {
                if (std.mem.eql(u8, name, "usize")) return .usize_type;
                if (name.len >= 2) {
                    const width = std.fmt.parseInt(u8, name[1..], 10) catch return null;
                    if (width >= 1 and width <= 64) return Type.u(width);
                }
                return null;
            },
            'i' => {
                if (std.mem.eql(u8, name, "isize")) return .isize_type;
                return null;
            },
            'b' => if (std.mem.eql(u8, name, "bool")) .boolean else null,
            'f' => {
                if (std.mem.eql(u8, name, "f32")) return .f32;
                if (std.mem.eql(u8, name, "f64")) return .f64;
                return null;
            },
            '?' => if (name.len >= 2) .{ .optional_type = .{ .child_name = name[1..] } } else null,
            'A' => if (std.mem.eql(u8, name, "Any")) .any_type else null,
            'v' => if (std.mem.eql(u8, name, "void")) .void_type else null,
            '[' => {
                // Sentinel-terminated slice: [:0]u8 → string_type
                if (name.len >= 5 and name[1] == ':') {
                    if (std.mem.indexOfScalar(u8, name, ']')) |close| {
                        const sentinel = name[2..close];
                        const elem = name[close + 1 ..];
                        if (std.mem.eql(u8, sentinel, "0") and std.mem.eql(u8, elem, "u8")) {
                            return .string_type;
                        }
                    }
                }
                // Many-pointer: [*]T
                if (name.len >= 4 and name[1] == '*' and name[2] == ']') {
                    return .{ .many_pointer_type = .{ .element_name = name[3..] } };
                }
                return null;
            },
            '*' => if (name.len >= 2) .{ .pointer_type = .{ .pointee_name = name[1..] } } else null,
            'V' => {
                // Vector(N,T)
                if (name.len >= 10 and std.mem.startsWith(u8, name, "Vector(") and name[name.len - 1] == ')') {
                    const inner = name[7 .. name.len - 1];
                    if (std.mem.indexOfScalar(u8, inner, ',')) |comma| {
                        const length = std.fmt.parseInt(u32, inner[0..comma], 10) catch return null;
                        const elem_name = inner[comma + 1 ..];
                        if (elem_name.len > 0) {
                            return .{ .vector_type = .{ .element_name = elem_name, .length = length } };
                        }
                    }
                }
                return null;
            },
            else => null,
        };
    }

    /// Returns the canonical type name for this type, or null for complex types.
    /// Used for looking up impl methods on non-struct types (e.g., s32.eq).
    pub fn toName(self: Type) ?[]const u8 {
        return switch (self) {
            .signed => |w| switch (w) {
                8 => "s8",
                16 => "s16",
                32 => "s32",
                64 => "s64",
                else => null,
            },
            .unsigned => |w| switch (w) {
                8 => "u8",
                16 => "u16",
                32 => "u32",
                64 => "u64",
                else => null,
            },
            .f32 => "f32",
            .f64 => "f64",
            .boolean => "bool",
            .string_type => "string",
            .void_type => "void",
            .usize_type => "usize",
            .isize_type => "isize",
            .struct_type => |n| n,
            .enum_type => |n| n,
            .union_type => |n| n,
            else => null,
        };
    }

    pub fn fromTypeExpr(node: *Node) ?Type {
        if (node.data != .type_expr) return null;
        return fromName(node.data.type_expr.name);
    }

    pub fn isEnum(self: Type) bool {
        return switch (self) {
            .enum_type => true,
            else => false,
        };
    }

    pub fn isStruct(self: Type) bool {
        return switch (self) {
            .struct_type => true,
            else => false,
        };
    }

    pub fn isUnion(self: Type) bool {
        return switch (self) {
            .union_type => true,
            else => false,
        };
    }

    pub fn isTuple(self: Type) bool {
        return switch (self) {
            .tuple_type => true,
            else => false,
        };
    }

    pub fn isOptional(self: Type) bool {
        return switch (self) {
            .optional_type => true,
            else => false,
        };
    }

    pub fn optionalChild(self: Type) ?[]const u8 {
        return switch (self) {
            .optional_type => |info| info.child_name,
            else => null,
        };
    }

    pub fn isAny(self: Type) bool {
        return switch (self) {
            .any_type => true,
            else => false,
        };
    }

    pub fn isString(self: Type) bool {
        return self == .string_type;
    }

    /// Returns true for both `string` (null-terminated) and `[]u8` (byte slice)
    pub fn isStringLike(self: Type) bool {
        if (self == .string_type) return true;
        if (self.isSlice()) {
            return std.mem.eql(u8, self.slice_type.element_name, "u8");
        }
        return false;
    }

    pub fn isSlice(self: Type) bool {
        return switch (self) {
            .slice_type => true,
            else => false,
        };
    }

    pub fn sliceElementType(self: Type) ?Type {
        return switch (self) {
            .slice_type => |info| fromName(info.element_name),
            else => null,
        };
    }

    pub fn isPointer(self: Type) bool {
        return switch (self) {
            .pointer_type => true,
            else => false,
        };
    }

    pub fn pointerPointeeType(self: Type) ?Type {
        return switch (self) {
            .pointer_type => |info| fromName(info.pointee_name),
            else => null,
        };
    }

    pub fn isManyPointer(self: Type) bool {
        return switch (self) {
            .many_pointer_type => true,
            else => false,
        };
    }

    pub fn manyPointerElementType(self: Type) ?Type {
        return switch (self) {
            .many_pointer_type => |info| fromName(info.element_name),
            else => null,
        };
    }

    pub fn isFunctionType(self: Type) bool {
        return switch (self) {
            .function_type => true,
            else => false,
        };
    }

    pub fn isClosureType(self: Type) bool {
        return switch (self) {
            .closure_type => true,
            else => false,
        };
    }

    /// Returns true for both bare function pointers and closures
    pub fn isCallable(self: Type) bool {
        return switch (self) {
            .function_type, .closure_type => true,
            else => false,
        };
    }

    pub fn isArray(self: Type) bool {
        return switch (self) {
            .array_type => true,
            else => false,
        };
    }

    pub fn isVector(self: Type) bool {
        return switch (self) {
            .vector_type => true,
            else => false,
        };
    }

    pub fn vectorElementType(self: Type) ?Type {
        return switch (self) {
            .vector_type => |info| fromName(info.element_name),
            else => null,
        };
    }

    pub fn isFloat(self: Type) bool {
        return switch (self) {
            .f32, .f64 => true,
            else => false,
        };
    }

    pub fn isInt(self: Type) bool {
        return self.isSigned() or self.isUnsigned();
    }

    pub fn isSigned(self: Type) bool {
        return switch (self) {
            .signed => true,
            else => false,
        };
    }

    pub fn isUnsigned(self: Type) bool {
        return switch (self) {
            .unsigned => true,
            else => false,
        };
    }

    pub fn bitWidth(self: Type) u32 {
        return switch (self) {
            .signed => |w| w,
            .unsigned => |w| w,
            .f32 => 32,
            .f64 => 64,
            .boolean => 1,
            .pointer_type, .many_pointer_type, .function_type => 64,
            .closure_type => 128, // { ptr, ptr } = 16 bytes
            else => 0,
        };
    }

    /// Check if this type can be implicitly converted to `target` without `xx`.
    /// Safe (implicit) conversions:
    ///   - Same type
    ///   - Both unsigned int, target width >= source width
    ///   - Both signed int, target width >= source width
    ///   - Unsigned to signed, target width strictly > source width
    ///   - Any int to any float
    ///   - Float to wider float (f32 → f64)
    /// Everything else requires `xx`.
    pub fn isImplicitlyConvertibleTo(self: Type, target: Type) bool {
        if (self.eql(target)) return true;
        // string <-> []u8: same layout, bidirectional implicit conversion
        if (self == .string_type and target.isSlice() and
            std.mem.eql(u8, target.slice_type.element_name, "u8")) return true;
        if (self.isSlice() and std.mem.eql(u8, self.slice_type.element_name, "u8") and
            target == .string_type) return true;
        // *void is universal pointer (both directions)
        if (self.isPointer() and target.isPointer()) {
            if (std.mem.eql(u8, self.pointer_type.pointee_name, "void")) return true;
            if (std.mem.eql(u8, target.pointer_type.pointee_name, "void")) return true;
        }
        // *T → [*]T: pointer to element is implicitly convertible to many-pointer
        // null (*void) → [*]T is also allowed
        if (self.isPointer() and target.isManyPointer()) {
            if (std.mem.eql(u8, self.pointer_type.pointee_name, "void")) return true;
            return std.mem.eql(u8, self.pointer_type.pointee_name, target.many_pointer_type.element_name);
        }
        // [*]T → *void: any many-pointer converts to void pointer
        if (self.isManyPointer() and target.isPointer()) {
            return std.mem.eql(u8, target.pointer_type.pointee_name, "void");
        }

        // Tuple → tuple: same field count and each field implicitly convertible
        if (self.isTuple() and target.isTuple()) {
            const si = self.tuple_type;
            const ti = target.tuple_type;
            if (si.field_types.len != ti.field_types.len) return false;
            for (si.field_types, ti.field_types) |sf, tf| {
                if (!sf.isImplicitlyConvertibleTo(tf)) return false;
            }
            return true;
        }

        // T → ?T: any type implicitly wraps into its optional
        if (target.isOptional()) {
            const child_name = target.optional_type.child_name;
            // null → ?T
            if (self.isPointer() and std.mem.eql(u8, self.pointer_type.pointee_name, "void")) return true;
            // ?T → ?U when T → U
            if (self.isOptional()) {
                const self_child = fromName(self.optional_type.child_name) orelse return false;
                const target_child = fromName(child_name) orelse return false;
                return self_child.isImplicitlyConvertibleTo(target_child);
            }
            // T → ?T: check if self matches the child type
            if (fromName(child_name)) |child_type| {
                return self.eql(child_type) or self.isImplicitlyConvertibleTo(child_type);
            }
            // Non-primitive child (struct/enum name): compare by name
            return switch (self) {
                .struct_type => |n| std.mem.eql(u8, n, child_name),
                .enum_type => |n| std.mem.eql(u8, n, child_name),
                .union_type => |n| std.mem.eql(u8, n, child_name),
                else => false,
            };
        }

        const src_float = self.isFloat();
        const dst_float = target.isFloat();
        const src_int = self.isInt();

        // Float → wider float
        if (src_float and dst_float) {
            return target.bitWidth() >= self.bitWidth();
        }

        // Int → float (always safe)
        if (src_int and dst_float) return true;

        // Both unsigned → target width >= source width
        if (self.isUnsigned() and target.isUnsigned()) {
            return target.bitWidth() >= self.bitWidth();
        }

        // Both signed → target width >= source width
        if (self.isSigned() and target.isSigned()) {
            return target.bitWidth() >= self.bitWidth();
        }

        // Unsigned → signed: target must be strictly wider
        if (self.isUnsigned() and target.isSigned()) {
            return target.bitWidth() > self.bitWidth();
        }

        // Everything else requires xx
        return false;
    }

    fn fmtAlloc(allocator: std.mem.Allocator, comptime fmt: []const u8, args: anytype) ![]const u8 {
        var buf: [128]u8 = undefined;
        const result = std.fmt.bufPrint(&buf, fmt, args) catch
            return try std.fmt.allocPrint(allocator, fmt, args);
        return try allocator.dupe(u8, result);
    }

    /// Format type name for mangling and display (e.g. "s32", "u8", "f64")
    pub fn displayName(self: Type, allocator: std.mem.Allocator) ![]const u8 {
        return switch (self) {
            .signed => |w| {
                var buf: [4]u8 = undefined;
                const result = std.fmt.bufPrint(&buf, "s{d}", .{w}) catch unreachable;
                return try allocator.dupe(u8, result);
            },
            .unsigned => |w| {
                var buf: [4]u8 = undefined;
                const result = std.fmt.bufPrint(&buf, "u{d}", .{w}) catch unreachable;
                return try allocator.dupe(u8, result);
            },
            .f32 => "f32",
            .f64 => "f64",
            .boolean => "bool",
            .string_type => "string",
            .void_type => "void",
            .any_type => "Any",
            .usize_type => "usize",
            .isize_type => "isize",
            .unresolved => "<unresolved>",
            .enum_type => |name| name,
            .struct_type => |name| name,
            .union_type => |name| name,
            .slice_type => |info| return fmtAlloc(allocator, "[]{s}", .{info.element_name}),
            .pointer_type => |info| return fmtAlloc(allocator, "*{s}", .{info.pointee_name}),
            .many_pointer_type => |info| return fmtAlloc(allocator, "[*]{s}", .{info.element_name}),
            .array_type => |info| return fmtAlloc(allocator, "[{d}]{s}", .{ info.length, info.element_name }),
            .vector_type => |info| return fmtAlloc(allocator, "Vector({d},{s})", .{ info.length, info.element_name }),
            .function_type => |info| {
                var buf = std.ArrayList(u8).empty;
                try buf.append(allocator, '(');
                for (info.param_types, 0..) |pt, i| {
                    if (i > 0) try buf.appendSlice(allocator, ", ");
                    try buf.appendSlice(allocator, try pt.displayName(allocator));
                }
                try buf.append(allocator, ')');
                if (!std.meta.eql(info.return_type.*, Type.void_type)) {
                    try buf.appendSlice(allocator, " -> ");
                    try buf.appendSlice(allocator, try info.return_type.displayName(allocator));
                }
                return try buf.toOwnedSlice(allocator);
            },
            .closure_type => |info| {
                var buf = std.ArrayList(u8).empty;
                try buf.appendSlice(allocator, "Closure(");
                for (info.param_types, 0..) |pt, i| {
                    if (i > 0) try buf.appendSlice(allocator, ", ");
                    try buf.appendSlice(allocator, try pt.displayName(allocator));
                }
                try buf.append(allocator, ')');
                if (!std.meta.eql(info.return_type.*, Type.void_type)) {
                    try buf.appendSlice(allocator, " -> ");
                    try buf.appendSlice(allocator, try info.return_type.displayName(allocator));
                }
                return try buf.toOwnedSlice(allocator);
            },
            .optional_type => |info| return fmtAlloc(allocator, "?{s}", .{info.child_name}),
            .meta_type => |info| info.name,
            .tuple_type => |info| {
                var buf = std.ArrayList(u8).empty;
                try buf.append(allocator, '(');
                for (info.field_types, 0..) |ft, i| {
                    if (i > 0) try buf.appendSlice(allocator, ", ");
                    if (info.field_names) |names| {
                        try buf.appendSlice(allocator, names[i]);
                        try buf.appendSlice(allocator, ": ");
                    }
                    try buf.appendSlice(allocator, try ft.displayName(allocator));
                }
                try buf.append(allocator, ')');
                return try buf.toOwnedSlice(allocator);
            },
        };
    }

    /// Widen two types to a common type for binary operations.
    /// Used for arithmetic type promotion (e.g., s16 + s32 → s32, int + float → float).
    pub fn widen(a: Type, b: Type) Type {
        // Same type → return it
        if (a.eql(b)) return a;

        // Tuple + tuple → return a if same field count
        if (a.isTuple() and b.isTuple()) {
            if (a.tuple_type.field_types.len == b.tuple_type.field_types.len) return a;
        }

        // Vector + vector of same dimensions → return a
        if (a.isVector() and b.isVector()) return a;
        // Vector + scalar → return vector (scalar will be broadcast)
        if (a.isVector() and !b.isVector()) return a;
        if (b.isVector() and !a.isVector()) return b;

        const a_float = a.isFloat();
        const b_float = b.isFloat();
        const a_int = a.isInt();
        const b_int = b.isInt();

        // Both float → wider float
        if (a_float and b_float) {
            return if (a.bitWidth() >= b.bitWidth()) a else b;
        }

        // int + float → float
        if (a_int and b_float) return b;
        if (b_int and a_float) return a;

        // Both signed → wider signed
        if (a.isSigned() and b.isSigned()) {
            return Type.s(@intCast(@max(a.bitWidth(), b.bitWidth())));
        }

        // Both unsigned → wider unsigned
        if (a.isUnsigned() and b.isUnsigned()) {
            return Type.u(@intCast(@max(a.bitWidth(), b.bitWidth())));
        }

        // signed + unsigned (mixed)
        if (a_int and b_int) {
            const aw = a.bitWidth();
            const bw = b.bitWidth();
            const max_w = @max(aw, bw);
            // If same width, need one extra bit for sign; otherwise max is enough
            const need: u32 = if (aw == bw) max_w + 1 else max_w;
            const capped: u8 = @intCast(@min(need, 128));
            return Type.s(capped);
        }

        // Optional types: widen inner types
        if (a.isOptional() and b.isOptional()) return a;

        // Pointer types: both are pointers → return first (all are opaque ptr at LLVM level)
        if ((a.isPointer() or a.isManyPointer()) and (b.isPointer() or b.isManyPointer())) return a;

        return a;
    }
};
