const std = @import("std");
const Allocator = std.mem.Allocator;
const bun = @import("bun");

pub const css = @import("./css_parser.zig");
pub const css_values = @import("./values/values.zig");
const Url = css_values.url.Url;
pub const Error = css.Error;
// const Location = css.Location;


/// Options for `analyze_dependencies` in `PrinterOptions`.
pub const DependencyOptions = struct {
    /// Whether to remove `@import` rules.
    remove_imports: bool,
};

/// A dependency.
pub const Dependency = union(enum) {
    /// An `@import` dependency.
    import: ImportDependency,
    /// A `url()` dependency.
    url: UrlDependency,
};

/// A line and column position within a source file.
pub const Location = struct {
    /// The line number, starting from 1.
    line: u32,
    /// The column number, starting from 1.
    column: u32,

    pub fn fromSourceLocation(loc: css.SourceLocation) Location {
        return Location{
            .line = loc.line + 1,
            .column = loc.column,
        };
    }

    pub fn hash(this: *const @This(), hasher: *std.hash.Wyhash) void {
        return css.implementHash(@This(), this, hasher);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// An `@import` dependency.
pub const ImportDependency = struct {
    /// The url to import.
    url: []const u8,
    /// The placeholder that the URL was replaced with.
    placeholder: []const u8,
    /// An optional `supports()` condition.
    supports: ?[]const u8,
    /// A media query.
    media: ?[]const u8,
    /// The location of the dependency in the source file.
    loc: SourceRange,

    pub fn new(allocator: Allocator, rule: *const css.css_rules.import.ImportRule, filename: []const u8, local_names: ?*const css.LocalsResultsMap, symbols: *const bun.JSAst.Symbol.Map) ImportDependency {
        const supports = if (rule.supports) |*supports| brk: {
            const s = css.to_css.string(
                allocator,
                css.css_rules.supports.SupportsCondition,
                supports,
                css.PrinterOptions.default(),
                null,
                local_names,
                symbols,
            ) catch bun.Output.panic(
                "Unreachable code: failed to stringify SupportsCondition.\n\nThis is a bug in Bun's CSS printer. Please file a bug report at https://github.com/oven-sh/bun/issues/new/choose",
                .{},
            );
            break :brk s;
        } else null;

        const media = if (rule.media.media_queries.items.len > 0) media: {
            const s = css.to_css.string(allocator, css.MediaList, &rule.media, css.PrinterOptions.default(), null, local_names, symbols) catch bun.Output.panic(
                "Unreachable code: failed to stringify MediaList.\n\nThis is a bug in Bun's CSS printer. Please file a bug report at https://github.com/oven-sh/bun/issues/new/choose",
                .{},
            );
            break :media s;
        } else null;

        const placeholder = css.css_modules.hash(allocator, "{s}_{s}", .{ filename, rule.url }, false);

        return ImportDependency{
            // TODO(zack): should we clone this? lightningcss does that
            .url = rule.url,
            .placeholder = placeholder,
            .supports = supports,
            .media = media,
            .loc = SourceRange.new(
                filename,
                css.dependencies.Location{ .line = rule.loc.line + 1, .column = rule.loc.column },
                8,
                rule.url.len + 2,
            ), // TODO: what about @import url(...)?
        };
    }
};

/// A `url()` dependency.
pub const UrlDependency = struct {
    /// The url of the dependency.
    url: []const u8,
    /// The placeholder that the URL was replaced with.
    placeholder: []const u8,
    /// The location of the dependency in the source file.
    loc: SourceRange,

    pub fn new(allocator: Allocator, url: *const Url, filename: []const u8, import_records: *const bun.BabyList(bun.ImportRecord)) UrlDependency {
        const theurl = import_records.at(url.import_record_idx).path.pretty;
        const placeholder = css.css_modules.hash(
            allocator,
            "{s}_{s}",
            .{ filename, theurl },
            false,
        );
        return UrlDependency{
            .url = theurl,
            .placeholder = placeholder,
            .loc = SourceRange.new(filename, url.loc, 4, theurl.len),
        };
    }
};

/// Represents the range of source code where a dependency was found.
pub const SourceRange = struct {
    /// The filename in which the dependency was found.
    file_path: []const u8,
    /// The starting line and column position of the dependency.
    start: Location,
    /// The ending line and column position of the dependency.
    end: Location,

    pub fn new(filename: []const u8, loc: Location, offset: u32, len: usize) SourceRange {
        return SourceRange{
            .file_path = filename,
            .start = Location{
                .line = loc.line,
                .column = loc.column + offset,
            },
            .end = Location{
                .line = loc.line,
                .column = loc.column + offset + @as(u32, @intCast(len)) - 1,
            },
        };
    }
};
