import { Dimension, Metric, Setting } from "@clarity-types/data";
import { Constant, JsonLD } from "@clarity-types/layout";
import * as dimension from "@src/data/dimension";
import * as metric from "@src/data/metric";

const digitsRegex = /[^0-9\.]/g;

/* JSON+LD (Linked Data) Recursive Parser */
// biome-ignore lint/suspicious/noExplicitAny: specifically parsing json with any type
export function ld(json: any): void {
    for (const key of Object.keys(json)) {
        let value = json[key];
        if (key === JsonLD.Type && typeof value === "string") {
            value = value.toLowerCase();
            /* Normalizations */
            value = value.indexOf(JsonLD.Article) >= 0 || value.indexOf(JsonLD.Posting) >= 0 ? JsonLD.Article : value;
            switch (value) {
                case JsonLD.Article:
                case JsonLD.Recipe:
                    dimension.log(Dimension.SchemaType, json[key]);
                    dimension.log(Dimension.AuthorName, json[JsonLD.Creator]);
                    dimension.log(Dimension.Headline, json[JsonLD.Headline]);
                    break;
                case JsonLD.Product:
                    dimension.log(Dimension.SchemaType, json[key]);
                    dimension.log(Dimension.ProductName, json[JsonLD.Name]);
                    dimension.log(Dimension.ProductSku, json[JsonLD.Sku]);
                    if (json[JsonLD.Brand]) {
                        dimension.log(Dimension.ProductBrand, json[JsonLD.Brand][JsonLD.Name]);
                    }
                    break;
                case JsonLD.AggregateRating:
                    if (json[JsonLD.RatingValue]) {
                        metric.max(Metric.RatingValue, num(json[JsonLD.RatingValue], Setting.RatingScale));
                        metric.max(Metric.BestRating, num(json[JsonLD.BestRating]));
                        metric.max(Metric.WorstRating, num(json[JsonLD.WorstRating]));
                    }
                    metric.max(Metric.RatingCount, num(json[JsonLD.RatingCount]));
                    metric.max(Metric.ReviewCount, num(json[JsonLD.ReviewCount]));
                    break;
                case JsonLD.Offer:
                    dimension.log(Dimension.ProductAvailability, json[JsonLD.Availability]);
                    dimension.log(Dimension.ProductCondition, json[JsonLD.ItemCondition]);
                    dimension.log(Dimension.ProductCurrency, json[JsonLD.PriceCurrency]);
                    dimension.log(Dimension.ProductSku, json[JsonLD.Sku]);
                    metric.max(Metric.ProductPrice, num(json[JsonLD.Price]));
                    break;
                case JsonLD.Brand:
                    dimension.log(Dimension.ProductBrand, json[JsonLD.Name]);
                    break;
            }
        }
        // Continue parsing nested objects
        if (value !== null && typeof value === "object") {
            ld(value);
        }
    }
}

function num(input: string | number, scale = 1): number {
    if (input !== null) {
        switch (typeof input) {
            case Constant.Number:
                return Math.round((input as number) * scale);
            case Constant.String:
                return Math.round(Number.parseFloat((input as string).replace(digitsRegex, Constant.Empty)) * scale);
        }
    }
    return null;
}
