const std = @import("std");
const ast = @import("../ast.zig");
const types = @import("types.zig");
const inst = @import("inst.zig");

const Node = ast.Node;
const TypeId = types.TypeId;

/// Owned copy of a generic struct template (AST pointers are copied/interned to survive imports)
pub const StructTemplate = struct {
    name: []const u8,
    type_params: []const TemplateParam,
    field_names: []const []const u8,
    field_type_nodes: []const *const Node, // raw AST pointers — must be copied from heap nodes
};
pub const TemplateParam = struct {
    name: []const u8,
    is_type_param: bool, // true for $T: Type, false for $N: u32
    is_variadic: bool = false, // `..$Ts: []Type` — binds remaining type args as a pack
};

pub const ProtocolMethodInfo = struct {
    name: []const u8,
    param_types: []const TypeId, // excluding self
    ret_type: TypeId,
    // True when the AST return type was `Self` (encoded here as *void).
    // Lets the dispatcher distinguish Self-disguised-as-*void (auto-unbox
    // on the caller side) from a literal `-> *void` (return as-is).
    ret_is_self: bool = false,
};

pub const ProtocolDeclInfo = struct {
    name: []const u8,
    is_inline: bool,
    methods: []const ProtocolMethodInfo,
};

pub const ModuleConstInfo = struct {
    value: *const Node,
    ty: TypeId,
};

pub const GlobalInfo = struct { id: inst.GlobalId, ty: TypeId };

/// Single lowering access point for declaration-name / import / visibility
/// facts. The architecture stream (`current/PLAN-ARCH.md`, phase A1) extracts
/// these out of the `Lowering` state bag incrementally. `Lowering` embeds one
/// `ProgramIndex` by value and reaches every moved fact through
/// `self.program_index.<field>`; later phases hand collaborator modules a
/// `*ProgramIndex` instead of `*Lowering`.
///
/// OWNS the declaration maps below. BORROWS `module_scopes` / `import_graph`
/// (pointers into maps owned by the compilation driver, `core.zig`) — those
/// are read-only views and are never freed here.
///
/// Per-map allocators are preserved exactly as they were on `Lowering`:
/// `import_flags` / `fn_ast_map` / `global_names` use the lowering allocator
/// (set in `init`); the rest default to `page_allocator`. Written only by the
/// declaration scan / registration code in `Lowering`; read everywhere else.
pub const ProgramIndex = struct {
    // ── Import / visibility ──
    /// Declaration name → is the function imported (declared `extern`)?
    import_flags: std.StringHashMap(bool),
    /// Per-module visible names, keyed by source file. Borrowed view.
    module_scopes: ?*std.StringHashMap(std.StringHashMap(void)) = null,
    /// Module path → set of directly imported paths (param_impl visibility
    /// filter). Borrowed view.
    import_graph: ?*std.StringHashMap(std.StringHashMap(void)) = null,

    // ── Declaration maps ──
    /// Function name → AST decl.
    fn_ast_map: std.StringHashMap(*const ast.FnDecl),
    /// sx alias → ForeignClassDecl (jni_class / objc_class / swift_class / ... — registered in scan pass).
    foreign_class_map: std.StringHashMap(*const ast.ForeignClassDecl) = std.StringHashMap(*const ast.ForeignClassDecl).init(std.heap.page_allocator),
    /// `#run` global name → GlobalId.
    global_names: std.StringHashMap(GlobalInfo),
    /// Type alias name → target TypeId. The single-source alias table; passed
    /// explicitly to `TypeResolver` / `type_bridge` resolution (no borrow).
    type_alias_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator),
    /// Generic struct name → template.
    struct_template_map: std.StringHashMap(StructTemplate) = std.StringHashMap(StructTemplate).init(std.heap.page_allocator),
    /// Protocol name → protocol info.
    protocol_decl_map: std.StringHashMap(ProtocolDeclInfo) = std.StringHashMap(ProtocolDeclInfo).init(std.heap.page_allocator),
    /// Protocol name → AST node.
    protocol_ast_map: std.StringHashMap(*const ast.ProtocolDecl) = std.StringHashMap(*const ast.ProtocolDecl).init(std.heap.page_allocator),
    /// Module-level value constants (e.g. AF_INET :s32: 2).
    module_const_map: std.StringHashMap(ModuleConstInfo) = std.StringHashMap(ModuleConstInfo).init(std.heap.page_allocator),
    /// UFCS alias name → target function name.
    ufcs_alias_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator),

    pub fn init(alloc: std.mem.Allocator) ProgramIndex {
        return .{
            .import_flags = std.StringHashMap(bool).init(alloc),
            .fn_ast_map = std.StringHashMap(*const ast.FnDecl).init(alloc),
            .global_names = std.StringHashMap(GlobalInfo).init(alloc),
        };
    }

    pub fn deinit(self: *ProgramIndex) void {
        // Owned maps only — module_scopes / import_graph are borrowed.
        self.import_flags.deinit();
        self.fn_ast_map.deinit();
        self.foreign_class_map.deinit();
        self.global_names.deinit();
        self.type_alias_map.deinit();
        self.struct_template_map.deinit();
        self.protocol_decl_map.deinit();
        self.protocol_ast_map.deinit();
        self.module_const_map.deinit();
        self.ufcs_alias_map.deinit();
    }
};
