/*
 * Copyright (c) 2025, Shannon Booth <shannon@serenityos.org>
 * Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibJS/Runtime/VM.h>
#include <LibWeb/HTML/ErrorInformation.h>

namespace Web::HTML {

// https://html.spec.whatwg.org/multipage/webappapis.html#extract-error
ErrorInformation extract_error_information(JS::VM& vm, JS::Value exception)
{
    // 1. Let attributes be an empty map keyed by IDL attributes.
    ErrorInformation attributes;

    // 2. Set attributes[error] to exception.
    attributes.error = exception;

    // 3. Set attributes[message], attributes[filename], attributes[lineno], and attributes[colno] to
    //    implementation-defined values derived from exception.
    attributes.message = [&] {
        if (auto object = exception.as_if<JS::Object>()) {
            if (MUST(object->has_own_property(vm.names.message))) {
                auto message = object->get_without_side_effects(vm.names.message);
                return message.to_string_without_side_effects();
            }
        }

        return MUST(String::formatted("Uncaught exception: {}", exception));
    }();

    // FIXME: This offset is relative to the javascript source. Other browsers appear to do it relative
    //        to the entire source document! Calculate that somehow.

    // NB: If we got an Error object, then try and extract the information from the location the object was made.
    if (auto object = exception.as_if<JS::Object>(); object && object->error_data()) {
        for (auto const& frame : object->error_data()->traceback()) {
            auto source_range = frame.source_range();
            if (source_range.start.line != 0 || source_range.start.column != 0) {
                attributes.filename = MUST(String::from_byte_string(source_range.filename()));
                attributes.lineno = source_range.start.line;
                attributes.colno = source_range.start.column;
                break;
            }
        }
    }
    // NB: Otherwise, we fall back to try and find the location of the invocation of the function itself.
    else {
        for (auto const& frame : vm.stack_trace()) {
            if (frame.source_range.has_value()) {
                auto const& source_range = *frame.source_range;
                attributes.filename = MUST(String::from_byte_string(source_range.filename()));
                attributes.lineno = source_range.start.line;
                attributes.colno = source_range.start.column;
                break;
            }
        }
    }

    // 4. Return attributes.
    return attributes;
}

}
