import { parseHTML } from "linkedom";
import { createLogger } from "../logger.js";
const log = createLogger("structured-data");
const KNOWN_TYPES = /* @__PURE__ */ new Set([
  "Article",
  "Product",
  "Recipe",
  "BreadcrumbList",
  "Organization",
  "Person"
]);
function extractStructuredData(html) {
  if (!html) return [];
  const { document: doc } = parseHTML(html);
  const out = [];
  out.push(...extractJsonLdBlocks(doc));
  out.push(...extractMicrodataBlocks(doc));
  out.push(...extractRdfaBlocks(doc));
  return out;
}
function extractJsonLdBlocks(doc) {
  const out = [];
  const scripts = doc.querySelectorAll('script[type="application/ld+json"]');
  for (const script of scripts) {
    const text = script.textContent?.trim();
    if (!text) continue;
    let parsed;
    try {
      parsed = JSON.parse(text);
    } catch (err) {
      log.warn("Failed to parse JSON-LD block", { error: String(err) });
      continue;
    }
    for (const node of flattenJsonLd(parsed)) {
      const type = normalizeType(node["@type"]);
      if (!type) continue;
      const fields = {};
      for (const [k, v] of Object.entries(node)) {
        if (k.startsWith("@")) continue;
        fields[k] = v;
      }
      out.push({ provenance: "json-ld", type, fields });
    }
  }
  return out;
}
function flattenJsonLd(value) {
  if (!value || typeof value !== "object") return [];
  if (Array.isArray(value)) return value.flatMap(flattenJsonLd);
  const obj = value;
  if (Array.isArray(obj["@graph"])) return obj["@graph"].flatMap(flattenJsonLd);
  return [obj];
}
function normalizeType(raw) {
  if (typeof raw === "string") {
    const tail = raw.split("/").pop();
    return tail || null;
  }
  if (Array.isArray(raw)) {
    for (const t of raw) {
      const norm = normalizeType(t);
      if (norm) return norm;
    }
  }
  return null;
}
function extractMicrodataBlocks(doc) {
  const out = [];
  const all = Array.from(doc.querySelectorAll("[itemscope]"));
  const tops = all.filter((el) => !hasItemscopeAncestor(el));
  for (const el of tops) {
    const node = readMicrodataNode(el);
    if (!node) continue;
    out.push(node);
  }
  return out;
}
function hasItemscopeAncestor(el) {
  let cur = el.parentElement;
  while (cur) {
    if (cur.hasAttribute("itemscope")) return true;
    cur = cur.parentElement;
  }
  return false;
}
function readMicrodataNode(el) {
  const itemtype = el.getAttribute("itemtype") ?? "";
  const type = itemtype ? itemtype.split("/").pop() : "";
  if (!type) return null;
  const fields = {};
  collectItemprops(el, fields);
  return { provenance: "microdata", type, fields };
}
function collectItemprops(root, target) {
  const stack = Array.from(root.children);
  while (stack.length) {
    const el = stack.shift();
    const prop = el.getAttribute("itemprop");
    if (prop) {
      let value;
      if (el.hasAttribute("itemscope")) {
        const nested = {};
        collectItemprops(el, nested);
        value = nested;
      } else {
        value = el.getAttribute("content") ?? el.getAttribute("href") ?? el.getAttribute("src") ?? (el.textContent ?? "").trim();
      }
      mergeProp(target, prop, value);
    }
    if (el.hasAttribute("itemscope")) continue;
    for (const c of el.children) stack.push(c);
  }
}
function mergeProp(target, prop, value) {
  if (target[prop] === void 0) {
    target[prop] = value;
    return;
  }
  if (Array.isArray(target[prop])) {
    target[prop].push(value);
    return;
  }
  target[prop] = [target[prop], value];
}
function extractRdfaBlocks(doc) {
  const out = [];
  const all = Array.from(doc.querySelectorAll("[typeof]"));
  const tops = all.filter((el) => !hasTypeofAncestor(el));
  for (const el of tops) {
    const typeAttr = el.getAttribute("typeof") ?? "";
    const type = typeAttr.split(/\s+/)[0]?.split(/[:/]/).pop() ?? "";
    if (!type) continue;
    const fields = {};
    collectRdfaProps(el, fields);
    out.push({ provenance: "rdfa", type, fields });
  }
  return out;
}
function hasTypeofAncestor(el) {
  let cur = el.parentElement;
  while (cur) {
    if (cur.hasAttribute("typeof")) return true;
    cur = cur.parentElement;
  }
  return false;
}
function collectRdfaProps(root, target) {
  const stack = Array.from(root.children);
  while (stack.length) {
    const el = stack.shift();
    const prop = el.getAttribute("property");
    if (prop) {
      const propName = prop.split(/[:/]/).pop() ?? prop;
      let value;
      if (el.hasAttribute("typeof")) {
        const nested = {};
        collectRdfaProps(el, nested);
        value = nested;
      } else {
        value = el.getAttribute("content") ?? el.getAttribute("href") ?? el.getAttribute("resource") ?? (el.textContent ?? "").trim();
      }
      mergeProp(target, propName, value);
    }
    if (el.hasAttribute("typeof")) continue;
    for (const c of el.children) stack.push(c);
  }
}
const KNOWN_SCHEMA_TYPES = KNOWN_TYPES;
export {
  KNOWN_SCHEMA_TYPES,
  extractStructuredData
};
//# sourceMappingURL=structured-data.js.map