const std = @import("std");

/// Options set by Zig build.zig and exposed via `terminal_options`.
pub const Options = struct {
    pub const Artifact = enum {
        /// Ghostty application
        ghostty,

        /// libghostty-vt, Zig module
        lib,
    };

    /// The target artifact to build. This will gate some functionality.
    artifact: Artifact,

    /// Whether Oniguruma regex support is available. If this isn't
    /// available, some features will be disabled. This may be outdated,
    /// but the specific disabled features are:
    ///
    /// - Kitty graphics protocol
    /// - Tmux control mode
    ///
    oniguruma: bool,

    /// Whether to build SIMD-accelerated code paths. This pulls in more
    /// build-time dependencies and adds libc as a runtime dependency,
    /// but results in significant performance improvements.
    simd: bool,

    /// True if we should enable the "slow" runtime safety checks. These
    /// are runtime safety checks that are slower than typical and should
    /// generally be disabled in production builds.
    slow_runtime_safety: bool,

    /// Force C ABI mode on or off. If not set, then it will be set based on
    /// Options.
    c_abi: bool,

    /// Optional feature gates, all enabled by default. See Features.
    features: Features = .{},

    /// The version of the application.
    version: std.SemanticVersion,

    /// Optional features for lib artifacts. Disabling a feature removes
    /// its C API exports and any stream-integrated handling from the
    /// build, primarily so size-conscious embedders (e.g. wasm) can trim
    /// the binary. The Ghostty application itself always builds with
    /// every feature enabled.
    ///
    /// Field names are the accepted tokens for `-Dvt-features`, which
    /// acts like `-Dcpu`: comma-separated modifications applied on top
    /// of the default (all enabled) set. `+feature` and `feature`
    /// enable, `-feature` disables. The special name `all` refers to
    /// every feature, so `-Dvt-features=-all,+render-state` builds only
    /// the render state API. Hyphens in names are interchangeable with
    /// underscores. Fields are emitted verbatim as bool options on the
    /// `terminal_options` module.
    pub const Features = packed struct {
        /// Terminal state serialization: encode a full terminal (screen
        /// contents, scrollback, cursor, styles, modes, etc.) to a
        /// compact binary format and decode it back into a live
        /// terminal. Used for session persistence, window restoration,
        /// and moving terminal state between processes.
        ///
        /// C API: `ghostty_snapshot_*` and, together with
        /// `grid_introspection`, `ghostty_tracked_grid_ref_snapshot`.
        snapshot: bool = true,

        /// Textual export of terminal contents as plain text, VT
        /// (replayable escape sequences), or HTML, with optional extras
        /// such as cursor position, palette, modes, and tabstops. Used
        /// for copy-to-clipboard, dump-to-file, and debugging.
        ///
        /// C API: `ghostty_formatter_*` and, together with `selection`,
        /// `ghostty_terminal_selection_format_*`.
        formatter: bool = true,

        /// Text selection: select word/line/output/all, selection
        /// ordering, adjustment, and containment math, and the
        /// click/drag gesture state machine (single/double/triple
        /// click behaviors). Selections are expressed in grid
        /// references, so enabling this also keeps the untracked grid
        /// reference constructors even if `grid_introspection` is
        /// disabled.
        ///
        /// C API: `ghostty_terminal_select_*`,
        /// `ghostty_terminal_selection_*`, `ghostty_selection_gesture_*`.
        selection: bool = true,

        /// The render state API: a coherent, update-in-place view of
        /// the visible screen (rows, cells, styles, cursor, colors,
        /// palette) designed to drive a renderer at frame rates. This
        /// is the primary read path for embedders that draw the
        /// terminal.
        ///
        /// C API: `ghostty_render_state_*`.
        render_state: bool = true,

        /// Encoding of host input events into the byte sequences a
        /// terminal application expects: keyboard input (including the
        /// Kitty keyboard protocol), mouse reporting, focus reporting,
        /// and (bracketed) paste. Any interactive embedder needs this;
        /// a read-only viewer does not.
        ///
        /// C API: `ghostty_key_event_*`, `ghostty_key_encoder_*`,
        /// `ghostty_mouse_event_*`, `ghostty_mouse_encoder_*`,
        /// `ghostty_focus_encode`, `ghostty_paste_*`.
        input_encode: bool = true,

        /// Color utilities: luminance, perceived luminance, and
        /// contrast math (e.g. for minimum-contrast rendering), color
        /// string parsing (`#rrggbb`, X11 color names, XParseColor
        /// `rgb:`/`rgbi:` device specs), the default 256-color palette
        /// and palette generation from configurable bases, and the X11
        /// color name table itself.
        ///
        /// C API: `ghostty_color_*`.
        color: bool = true,

        /// Direct inspection of grid contents at a position via grid
        /// references: cell and row data getters (codepoints,
        /// graphemes, style, hyperlink URI, wrap state, ...) plus
        /// tracked grid references that stay valid across terminal
        /// updates. Used for hyperlink hover/open, accessibility, and
        /// tests; deliberately not built for render loops (use
        /// `render_state` for that).
        ///
        /// C API: `ghostty_grid_ref_*`, `ghostty_tracked_grid_ref_*`,
        /// `ghostty_cell_get*`, `ghostty_row_get*`, and (shared with
        /// `selection`) `ghostty_terminal_grid_ref` /
        /// `ghostty_terminal_point_from_grid_ref`.
        grid_introspection: bool = true,

        /// The APC glyph protocol: a Ghostty extension that lets
        /// terminal applications register custom font glyphs (glyf
        /// outlines) for private-use-area codepoints. Embedders that
        /// don't render registered glyphs can disable this; the
        /// sequences are still consumed and safely ignored.
        ///
        /// No C API surface of its own; this gates the stream handling
        /// and glossary storage.
        glyph_protocol: bool = true,

        /// The Kitty graphics protocol: APC command parsing, image
        /// transmission and storage (PNG decoding via the sys
        /// interface), and placement tracking, plus the read APIs a
        /// renderer uses to draw placed images. Disabled sequences are
        /// still consumed and safely ignored.
        ///
        /// This requires the ability to get timestamps from the OS, so
        /// it is always disabled on freestanding targets (e.g.
        /// wasm32-freestanding) regardless of this setting.
        ///
        /// C API: `ghostty_kitty_graphics_*`.
        kitty_graphics: bool = true,

        pub fn parse(list: []const u8) error{UnknownFeature}!Features {
            // Modifications apply on top of the default set.
            var result: Features = .{};

            var it = std.mem.splitAny(u8, list, ", ");
            while (it.next()) |raw| {
                if (raw.len == 0) continue;

                var name = raw;
                var enable = true;
                switch (name[0]) {
                    '+' => name = name[1..],
                    '-' => {
                        enable = false;
                        name = name[1..];
                    },
                    else => {},
                }

                // A dangling sign with no name is malformed.
                if (name.len == 0) return error.UnknownFeature;

                // The special name "all" refers to every feature.
                if (std.mem.eql(u8, name, "all")) {
                    inline for (@typeInfo(Features).@"struct".fields) |field| {
                        @field(result, field.name) = enable;
                    }
                    continue;
                }

                var found = false;
                inline for (@typeInfo(Features).@"struct".fields) |field| {
                    if (eqlName(name, field.name)) {
                        @field(result, field.name) = enable;
                        found = true;
                    }
                }
                if (!found) return error.UnknownFeature;
            }

            return result;
        }

        /// Feature name equality where '-' in the input matches '_' in
        /// the field name.
        fn eqlName(input: []const u8, name: []const u8) bool {
            if (input.len != name.len) return false;
            for (input, name) |a, b| {
                const norm: u8 = if (a == '-') '_' else a;
                if (norm != b) return false;
            }
            return true;
        }

        test "parse" {
            const testing = std.testing;

            const none: Features = @bitCast(
                @as(@typeInfo(Features).@"struct".backing_integer.?, 0),
            );

            // Empty input keeps the defaults.
            try testing.expectEqual(Features{}, try Features.parse(""));

            // All three token forms, comma separated.
            try testing.expectEqual(
                Features{ .snapshot = false },
                try Features.parse("-snapshot"),
            );
            try testing.expectEqual(
                Features{ .snapshot = false, .formatter = false },
                try Features.parse("-snapshot,+render_state,-formatter,color"),
            );

            // The special "all" name refers to every feature.
            try testing.expectEqual(none, try Features.parse("-all"));
            try testing.expectEqual(Features{}, try Features.parse("-all,+all"));
            var render_only = none;
            render_only.render_state = true;
            try testing.expectEqual(
                render_only,
                try Features.parse("-all,+render-state"),
            );

            // Hyphens are interchangeable with underscores.
            try testing.expectEqual(
                Features{ .input_encode = false, .grid_introspection = false },
                try Features.parse("-input-encode,-grid_introspection"),
            );

            // Later tokens win.
            try testing.expectEqual(
                Features{},
                try Features.parse("-snapshot,+snapshot"),
            );

            // Unknown names and dangling signs are errors.
            try testing.expectError(error.UnknownFeature, Features.parse("bogus"));
            try testing.expectError(error.UnknownFeature, Features.parse("-snapshot,-"));
        }
    };

    /// Add the required build options for the terminal module.
    ///
    /// The memory referenced by self is expected to stick around (it isn't
    /// copied), since we expect we're in a build environment.
    pub fn add(
        self: Options,
        b: *std.Build,
        m: *std.Build.Module,
    ) void {
        const opts = b.addOptions();
        opts.addOption(Artifact, "artifact", self.artifact);
        opts.addOption(bool, "c_abi", self.c_abi);
        opts.addOption(bool, "oniguruma", self.oniguruma);
        opts.addOption(bool, "simd", self.simd);
        opts.addOption(bool, "slow_runtime_safety", self.slow_runtime_safety);

        // These are synthesized based on other options.
        opts.addOption(bool, "tmux_control_mode", self.oniguruma);

        // Feature gates, emitted as flat bools (e.g. `options.snapshot`).
        const target = m.resolved_target.?.result;
        inline for (@typeInfo(Features).@"struct".fields) |field| {
            var value = @field(self.features, field.name);

            // Kitty graphics requires the ability to get timestamps and
            // there is no way to do that on freestanding targets, so it
            // is always disabled there regardless of the feature setting.
            if (comptime std.mem.eql(u8, field.name, "kitty_graphics")) {
                if (target.cpu.arch == .wasm32 and target.os.tag == .freestanding)
                    value = false;
            }

            opts.addOption(bool, field.name, value);
        }

        // Version information.
        opts.addOption(
            []const u8,
            "version_string",
            b.fmt(
                "{f}",
                .{self.version},
            ),
        );
        opts.addOption(usize, "version_major", self.version.major);
        opts.addOption(usize, "version_minor", self.version.minor);
        opts.addOption(usize, "version_patch", self.version.patch);
        opts.addOption(?[]const u8, "version_pre", self.version.pre);
        opts.addOption(?[]const u8, "version_build", self.version.build);

        m.addOptions("terminal_options", opts);
    }
};

test {
    _ = Options.Features;
}
