//! The ACPI namespace the AML parser builds: a tree of named nodes, plus the name
//! resolution rules the parser needs while it walks (so a method invocation can be
//! resolved to its declaration to learn its argument count).
//!
//! Nodes are individually allocated and linked intrusively (first-child /
//! next-sibling), the same shape as the device tree in `device.zig`.

const std = @import("std");

pub const NodeKind = enum {
    root,
    scope,
    device,
    method,
    name,
    region, // OperationRegion
    field, // a Field unit
    mutex,
    event,
    processor,
    power_resource,
    thermal_zone,
    alias,
    external,
    other,
};

pub const Node = struct {
    /// The 4-byte NameSeg identifying this node within its parent. The root uses
    /// all-zero.
    segment: [4]u8 = .{ 0, 0, 0, 0 },
    kind: NodeKind = .other,
    /// For Method / External: the declared argument count (0..7). Used to resolve
    /// how many TermArgs a method invocation consumes.
    arg_count: u8 = 0,
    /// For Name: the AML bytes of its DataReferenceObject (so a value like a sleep
    /// state's (`_Sx`) Package can be parsed on demand). For Method: the AML bytes of the body,
    /// interpreted on demand by the evaluator. Empty otherwise.
    value: []const u8 = &.{},

    // OperationRegion metadata (kind == .region): the address space, plus the AML
    // of the offset/length expressions (evaluated lazily, usually constants).
    region_space: u8 = 0,
    region_offset_aml: []const u8 = &.{},
    region_len_aml: []const u8 = &.{},

    // Field-unit metadata (kind == .field): which region it lives in and its bit
    // position/width/access, so the evaluator can read/write it.
    region: ?*Node = null,
    bit_offset: u32 = 0,
    bit_width: u32 = 0,
    access_type: u8 = 0,

    parent: ?*Node = null,
    first_child: ?*Node = null,
    next_sibling: ?*Node = null,

    /// Depth-first count of this node and everything under it.
    pub fn subtreeCount(self: *const Node) usize {
        var n: usize = 1;
        var c = self.first_child;
        while (c) |child| : (c = child.next_sibling) n += child.subtreeCount();
        return n;
    }
};

pub const Namespace = struct {
    allocator: std.mem.Allocator,
    root: *Node,

    pub fn init(allocator: std.mem.Allocator) !Namespace {
        const root = try allocator.create(Node);
        root.* = .{ .kind = .root };
        return .{ .allocator = allocator, .root = root };
    }

    pub fn nodeCount(self: *const Namespace) usize {
        return self.root.subtreeCount();
    }

    fn findChild(parent: *Node, segment: [4]u8) ?*Node {
        var c = parent.first_child;
        while (c) |child| : (c = child.next_sibling) {
            if (std.mem.eql(u8, &child.segment, &segment)) return child;
        }
        return null;
    }

    /// The direct child of `node` named `segment`, or null. Unlike `resolve`, this does
    /// not apply the search-rule walk-up — it looks only at immediate children (for
    /// reading a device's own hardware ID (`_HID`) / current resource settings (`_CRS`)).
    pub fn childOf(node: *Node, segment: [4]u8) ?*Node {
        return findChild(node, segment);
    }

    fn newChild(self: *Namespace, parent: *Node, segment: [4]u8, kind: NodeKind) !*Node {
        const n = try self.allocator.create(Node);
        n.* = .{ .segment = segment, .kind = kind, .parent = parent };
        // Append at the tail so a dump reads in declaration order.
        if (parent.first_child == null) {
            parent.first_child = n;
        } else {
            var current = parent.first_child.?;
            while (current.next_sibling) |sib| current = sib;
            current.next_sibling = n;
        }
        return n;
    }

    /// Create a Field unit node directly under `scope` (field units live in the
    /// scope of the Field/IndexField/BankField, not under the region).
    pub fn newFieldUnit(self: *Namespace, scope: *Node, segment: [4]u8) !*Node {
        return self.findOrCreate(scope, segment, .field);
    }

    fn findOrCreate(self: *Namespace, parent: *Node, segment: [4]u8, kind: NodeKind) !*Node {
        if (findChild(parent, segment)) |existing| {
            // Reopening a scope (e.g. Scope(\_SB) after Device \_SB) keeps the more
            // specific kind rather than downgrading to a plain scope.
            if (existing.kind == .scope and kind != .scope) existing.kind = kind;
            return existing;
        }
        return self.newChild(parent, segment, kind);
    }

    /// The node a definition's NameString names, creating any intermediate scopes.
    /// The final segment is created (or found) with `kind`; intermediates are
    /// scopes. Returns the namespace root for a NullName (empty path).
    pub fn place(
        self: *Namespace,
        current: *Node,
        rooted: bool,
        parents: u8,
        segments: []const [4]u8,
        kind: NodeKind,
    ) !*Node {
        var base = startNode(self, current, rooted, parents);
        if (segments.len == 0) return base;
        var i: usize = 0;
        while (i + 1 < segments.len) : (i += 1) {
            base = try self.findOrCreate(base, segments[i], .scope);
        }
        return self.findOrCreate(base, segments[segments.len - 1], kind);
    }

    /// Resolve a NameString *reference* to an existing node, or null. A single
    /// relative segment uses the ACPI search rule (walk up the ancestors); any
    /// rooted, parented, or multi-segment path is resolved exactly.
    pub fn resolve(
        self: *Namespace,
        current: *Node,
        rooted: bool,
        parents: u8,
        segments: []const [4]u8,
    ) ?*Node {
        if (segments.len == 0) return null;

        if (!rooted and parents == 0 and segments.len == 1) {
            // Search rule: this scope, then each ancestor up to the root.
            var scope: ?*Node = current;
            while (scope) |s| : (scope = s.parent) {
                if (findChild(s, segments[0])) |n| return n;
            }
            return null;
        }

        var base = startNode(self, current, rooted, parents);
        for (segments) |segment| {
            base = findChild(base, segment) orelse return null;
        }
        return base;
    }

    fn startNode(self: *Namespace, current: *Node, rooted: bool, parents: u8) *Node {
        if (rooted) return self.root;
        var base = current;
        var up = parents;
        while (up > 0) : (up -= 1) base = base.parent orelse self.root;
        return base;
    }
};
