import { parseHTML } from "linkedom";
import { extractStructuredData } from "./structured-data.js";
import { extractWithLLM } from "./llm-fallback.js";
import { applyEvidenceFilter, getSourceText } from "./schema-truth.js";
const PROVENANCE_PRIORITY = [
  "json-ld",
  "microdata",
  "rdfa"
];
function extractWithSchema(html, schema) {
  return extractWithSchemaDetailed(html, schema).values;
}
function extractWithSchemaDetailed(html, schema) {
  const values = {};
  const provenance = {};
  if (!html || !schema.properties) return { values, provenance };
  const blocks = extractStructuredData(html);
  for (const source of PROVENANCE_PRIORITY) {
    for (const block of blocks) {
      if (block.provenance !== source) continue;
      for (const fieldName of Object.keys(schema.properties)) {
        if (values[fieldName] !== void 0) continue;
        const v = pickField(block.fields, fieldName);
        if (v !== void 0) {
          values[fieldName] = v;
          provenance[fieldName] = source;
        }
      }
    }
  }
  const allCovered = Object.keys(schema.properties).every(
    (k) => values[k] !== void 0
  );
  if (allCovered) return { values, provenance };
  const { document: doc } = parseHTML(html);
  for (const [fieldName, fieldSchema] of Object.entries(schema.properties)) {
    if (values[fieldName] !== void 0) continue;
    const v = findFieldValue(doc, fieldName, fieldSchema);
    if (v !== void 0) {
      values[fieldName] = v;
      provenance[fieldName] = "heuristic";
    }
  }
  return { values, provenance };
}
async function extractWithSchemaDetailedAsync(html, schema, opts = {}) {
  const det = extractWithSchemaDetailed(html, schema);
  if (!schema.required || schema.required.length === 0) {
    return { ...det, warnings: [] };
  }
  const missing = schema.required.filter((k) => det.values[k] === void 0);
  if (missing.length === 0) {
    return { ...det, warnings: [] };
  }
  const llm = await extractWithLLM({
    html,
    jsonSchema: schema,
    partial: det.values,
    missing,
    signal: opts.signal,
    budget: opts.budget
  });
  const values = { ...det.values };
  const provenance = { ...det.provenance };
  const llmFilledFields = [];
  for (const key of missing) {
    if (llm.values[key] !== void 0 && values[key] === void 0) {
      values[key] = llm.values[key];
      provenance[key] = "llm";
      llmFilledFields.push(key);
    }
  }
  const warnings = [...llm.warnings];
  if (llmFilledFields.length > 0) {
    const sourceText = getSourceText(html);
    const filtered = applyEvidenceFilter({
      values,
      provenance,
      sourceText,
      fields: llmFilledFields
    });
    if (filtered.rejectedFields.length > 0) {
      warnings.push(
        `evidence-only filter: nulled ${filtered.rejectedFields.length} field(s) the LLM proposed but were not present in source text (${filtered.rejectedFields.join(", ")}).`
      );
    }
    return { values: filtered.values, provenance, warnings };
  }
  return { values, provenance, warnings };
}
function pickField(fields, name) {
  if (fields[name] !== void 0) return fields[name];
  for (const v of Object.values(fields)) {
    if (v && typeof v === "object" && !Array.isArray(v)) {
      const nested = v[name];
      if (nested !== void 0) return nested;
    }
  }
  return void 0;
}
function findFieldValue(doc, fieldName, schema) {
  const normalizedName = fieldName.toLowerCase().replace(/_/g, "-");
  const compactName = fieldName.replace(/_/g, "").toLowerCase();
  const variants = [fieldName, normalizedName, compactName];
  if (schema.type === "array") {
    return findArrayValues(doc, variants);
  }
  return findSingleValue(doc, variants);
}
function cssEscape(value) {
  return value.replace(/([^\w-])/g, "\\$1");
}
function findSingleValue(doc, variants) {
  for (const name of variants) {
    const byItemprop = doc.querySelector(`[itemprop="${name}"]`);
    if (byItemprop) {
      const text = byItemprop.getAttribute("content") ?? byItemprop.textContent?.trim();
      if (text) return text;
    }
    const byClass = doc.querySelector(`[class*="${name}"]`);
    if (byClass) {
      const text = byClass.textContent?.trim();
      if (text) return text;
    }
    const allWithAria = doc.querySelectorAll("[aria-label]");
    for (const el of allWithAria) {
      const label = el.getAttribute("aria-label")?.toLowerCase().replace(/\s+/g, "-") ?? "";
      if (label === name.toLowerCase()) {
        const text = el.textContent?.trim();
        if (text) return text;
      }
    }
    const byId = doc.querySelector(`#${cssEscape(name)}`);
    if (byId) {
      const text = byId.textContent?.trim();
      if (text) return text;
    }
    const byData = doc.querySelector(`[data-${name}]`);
    if (byData) {
      return byData.getAttribute(`data-${name}`) ?? byData.textContent?.trim() ?? void 0;
    }
  }
  return void 0;
}
function findArrayValues(doc, variants) {
  for (const name of variants) {
    const container = doc.querySelector(`[class*="${name}"]`);
    if (container) {
      const items = container.querySelectorAll('li, [class*="item"]');
      if (items.length > 0) {
        return Array.from(items).map((el) => (el.textContent ?? "").trim()).filter(Boolean);
      }
    }
    const singular = name.replace(/s$/, "");
    const elements = doc.querySelectorAll(`[class*="${singular}"]`);
    if (elements.length > 1) {
      return Array.from(elements).map((el) => (el.textContent ?? "").trim()).filter(Boolean);
    }
  }
  return void 0;
}
export {
  extractWithSchema,
  extractWithSchemaDetailed,
  extractWithSchemaDetailedAsync
};
//# sourceMappingURL=schema.js.map