const std = @import("std");

const Parser = @import("../../osc.zig").Parser;
const Command = @import("../../osc.zig").Command;

const log = std.log.scoped(.osc_hyperlink);

/// Parse OSC 8 hyperlinks
pub fn parse(parser: *Parser, _: ?u8) ?*Command {
    const cap = if (parser.capture) |*c| c else {
        parser.state = .invalid;
        return null;
    };
    cap.writer.writeByte(0) catch {
        parser.state = .invalid;
        return null;
    };
    const data = cap.trailing();
    const s = std.mem.indexOfScalar(u8, data, ';') orelse {
        parser.state = .invalid;
        return null;
    };

    parser.command = .{
        .hyperlink_start = .{
            .uri = data[s + 1 .. data.len - 1 :0],
        },
    };

    data[s] = 0;
    const kvs = data[0 .. s + 1];
    std.mem.replaceScalar(u8, kvs, ':', 0);
    var kv_start: usize = 0;
    while (kv_start < kvs.len) {
        const kv_end = std.mem.indexOfScalarPos(u8, kvs, kv_start + 1, 0) orelse break;
        const kv = data[kv_start .. kv_end + 1];
        const v = std.mem.indexOfScalar(u8, kv, '=') orelse break;
        const key = kv[0..v];
        const value = kv[v + 1 .. kv.len - 1 :0];
        if (std.mem.eql(u8, key, "id")) {
            if (value.len > 0) parser.command.hyperlink_start.id = value;
        } else {
            log.warn("unknown hyperlink option: '{s}'", .{key});
        }
        kv_start = kv_end + 1;
    }

    if (parser.command.hyperlink_start.uri.len == 0) {
        if (parser.command.hyperlink_start.id != null) {
            parser.state = .invalid;
            return null;
        }
        parser.command = .hyperlink_end;
    }

    return &parser.command;
}

test "OSC 8: hyperlink" {
    const testing = std.testing;

    var p: Parser = .init(null);

    const input = "8;;http://example.com";
    for (input) |ch| p.next(ch);

    const cmd = p.end('\x1b').?.*;
    try testing.expect(cmd == .hyperlink_start);
    try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
}

test "OSC 8: hyperlink with id set" {
    const testing = std.testing;

    var p: Parser = .init(null);

    const input = "8;id=foo;http://example.com";
    for (input) |ch| p.next(ch);

    const cmd = p.end('\x1b').?.*;
    try testing.expect(cmd == .hyperlink_start);
    try testing.expectEqualStrings(cmd.hyperlink_start.id.?, "foo");
    try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
}

test "OSC 8: hyperlink with empty id" {
    const testing = std.testing;

    var p: Parser = .init(null);

    const input = "8;id=;http://example.com";
    for (input) |ch| p.next(ch);

    const cmd = p.end('\x1b').?.*;
    try testing.expect(cmd == .hyperlink_start);
    try testing.expectEqual(null, cmd.hyperlink_start.id);
    try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
}

test "OSC 8: hyperlink with incomplete key" {
    const testing = std.testing;

    var p: Parser = .init(null);

    const input = "8;id;http://example.com";
    for (input) |ch| p.next(ch);

    const cmd = p.end('\x1b').?.*;
    try testing.expect(cmd == .hyperlink_start);
    try testing.expectEqual(null, cmd.hyperlink_start.id);
    try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
}

test "OSC 8: hyperlink with empty key" {
    const testing = std.testing;

    var p: Parser = .init(null);

    const input = "8;=value;http://example.com";
    for (input) |ch| p.next(ch);

    const cmd = p.end('\x1b').?.*;
    try testing.expect(cmd == .hyperlink_start);
    try testing.expectEqual(null, cmd.hyperlink_start.id);
    try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
}

test "OSC 8: hyperlink with empty key and id" {
    const testing = std.testing;

    var p: Parser = .init(null);

    const input = "8;=value:id=foo;http://example.com";
    for (input) |ch| p.next(ch);

    const cmd = p.end('\x1b').?.*;
    try testing.expect(cmd == .hyperlink_start);
    try testing.expectEqualStrings(cmd.hyperlink_start.id.?, "foo");
    try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
}

test "OSC 8: hyperlink with empty uri" {
    const testing = std.testing;

    var p: Parser = .init(null);

    const input = "8;id=foo;";
    for (input) |ch| p.next(ch);

    const cmd = p.end('\x1b');
    try testing.expect(cmd == null);
}

test "OSC 8: hyperlink end" {
    const testing = std.testing;

    var p: Parser = .init(null);

    const input = "8;;";
    for (input) |ch| p.next(ch);

    const cmd = p.end('\x1b').?.*;
    try testing.expect(cmd == .hyperlink_end);
}
