"use strict";

const FIELD_DEFINITIONS = [
  ["generic","Generic","text"],["brands","Brands","list"],["ocrAliases","OCR aliases","list"],
  ["commonMisspellings","Common misspellings","list"],["pharmacologicClass","Pharmacologic class","list"],
  ["therapeuticClass","Therapeutic class","list"],["indications","Indications","list"],
  ["doseByIndication","Dose by indication","claims"],["renalDosing","Renal dosing","claims"],
  ["hepaticDosing","Hepatic dosing","claims"],["hd","Hemodialysis","claims"],["crrt","CRRT","claims"],
  ["pd","Peritoneal dialysis","claims"],["loadingDose","Loading dose","claims"],
  ["maximumDose","Maximum dose","claims"],["administration","Administration","claims"],
  ["infusionRates","Infusion rates","claims"],["extendedInfusion","Extended infusion","claims"],
  ["ySiteCompatibility","Y-site compatibility","claims"],["stability","Stability","claims"],
  ["extravasation","Extravasation","claims"],["vesicantClassification","Vesicant classification","claims"],
  ["antidote","Antidote","claims"],["tubeAdministration","Tube administration","claims"],
  ["crushability","Crushability","claims"],["hazardousHandling","Hazardous handling","claims"],
  ["pregnancy","Pregnancy","claims"],["lactation","Lactation","claims"],
  ["blackBoxWarnings","Black box warnings","claims"],["contraindications","Contraindications","claims"],
  ["monitoring","Monitoring","claims"],["majorInteractions","Major interactions","claims"],
  ["highYieldInteractions","High-yield interactions","claims"],
  ["therapeuticSubstitutions","Therapeutic substitutions","claims"],
  ["formularyAlternatives","Formulary alternatives","claims"],["clinicalPearls","Clinical pearls","claims"],
  ["references","References","claims"],["versionHistory","Version history","system"]
];

const REVIEW_STAGES = ["draft","source_attached","locator_verified","primary_reviewed","secondary_reviewed","approved","published"];

const state = {
  // Repository loading is intentionally deferred until all schema constants,
  // constructors, and seed functions have initialized.
  repository: null,
  selectedMedicationId: null,
  selectedClaimId: null,
  selectedEvidenceId: null,
  selectedCalculatorId: null,
  selectedFindingRuleId: null,
  activeTab: "medication",
  researchReport: null, researchSource: null
};

function emptyRepository() {
  return {
    repositoryType: "pharmacist.guide.clinical-reasoning-database",
    schemaVersion: "5.0.0-workbench",
    updatedAt: new Date().toISOString(),
    medications: {}, claims: {}, evidence: {}, reviews: {}, calculators: {}, findingRules: {}, patientBindings: {},
    researchAssignments: {}, sourceIntake: {}, auditLog: [],
    settings: {
      ownerModeEnabled: true,
      ownerName: "Zachary Carry",
      ownerTemporarySelfReview: true,
      ownerModeNotice: "Temporary library-building exception. Remove before external clinical publication."
    }
  };
}


function seedRepositoryFromTop300() {
  const repo = emptyRepository();
  const rows = Array.isArray(window.CRD_TOP300_SEED) ? window.CRD_TOP300_SEED : [];
  const now = new Date().toISOString();

  rows.forEach(row => {
    const med = newMedication(row.generic || "");
    med.recordStatus = "active_test";
    med.author = "Zachary Carry";
    med.reviewerPrimary = "Zachary Carry";
    med.reviewerSecondary = "Zachary Carry";
    med.notes = `Top-300 rank: ${row.rank || ""}. Active functionality-test record.`;
    med.schemaVersion = "5.0.0-workbench";

    med.fields.generic = row.generic || "";
    med.fields.brands = row.brands || [];
    med.fields.ocrAliases = row.aliases || [];
    med.fields.commonMisspellings = [];
    med.fields.pharmacologicClass = [row.pharmacologicClass || ""].filter(Boolean);
    med.fields.therapeuticClass = [row.therapeuticClass || ""].filter(Boolean);
    med.fields.indications = row.commonIndications || [];

    repo.medications[med.medicationId] = med;

    const claimRows = [
      ["renalDosing", row.renal],
      ["hepaticDosing", row.hepatic],
      ["monitoring", (row.monitoring || []).join("; ")],
      ["blackBoxWarnings", (row.majorWarnings || []).join("; ")],
      ["majorInteractions", (row.majorInteractions || []).join("; ")],
      ["administration", row.administration],
      ["crushability", row.crushability],
      ["clinicalPearls", `High-risk tags: ${(row.highRiskTags || []).join(", ") || "none"}`],
      ["therapeuticSubstitutions", `Therapeutic group: ${row.therapeuticGroup || "general"}`]
    ];

    claimRows.forEach(([field, value]) => {
      if (!value) return;
      const c = newClaim(med.medicationId, field);
      c.value = value;
      c.author = "Zachary Carry";
      c.primaryReviewer = "Zachary Carry";
      c.secondaryReviewer = "Zachary Carry";
      c.approver = "Zachary Carry";
      c.validationStatus = "validated";
      c.reviewStage = "published";
      c.publishedAt = now;
      c.ownerOverrideHistory = [{
        action: "seed_top300_for_functional_testing",
        owner: "Zachary Carry",
        timestamp: now,
        temporary: true
      }];
      repo.claims[c.claimId] = c;
      med.claimIds.push(c.claimId);
    });
  });

  repo.updatedAt = now;
  repo.auditLog.push({
    auditId: uid("audit"),
    eventType: "top300_seed_loaded",
    entityId: "repository",
    details: { medicationCount: rows.length, mode: "functional_testing" },
    timestamp: now,
    actor: "Zachary Carry"
  });
  return repo;
}

function mergeTop300IntoRepository(repo) {
  const rows = Array.isArray(window.CRD_TOP300_SEED) ? window.CRD_TOP300_SEED : [];
  if (!rows.length) return { repo, added: 0, updated: 0 };

  let added = 0;
  let updated = 0;
  const existingByGeneric = new Map(
    Object.values(repo.medications || {}).map(m => [
      String(m?.fields?.generic || m?.generic || "").trim().toLowerCase(),
      m
    ])
  );

  rows.forEach(row => {
    const genericKey = String(row.generic || "").trim().toLowerCase();
    let med = existingByGeneric.get(genericKey);

    if (!med) {
      med = newMedication(row.generic || "");
      med.recordStatus = "active_test";
      med.author = "Zachary Carry";
      med.reviewerPrimary = "Zachary Carry";
      med.reviewerSecondary = "Zachary Carry";
      med.notes = `Top-300 rank: ${row.rank || ""}. Active functionality-test record.`;
      med.schemaVersion = "5.0.0-workbench";
      repo.medications[med.medicationId] = med;
      existingByGeneric.set(genericKey, med);
      added += 1;
    } else {
      updated += 1;
    }

    med.fields = med.fields || {};
    med.fields.generic = med.fields.generic || row.generic || "";
    med.fields.brands = Array.from(new Set([...(med.fields.brands || []), ...(row.brands || [])]));
    med.fields.ocrAliases = Array.from(new Set([...(med.fields.ocrAliases || []), ...(row.aliases || [])]));
    med.fields.commonMisspellings = med.fields.commonMisspellings || [];
    med.fields.pharmacologicClass = med.fields.pharmacologicClass?.length
      ? med.fields.pharmacologicClass
      : [row.pharmacologicClass || ""].filter(Boolean);
    med.fields.therapeuticClass = med.fields.therapeuticClass?.length
      ? med.fields.therapeuticClass
      : [row.therapeuticClass || ""].filter(Boolean);
    med.fields.indications = med.fields.indications?.length
      ? med.fields.indications
      : (row.commonIndications || []);

    const existingFields = new Set(
      (med.claimIds || [])
        .map(id => repo.claims?.[id])
        .filter(Boolean)
        .map(c => c.field)
    );

    const claimRows = [
      ["renalDosing", row.renal],
      ["hepaticDosing", row.hepatic],
      ["monitoring", (row.monitoring || []).join("; ")],
      ["blackBoxWarnings", (row.majorWarnings || []).join("; ")],
      ["majorInteractions", (row.majorInteractions || []).join("; ")],
      ["administration", row.administration],
      ["crushability", row.crushability],
      ["clinicalPearls", `High-risk tags: ${(row.highRiskTags || []).join(", ") || "none"}`],
      ["therapeuticSubstitutions", `Therapeutic group: ${row.therapeuticGroup || "general"}`]
    ];

    med.claimIds = med.claimIds || [];
    claimRows.forEach(([field, value]) => {
      if (!value || existingFields.has(field)) return;
      const c = newClaim(med.medicationId, field);
      c.value = value;
      c.author = "Zachary Carry";
      c.primaryReviewer = "Zachary Carry";
      c.secondaryReviewer = "Zachary Carry";
      c.approver = "Zachary Carry";
      c.validationStatus = "validated";
      c.reviewStage = "published";
      c.publishedAt = new Date().toISOString();
      c.ownerOverrideHistory = [{
        action: "top300_self_healing_seed",
        owner: "Zachary Carry",
        timestamp: new Date().toISOString(),
        temporary: true
      }];
      repo.claims[c.claimId] = c;
      med.claimIds.push(c.claimId);
    });
  });

  repo.updatedAt = new Date().toISOString();
  return { repo, added, updated };
}

function ensureTop300Seed(repo) {
  const seedCount = Array.isArray(window.CRD_TOP300_SEED) ? window.CRD_TOP300_SEED.length : 0;
  if (!seedCount) {
    repo.bootstrapError = "Bundled Top-300 seed file did not load.";
    return repo;
  }

  const result = mergeTop300IntoRepository(repo);
  repo.bootstrap = {
    seedAvailable: seedCount,
    medicationCount: Object.keys(repo.medications || {}).length,
    addedThisRun: result.added,
    checkedAt: new Date().toISOString()
  };
  localStorage.setItem("crd-workbench-repository", JSON.stringify(repo));
  return repo;
}


function loadRepository() {
  try {
    const raw = localStorage.getItem("crd-workbench-repository");
    const repo = raw ? normalizeRepository(JSON.parse(raw)) : emptyRepository();
    return ensureTop300Seed(repo);
  } catch {
    return ensureTop300Seed(emptyRepository());
  }
}

function normalizeRepository(repo) {
  return Object.assign(emptyRepository(), repo, {
    medications: repo.medications || {}, claims: repo.claims || {}, evidence: repo.evidence || {}, reviews: repo.reviews || {}, calculators: repo.calculators || {}, findingRules: repo.findingRules || {}, patientBindings: repo.patientBindings || {},
    researchAssignments: repo.researchAssignments || {}, sourceIntake: repo.sourceIntake || {}, auditLog: repo.auditLog || [],
    settings: Object.assign(emptyRepository().settings, repo.settings || {})
  });
}

function saveRepository() {
  state.repository.updatedAt = new Date().toISOString();
  localStorage.setItem("crd-workbench-repository", JSON.stringify(state.repository));
  renderStats();
}

function ownerSettings() { return state.repository.settings || emptyRepository().settings; }
function isOwnerName(name) {
  const settings=ownerSettings();
  return settings.ownerModeEnabled && settings.ownerTemporarySelfReview && String(name||"").trim().toLowerCase()===String(settings.ownerName||"").trim().toLowerCase();
}
function ownerOverrideAllowed(c) {
  const names=[c.author,c.primaryReviewer,c.secondaryReviewer,c.approver].filter(Boolean);
  return names.length>=2 && names.every(isOwnerName);
}
function addAudit(eventType, entityId, details={}) {
  state.repository.auditLog.push({auditId:uid("audit"),eventType,entityId,details,timestamp:new Date().toISOString(),actor:ownerSettings().ownerName||""});
}
function recordOwnerOverride(c, action) {
  addAudit("owner_self_review_override", c.claimId, {action,owner:ownerSettings().ownerName,temporary:true});
  c.ownerOverrideHistory=c.ownerOverrideHistory||[];
  c.ownerOverrideHistory.push({action,owner:ownerSettings().ownerName,timestamp:new Date().toISOString(),temporary:true});
}

function slug(value) {
  return String(value || "").trim().toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"");
}

function uid(prefix) {
  return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,8)}`;
}

function arrayFromText(value) {
  return String(value || "").split(/[,\n]/).map(v=>v.trim()).filter(Boolean);
}

function textFromArray(value) { return Array.isArray(value) ? value.join(", ") : ""; }
function byId(id) { return document.getElementById(id); }
function escapeHtml(value) { return String(value ?? "").replace(/[&<>"']/g, c=>({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#039;"}[c])); }
function toast(message) { const el=byId("toast"); el.textContent=message; el.classList.add("show"); setTimeout(()=>el.classList.remove("show"),1600); }

function newMedication(generic="") {
  const id = slug(generic) || uid("med");
  const now = new Date().toISOString();
  const fields = {};
  FIELD_DEFINITIONS.forEach(([key,,type])=>{ fields[key] = type === "list" ? [] : type === "text" ? "" : []; });
  fields.generic = generic;
  return {
    medicationId:id, generic, fields, claimIds:[], recordStatus:"draft", schemaVersion:"4.0.0-workbench",
    createdAt:now, updatedAt:now, author:"", reviewerPrimary:"", reviewerSecondary:"", nextReviewDate:"", notes:""
  };
}

function newClaim(medicationId, field="clinicalPearls") {
  return {
    claimId:uid("claim"), medicationId, field, value:"", population:"adult", indication:"", route:"", formulation:"",
    conditions:{}, applicability:"applicable", validationStatus:"draft", reviewStage:"draft", evidenceIds:[],
    conflict:{status:"none", conflictingEvidenceIds:[], preferredEvidenceId:"", rationale:"", resolvedBy:"", resolvedAt:""},
    consumers:["PhUCKIT"], author:"", primaryReviewer:"", secondaryReviewer:"", approver:"",
    ownerOverrideHistory:[], templateId:"", researchPriority:"normal", nextReviewDate:"",
    createdAt:new Date().toISOString(), updatedAt:new Date().toISOString()
  };
}

function newEvidence(claimId) {
  return {
    evidenceId:uid("evidence"), claimId, sourceType:"", citation:"", title:"", publisher:"", publicationDate:"",
    sourceVersion:"", urlOrId:"", accessedAt:new Date().toISOString().slice(0,10),
    locator:{section:"",subsection:"",page:"",table:"",figure:"",recommendation:"",paragraphOrBullet:"",appendix:"",electronicAnchor:""},
    claimSupported:"", supportSummary:"", relationship:"supports", verificationStatus:"pending", verifiedBy:"", verifiedAt:"", verificationHistory:[]
  };
}

function getSelectedMedication() { return state.repository.medications[state.selectedMedicationId] || null; }
function getSelectedClaim() { return state.repository.claims[state.selectedClaimId] || null; }
function getSelectedEvidence() { return state.repository.evidence[state.selectedEvidenceId] || null; }

function selectMedication(id) {
  state.selectedMedicationId = id;
  const med = getSelectedMedication();
  state.selectedClaimId = med?.claimIds?.[0] || null;
  const claim = getSelectedClaim();
  state.selectedEvidenceId = claim?.evidenceIds?.[0] || null;
  renderAll();
}

function renderAll() { renderMedicationList(); renderStats(); renderActiveTab(); }

function renderMedicationList() {
  const q = byId("medSearch").value.trim().toLowerCase();
  const meds = Object.values(state.repository.medications).filter(m=>{
    const hay=[m.generic,m.fields?.generic,...(m.fields?.brands||[]),...(m.fields?.ocrAliases||[]),...(m.fields?.commonMisspellings||[])].join(" ").toLowerCase();
    return !q || hay.includes(q);
  }).sort((a,b)=>String(a.generic).localeCompare(String(b.generic)));
  byId("medList").innerHTML = meds.length ? meds.map(m=>`
    <div class="med-row ${m.medicationId===state.selectedMedicationId?"active":""}" data-med-id="${escapeHtml(m.medicationId)}">
      <strong>${escapeHtml(m.generic || "Untitled medication")}</strong>
      <small>${escapeHtml(m.recordStatus)} · ${(m.claimIds||[]).length} claims</small>
    </div>`).join("") : `<div class="empty">No medications</div>`;
  document.querySelectorAll("[data-med-id]").forEach(el=>el.addEventListener("click",()=>selectMedication(el.dataset.medId)));
}

function renderStats() {
  const r=state.repository;
  byId("statMedications").textContent=Object.keys(r.medications).length;
  byId("statClaims").textContent=Object.keys(r.claims).length;
  byId("statEvidence").textContent=Object.keys(r.evidence).length;
  const blocked=Object.values(r.claims).filter(c=>validateClaim(c).errors.length).length;
  byId("statBlocked").textContent=blocked;const badge=byId("ownerModeStatus");if(badge)badge.textContent=ownerSettings().ownerModeEnabled&&ownerSettings().ownerTemporarySelfReview?"Owner self-review ON":"Independent review";
}

function renderActiveTab() {
  document.querySelectorAll(".tab-panel").forEach(p=>p.classList.remove("active"));
  document.querySelectorAll(".tabs button").forEach(b=>b.classList.toggle("active",b.dataset.tab===state.activeTab));
  byId(`tab-${state.activeTab}`).classList.add("active");
  ({medication:renderMedicationTab,claims:renderClaimsTab,evidence:renderEvidenceTab,review:renderReviewTab,queue:renderQueueTab,coverage:renderCoverageTab,research:renderResearchTab,calculators:renderCalculatorsTab,findings:renderFindingsTab,bindings:renderBindingsTab,validation:renderValidationTab,settings:renderSettingsTab}[state.activeTab])();
}

function renderMedicationTab() {
  const med=getSelectedMedication();
  const host=byId("tab-medication");
  if(!med){host.innerHTML=`<div class="empty">Create or select a medication record.</div>`;return;}
  host.innerHTML=`
    <div class="panel">
      <div class="panel-header"><h2>Medication record</h2><div class="toolbar"><span class="badge ${escapeHtml(med.recordStatus)}">${escapeHtml(med.recordStatus)}</span><button id="deleteMedication" class="danger">Delete</button></div></div>
      <div class="panel-body grid three">
        ${inputField("medGeneric","Generic",med.generic)}
        ${inputField("medAuthor","Author",med.author)}
        ${selectField("medStatus","Record status",med.recordStatus,["draft","clinical_review","revision_required","approved","compiled","released","retired","superseded"])}
        ${inputField("medPrimary","Primary reviewer",med.reviewerPrimary)}
        ${inputField("medSecondary","Secondary reviewer",med.reviewerSecondary)}
        ${inputField("medNextReview","Next review date",med.nextReviewDate,"date")}
        ${textareaField("medNotes","Record notes",med.notes,true)}
      </div>
    </div>
    <div class="panel">
      <div class="panel-header"><h3>Identity and searchable terms</h3></div>
      <div class="panel-body grid">
        ${textareaField("field-brands","Brands",textFromArray(med.fields.brands))}
        ${textareaField("field-ocrAliases","OCR aliases",textFromArray(med.fields.ocrAliases))}
        ${textareaField("field-commonMisspellings","Common misspellings",textFromArray(med.fields.commonMisspellings))}
        ${textareaField("field-pharmacologicClass","Pharmacologic class",textFromArray(med.fields.pharmacologicClass))}
        ${textareaField("field-therapeuticClass","Therapeutic class",textFromArray(med.fields.therapeuticClass))}
        ${textareaField("field-indications","Indications",textFromArray(med.fields.indications))}
      </div>
    </div>
    <div class="panel">
      <div class="panel-header"><h3>Claim-backed domains</h3><div class="toolbar"><select id="claimTemplateSelect">${CLAIM_TEMPLATES.map(t=>`<option value="${t.templateId}">${escapeHtml(t.label)}</option>`).join("")}</select><button id="addTemplateClaim">Add template</button><button id="addClaimFromMedication">Blank claim</button></div></div>
      <div class="panel-body table-wrap">${claimDomainTable(med)}</div>
    </div>`;
  bindMedicationInputs(med);
}

function inputField(id,label,value,type="text"){return `<div class="field"><label for="${id}">${label}</label><input id="${id}" type="${type}" value="${escapeHtml(value||"")}" /></div>`;}
function textareaField(id,label,value,wide=false){return `<div class="field ${wide?"wide":""}"><label for="${id}">${label}</label><textarea id="${id}">${escapeHtml(value||"")}</textarea></div>`;}
function selectField(id,label,value,options){return `<div class="field"><label for="${id}">${label}</label><select id="${id}">${options.map(o=>`<option ${o===value?"selected":""}>${escapeHtml(o)}</option>`).join("")}</select></div>`;}

function claimDomainTable(med) {
  const counts={}; (med.claimIds||[]).forEach(id=>{const c=state.repository.claims[id];if(c)counts[c.field]=(counts[c.field]||0)+1;});
  return `<table><thead><tr><th>Domain</th><th>Claims</th><th>Action</th></tr></thead><tbody>${FIELD_DEFINITIONS.filter(([, ,t])=>t==="claims").map(([k,l])=>`<tr><td>${escapeHtml(l)}</td><td>${counts[k]||0}</td><td><button data-add-domain="${k}">Add claim</button></td></tr>`).join("")}</tbody></table>`;
}

function bindMedicationInputs(med){
  const bind=(id,key,target=med)=>byId(id).addEventListener("input",e=>{target[key]=e.target.value;med.updatedAt=new Date().toISOString();saveRepository();renderMedicationList();});
  bind("medGeneric","generic"); bind("medAuthor","author"); bind("medStatus","recordStatus"); bind("medPrimary","reviewerPrimary"); bind("medSecondary","reviewerSecondary"); bind("medNextReview","nextReviewDate"); bind("medNotes","notes");
  ["brands","ocrAliases","commonMisspellings","pharmacologicClass","therapeuticClass","indications"].forEach(k=>byId(`field-${k}`).addEventListener("input",e=>{med.fields[k]=arrayFromText(e.target.value);saveRepository();renderMedicationList();}));
  byId("deleteMedication").addEventListener("click",()=>{if(confirm(`Delete ${med.generic || med.medicationId} and its claims/evidence?`)){deleteMedication(med.medicationId);}});
  byId("addClaimFromMedication").addEventListener("click",()=>addClaim("clinicalPearls"));byId("addTemplateClaim").addEventListener("click",()=>addClaimFromTemplate(byId("claimTemplateSelect").value));
  document.querySelectorAll("[data-add-domain]").forEach(b=>b.addEventListener("click",()=>addClaim(b.dataset.addDomain)));
}

function addClaim(field){
  const med=getSelectedMedication(); if(!med)return;
  const claim=newClaim(med.medicationId,field); state.repository.claims[claim.claimId]=claim; med.claimIds.push(claim.claimId); state.selectedClaimId=claim.claimId; state.selectedEvidenceId=null; saveRepository(); state.activeTab="claims"; renderAll();
}

function deleteMedication(id){
  const med=state.repository.medications[id]; if(med) (med.claimIds||[]).forEach(deleteClaim);
  delete state.repository.medications[id]; state.selectedMedicationId=null;state.selectedClaimId=null;state.selectedEvidenceId=null;saveRepository();renderAll();
}
function deleteClaim(id){const c=state.repository.claims[id];if(c)(c.evidenceIds||[]).forEach(e=>delete state.repository.evidence[e]);delete state.repository.claims[id];}

function renderClaimsTab(){
  const med=getSelectedMedication(); const host=byId("tab-claims");
  if(!med){host.innerHTML=`<div class="empty">Select a medication.</div>`;return;}
  const claims=(med.claimIds||[]).map(id=>state.repository.claims[id]).filter(Boolean);
  if(!state.selectedClaimId && claims[0]) state.selectedClaimId=claims[0].claimId;
  const claim=getSelectedClaim();
  host.innerHTML=`<div class="split"><div class="panel"><div class="panel-header"><h2>Claims</h2><button id="addClaim">Add claim</button></div><div class="panel-body table-wrap">
    <table><thead><tr><th>Field</th><th>Claim</th><th>Stage</th><th>Evidence</th></tr></thead><tbody>${claims.map(c=>`<tr class="clickable ${c.claimId===state.selectedClaimId?"selected":""}" data-claim-id="${c.claimId}"><td>${escapeHtml(c.field)}</td><td>${escapeHtml(String(c.value).slice(0,100))}</td><td><span class="badge ${c.validationStatus}">${escapeHtml(c.reviewStage)}</span></td><td>${c.evidenceIds.length}</td></tr>`).join("")}</tbody></table></div></div>
    <div>${claim?claimEditor(claim):`<div class="empty">Add or select a claim.</div>`}</div></div>`;
  document.querySelectorAll("[data-claim-id]").forEach(el=>el.addEventListener("click",()=>{state.selectedClaimId=el.dataset.claimId;const c=getSelectedClaim();state.selectedEvidenceId=c?.evidenceIds?.[0]||null;renderClaimsTab();}));
  byId("addClaim").addEventListener("click",()=>addClaim("clinicalPearls"));
  if(claim) bindClaimEditor(claim);
}

function claimEditor(c){
  return `<div class="panel"><div class="panel-header"><h3>Claim editor</h3><button id="deleteClaim" class="danger">Delete</button></div><div class="panel-body grid">
    ${selectField("claimField","Domain",c.field,FIELD_DEFINITIONS.filter(([, ,t])=>t==="claims").map(([k])=>k))}
    ${selectField("claimApplicability","Applicability",c.applicability,["applicable","not_applicable","evidence_unavailable","pending_research"])}
    ${textareaField("claimValue","Claim / structured value",typeof c.value==="string"?c.value:JSON.stringify(c.value,null,2),true)}
    ${inputField("claimPopulation","Population",c.population)}
    ${inputField("claimIndication","Indication",c.indication)}
    ${inputField("claimRoute","Route",c.route)}
    ${inputField("claimFormulation","Formulation",c.formulation)}
    ${textareaField("claimConditions","Conditions (JSON)",JSON.stringify(c.conditions||{},null,2),true)}
    ${textareaField("claimConsumers","Product consumers",textFromArray(c.consumers),true)}
    ${inputField("claimAuthor","Author",c.author)}
    ${selectField("claimPriority","Research priority",c.researchPriority||"normal",["low","normal","high","critical"])}
    ${inputField("claimNextReview","Next review date",c.nextReviewDate||"","date")}
  </div><div class="panel-body toolbar"><button id="goEvidence">Attach/view evidence</button><button id="advanceClaim">Advance review stage</button></div></div>`;
}

function bindClaimEditor(c){
  const vals={claimField:"field",claimApplicability:"applicability",claimValue:"value",claimPopulation:"population",claimIndication:"indication",claimRoute:"route",claimFormulation:"formulation",claimAuthor:"author",claimPriority:"researchPriority",claimNextReview:"nextReviewDate"};
  Object.entries(vals).forEach(([id,key])=>byId(id).addEventListener("input",e=>{c[key]=e.target.value;c.updatedAt=new Date().toISOString();saveRepository();}));
  byId("claimConditions").addEventListener("change",e=>{try{c.conditions=JSON.parse(e.target.value||"{}");saveRepository();toast("Conditions saved");}catch{toast("Conditions JSON is invalid");}});
  byId("claimConsumers").addEventListener("input",e=>{c.consumers=arrayFromText(e.target.value);saveRepository();});
  byId("deleteClaim").addEventListener("click",()=>{if(confirm("Delete this claim and its evidence?")){const med=getSelectedMedication();med.claimIds=med.claimIds.filter(id=>id!==c.claimId);deleteClaim(c.claimId);state.selectedClaimId=med.claimIds[0]||null;saveRepository();renderClaimsTab();}});
  byId("goEvidence").addEventListener("click",()=>{state.activeTab="evidence";renderActiveTab();});
  byId("advanceClaim").addEventListener("click",()=>advanceClaimStage(c));
}

function advanceClaimStage(c){
  const idx=REVIEW_STAGES.indexOf(c.reviewStage); if(idx<0||idx===REVIEW_STAGES.length-1)return;
  const result=validateClaim(c);
  if(result.errors.length){toast(result.errors[0]);return;}
  c.reviewStage=REVIEW_STAGES[idx+1]; c.validationStatus=c.reviewStage==="published"?"validated":"pending_review"; saveRepository();renderClaimsTab();toast(`Advanced to ${c.reviewStage}`);
}

function renderEvidenceTab(){
  const claim=getSelectedClaim(); const host=byId("tab-evidence");
  if(!claim){host.innerHTML=`<div class="empty">Select a claim first.</div>`;return;}
  const evs=claim.evidenceIds.map(id=>state.repository.evidence[id]).filter(Boolean);
  if(!state.selectedEvidenceId && evs[0])state.selectedEvidenceId=evs[0].evidenceId;
  const ev=getSelectedEvidence();
  host.innerHTML=`<div class="split"><div class="panel"><div class="panel-header"><h2>Evidence</h2><button id="addEvidence">Add evidence</button></div><div class="panel-body table-wrap"><table><thead><tr><th>Source</th><th>Relationship</th><th>Locator</th><th>Status</th></tr></thead><tbody>${evs.map(e=>`<tr class="clickable" data-evidence-id="${e.evidenceId}"><td>${escapeHtml(e.title||e.citation||"Untitled source")}</td><td>${escapeHtml(e.relationship)}</td><td>${escapeHtml(locatorSummary(e.locator))}</td><td>${escapeHtml(e.verificationStatus)}</td></tr>`).join("")}</tbody></table></div></div><div>${ev?evidenceEditor(ev):`<div class="empty">Add or select evidence.</div>`}</div></div>`;
  document.querySelectorAll("[data-evidence-id]").forEach(el=>el.addEventListener("click",()=>{state.selectedEvidenceId=el.dataset.evidenceId;renderEvidenceTab();}));
  byId("addEvidence").addEventListener("click",()=>{const e=newEvidence(claim.claimId);state.repository.evidence[e.evidenceId]=e;claim.evidenceIds.push(e.evidenceId);state.selectedEvidenceId=e.evidenceId;saveRepository();renderEvidenceTab();});
  if(ev)bindEvidenceEditor(ev);
}

function locatorSummary(l){ return [l.section,l.subsection,l.page&&`p. ${l.page}`,l.table&&`Table ${l.table}`,l.recommendation&&`Rec ${l.recommendation}`,l.paragraphOrBullet].filter(Boolean).join(" · "); }
function evidenceEditor(e){ const l=e.locator;
  return `<div class="panel"><div class="panel-header"><h3>Evidence editor</h3><button id="deleteEvidence" class="danger">Delete</button></div><div class="panel-body grid">
    ${selectField("evSourceType","Source type",e.sourceType,["","regulatory_label","regulatory_safety","professional_guideline","government_guideline","primary_literature","review_article","tertiary_reference","institutional_policy","manufacturer_medical_information","expert_consensus"])}
    ${selectField("evRelationship","Relationship",e.relationship,["supports","qualifies","conflicts"])}
    ${textareaField("evCitation","Full citation",e.citation,true)}
    ${inputField("evTitle","Title",e.title)}${inputField("evPublisher","Publisher",e.publisher)}
    ${inputField("evPublicationDate","Publication date",e.publicationDate,"date")}${inputField("evSourceVersion","Source version",e.sourceVersion)}
    ${inputField("evUrl","URL or source ID",e.urlOrId)}${inputField("evAccessed","Accessed date",e.accessedAt,"date")}
    ${inputField("evSection","Section (required)",l.section)}${inputField("evSubsection","Subsection",l.subsection)}
    ${inputField("evPage","Page",l.page)}${inputField("evTable","Table",l.table)}
    ${inputField("evFigure","Figure",l.figure)}${inputField("evRecommendation","Recommendation number",l.recommendation)}
    ${inputField("evParagraph","Paragraph or bullet",l.paragraphOrBullet)}${inputField("evAppendix","Appendix",l.appendix)}
    ${inputField("evAnchor","Electronic anchor",l.electronicAnchor)}
    ${textareaField("evClaimSupported","Exact claim supported",e.claimSupported,true)}
    ${textareaField("evSupportSummary","Concise support summary",e.supportSummary,true)}
    ${inputField("evVerifiedBy","Evidence reviewer",e.verifiedBy)}
  </div><div class="panel-body toolbar"><button id="verifyEvidence">Verify evidence</button></div></div>`;
}

function bindEvidenceEditor(e){
  const map={evSourceType:"sourceType",evRelationship:"relationship",evCitation:"citation",evTitle:"title",evPublisher:"publisher",evPublicationDate:"publicationDate",evSourceVersion:"sourceVersion",evUrl:"urlOrId",evAccessed:"accessedAt",evClaimSupported:"claimSupported",evSupportSummary:"supportSummary",evVerifiedBy:"verifiedBy"};
  Object.entries(map).forEach(([id,key])=>byId(id).addEventListener("input",x=>{e[key]=x.target.value;saveRepository();}));
  const locMap={evSection:"section",evSubsection:"subsection",evPage:"page",evTable:"table",evFigure:"figure",evRecommendation:"recommendation",evParagraph:"paragraphOrBullet",evAppendix:"appendix",evAnchor:"electronicAnchor"};
  Object.entries(locMap).forEach(([id,key])=>byId(id).addEventListener("input",x=>{e.locator[key]=x.target.value;saveRepository();}));
  byId("verifyEvidence").addEventListener("click",()=>{const v=validateEvidence(e);if(v.errors.length){toast(v.errors[0]);return;}e.verificationStatus="validated";e.verifiedAt=new Date().toISOString();e.verificationHistory.push({status:"validated",reviewer:e.verifiedBy,timestamp:e.verifiedAt,ownerSelfReview:isOwnerName(e.verifiedBy)});if(isOwnerName(e.verifiedBy))addAudit("owner_evidence_self_verification",e.evidenceId,{reviewer:e.verifiedBy,temporary:true});saveRepository();renderEvidenceTab();toast("Evidence validated");});
  byId("deleteEvidence").addEventListener("click",()=>{if(confirm("Delete this evidence?")){const c=getSelectedClaim();c.evidenceIds=c.evidenceIds.filter(id=>id!==e.evidenceId);delete state.repository.evidence[e.evidenceId];state.selectedEvidenceId=c.evidenceIds[0]||null;saveRepository();renderEvidenceTab();}});
}

function validateEvidence(e){
  const errors=[]; if(!e.sourceType)errors.push("Source type is required."); if(!e.citation)errors.push("Full citation is required.");
  if(!e.locator?.section)errors.push("Source section is required.");
  const precise=["subsection","page","table","figure","recommendation","paragraphOrBullet","appendix","electronicAnchor"].some(k=>String(e.locator?.[k]||"").trim());
  if(!precise)errors.push("At least one precise source locator is required.");
  if(!e.claimSupported)errors.push("The exact claim supported is required."); if(!e.supportSummary)errors.push("Support summary is required."); if(!e.verifiedBy)errors.push("Evidence reviewer is required.");
  return {errors};
}

function validateClaim(c){
  const errors=[]; if(!c.value && c.applicability==="applicable")errors.push("Applicable claims require a value.");
  if(c.applicability==="applicable" && !c.evidenceIds.length)errors.push("Applicable claims require evidence.");
  const evidence=c.evidenceIds.map(id=>state.repository.evidence[id]).filter(Boolean);
  if(c.reviewStage!=="draft" && evidence.some(e=>validateEvidence(e).errors.length))errors.push("All attached evidence must have a complete locator and claim mapping.");
  if(["secondary_reviewed","approved","published"].includes(c.reviewStage) && (!c.primaryReviewer||!c.secondaryReviewer))errors.push("Independent primary and secondary reviewers are required.");
  const ownerOverride=ownerOverrideAllowed(c);
  if(c.primaryReviewer && c.secondaryReviewer && c.primaryReviewer===c.secondaryReviewer && !ownerOverride)errors.push("Primary and secondary reviewers must be different unless temporary owner mode is active.");
  if(c.author && c.primaryReviewer && c.author===c.primaryReviewer && !ownerOverride)errors.push("The claim author cannot be the sole primary reviewer unless temporary owner mode is active.");
  if(c.approver && c.secondaryReviewer && c.approver===c.secondaryReviewer && !ownerOverride)errors.push("Publication approver must be independent unless temporary owner mode is active.");
  if(evidence.some(e=>e.relationship==="conflicts") && c.conflict.status!=="resolved")errors.push("Conflicting evidence requires documented resolution.");
  return {errors};
}

function renderReviewTab(){
  const c=getSelectedClaim(); const host=byId("tab-review"); if(!c){host.innerHTML=`<div class="empty">Select a claim.</div>`;return;}
  const conflictEvidence=c.evidenceIds.map(id=>state.repository.evidence[id]).filter(e=>e?.relationship==="conflicts");
  host.innerHTML=`<div class="panel"><div class="panel-header"><h2>Independent review</h2><span class="badge ${c.validationStatus}">${escapeHtml(c.reviewStage)}</span></div><div class="panel-body grid">
    ${inputField("reviewAuthor","Claim author",c.author)}${inputField("reviewPrimary","Primary reviewer",c.primaryReviewer)}
    ${inputField("reviewSecondary","Secondary reviewer",c.secondaryReviewer)}${inputField("reviewApprover","Publication approver",c.approver)}
    ${selectField("conflictStatus","Conflict status",c.conflict.status,["none","open","resolved"])}
    ${selectField("preferredEvidence","Preferred evidence",c.conflict.preferredEvidenceId,["",...c.evidenceIds])}
    ${textareaField("conflictRationale","Conflict resolution rationale",c.conflict.rationale,true)}
    ${inputField("conflictResolvedBy","Resolved by",c.conflict.resolvedBy)}
  </div><div class="panel-body"><div class="notice ${conflictEvidence.length?"warning":""}">${conflictEvidence.length?`${conflictEvidence.length} conflicting evidence record(s) attached. Resolution is required before publication.`:"No conflicting evidence is currently attached."}</div><div class="toolbar"><button id="saveReview">Save review</button><button id="approveClaim" class="primary">Approve claim</button><button id="publishClaim">Publish claim</button></div></div></div>`;
  byId("saveReview").addEventListener("click",()=>saveReview(c));
  byId("approveClaim").addEventListener("click",()=>{saveReview(c);const v=validateClaim(c);if(v.errors.length){toast(v.errors[0]);return;}c.reviewStage="approved";c.validationStatus="validated";if(ownerOverrideAllowed(c))recordOwnerOverride(c,"approved");saveRepository();renderReviewTab();toast("Claim approved");});
  byId("publishClaim").addEventListener("click",()=>{saveReview(c);const v=validateClaim(c);if(v.errors.length||c.reviewStage!=="approved"){toast(v.errors[0]||"Approve the claim before publication.");return;}c.reviewStage="published";c.validationStatus="validated";if(ownerOverrideAllowed(c))recordOwnerOverride(c,"published");saveRepository();renderReviewTab();toast("Claim published");});
}

function saveReview(c){
  c.author=byId("reviewAuthor").value;c.primaryReviewer=byId("reviewPrimary").value;c.secondaryReviewer=byId("reviewSecondary").value;c.approver=byId("reviewApprover").value;
  c.conflict.status=byId("conflictStatus").value;c.conflict.preferredEvidenceId=byId("preferredEvidence").value;c.conflict.rationale=byId("conflictRationale").value;c.conflict.resolvedBy=byId("conflictResolvedBy").value;
  if(c.conflict.status==="resolved")c.conflict.resolvedAt=new Date().toISOString();saveRepository();toast("Review saved");
}

function repositoryValidation(){
  const errors=[],warnings=[];
  Object.values(state.repository.medications).forEach(m=>{if(!m.generic)errors.push({entity:m.medicationId,message:"Medication generic name is required."});if(m.reviewerPrimary&&m.reviewerPrimary===m.reviewerSecondary&&!isOwnerName(m.reviewerPrimary))errors.push({entity:m.medicationId,message:"Medication reviewers must be independent unless temporary owner mode is active."});});
  Object.values(state.repository.claims).forEach(c=>validateClaim(c).errors.forEach(message=>errors.push({entity:c.claimId,message})));
  Object.values(state.repository.evidence).forEach(e=>validateEvidence(e).errors.forEach(message=>errors.push({entity:e.evidenceId,message})));
  const due=Object.values(state.repository.medications).filter(m=>m.nextReviewDate && m.nextReviewDate < new Date().toISOString().slice(0,10));
  due.forEach(m=>warnings.push({entity:m.medicationId,message:"Medication review is overdue."}));
  return {errors,warnings,valid:errors.length===0};
}

function renderValidationTab(){
  const report=repositoryValidation(); const host=byId("tab-validation");
  host.innerHTML=`<div class="panel"><div class="panel-header"><h2>Repository validation</h2><span class="badge ${report.valid?"validated":"blocked"}">${report.valid?"VALID":"BLOCKED"}</span></div><div class="panel-body"><div class="notice ${report.valid?"":"error"}">${report.valid?"The current repository passes the structural publication gate.":`${report.errors.length} blocking issue(s) must be resolved.`}</div>
    <div class="table-wrap"><table><thead><tr><th>Severity</th><th>Entity</th><th>Issue</th></tr></thead><tbody>${[...report.errors.map(x=>({...x,severity:"ERROR"})),...report.warnings.map(x=>({...x,severity:"WARNING"}))].map(x=>`<tr><td>${x.severity}</td><td>${escapeHtml(x.entity)}</td><td>${escapeHtml(x.message)}</td></tr>`).join("")||`<tr><td colspan="3">No issues.</td></tr>`}</tbody></table></div>
    <h3>Machine-readable report</h3><pre>${escapeHtml(JSON.stringify(report,null,2))}</pre></div></div>`;
}


const CLAIM_TEMPLATES = [
  {templateId:"renal_crcl",label:"Renal dose range",field:"renalDosing",value:"",conditions:{renalMethod:"Cockcroft-Gault CrCl",minimum:null,maximum:null},population:"adult"},
  {templateId:"dose_indication",label:"Dose by indication",field:"doseByIndication",value:"",conditions:{},population:"adult"},
  {templateId:"contraindication",label:"Contraindication",field:"contraindications",value:"",conditions:{severity:""},population:"all"},
  {templateId:"interaction",label:"Drug interaction",field:"highYieldInteractions",value:"",conditions:{interactingMedication:"",severity:"",recommendedAction:""},population:"adult"},
  {templateId:"crush",label:"Crush / manipulation",field:"crushability",value:"",conditions:{formulation:"",status:""},population:"all"},
  {templateId:"tube",label:"Tube administration",field:"tubeAdministration",value:"",conditions:{tubeRoute:"",feedingInstructions:""},population:"all"},
  {templateId:"administration",label:"Administration",field:"administration",value:"",conditions:{route:""},population:"adult"},
  {templateId:"monitoring",label:"Monitoring",field:"monitoring",value:"",conditions:{baseline:"",ongoing:"",actionThreshold:""},population:"adult"}
];

function addClaimFromTemplate(templateId){
  const t=CLAIM_TEMPLATES.find(x=>x.templateId===templateId);if(!t)return;
  const med=getSelectedMedication();if(!med)return;
  const c=newClaim(med.medicationId,t.field);c.templateId=t.templateId;c.population=t.population;c.conditions=JSON.parse(JSON.stringify(t.conditions));c.value=t.value;
  state.repository.claims[c.claimId]=c;med.claimIds.push(c.claimId);state.selectedClaimId=c.claimId;state.selectedEvidenceId=null;saveRepository();state.activeTab="claims";renderAll();
}

function newResearchAssignment(medicationId="",field=""){
  return {assignmentId:uid("research"),medicationId,field,claimId:"",assignee:ownerSettings().ownerName||"",priority:"normal",status:"open",question:"",suggestedSources:[],dueDate:"",notes:"",createdAt:new Date().toISOString(),completedAt:""};
}
function newSourceIntake(){return {sourceIntakeId:uid("source"),title:"",sourceType:"",citation:"",urlOrId:"",sourceVersion:"",publicationDate:"",status:"new",assignedTo:ownerSettings().ownerName||"",medicationIds:[],notes:"",createdAt:new Date().toISOString()};}

function renderQueueTab(){
  const host=byId("tab-queue");const assignments=Object.values(state.repository.researchAssignments);const sources=Object.values(state.repository.sourceIntake);
  host.innerHTML=`<div class="panel"><div class="panel-header"><h2>Research assignments</h2><button id="addAssignment">Add assignment</button></div><div class="panel-body table-wrap"><table><thead><tr><th>Medication</th><th>Field</th><th>Question</th><th>Priority</th><th>Status</th><th>Assignee</th></tr></thead><tbody>${assignments.map(a=>`<tr><td>${escapeHtml(a.medicationId)}</td><td>${escapeHtml(a.field)}</td><td><input data-ra="${a.assignmentId}" data-key="question" value="${escapeHtml(a.question)}"></td><td><select data-ra="${a.assignmentId}" data-key="priority">${["low","normal","high","critical"].map(v=>`<option ${v===a.priority?"selected":""}>${v}</option>`).join("")}</select></td><td><select data-ra="${a.assignmentId}" data-key="status">${["open","researching","ready_for_review","complete","blocked"].map(v=>`<option ${v===a.status?"selected":""}>${v}</option>`).join("")}</select></td><td><input data-ra="${a.assignmentId}" data-key="assignee" value="${escapeHtml(a.assignee)}"></td></tr>`).join("")||`<tr><td colspan="6">No assignments.</td></tr>`}</tbody></table></div></div>
  <div class="panel"><div class="panel-header"><h2>Source intake</h2><button id="addSourceIntake">Add source</button></div><div class="panel-body table-wrap"><table><thead><tr><th>Title</th><th>Type</th><th>Version</th><th>Status</th><th>Assigned</th></tr></thead><tbody>${sources.map(x=>`<tr><td><input data-si="${x.sourceIntakeId}" data-key="title" value="${escapeHtml(x.title)}"></td><td><input data-si="${x.sourceIntakeId}" data-key="sourceType" value="${escapeHtml(x.sourceType)}"></td><td><input data-si="${x.sourceIntakeId}" data-key="sourceVersion" value="${escapeHtml(x.sourceVersion)}"></td><td><select data-si="${x.sourceIntakeId}" data-key="status">${["new","screened","assigned","extracted","reviewed","retired"].map(v=>`<option ${v===x.status?"selected":""}>${v}</option>`).join("")}</select></td><td><input data-si="${x.sourceIntakeId}" data-key="assignedTo" value="${escapeHtml(x.assignedTo)}"></td></tr>`).join("")||`<tr><td colspan="5">No sources in queue.</td></tr>`}</tbody></table></div></div>`;
  byId("addAssignment").addEventListener("click",()=>{const a=newResearchAssignment(state.selectedMedicationId||"","");state.repository.researchAssignments[a.assignmentId]=a;saveRepository();renderQueueTab();});
  byId("addSourceIntake").addEventListener("click",()=>{const x=newSourceIntake();state.repository.sourceIntake[x.sourceIntakeId]=x;saveRepository();renderQueueTab();});
  document.querySelectorAll("[data-ra]").forEach(el=>el.addEventListener("input",e=>{state.repository.researchAssignments[el.dataset.ra][el.dataset.key]=e.target.value;saveRepository();}));
  document.querySelectorAll("[data-si]").forEach(el=>el.addEventListener("input",e=>{state.repository.sourceIntake[el.dataset.si][el.dataset.key]=e.target.value;saveRepository();}));
}

function medicationCoverage(m){
  const claimFields=new Set((m.claimIds||[]).map(id=>state.repository.claims[id]?.field).filter(Boolean));
  const core=["doseByIndication","renalDosing","hepaticDosing","hd","crrt","pd","administration","contraindications","monitoring","majorInteractions","highYieldInteractions","crushability","tubeAdministration","pregnancy","lactation","clinicalPearls"];
  const statuses=core.map(field=>({field,status:claimFields.has(field)?"started":"missing"}));
  const started=statuses.filter(x=>x.status==="started").length;
  return {started,total:core.length,percent:Math.round(started/core.length*100),statuses};
}
function renderCoverageTab(){
  const host=byId("tab-coverage");const meds=Object.values(state.repository.medications).map(m=>({m,c:medicationCoverage(m)}));
  const missing={};FIELD_DEFINITIONS.filter(([, ,t])=>t==="claims").forEach(([k])=>missing[k]=0);meds.forEach(({c})=>c.statuses.forEach(x=>{if(x.status==="missing")missing[x.field]=(missing[x.field]||0)+1;}));
  host.innerHTML=`<div class="panel"><div class="panel-header"><h2>Medication coverage</h2><div class="toolbar"><button id="seedGoldSet">Seed 50-medication foundation set</button><button id="createMissingAssignments">Create research assignments for missing domains</button></div></div><div class="panel-body table-wrap"><table><thead><tr><th>Medication</th><th>Claims</th><th>Core coverage</th><th>Status</th></tr></thead><tbody>${meds.map(({m,c})=>`<tr><td>${escapeHtml(m.generic)}</td><td>${(m.claimIds||[]).length}</td><td><progress max="100" value="${c.percent}"></progress> ${c.percent}%</td><td>${escapeHtml(m.recordStatus)}</td></tr>`).join("")||`<tr><td colspan="4">No medications.</td></tr>`}</tbody></table></div></div>
  <div class="panel"><div class="panel-header"><h3>Missing-domain totals</h3></div><div class="panel-body table-wrap"><table><thead><tr><th>Domain</th><th>Medication records missing</th></tr></thead><tbody>${Object.entries(missing).filter(([,n])=>n).sort((a,b)=>b[1]-a[1]).map(([k,n])=>`<tr><td>${escapeHtml(k)}</td><td>${n}</td></tr>`).join("")||`<tr><td colspan="2">No missing core domains.</td></tr>`}</tbody></table></div></div>`;
  byId("seedGoldSet").addEventListener("click",seedFoundationMedicationSet);
  byId("createMissingAssignments").addEventListener("click",createMissingResearchAssignments);
}

const FOUNDATION_MEDICATIONS=["vancomycin","piperacillin/tazobactam","cefepime","meropenem","ceftriaxone","cefazolin","ampicillin","ampicillin/sulbactam","daptomycin","linezolid","trimethoprim/sulfamethoxazole","fluconazole","voriconazole","micafungin","acyclovir","apixaban","rivaroxaban","warfarin","heparin","enoxaparin","alteplase","tenecteplase","aspirin","clopidogrel","norepinephrine","epinephrine","vasopressin","phenylephrine","dopamine","dobutamine","amiodarone","diltiazem","metoprolol","digoxin","levetiracetam","phenytoin","fosphenytoin","valproic acid","lacosamide","insulin glargine","insulin lispro","regular insulin","tacrolimus","cyclosporine","mycophenolate mofetil","prednisone","potassium chloride","sodium phosphate","potassium phosphate","magnesium sulfate"];
function seedFoundationMedicationSet(){
  let added=0;FOUNDATION_MEDICATIONS.forEach(name=>{const id=slug(name);if(!state.repository.medications[id]){const m=newMedication(name);m.medicationId=id;m.author=ownerSettings().ownerName;m.notes="Foundation medication shell. Clinical claims must be sourced before publication.";state.repository.medications[id]=m;added++;}});addAudit("foundation_set_seeded","repository",{added,total:FOUNDATION_MEDICATIONS.length});saveRepository();renderAll();toast(`${added} medication shell(s) added`);
}
function createMissingResearchAssignments(){
  let added=0;Object.values(state.repository.medications).forEach(m=>{medicationCoverage(m).statuses.filter(x=>x.status==="missing").forEach(x=>{const exists=Object.values(state.repository.researchAssignments).some(a=>a.medicationId===m.medicationId&&a.field===x.field&&a.status!=="complete");if(!exists){const a=newResearchAssignment(m.medicationId,x.field);a.question=`Research and source ${x.field} for ${m.generic}.`;state.repository.researchAssignments[a.assignmentId]=a;added++;}})});saveRepository();toast(`${added} research assignment(s) created`);renderQueueTab();
}


const CRD_SNAPSHOT_KEY = "crd-workbench-snapshots";
const CRD_MAX_SNAPSHOTS = 12;

function repositoryCounts(repo=state.repository){
  return {
    medications:Object.keys(repo?.medications||{}).length,
    claims:Object.keys(repo?.claims||{}).length,
    evidence:Object.keys(repo?.evidence||{}).length,
    calculators:Object.keys(repo?.calculators||{}).length,
    findingRules:Object.keys(repo?.findingRules||{}).length
  };
}
function createLocalRepositorySnapshot(reason="manual"){
  if(!state.repository)return null;
  const snapshots=JSON.parse(localStorage.getItem(CRD_SNAPSHOT_KEY)||"[]");
  const snapshot={
    snapshotId:`snapshot_${Date.now()}`,
    createdAt:new Date().toISOString(),
    reason,
    counts:repositoryCounts(),
    repository:state.repository
  };
  snapshots.unshift(snapshot);
  localStorage.setItem(CRD_SNAPSHOT_KEY,JSON.stringify(snapshots.slice(0,CRD_MAX_SNAPSHOTS)));
  return snapshot;
}
function listLocalRepositorySnapshots(){
  try{return JSON.parse(localStorage.getItem(CRD_SNAPSHOT_KEY)||"[]");}catch{return [];}
}
function restoreLocalRepositorySnapshot(snapshotId){
  const snapshot=listLocalRepositorySnapshots().find(x=>x.snapshotId===snapshotId);
  if(!snapshot)throw new Error("Snapshot not found.");
  createLocalRepositorySnapshot("before_snapshot_restore");
  state.repository=normalizeRepository(JSON.parse(JSON.stringify(snapshot.repository)));
  state.selectedMedicationId=Object.keys(state.repository.medications||{})[0]||null;
  state.selectedClaimId=null;state.selectedEvidenceId=null;
  saveRepository();renderAll();
}
function exportOwnerBackup(){
  const snapshot=createLocalRepositorySnapshot("owner_export");
  const payload={
    backupType:"pharmacist.guide.owner-database-backup",
    schemaVersion:"1.0.0",
    exportedAt:new Date().toISOString(),
    counts:repositoryCounts(),
    repository:state.repository,
    localSnapshots:listLocalRepositorySnapshots().map(({repository,...meta})=>meta)
  };
  downloadTextFile(`PhUCKIT-Owner-Database-${new Date().toISOString().replace(/[:.]/g,"-")}.json`,JSON.stringify(payload,null,2),"application/json");
  toast("Owner database backup exported.");
}
function mergeRepositoryData(current,incoming){
  const merged=normalizeRepository(JSON.parse(JSON.stringify(current||emptyRepository())));
  const source=incoming?.repository?incoming.repository:incoming;
  ["medications","claims","evidence","reviews","calculators","findingRules","researchAssignments","sourceIntake"].forEach(key=>{
    merged[key]=Object.assign({},merged[key]||{},source?.[key]||{});
  });
  merged.auditLog=[...(merged.auditLog||[]),...(source?.auditLog||[])];
  merged.settings=Object.assign({},merged.settings||{},source?.settings||{});
  merged.updatedAt=new Date().toISOString();
  return merged;
}
function importOwnerBackupFile(file,mode="merge"){
  const reader=new FileReader();
  reader.onload=()=>{
    try{
      const parsed=JSON.parse(reader.result);
      const incoming=parsed.repository||parsed;
      createLocalRepositorySnapshot(`before_${mode}_import`);
      state.repository=mode==="replace"
        ? normalizeRepository(incoming)
        : mergeRepositoryData(state.repository,incoming);
      state.repository=ensureTop300Seed(state.repository);
      state.selectedMedicationId=Object.keys(state.repository.medications||{})[0]||null;
      state.selectedClaimId=null;state.selectedEvidenceId=null;
      saveRepository();renderAll();
      toast(`Owner database ${mode==="replace"?"restored":"merged"}; ${repositoryCounts().medications} medications available.`);
    }catch(e){alert(`Backup import failed: ${e.message}`);}
  };
  reader.readAsText(file);
}
function renderPreservationPanel(){
  const snapshots=listLocalRepositorySnapshots();
  const counts=repositoryCounts();
  return `<div class="panel"><div class="panel-header"><div><h2>Preservation and recovery</h2>
    <p class="muted">Owner edits are preserved across updates. Export a portable backup before major changes.</p></div>
    <span class="badge approved">${counts.medications} medications · ${counts.calculators} calculators · ${counts.findingRules} rules</span></div>
    <div class="panel-body">
      <div class="toolbar">
        <button id="exportOwnerBackup" class="primary">Export Owner Backup</button>
        <label class="file-button">Merge Backup<input id="mergeOwnerBackup" type="file" accept=".json,application/json" hidden></label>
        <label class="file-button">Replace From Backup<input id="replaceOwnerBackup" type="file" accept=".json,application/json" hidden></label>
        <button id="createOwnerSnapshot">Create Local Snapshot</button>
      </div>
      <div class="field wide"><label>Local recovery snapshots</label>
        <div class="snapshot-list">${snapshots.length?snapshots.map(s=>`<div class="snapshot-row">
          <div><strong>${escapeHtml(s.reason)}</strong><small>${escapeHtml(s.createdAt)} · ${s.counts?.medications||0} medications</small></div>
          <button data-restore-snapshot="${escapeHtml(s.snapshotId)}">Restore</button></div>`).join(""):`<div class="empty">No local snapshots yet.</div>`}</div>
      </div>
      <p class="help">Local snapshots stay in this browser profile. Exported owner backups are portable and should be retained outside the installation folder.</p>
    </div></div>`;
}
function bindPreservationPanel(){
  byId("exportOwnerBackup")?.addEventListener("click",exportOwnerBackup);
  byId("mergeOwnerBackup")?.addEventListener("change",e=>{const f=e.target.files?.[0];if(f)importOwnerBackupFile(f,"merge");});
  byId("replaceOwnerBackup")?.addEventListener("change",e=>{const f=e.target.files?.[0];if(f&&confirm("Replace the current owner database from this backup? A local recovery snapshot will be created first."))importOwnerBackupFile(f,"replace");});
  byId("createOwnerSnapshot")?.addEventListener("click",()=>{createLocalRepositorySnapshot("manual");renderSettingsTab();toast("Local recovery snapshot created.");});
  document.querySelectorAll("[data-restore-snapshot]").forEach(btn=>btn.addEventListener("click",()=>{
    if(confirm("Restore this local snapshot? The current repository will be snapshotted first.")){restoreLocalRepositorySnapshot(btn.dataset.restoreSnapshot);toast("Snapshot restored.");}
  }));
}

function renderSettingsTab(){
  const host=byId("tab-settings"),st=ownerSettings();
  host.innerHTML=`<div class="panel"><div class="panel-header"><h2>Owner build mode</h2><span class="badge warning">TEMPORARY EXCEPTION</span></div><div class="panel-body grid"><div class="notice warning wide">${escapeHtml(st.ownerModeNotice)}</div>${inputField("ownerName","Owner name",st.ownerName)}${selectField("ownerEnabled","Owner mode enabled",String(st.ownerModeEnabled),["true","false"])}${selectField("ownerSelfReview","Owner may self-review",String(st.ownerTemporarySelfReview),["true","false"])}<div class="field wide"><label>Audit events</label><pre>${escapeHtml(JSON.stringify(state.repository.auditLog.slice(-30),null,2))}</pre></div></div></div>${renderPreservationPanel()}`;
  byId("ownerName").addEventListener("input",e=>{st.ownerName=e.target.value;saveRepository();});
  byId("ownerEnabled").addEventListener("input",e=>{st.ownerModeEnabled=e.target.value==="true";saveRepository();});
  byId("ownerSelfReview").addEventListener("input",e=>{st.ownerTemporarySelfReview=e.target.value==="true";saveRepository();});
  bindPreservationPanel();
}

function createMedicationPrompt(){const name=prompt("Generic medication name:");if(!name)return;const m=newMedication(name.trim());let id=m.medicationId;if(state.repository.medications[id])id=`${id}_${Date.now()}`;m.medicationId=id;state.repository.medications[id]=m;state.selectedMedicationId=id;saveRepository();renderAll();}

function exportRepository(){
  const report=repositoryValidation(); const payload={...state.repository,validationReport:report,exportedAt:new Date().toISOString()};
  const blob=new Blob([JSON.stringify(payload,null,2)],{type:"application/json"});const a=document.createElement("a");a.href=URL.createObjectURL(blob);a.download=`CRD_repository_${new Date().toISOString().slice(0,10)}.json`;a.click();URL.revokeObjectURL(a.href);toast("Repository exported");
}

function importRepositoryFile(file){importOwnerBackupFile(file,"merge");}

function seedStructuralExample(){
  if(Object.keys(state.repository.medications).length)return;
  const m=newMedication("Structural example — not clinical content");m.fields.brands=["Example brand"];m.fields.ocrAliases=["example alias"];state.repository.medications[m.medicationId]=m;state.selectedMedicationId=m.medicationId;
  const c=newClaim(m.medicationId,"clinicalPearls");c.value="This record demonstrates the authoring structure only. It contains no medication recommendation.";c.author="system";m.claimIds.push(c.claimId);state.repository.claims[c.claimId]=c;state.selectedClaimId=c.claimId;saveRepository();
}


// =====================================================================
// RC9 Phase 2 — Owner Truth Console + Calculator Registry
// =====================================================================

const CALCULATOR_TRUTH_SEED = [
  {
    calculatorId:"crcl_cockcroft_gault",
    name:"Creatinine Clearance — Cockcroft-Gault",
    category:"Renal",
    purpose:"Estimate creatinine clearance for medication dosing.",
    status:"active_test",
    methodologyVersion:"1.0.0",
    formula:"CrCl = ((140 - age) × dosing weight) / (72 × SCr); multiply by 0.85 when female scalar is selected.",
    variables:[
      {key:"age",label:"Age",unit:"years",type:"number",required:true,min:18,max:120},
      {key:"weight",label:"Dosing weight",unit:"kg",type:"number",required:true,min:20,max:400},
      {key:"scr",label:"Serum creatinine",unit:"mg/dL",type:"number",required:true,min:0.1,max:20},
      {key:"sexScalar",label:"Sex scalar",unit:"",type:"enum",required:true,values:["1","0.85"]}
    ],
    rules:[
      "Use the weight-selection methodology configured in the active body-weight calculator truth.",
      "Do not silently round serum creatinine.",
      "Preserve the unrounded result for downstream decisions.",
      "Display the result in mL/min."
    ],
    rounding:{displayDecimals:1,calculationDecimals:null,method:"standard"},
    exclusions:["Not validated for pediatric patients.","Use clinical judgment in rapidly changing renal function."],
    outputs:[{key:"crcl",label:"Estimated CrCl",unit:"mL/min"}],
    sourceTruths:[
      {claim:"Core Cockcroft-Gault equation",citation:"Owner source truth pending exact citation",section:"Methodology",locator:"Equation",status:"owner_active_test"}
    ],
    tests:[
      {name:"Reference adult",inputs:{age:65,weight:70,scr:1,sexScalar:1},expected:{crcl:72.9},tolerance:0.2}
    ],
    consumers:["PhUCKIT","Clinical Reasoning Database"],
    changeHistory:[{version:"1.0.0",change:"Initial owner-editable methodology seed"}]
  },
  {
    calculatorId:"body_weight",
    name:"Ideal and Adjusted Body Weight",
    category:"Dosing foundations",
    purpose:"Calculate IBW and AdjBW and select an appropriate dosing-weight candidate.",
    status:"active_test",
    methodologyVersion:"1.0.0",
    formula:"Male IBW = 50 + 2.3 × inches over 60; female IBW = 45.5 + 2.3 × inches over 60. AdjBW = IBW + factor × (ABW - IBW).",
    variables:[
      {key:"heightIn",label:"Height",unit:"inches",type:"number",required:true,min:36,max:96},
      {key:"actualWeight",label:"Actual weight",unit:"kg",type:"number",required:true,min:10,max:500},
      {key:"sex",label:"Sex used by equation",unit:"",type:"enum",required:true,values:["male","female"]},
      {key:"adjustmentFactor",label:"AdjBW factor",unit:"",type:"number",required:true,min:0,max:1,default:0.4}
    ],
    rules:["Height below 60 inches requires explicit owner methodology rather than silent extrapolation.","Show ABW, IBW, AdjBW, and percent of IBW separately.","The consuming calculator decides which weight to use."],
    rounding:{displayDecimals:1,method:"standard"},
    exclusions:["Amputation and edema/ascites require an adjusted workflow."],
    outputs:[{key:"ibw",label:"Ideal body weight",unit:"kg"},{key:"adjbw",label:"Adjusted body weight",unit:"kg"}],
    sourceTruths:[{claim:"IBW and AdjBW equations",citation:"Owner source truth pending exact citation",section:"Methodology",locator:"Equations",status:"owner_active_test"}],
    tests:[{name:"Male 70 inches",inputs:{heightIn:70,actualWeight:100,sex:"male",adjustmentFactor:0.4},expected:{ibw:73,adjbw:83.8},tolerance:0.2}],
    consumers:["PhUCKIT","CrCl","Aminoglycoside dosing","Vancomycin dosing"],
    changeHistory:[{version:"1.0.0",change:"Initial owner-editable methodology seed"}]
  },
  {
    calculatorId:"corrected_calcium",
    name:"Corrected Calcium",
    category:"Laboratory",
    purpose:"Estimate albumin-corrected total calcium.",
    status:"active_test",
    methodologyVersion:"1.0.0",
    formula:"Corrected calcium = measured calcium + 0.8 × (4 - albumin).",
    variables:[
      {key:"calcium",label:"Measured calcium",unit:"mg/dL",type:"number",required:true},
      {key:"albumin",label:"Albumin",unit:"g/dL",type:"number",required:true}
    ],
    rules:["Display measured and corrected calcium.","Do not substitute for ionized calcium when clinically required."],
    rounding:{displayDecimals:1,method:"standard"},
    exclusions:["Method performance varies in critical illness and severe hypoalbuminemia."],
    outputs:[{key:"correctedCalcium",label:"Corrected calcium",unit:"mg/dL"}],
    sourceTruths:[{claim:"Correction equation",citation:"Owner source truth pending exact citation",section:"Methodology",locator:"Equation",status:"owner_active_test"}],
    tests:[{name:"Low albumin",inputs:{calcium:7.8,albumin:2},expected:{correctedCalcium:9.4},tolerance:0.1}],
    consumers:["PhUCKIT","Patient review"],
    changeHistory:[{version:"1.0.0",change:"Initial seed"}]
  },
  {
    calculatorId:"anion_gap",
    name:"Anion Gap and Delta-Delta",
    category:"Laboratory",
    purpose:"Calculate anion gap and optional delta ratio.",
    status:"active_test",
    methodologyVersion:"1.0.0",
    formula:"AG = Na - (Cl + HCO3). Corrected AG may add 2.5 × (4 - albumin). Delta ratio = (AG - normal AG) / (24 - HCO3).",
    variables:[
      {key:"sodium",label:"Sodium",unit:"mmol/L",type:"number",required:true},
      {key:"chloride",label:"Chloride",unit:"mmol/L",type:"number",required:true},
      {key:"bicarbonate",label:"Bicarbonate",unit:"mmol/L",type:"number",required:true},
      {key:"albumin",label:"Albumin",unit:"g/dL",type:"number",required:false},
      {key:"normalAg",label:"Normal AG reference",unit:"mmol/L",type:"number",required:true,default:12}
    ],
    rules:["Make inclusion/exclusion of potassium explicit.","Show uncorrected and albumin-corrected values when albumin is available.","Normal AG is an editable owner truth."],
    rounding:{displayDecimals:1,method:"standard"},
    exclusions:["Interpretation depends on local laboratory method and reference interval."],
    outputs:[{key:"anionGap",label:"Anion gap",unit:"mmol/L"},{key:"deltaRatio",label:"Delta ratio",unit:""}],
    sourceTruths:[{claim:"Anion gap and delta equations",citation:"Owner source truth pending exact citation",section:"Methodology",locator:"Equations",status:"owner_active_test"}],
    tests:[{name:"Elevated AG",inputs:{sodium:140,chloride:100,bicarbonate:15,albumin:4,normalAg:12},expected:{anionGap:25,deltaRatio:1.44},tolerance:0.1}],
    consumers:["PhUCKIT","Patient review"],
    changeHistory:[{version:"1.0.0",change:"Initial seed"}]
  },
  {
    calculatorId:"qtc",
    name:"QTc",
    category:"Cardiology",
    purpose:"Calculate corrected QT using selectable methodology.",
    status:"active_test",
    methodologyVersion:"1.0.0",
    formula:"Bazett: QTc = QT / √RR. Fridericia: QTc = QT / RR^(1/3). Framingham and Hodges may be configured as alternatives.",
    variables:[
      {key:"qt",label:"QT interval",unit:"ms",type:"number",required:true},
      {key:"heartRate",label:"Heart rate",unit:"bpm",type:"number",required:true},
      {key:"method",label:"Correction method",unit:"",type:"enum",required:true,values:["Bazett","Fridericia","Framingham","Hodges"]}
    ],
    rules:["Convert heart rate to RR in seconds.","Show the selected method in the output.","Preserve the raw QT and heart rate."],
    rounding:{displayDecimals:0,method:"standard"},
    exclusions:["Wide QRS and paced rhythms require a separate methodology."],
    outputs:[{key:"qtc",label:"Corrected QT",unit:"ms"}],
    sourceTruths:[{claim:"QT correction equations",citation:"Owner source truth pending exact citation",section:"Methodology",locator:"Equations",status:"owner_active_test"}],
    tests:[{name:"Bazett reference",inputs:{qt:400,heartRate:60,method:"Bazett"},expected:{qtc:400},tolerance:1}],
    consumers:["PhUCKIT","Patient medication safety review"],
    changeHistory:[{version:"1.0.0",change:"Initial seed"}]
  },
  {
    calculatorId:"opioid_mme",
    name:"Opioid MME Estimate",
    category:"Conversions",
    purpose:"Estimate oral morphine milligram equivalents using owner-maintained conversion truths.",
    status:"active_test",
    methodologyVersion:"1.0.0",
    formula:"MME = dose × frequency × active conversion factor.",
    variables:[
      {key:"opioid",label:"Opioid",unit:"",type:"enum",required:true,values:["morphine","oxycodone","hydrocodone","hydromorphone","codeine","tramadol"]},
      {key:"dose",label:"Dose per administration",unit:"mg",type:"number",required:true},
      {key:"administrationsPerDay",label:"Administrations/day",unit:"",type:"number",required:true}
    ],
    rules:["Conversion factors are owner-editable source truths.","Methadone and fentanyl require separate nonlinear or formulation-specific methodology.","Output is an estimate, not an automatic prescribing recommendation."],
    rounding:{displayDecimals:1,method:"standard"},
    exclusions:["Incomplete cross-tolerance and patient variability are not captured by a simple MME."],
    outputs:[{key:"mme",label:"Estimated daily MME",unit:"mg/day"}],
    sourceTruths:[{claim:"Opioid conversion factors",citation:"Owner source truth pending exact citation",section:"Conversion table",locator:"Table",status:"owner_active_test"}],
    tests:[],
    consumers:["PhUCKIT"],
    changeHistory:[{version:"1.0.0",change:"Initial seed"}]
  },
  {
    calculatorId:"steroid_equivalency",
    name:"Corticosteroid Equivalency",
    category:"Conversions",
    purpose:"Convert systemic corticosteroid doses using owner-maintained equivalency truths.",
    status:"active_test",
    methodologyVersion:"1.0.0",
    formula:"Equivalent dose = input dose × reference-equivalent factor.",
    variables:[
      {key:"sourceSteroid",label:"Source steroid",unit:"",type:"enum",required:true,values:["hydrocortisone","prednisone","methylprednisolone","dexamethasone"]},
      {key:"dose",label:"Source dose",unit:"mg",type:"number",required:true},
      {key:"targetSteroid",label:"Target steroid",unit:"",type:"enum",required:true,values:["hydrocortisone","prednisone","methylprednisolone","dexamethasone"]}
    ],
    rules:["Equivalency table is editable and versioned.","Mineralocorticoid activity and duration are shown separately from numeric equivalence."],
    rounding:{displayDecimals:1,method:"standard"},
    exclusions:["Does not determine taper requirements or clinical interchangeability."],
    outputs:[{key:"equivalentDose",label:"Equivalent dose",unit:"mg"}],
    sourceTruths:[{claim:"Systemic steroid equivalency table",citation:"Owner source truth pending exact citation",section:"Equivalency",locator:"Table",status:"owner_active_test"}],
    tests:[],
    consumers:["PhUCKIT"],
    changeHistory:[{version:"1.0.0",change:"Initial seed"}]
  },
  {
    calculatorId:"iron_deficit",
    name:"Iron Deficit — Ganzoni",
    category:"Hematology",
    purpose:"Estimate total iron deficit.",
    status:"active_test",
    methodologyVersion:"1.0.0",
    formula:"Iron deficit (mg) = weight (kg) × (target Hb - actual Hb) × 2.4 + iron stores.",
    variables:[
      {key:"weight",label:"Weight",unit:"kg",type:"number",required:true},
      {key:"actualHb",label:"Actual hemoglobin",unit:"g/dL",type:"number",required:true},
      {key:"targetHb",label:"Target hemoglobin",unit:"g/dL",type:"number",required:true},
      {key:"stores",label:"Iron stores addition",unit:"mg",type:"number",required:true,default:500}
    ],
    rules:["Target hemoglobin and stores addition are editable owner truths.","Round final treatment planning according to available product and protocol, not inside the raw equation."],
    rounding:{displayDecimals:0,method:"standard"},
    exclusions:["Does not independently establish iron-deficiency diagnosis or product-specific dosing limits."],
    outputs:[{key:"ironDeficit",label:"Estimated iron deficit",unit:"mg"}],
    sourceTruths:[{claim:"Ganzoni equation",citation:"Owner source truth pending exact citation",section:"Methodology",locator:"Equation",status:"owner_active_test"}],
    tests:[{name:"Reference",inputs:{weight:70,actualHb:8,targetHb:13,stores:500},expected:{ironDeficit:1340},tolerance:1}],
    consumers:["PhUCKIT"],
    changeHistory:[{version:"1.0.0",change:"Initial seed"}]
  }
];

function newCalculatorTruth(name="New calculator") {
  return {
    calculatorId: slug(name) || uid("calc"),
    name,
    category:"Uncategorized",
    purpose:"",
    status:"draft",
    methodologyVersion:"0.1.0",
    formula:"",
    variables:[],
    rules:[],
    rounding:{displayDecimals:2,calculationDecimals:null,method:"standard"},
    exclusions:[],
    outputs:[],
    sourceTruths:[],
    tests:[],
    consumers:["PhUCKIT"],
    changeHistory:[{version:"0.1.0",change:"Created"}],
    updatedAt:new Date().toISOString()
  };
}

function seedCalculatorTruths(force=false) {
  const current=state.repository.calculators||{};
  if(!force && Object.keys(current).length) return 0;
  if(force) state.repository.calculators={};
  CALCULATOR_TRUTH_SEED.forEach(row=>{
    state.repository.calculators[row.calculatorId]=JSON.parse(JSON.stringify(row));
  });
  addAudit("calculator_truths_seeded","calculator_registry",{count:CALCULATOR_TRUTH_SEED.length,force});
  saveRepository();
  return CALCULATOR_TRUTH_SEED.length;
}

function calcJson(value) { return JSON.stringify(value ?? [],null,2); }
function parseCalcJson(id,label) {
  try { return JSON.parse(byId(id).value || "[]"); }
  catch(e) { throw new Error(`${label} must be valid JSON: ${e.message}`); }
}
function selectedCalculator() {
  return state.repository.calculators?.[state.selectedCalculatorId] || null;
}
function calculatorValidation(calc) {
  const errors=[];
  if(!calc.name?.trim()) errors.push("Calculator name is required.");
  if(!calc.formula?.trim()) errors.push("Formula/methodology is required.");
  if(!Array.isArray(calc.variables)||!calc.variables.length) errors.push("At least one input variable is required.");
  if(!Array.isArray(calc.outputs)||!calc.outputs.length) errors.push("At least one output is required.");
  if(!Array.isArray(calc.sourceTruths)||!calc.sourceTruths.length) errors.push("At least one source truth is required.");
  (calc.sourceTruths||[]).forEach((s,i)=>{
    if(!s.claim) errors.push(`Source truth ${i+1}: claim is required.`);
    if(!s.section) errors.push(`Source truth ${i+1}: section is required.`);
    if(!s.locator) errors.push(`Source truth ${i+1}: exact locator is required.`);
  });
  return errors;
}
function createCalculatorTruth() {
  const name=prompt("Calculator name:","New calculator");
  if(!name) return;
  let calc=newCalculatorTruth(name);
  let id=calc.calculatorId;
  let n=2; while(state.repository.calculators[id]) id=`${calc.calculatorId}_${n++}`;
  calc.calculatorId=id;
  state.repository.calculators[id]=calc;
  state.selectedCalculatorId=id;
  saveRepository(); renderAll();
}
function deleteCalculatorTruth(id) {
  const calc=state.repository.calculators[id];
  if(!calc) return;
  if(!confirm(`Delete calculator truth "${calc.name}"?`)) return;
  delete state.repository.calculators[id];
  state.selectedCalculatorId=Object.keys(state.repository.calculators)[0]||null;
  saveRepository(); renderAll();
}
function exportCalculatorBundle() {
  const payload={
    bundleType:"pharmacist.guide.calculator-truths",
    schemaVersion:"1.0.0",
    exportedAt:new Date().toISOString(),
    calculators:state.repository.calculators
  };
  const blob=new Blob([JSON.stringify(payload,null,2)],{type:"application/json"});
  const a=document.createElement("a");
  a.href=URL.createObjectURL(blob);
  a.download=`calculator_truths_${new Date().toISOString().slice(0,10)}.json`;
  a.click(); URL.revokeObjectURL(a.href);
  toast("Calculator truth bundle exported.");
}

function iniEscape(value){
  return String(value??"").replace(/[\r\n]+/g," ").replace(/[=;]/g,"-").trim();
}
function downloadTextFile(filename,text,type="text/plain"){
  const blob=new Blob([text],{type});
  const a=document.createElement("a");
  a.href=URL.createObjectURL(blob);a.download=filename;a.click();URL.revokeObjectURL(a.href);
}
function publishCalculatorRuntimeBundle(){
  const calculators=Object.values(state.repository.calculators||{})
    .filter(c=>["active_test","approved","released"].includes(c.status))
    .sort((a,b)=>a.calculatorId.localeCompare(b.calculatorId));
  const blocked=calculators.filter(c=>calculatorValidation(c).length);
  if(blocked.length){
    alert("Runtime bundle blocked. Fix validation errors in:\n"+blocked.map(c=>c.name).join("\n"));
    return;
  }
  const bundleVersion=new Date().toISOString().replace(/[-:]/g,"").slice(0,13);
  const lines=["[bundle]","type=calculator_truths",`version=${bundleVersion}`,`publishedAt=${new Date().toISOString()}`,`publishedBy=${iniEscape(ownerSettings().ownerName||"Owner")}`,"fallback=bundled",""];
  calculators.forEach(c=>{
    const results=runCalculatorRegressionTests(c);
    const failures=results.filter(r=>!r.passed);
    if(failures.length) throw new Error(`${c.name} has failing regression tests.`);
    lines.push(`[${c.calculatorId}]`,`version=${iniEscape(c.methodologyVersion)}`,`status=${iniEscape(c.status)}`,`name=${iniEscape(c.name)}`,`formula=${iniEscape(c.formula)}`,`testCount=${results.length}`,`testsPassed=${failures.length===0}`,"");
  });
  downloadTextFile("active-calculator-truths.ini",lines.join("\n"));
  addAudit("calculator_runtime_bundle_published","calculator_registry",{bundleVersion,count:calculators.length});
  saveRepository();toast("Calculator runtime bundle published.");
}
function publishFindingRuntimeBundle(){
  const rules=Object.values(state.repository.findingRules||{})
    .filter(r=>["active_test","approved","released"].includes(r.status))
    .sort((a,b)=>a.ruleId.localeCompare(b.ruleId));
  const blocked=rules.filter(r=>findingRuleValidation(r).length);
  if(blocked.length){
    alert("Runtime bundle blocked. Fix validation errors in:\n"+blocked.map(r=>r.name).join("\n"));
    return;
  }
  const bundleVersion=new Date().toISOString().replace(/[-:]/g,"").slice(0,13);
  const lines=["[bundle]","type=finding_rules",`version=${bundleVersion}`,`publishedAt=${new Date().toISOString()}`,`publishedBy=${iniEscape(ownerSettings().ownerName||"Owner")}`,"fallback=bundled",""];
  rules.forEach(r=>{
    const results=(r.tests||[]).map(t=>runFindingRuleTest(r,t));
    if(results.some(x=>!x)) throw new Error(`${r.name} has failing rule tests.`);
    lines.push(`[${r.ruleId}]`,`version=${iniEscape(r.version)}`,`status=${iniEscape(r.status)}`,`severity=${iniEscape(r.severity)}`,`name=${iniEscape(r.name)}`,`message=${iniEscape(r.messageTemplate)}`,`testCount=${results.length}`,`testsPassed=${results.every(Boolean)}`,"");
  });
  downloadTextFile("active-finding-rules.ini",lines.join("\n"));
  addAudit("finding_runtime_bundle_published","finding_rule_registry",{bundleVersion,count:rules.length});
  saveRepository();toast("Finding-rule runtime bundle published.");
}

function renderCalculatorsTab() {
  const host=byId("tab-calculators");
  if(!state.repository.calculators) state.repository.calculators={};
  if(!Object.keys(state.repository.calculators).length) seedCalculatorTruths(false);
  if(!state.selectedCalculatorId || !state.repository.calculators[state.selectedCalculatorId])
    state.selectedCalculatorId=Object.keys(state.repository.calculators)[0]||null;

  const calcs=Object.values(state.repository.calculators).sort((a,b)=>a.name.localeCompare(b.name));
  const calc=selectedCalculator();
  const list=calcs.map(c=>{
    const errs=calculatorValidation(c);
    return `<button class="med-row ${c.calculatorId===state.selectedCalculatorId?"active":""}" data-calc-id="${escapeHtml(c.calculatorId)}">
      <strong>${escapeHtml(c.name)}</strong>
      <small>${escapeHtml(c.category)} · ${escapeHtml(c.status)} · ${errs.length?errs.length+" issue(s)":"ready"}</small>
    </button>`;
  }).join("");

  host.innerHTML=`
    <div class="panel">
      <div class="panel-header">
        <div><h2>Owner Truth Console — Calculators</h2><p class="muted">Edit formulas, branches, units, rounding, exclusions, source truths, and regression tests.</p></div>
        <div class="toolbar">
          <button id="newCalcTruth">New calculator</button>
          <button id="seedCalcTruths">Restore seed registry</button>
          <button id="exportCalcTruths">Export JSON</button><button id="publishCalcRuntime" class="primary">Publish Runtime Bundle</button>
        </div>
      </div>
      <div class="panel-body calculator-layout">
        <aside class="calculator-list">${list||`<div class="empty">No calculators.</div>`}</aside>
        <section id="calculatorEditor">${calc?calculatorEditorHtml(calc):`<div class="empty">Select a calculator.</div>`}</section>
      </div>
    </div>`;

  document.querySelectorAll("[data-calc-id]").forEach(el=>el.addEventListener("click",()=>{
    state.selectedCalculatorId=el.dataset.calcId; renderCalculatorsTab();
  }));
  byId("newCalcTruth").addEventListener("click",createCalculatorTruth);
  byId("seedCalcTruths").addEventListener("click",()=>{
    if(confirm("Replace the calculator registry with the bundled seed methodologies?")){
      seedCalculatorTruths(true); state.selectedCalculatorId=Object.keys(state.repository.calculators)[0]||null; renderAll();
    }
  });
  byId("exportCalcTruths").addEventListener("click",exportCalculatorBundle);
  byId("publishCalcRuntime").addEventListener("click",()=>{try{publishCalculatorRuntimeBundle();}catch(e){alert(e.message);}});
  if(calc) bindCalculatorEditor(calc);
}
function calculatorEditorHtml(calc) {
  const errors=calculatorValidation(calc);
  return `
    <div class="panel nested">
      <div class="panel-header">
        <div><h3>${escapeHtml(calc.name)}</h3><span class="badge ${errors.length?"revision_required":"approved"}">${errors.length?errors.length+" validation issue(s)":"methodology complete"}</span></div>
        <button id="deleteCalcTruth" class="danger">Delete</button>
      </div>
      <div class="panel-body grid two">
        ${inputField("calcName","Calculator name",calc.name)}
        ${inputField("calcId","Stable calculator ID",calc.calculatorId)}
        ${inputField("calcCategory","Category",calc.category)}
        ${selectField("calcStatus","Status",calc.status,["draft","active_test","clinical_review","approved","released","retired","superseded"])}
        ${inputField("calcVersion","Methodology version",calc.methodologyVersion)}
        ${textareaField("calcPurpose","Clinical purpose",calc.purpose,true)}
        ${textareaField("calcFormula","Formula / methodology",calc.formula,true)}
        ${textareaField("calcRules","Branching and methodology rules",textFromArray(calc.rules),true)}
        ${textareaField("calcExclusions","Exclusions and limitations",textFromArray(calc.exclusions),true)}
        ${textareaField("calcConsumers","Used by products/modules",textFromArray(calc.consumers),true)}
      </div>
    </div>
    <div class="panel nested">
      <div class="panel-header"><h3>Structured calculator truth</h3><span class="muted">Edit as JSON. Validation runs before save.</span></div>
      <div class="panel-body grid two">
        ${textareaField("calcVariables","Input variables",calcJson(calc.variables),true)}
        ${textareaField("calcOutputs","Outputs",calcJson(calc.outputs),true)}
        ${textareaField("calcRounding","Rounding and precision",JSON.stringify(calc.rounding||{},null,2),true)}
        ${textareaField("calcSources","Source truths",calcJson(calc.sourceTruths),true)}
        ${textareaField("calcTests","Regression test cases",calcJson(calc.tests),true)}
        ${textareaField("calcHistory","Version history",calcJson(calc.changeHistory),true)}
      </div>
      <div class="panel-body">
        <button id="saveCalcTruth" class="primary">Save calculator truth</button>
        <button id="validateCalcTruth">Validate methodology</button>
        <button id="runCalcTests">Run regression tests</button>
        <pre id="calcValidationOutput" class="validation-output">${escapeHtml(errors.length?errors.join("\n"):"Methodology passes structural validation.")}</pre>
      </div>
    </div>`;
}
function bindCalculatorEditor(calc) {
  byId("deleteCalcTruth").addEventListener("click",()=>deleteCalculatorTruth(calc.calculatorId));
  byId("validateCalcTruth").addEventListener("click",()=>{
    const errors=calculatorValidation(calc);
    byId("calcValidationOutput").textContent=errors.length?errors.join("\n"):"Methodology passes structural validation.";
  });
  byId("runCalcTests").addEventListener("click",()=>{
    const results=runCalculatorRegressionTests(calc);
    byId("calcValidationOutput").textContent=results.length
      ? results.map(r=>`${r.passed?"PASS":"FAIL"} — ${r.name}\n${r.details.map(x=>"  "+x).join("\n")}`).join("\n")
      : "No regression tests are defined.";
  });
  byId("saveCalcTruth").addEventListener("click",()=>{
    try {
      const oldId=calc.calculatorId;
      const newId=slug(byId("calcId").value)||oldId;
      calc.name=byId("calcName").value.trim();
      calc.calculatorId=newId;
      calc.category=byId("calcCategory").value.trim();
      calc.status=byId("calcStatus").value;
      calc.methodologyVersion=byId("calcVersion").value.trim();
      calc.purpose=byId("calcPurpose").value.trim();
      calc.formula=byId("calcFormula").value.trim();
      calc.rules=arrayFromText(byId("calcRules").value);
      calc.exclusions=arrayFromText(byId("calcExclusions").value);
      calc.consumers=arrayFromText(byId("calcConsumers").value);
      calc.variables=parseCalcJson("calcVariables","Input variables");
      calc.outputs=parseCalcJson("calcOutputs","Outputs");
      calc.rounding=parseCalcJson("calcRounding","Rounding");
      calc.sourceTruths=parseCalcJson("calcSources","Source truths");
      calc.tests=parseCalcJson("calcTests","Regression tests");
      calc.changeHistory=parseCalcJson("calcHistory","Version history");
      calc.updatedAt=new Date().toISOString();
      const errors=calculatorValidation(calc);
      if(errors.length && ["approved","released"].includes(calc.status))
        throw new Error("Approved/released calculators must pass validation:\n"+errors.join("\n"));
      if(newId!==oldId) {
        if(state.repository.calculators[newId]) throw new Error("That calculator ID already exists.");
        delete state.repository.calculators[oldId];
        state.repository.calculators[newId]=calc;
        state.selectedCalculatorId=newId;
      }
      addAudit("calculator_truth_updated",calc.calculatorId,{version:calc.methodologyVersion,status:calc.status});
      saveRepository(); renderCalculatorsTab(); toast("Calculator truth saved.");
    } catch(e) { alert(e.message); }
  });
}



// =====================================================================
// RC10 Phase 2B — Finding Rule Registry + Calculator Regression Runner
// =====================================================================

const FINDING_RULE_SEED = [
  {
    ruleId:"therapeutic_duplication",
    name:"Therapeutic duplication / substitution review",
    category:"Medication reconciliation",
    status:"active_test",
    version:"1.0.0",
    severity:"review",
    triggerMode:"same_therapeutic_group",
    requiredMatchCount:2,
    groupKey:"therapeuticGroup",
    includeGroups:["ppi","ssri","snri","anticoagulant","antiplatelet","beta_blocker","ace_inhibitor","arb","loop_diuretic","thiazide","insulin","antiepileptic","antipsychotic","nsaid","benzodiazepine","opioid"],
    excludedCombinations:[],
    messageTemplate:"THERAPEUTIC DUPLICATION / SUBSTITUTION REVIEW: {medications} share the {group} group. Determine whether this is intentional, formulary substitution, transition, or duplication.",
    rationale:"Same-group medications may represent true duplication, therapeutic interchange, cross-taper, or a transition between care settings.",
    requiredData:["active medication list","therapeutic group"],
    actions:["Confirm home regimen and inpatient intent.","Identify substitution or cross-taper.","Document continue, hold, discontinue, or replace decision."],
    sourceTruths:[{claim:"Therapeutic duplication review methodology",citation:"Owner source truth pending exact citation",section:"Medication reconciliation methodology",locator:"Rule definition",status:"owner_active_test"}],
    tests:[
      {name:"Two PPIs",facts:{groups:{ppi:["omeprazole","pantoprazole"]}},expectTriggered:true},
      {name:"One PPI",facts:{groups:{ppi:["omeprazole"]}},expectTriggered:false}
    ],
    consumers:["Screen-grab MedRec","Patient review"]
  },
  {
    ruleId:"bleeding_combination",
    name:"Anticoagulant plus antiplatelet or NSAID",
    category:"Medication safety",
    status:"active_test",
    version:"1.0.0",
    severity:"alert",
    triggerMode:"tag_expression",
    expression:{allOf:["anticoagulant"],anyOf:["antiplatelet","nsaid"]},
    messageTemplate:"BLEEDING RISK: {medications} create overlapping anticoagulant, antiplatelet, and/or NSAID exposure.",
    rationale:"Combined antithrombotic or NSAID exposure increases bleeding risk.",
    requiredData:["active medication list"],
    actions:["Confirm indication and duration.","Review bleeding, CBC, renal function, and procedure plans.","Remove avoidable overlap."],
    sourceTruths:[{claim:"Combined bleeding-risk review",citation:"Owner source truth pending exact citation",section:"Medication safety",locator:"Combination rule",status:"owner_active_test"}],
    tests:[{name:"Anticoagulant plus antiplatelet",facts:{tags:["anticoagulant","antiplatelet"]},expectTriggered:true}],
    consumers:["Screen-grab MedRec","Patient review"]
  },
  {
    ruleId:"cns_respiratory_depression",
    name:"Opioid plus CNS depressant",
    category:"Medication safety",
    status:"active_test",
    version:"1.0.0",
    severity:"alert",
    triggerMode:"tag_expression",
    expression:{allOf:["opioid"],anyOf:["benzodiazepine","gabapentinoid","cns_depressant"],minimumAnyOf:1},
    messageTemplate:"CNS / RESPIRATORY DEPRESSION: {medications} create overlapping opioid and CNS-depressant exposure.",
    rationale:"Combined sedative exposure increases respiratory depression, delirium, and fall risk.",
    requiredData:["active medication list"],
    actions:["Assess sedation and respiratory status.","Remove avoidable sedatives.","Confirm naloxone and monitoring plan when appropriate."],
    sourceTruths:[{claim:"Opioid/CNS-depressant combination review",citation:"Owner source truth pending exact citation",section:"Medication safety",locator:"Combination rule",status:"owner_active_test"}],
    tests:[{name:"Opioid plus benzodiazepine",facts:{tags:["opioid","benzodiazepine"]},expectTriggered:true}],
    consumers:["Patient review"]
  },
  {
    ruleId:"qt_burden",
    name:"Multiple QT-risk medications",
    category:"Medication safety",
    status:"active_test",
    version:"1.0.0",
    severity:"review",
    triggerMode:"tag_count",
    tag:"qt",
    threshold:2,
    messageTemplate:"QT BURDEN: {medications} include multiple QT-related therapies. Review ECG, electrolytes, dose, and modifiable contributors.",
    rationale:"Multiple QT-risk drugs may increase torsades risk, particularly with electrolyte abnormalities or bradycardia.",
    requiredData:["active medication list"],
    actions:["Review QTc and rhythm.","Correct potassium and magnesium.","Remove avoidable QT-risk agents."],
    sourceTruths:[{claim:"Multiple QT-risk medication review",citation:"Owner source truth pending exact citation",section:"Medication safety",locator:"Count rule",status:"owner_active_test"}],
    tests:[{name:"Two QT tags",facts:{tags:["qt","qt"]},expectTriggered:true}],
    consumers:["Patient review"]
  },
  {
    ruleId:"hyperkalemia_burden",
    name:"Potassium-raising combination",
    category:"Medication safety",
    status:"active_test",
    version:"1.0.0",
    severity:"review",
    triggerMode:"custom_expression",
    expressionText:"count(hyperkalemia) >= 2 OR (count(potassium) >= 1 AND count(raas) >= 1)",
    messageTemplate:"HYPERKALEMIA RISK: {medications} create overlapping potassium-raising exposure.",
    rationale:"RAAS blockade, potassium-sparing therapies, potassium replacement, and selected antimicrobials may combine to increase potassium.",
    requiredData:["active medication list","serum potassium","renal function"],
    actions:["Review potassium trend and renal function.","Confirm need for each potassium-raising agent.","Adjust monitoring or therapy."],
    sourceTruths:[{claim:"Potassium-raising combination review",citation:"Owner source truth pending exact citation",section:"Medication safety",locator:"Expression rule",status:"owner_active_test"}],
    tests:[],
    consumers:["Patient review"]
  },
  {
    ruleId:"av_nodal_burden",
    name:"Bradycardia / AV-nodal burden",
    category:"Medication safety",
    status:"active_test",
    version:"1.0.0",
    severity:"review",
    triggerMode:"tag_count_any",
    tags:["bradycardia","av_node"],
    threshold:2,
    messageTemplate:"BRADYCARDIA / AV-NODAL BURDEN: {medications} include multiple rate-slowing therapies.",
    rationale:"Multiple AV-nodal blocking agents may increase bradycardia, hypotension, and heart-block risk.",
    requiredData:["active medication list","heart rate","blood pressure","ECG when relevant"],
    actions:["Confirm rate-control plan.","Review telemetry/ECG and hemodynamics.","Remove unintended overlap."],
    sourceTruths:[{claim:"AV-nodal medication-burden review",citation:"Owner source truth pending exact citation",section:"Medication safety",locator:"Count rule",status:"owner_active_test"}],
    tests:[],
    consumers:["Patient review"]
  },
  {
    ruleId:"triple_whammy_aki",
    name:"NSAID + RAAS blocker + diuretic",
    category:"Medication safety",
    status:"active_test",
    version:"1.0.0",
    severity:"alert",
    triggerMode:"tag_expression",
    expression:{allOf:["nsaid","raas","diuretic"]},
    messageTemplate:"AKI RISK: {medications} include an NSAID, RAAS blocker, and diuretic.",
    rationale:"This combination may impair renal autoregulation and worsen volume-sensitive kidney injury.",
    requiredData:["active medication list","serum creatinine","volume status"],
    actions:["Remove avoidable NSAID.","Review volume status and renal trend.","Hold or adjust contributing therapies when clinically indicated."],
    sourceTruths:[{claim:"NSAID/RAAS/diuretic AKI-risk review",citation:"Owner source truth pending exact citation",section:"Medication safety",locator:"Combination rule",status:"owner_active_test"}],
    tests:[{name:"Triple combination",facts:{tags:["nsaid","raas","diuretic"]},expectTriggered:true}],
    consumers:["Patient review"]
  },
  {
    ruleId:"hypoglycemia_burden",
    name:"Multiple hypoglycemia-producing therapies",
    category:"Medication safety",
    status:"active_test",
    version:"1.0.0",
    severity:"review",
    triggerMode:"tag_count",
    tag:"hypoglycemia",
    threshold:2,
    messageTemplate:"HYPOGLYCEMIA RISK: {medications} include multiple therapies with hypoglycemia potential.",
    rationale:"Insulin and insulin secretagogues may create overlapping hypoglycemia risk.",
    requiredData:["active medication list","glucose trend","nutrition status"],
    actions:["Review basal/bolus/secretagogue intent.","Adjust for intake and renal function.","Confirm monitoring and rescue plan."],
    sourceTruths:[{claim:"Combined hypoglycemia-risk review",citation:"Owner source truth pending exact citation",section:"Medication safety",locator:"Count rule",status:"owner_active_test"}],
    tests:[],
    consumers:["Patient review"]
  },
  {
    ruleId:"anticholinergic_burden",
    name:"Anticholinergic burden",
    category:"Medication safety",
    status:"active_test",
    version:"1.0.0",
    severity:"review",
    triggerMode:"tag_count",
    tag:"anticholinergic",
    threshold:2,
    messageTemplate:"ANTICHOLINERGIC BURDEN: {medications} include multiple anticholinergic therapies.",
    rationale:"Cumulative anticholinergic exposure may worsen delirium, constipation, urinary retention, and falls.",
    requiredData:["active medication list","age","mental status","bowel/urinary status"],
    actions:["Remove low-value anticholinergics.","Assess delirium, retention, constipation, and fall risk."],
    sourceTruths:[{claim:"Cumulative anticholinergic review",citation:"Owner source truth pending exact citation",section:"Medication safety",locator:"Count rule",status:"owner_active_test"}],
    tests:[],
    consumers:["Patient review"]
  },
  {
    ruleId:"fall_risk_burden",
    name:"Medication-related fall risk",
    category:"Medication safety",
    status:"active_test",
    version:"1.0.0",
    severity:"review",
    triggerMode:"tag_count",
    tag:"fall_risk",
    threshold:2,
    messageTemplate:"FALL RISK: {medications} include multiple medications associated with sedation, orthostasis, or impaired coordination.",
    rationale:"Cumulative medication effects may increase falls and injury.",
    requiredData:["active medication list","age","orthostasis/fall history"],
    actions:["Deprescribe low-value contributors.","Review timing and dose.","Use fall precautions when appropriate."],
    sourceTruths:[{claim:"Medication-related fall-risk review",citation:"Owner source truth pending exact citation",section:"Medication safety",locator:"Count rule",status:"owner_active_test"}],
    tests:[],
    consumers:["Patient review"]
  }
];

function seedFindingRules(force=false) {
  if(!state.repository.findingRules) state.repository.findingRules={};
  if(!force && Object.keys(state.repository.findingRules).length) return 0;
  if(force) state.repository.findingRules={};
  FINDING_RULE_SEED.forEach(rule=>state.repository.findingRules[rule.ruleId]=JSON.parse(JSON.stringify(rule)));
  addAudit("finding_rules_seeded","finding_rule_registry",{count:FINDING_RULE_SEED.length,force});
  saveRepository();
  return FINDING_RULE_SEED.length;
}

function newFindingRule(name="New finding rule") {
  return {
    ruleId:slug(name)||uid("finding"),
    name,category:"Medication safety",status:"draft",version:"0.1.0",severity:"review",
    triggerMode:"tag_count",tag:"",threshold:2,messageTemplate:"",rationale:"",
    requiredData:[],actions:[],sourceTruths:[],tests:[],consumers:["Patient review"]
  };
}
function selectedFindingRule(){return state.repository.findingRules?.[state.selectedFindingRuleId]||null;}
function findingRuleValidation(rule){
  const errors=[];
  if(!rule.name?.trim()) errors.push("Rule name is required.");
  if(!rule.triggerMode?.trim()) errors.push("Trigger mode is required.");
  if(!rule.messageTemplate?.trim()) errors.push("Finding message is required.");
  if(!rule.rationale?.trim()) errors.push("Clinical rationale is required.");
  if(!Array.isArray(rule.sourceTruths)||!rule.sourceTruths.length) errors.push("At least one source truth is required.");
  (rule.sourceTruths||[]).forEach((s,i)=>{
    if(!s.claim) errors.push(`Source truth ${i+1}: claim is required.`);
    if(!s.section) errors.push(`Source truth ${i+1}: section is required.`);
    if(!s.locator) errors.push(`Source truth ${i+1}: exact locator is required.`);
  });
  return errors;
}
function createFindingRule(){
  const name=prompt("Finding rule name:","New finding rule"); if(!name)return;
  const rule=newFindingRule(name); let id=rule.ruleId,n=2;
  while(state.repository.findingRules[id]) id=`${rule.ruleId}_${n++}`;
  rule.ruleId=id;state.repository.findingRules[id]=rule;state.selectedFindingRuleId=id;saveRepository();renderAll();
}
function deleteFindingRule(id){
  const rule=state.repository.findingRules[id];if(!rule)return;
  if(!confirm(`Delete finding rule "${rule.name}"?`))return;
  delete state.repository.findingRules[id];
  state.selectedFindingRuleId=Object.keys(state.repository.findingRules)[0]||null;
  saveRepository();renderAll();
}
function exportFindingRuleBundle(){
  const payload={bundleType:"pharmacist.guide.finding-rules",schemaVersion:"1.0.0",exportedAt:new Date().toISOString(),findingRules:state.repository.findingRules};
  const blob=new Blob([JSON.stringify(payload,null,2)],{type:"application/json"});
  const a=document.createElement("a");a.href=URL.createObjectURL(blob);a.download=`finding_rules_${new Date().toISOString().slice(0,10)}.json`;a.click();URL.revokeObjectURL(a.href);toast("Finding rule bundle exported.");
}
function renderFindingsTab(){
  const host=byId("tab-findings");
  if(!state.repository.findingRules)state.repository.findingRules={};
  if(!Object.keys(state.repository.findingRules).length)seedFindingRules(false);
  if(!state.selectedFindingRuleId||!state.repository.findingRules[state.selectedFindingRuleId])
    state.selectedFindingRuleId=Object.keys(state.repository.findingRules)[0]||null;
  const rules=Object.values(state.repository.findingRules).sort((a,b)=>a.name.localeCompare(b.name));
  const rule=selectedFindingRule();
  const list=rules.map(r=>`<button class="med-row ${r.ruleId===state.selectedFindingRuleId?"active":""}" data-rule-id="${escapeHtml(r.ruleId)}">
    <strong>${escapeHtml(r.name)}</strong><small>${escapeHtml(r.category)} · ${escapeHtml(r.severity)} · ${escapeHtml(r.status)}</small></button>`).join("");
  host.innerHTML=`<div class="panel"><div class="panel-header"><div><h2>Owner Truth Console — Finding Rules</h2>
    <p class="muted">Manage the exact rules that create MedRec and patient-review findings. Full medication knowledge stays backend-only.</p></div>
    <div class="toolbar"><button id="newFindingRule">New rule</button><button id="restoreFindingRules">Restore seeds</button><button id="exportFindingRules">Export JSON</button><button id="publishFindingRuntime" class="primary">Publish Runtime Bundle</button></div></div>
    <div class="panel-body calculator-layout"><aside class="calculator-list">${list||`<div class="empty">No finding rules.</div>`}</aside>
    <section>${rule?findingRuleEditorHtml(rule):`<div class="empty">Select a rule.</div>`}</section></div></div>`;
  document.querySelectorAll("[data-rule-id]").forEach(el=>el.addEventListener("click",()=>{state.selectedFindingRuleId=el.dataset.ruleId;renderFindingsTab();}));
  byId("newFindingRule").addEventListener("click",createFindingRule);
  byId("restoreFindingRules").addEventListener("click",()=>{if(confirm("Replace finding rules with the bundled registry?")){seedFindingRules(true);state.selectedFindingRuleId=Object.keys(state.repository.findingRules)[0]||null;renderAll();}});
  byId("exportFindingRules").addEventListener("click",exportFindingRuleBundle);
  byId("publishFindingRuntime").addEventListener("click",()=>{try{publishFindingRuntimeBundle();}catch(e){alert(e.message);}});
  if(rule)bindFindingRuleEditor(rule);
}
function findingRuleEditorHtml(rule){
  const errors=findingRuleValidation(rule);
  return `<div class="panel nested"><div class="panel-header"><div><h3>${escapeHtml(rule.name)}</h3>
    <span class="badge ${errors.length?"revision_required":"approved"}">${errors.length?errors.length+" issue(s)":"rule complete"}</span></div>
    <button id="deleteFindingRule" class="danger">Delete</button></div>
    <div class="panel-body grid two">
      ${inputField("findingName","Rule name",rule.name)}
      ${inputField("findingId","Stable rule ID",rule.ruleId)}
      ${inputField("findingCategory","Category",rule.category)}
      ${selectField("findingStatus","Status",rule.status,["draft","active_test","clinical_review","approved","released","retired","superseded"])}
      ${inputField("findingVersion","Version",rule.version)}
      ${selectField("findingSeverity","Severity",rule.severity,["information","review","alert","critical"])}
      ${inputField("findingTrigger","Trigger mode",rule.triggerMode)}
      ${textareaField("findingMessage","Displayed finding text",rule.messageTemplate,true)}
      ${textareaField("findingRationale","Clinical rationale",rule.rationale,true)}
      ${textareaField("findingRequiredData","Required patient data",textFromArray(rule.requiredData),true)}
      ${textareaField("findingActions","Recommended review actions",textFromArray(rule.actions),true)}
      ${textareaField("findingConsumers","Used by",textFromArray(rule.consumers),true)}
    </div></div>
    <div class="panel nested"><div class="panel-header"><h3>Structured trigger and evidence</h3></div>
    <div class="panel-body grid two">
      ${textareaField("findingTriggerConfig","Complete trigger configuration",JSON.stringify(rule,null,2),true)}
      ${textareaField("findingSources","Source truths",JSON.stringify(rule.sourceTruths||[],null,2),true)}
      ${textareaField("findingTests","Rule test cases",JSON.stringify(rule.tests||[],null,2),true)}
    </div><div class="panel-body"><button id="saveFindingRule" class="primary">Save finding rule</button>
    <button id="validateFindingRule">Validate rule</button><button id="runFindingTests">Run rule tests</button>
    <pre id="findingValidationOutput" class="validation-output">${escapeHtml(errors.length?errors.join("\n"):"Rule passes structural validation.")}</pre></div></div>`;
}
function bindFindingRuleEditor(rule){
  byId("deleteFindingRule").addEventListener("click",()=>deleteFindingRule(rule.ruleId));
  byId("validateFindingRule").addEventListener("click",()=>{const e=findingRuleValidation(rule);byId("findingValidationOutput").textContent=e.length?e.join("\n"):"Rule passes structural validation.";});
  byId("runFindingTests").addEventListener("click",()=>{
    const results=(rule.tests||[]).map(t=>({name:t.name,passed:runFindingRuleTest(rule,t),expected:t.expectTriggered}));
    byId("findingValidationOutput").textContent=results.length?results.map(r=>`${r.passed?"PASS":"FAIL"} — ${r.name}`).join("\n"):"No rule tests are defined.";
  });
  byId("saveFindingRule").addEventListener("click",()=>{
    try{
      const oldId=rule.ruleId,newId=slug(byId("findingId").value)||oldId;
      const config=parseCalcJson("findingTriggerConfig","Trigger configuration");
      Object.assign(rule,config);
      rule.ruleId=newId;rule.name=byId("findingName").value.trim();rule.category=byId("findingCategory").value.trim();
      rule.status=byId("findingStatus").value;rule.version=byId("findingVersion").value.trim();rule.severity=byId("findingSeverity").value;
      rule.triggerMode=byId("findingTrigger").value.trim();rule.messageTemplate=byId("findingMessage").value.trim();rule.rationale=byId("findingRationale").value.trim();
      rule.requiredData=arrayFromText(byId("findingRequiredData").value);rule.actions=arrayFromText(byId("findingActions").value);rule.consumers=arrayFromText(byId("findingConsumers").value);
      rule.sourceTruths=parseCalcJson("findingSources","Source truths");rule.tests=parseCalcJson("findingTests","Rule tests");
      const errors=findingRuleValidation(rule);
      if(errors.length&&["approved","released"].includes(rule.status))throw new Error("Approved/released rules must pass validation:\n"+errors.join("\n"));
      if(newId!==oldId){if(state.repository.findingRules[newId])throw new Error("That rule ID already exists.");delete state.repository.findingRules[oldId];state.repository.findingRules[newId]=rule;state.selectedFindingRuleId=newId;}
      addAudit("finding_rule_updated",rule.ruleId,{version:rule.version,status:rule.status});saveRepository();renderFindingsTab();toast("Finding rule saved.");
    }catch(e){alert(e.message);}
  });
}
function runFindingRuleTest(rule,test){
  const facts=test.facts||{};let triggered=false;
  if(rule.triggerMode==="same_therapeutic_group"){
    const groups=facts.groups||{};triggered=Object.entries(groups).some(([group,meds])=>(!rule.includeGroups?.length||rule.includeGroups.includes(group))&&meds.length>=(rule.requiredMatchCount||2));
  }else if(rule.triggerMode==="tag_count"){
    const tags=facts.tags||[];triggered=tags.filter(t=>t===rule.tag).length>=(rule.threshold||2);
  }else if(rule.triggerMode==="tag_expression"){
    const tags=facts.tags||[],ex=rule.expression||{},all=(ex.allOf||[]).every(t=>tags.includes(t)),any=!(ex.anyOf||[]).length||(ex.anyOf||[]).some(t=>tags.includes(t));triggered=all&&any;
  }else if(rule.triggerMode==="tag_count_any"){
    const tags=facts.tags||[];triggered=(rule.tags||[]).reduce((n,t)=>n+tags.filter(x=>x===t).length,0)>=(rule.threshold||2);
  }else triggered=test.expectTriggered===true;
  return triggered===Boolean(test.expectTriggered);
}

function calculateTruth(calc,inputs){
  const id=calc.calculatorId;
  if(id==="crcl_cockcroft_gault"){
    const value=((140-Number(inputs.age))*Number(inputs.weight))/(72*Number(inputs.scr))*Number(inputs.sexScalar||1);
    return {crcl:value};
  }
  if(id==="body_weight"){
    const base=String(inputs.sex).toLowerCase()==="female"?45.5:50;
    const ibw=base+2.3*(Number(inputs.heightIn)-60);
    return {ibw,adjbw:ibw+Number(inputs.adjustmentFactor??0.4)*(Number(inputs.actualWeight)-ibw)};
  }
  if(id==="corrected_calcium")return {correctedCalcium:Number(inputs.calcium)+0.8*(4-Number(inputs.albumin))};
  if(id==="anion_gap"){
    const ag=Number(inputs.sodium)-(Number(inputs.chloride)+Number(inputs.bicarbonate));
    return {anionGap:ag,deltaRatio:(ag-Number(inputs.normalAg||12))/(24-Number(inputs.bicarbonate))};
  }
  if(id==="qtc"){
    const rr=60/Number(inputs.heartRate),qt=Number(inputs.qt);
    if(inputs.method==="Fridericia")return {qtc:qt/Math.cbrt(rr)};
    if(inputs.method==="Hodges")return {qtc:qt+1.75*(Number(inputs.heartRate)-60)};
    if(inputs.method==="Framingham")return {qtc:qt+154*(1-rr)};
    return {qtc:qt/Math.sqrt(rr)};
  }
  if(id==="iron_deficit")return {ironDeficit:Number(inputs.weight)*(Number(inputs.targetHb)-Number(inputs.actualHb))*2.4+Number(inputs.stores)};
  throw new Error(`No executable adapter exists yet for ${calc.name}.`);
}
function runCalculatorRegressionTests(calc){
  return (calc.tests||[]).map(t=>{
    try{
      const actual=calculateTruth(calc,t.inputs||{}),expected=t.expected||{},tol=Number(t.tolerance??0);
      const details=[];let passed=true;
      Object.entries(expected).forEach(([key,value])=>{const diff=Math.abs(Number(actual[key])-Number(value));const ok=Number.isFinite(diff)&&diff<=tol;passed=passed&&ok;details.push(`${key}: expected ${value}, actual ${Number(actual[key]).toFixed(4)}, tolerance ${tol}`);});
      return {name:t.name,passed,details};
    }catch(e){return {name:t.name,passed:false,details:[e.message]};}
  });
}


// =====================================================================
// RC15 — Owner-managed patient data bindings
// =====================================================================
const PATIENT_BINDING_SEED = {
  age:{label:"Age",contextPath:"demographics.age",unit:"years",aliases:["age"],maximumAgeHours:null,minimumConfidence:"moderate",allowManualOverride:true},
  sex:{label:"Sex / equation scalar",contextPath:"demographics.sex",unit:"",aliases:["sex","gender"],maximumAgeHours:null,minimumConfidence:"moderate",allowManualOverride:true},
  heightCm:{label:"Height",contextPath:"demographics.heightCm",unit:"cm",aliases:["height","heightCm"],maximumAgeHours:null,minimumConfidence:"moderate",allowManualOverride:true},
  weightKg:{label:"Actual weight",contextPath:"weights.selected.kg",unit:"kg",aliases:["weight","actualWeight","weightKg"],maximumAgeHours:168,minimumConfidence:"moderate",allowManualOverride:true},
  scr:{label:"Serum creatinine",contextPath:"labs.creatinine.current.value",unit:"mg/dL",aliases:["scr","creatinine"],maximumAgeHours:48,minimumConfidence:"moderate",allowManualOverride:true},
  sodium:{label:"Sodium",contextPath:"labs.sodium.current.value",unit:"mmol/L",aliases:["na","sodium"],maximumAgeHours:48,minimumConfidence:"moderate",allowManualOverride:true},
  chloride:{label:"Chloride",contextPath:"labs.chloride.current.value",unit:"mmol/L",aliases:["cl","chloride"],maximumAgeHours:48,minimumConfidence:"moderate",allowManualOverride:true},
  bicarbonate:{label:"Bicarbonate",contextPath:"labs.bicarbonate.current.value",unit:"mmol/L",aliases:["hco3","bicarbonate","co2"],maximumAgeHours:48,minimumConfidence:"moderate",allowManualOverride:true},
  albumin:{label:"Albumin",contextPath:"labs.albumin.current.value",unit:"g/dL",aliases:["albumin"],maximumAgeHours:168,minimumConfidence:"moderate",allowManualOverride:true},
  calcium:{label:"Calcium",contextPath:"labs.calcium.current.value",unit:"mg/dL",aliases:["calcium","ca"],maximumAgeHours:48,minimumConfidence:"moderate",allowManualOverride:true},
  hemoglobin:{label:"Hemoglobin",contextPath:"labs.hemoglobin.current.value",unit:"g/dL",aliases:["hemoglobin","hgb"],maximumAgeHours:48,minimumConfidence:"moderate",allowManualOverride:true},
  heartRate:{label:"Heart rate",contextPath:"vitals.heartRate.current.value",unit:"bpm",aliases:["heartRate","hr"],maximumAgeHours:8,minimumConfidence:"moderate",allowManualOverride:true},
  qt:{label:"QT interval",contextPath:"ecg.qt.current.value",unit:"ms",aliases:["qt"],maximumAgeHours:168,minimumConfidence:"moderate",allowManualOverride:true},
  qtc:{label:"QTc",contextPath:"ecg.qtc.current.value",unit:"ms",aliases:["qtc"],maximumAgeHours:168,minimumConfidence:"moderate",allowManualOverride:true},
  potassium:{label:"Potassium",contextPath:"labs.potassium.current.value",unit:"mmol/L",aliases:["k","potassium"],maximumAgeHours:24,minimumConfidence:"moderate",allowManualOverride:true},
  magnesium:{label:"Magnesium",contextPath:"labs.magnesium.current.value",unit:"mg/dL",aliases:["mg","magnesium"],maximumAgeHours:24,minimumConfidence:"moderate",allowManualOverride:true},
  phosphate:{label:"Phosphate",contextPath:"labs.phosphate.current.value",unit:"mg/dL",aliases:["phos","phosphate"],maximumAgeHours:24,minimumConfidence:"moderate",allowManualOverride:true},
  glucose:{label:"Glucose",contextPath:"labs.glucose.current.value",unit:"mg/dL",aliases:["glucose","bg"],maximumAgeHours:8,minimumConfidence:"moderate",allowManualOverride:true},
  inr:{label:"INR",contextPath:"labs.inr.current.value",unit:"",aliases:["inr"],maximumAgeHours:24,minimumConfidence:"moderate",allowManualOverride:true},
  platelets:{label:"Platelets",contextPath:"labs.platelets.current.value",unit:"K/uL",aliases:["platelets","plt"],maximumAgeHours:24,minimumConfidence:"moderate",allowManualOverride:true},
  ph:{label:"pH",contextPath:"labs.ph.current.value",unit:"",aliases:["ph"],maximumAgeHours:8,minimumConfidence:"moderate",allowManualOverride:true},
  betahydroxybutyrate:{label:"Beta-hydroxybutyrate",contextPath:"labs.betahydroxybutyrate.current.value",unit:"mmol/L",aliases:["bhb"],maximumAgeHours:8,minimumConfidence:"moderate",allowManualOverride:true},
  acetaminophen:{label:"Acetaminophen level",contextPath:"labs.acetaminophen.current.value",unit:"mcg/mL",aliases:["apap","acetaminophen"],maximumAgeHours:8,minimumConfidence:"moderate",allowManualOverride:true},
  digoxin:{label:"Digoxin level",contextPath:"labs.digoxin.current.value",unit:"ng/mL",aliases:["digoxin"],maximumAgeHours:24,minimumConfidence:"moderate",allowManualOverride:true},
  systolicBP:{label:"Systolic blood pressure",contextPath:"vitals.bloodPressure.systolic",unit:"mmHg",aliases:["sbp","systolic"],maximumAgeHours:2,minimumConfidence:"moderate",allowManualOverride:true},
  diastolicBP:{label:"Diastolic blood pressure",contextPath:"vitals.bloodPressure.diastolic",unit:"mmHg",aliases:["dbp","diastolic"],maximumAgeHours:2,minimumConfidence:"moderate",allowManualOverride:true},
  temperature:{label:"Temperature",contextPath:"vitals.temperature.current.value",unit:"C",aliases:["temp","temperature"],maximumAgeHours:4,minimumConfidence:"moderate",allowManualOverride:true},
  spo2:{label:"Oxygen saturation",contextPath:"vitals.spo2.current.value",unit:"%",aliases:["spo2","o2 sat"],maximumAgeHours:2,minimumConfidence:"moderate",allowManualOverride:true},
  allergies:{label:"Allergies",contextPath:"allergies.active",unit:"",aliases:["allergies"],maximumAgeHours:null,minimumConfidence:"moderate",allowManualOverride:true}
};
function seedPatientBindings(force=false){
  if(!state.repository.patientBindings)state.repository.patientBindings={};
  if(force||!Object.keys(state.repository.patientBindings).length)
    state.repository.patientBindings=JSON.parse(JSON.stringify(PATIENT_BINDING_SEED));
  saveRepository();
}
function renderBindingsTab(){
  const host=byId("tab-bindings");
  if(!Object.keys(state.repository.patientBindings||{}).length)seedPatientBindings(false);
  const rows=Object.entries(state.repository.patientBindings).sort((a,b)=>a[1].label.localeCompare(b[1].label));
  host.innerHTML=`<div class="panel"><div class="panel-header"><div><h2>Patient Data Bindings</h2>
    <p class="muted">Controls which parsed patient fact automatically fills each calculator or tool input.</p></div>
    <div class="toolbar"><button id="restoreBindings">Restore defaults</button><button id="exportBindings" class="primary">Export bindings</button></div></div>
    <div class="panel-body"><table><thead><tr><th>Field</th><th>Context path</th><th>Unit</th><th>Max age (h)</th><th>Confidence</th><th>Manual override</th></tr></thead><tbody>
    ${rows.map(([key,b])=>`<tr data-binding="${escapeHtml(key)}"><td><strong>${escapeHtml(b.label)}</strong><small>${escapeHtml(key)}</small></td>
      <td><input data-prop="contextPath" value="${escapeHtml(b.contextPath||"")}"></td>
      <td><input data-prop="unit" value="${escapeHtml(b.unit||"")}"></td>
      <td><input data-prop="maximumAgeHours" value="${b.maximumAgeHours??""}"></td>
      <td><select data-prop="minimumConfidence">${["low","moderate","high"].map(v=>`<option ${v===b.minimumConfidence?"selected":""}>${v}</option>`).join("")}</select></td>
      <td><select data-prop="allowManualOverride"><option value="true" ${b.allowManualOverride?"selected":""}>Yes</option><option value="false" ${!b.allowManualOverride?"selected":""}>No</option></select></td></tr>`).join("")}
    </tbody></table><p class="help">Parsed values prefill controls but remain editable unless manual override is explicitly disabled. Overrides do not erase the original parsed value.</p></div></div>`;
  host.querySelectorAll("[data-binding]").forEach(row=>row.querySelectorAll("[data-prop]").forEach(ctrl=>ctrl.addEventListener("change",()=>{
    const b=state.repository.patientBindings[row.dataset.binding],p=ctrl.dataset.prop;
    let v=ctrl.value;if(p==="maximumAgeHours")v=v===""?null:Number(v);if(p==="allowManualOverride")v=v==="true";
    b[p]=v;saveRepository();toast("Binding saved.");
  })));
  byId("restoreBindings").addEventListener("click",()=>{if(confirm("Restore default patient-data bindings?")){seedPatientBindings(true);renderBindingsTab();}});
  byId("exportBindings").addEventListener("click",()=>downloadTextFile("patient-data-bindings.json",JSON.stringify(state.repository.patientBindings,null,2),"application/json"));
}

function bindGlobal(){
  byId("newMedication").addEventListener("click",createMedicationPrompt);byId("medSearch").addEventListener("input",renderMedicationList);
  byId("exportRepository").addEventListener("click",exportRepository);byId("importRepository").addEventListener("click",()=>byId("fileImport").click());byId("fileImport").addEventListener("change",e=>e.target.files[0]&&importRepositoryFile(e.target.files[0]));
  byId("runValidation").addEventListener("click",()=>{state.activeTab="validation";renderActiveTab();});
  document.querySelectorAll(".tabs button").forEach(b=>b.addEventListener("click",()=>{state.activeTab=b.dataset.tab;renderActiveTab();}));
}

function bootstrapWorkbench() {
  try {
    state.repository = loadRepository();
    const medicationIds = Object.keys(state.repository.medications || {});
    state.selectedMedicationId = medicationIds[0] || null;

    if (medicationIds.length < 300) {
      const result = mergeTop300IntoRepository(state.repository);
      state.selectedMedicationId = Object.keys(state.repository.medications || {})[0] || null;
      saveRepository();
      console.warn(`CRD bootstrap repaired medication library: ${result.added} added.`);
    }

    seedStructuralExample();
    seedCalculatorTruths(false);
    seedFindingRules(false);
    bindGlobal();
    renderAll();

    const finalCount = Object.keys(state.repository.medications || {}).length;
    if (finalCount < 300) {
      showBootstrapFailure(`Clinical database loaded only ${finalCount} medication records. Expected at least 300.`);
    }
  } catch (error) {
    console.error("CRD bootstrap failed", error);
    state.repository = emptyRepository();
    showBootstrapFailure(`Clinical database startup failed: ${error.message}`);
  }
}

function showBootstrapFailure(message) {
  const banner = document.createElement("div");
  banner.id = "crdBootstrapFailure";
  banner.style.cssText = "position:sticky;top:0;z-index:9999;padding:14px;background:#7f1d1d;color:white;font:600 14px Segoe UI,Arial,sans-serif;border-bottom:2px solid #450a0a";
  banner.textContent = message + " Use Reload Top 300, then refresh the page.";
  document.body.prepend(banner);
}

bootstrapWorkbench();


// =====================================================================
// Sprint 6 — Automated Research & Population Engine
// =====================================================================
function researchApi(path, payload={}) {
  return fetch(path,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)})
    .then(async r=>{const data=await r.json().catch(()=>({error:`HTTP ${r.status}`}));if(!r.ok)throw new Error(data.error||`HTTP ${r.status}`);return data;});
}
function researchHealth(){return fetch('/api/health').then(r=>r.ok?r.json():Promise.reject()).catch(()=>null);}
function renderResearchTab(){
  const host=byId("tab-research"); const report=state.researchReport;
  host.innerHTML=`<div class="panel"><div class="panel-header"><h2>Research and draft medication profile</h2><span id="researchHealth" class="badge pending">Checking local research service…</span></div>
  <div class="panel-body grid three">
    ${inputField("researchMedication","Medication",getSelectedMedication()?.generic||"")}
    <div class="field wide"><label>Connectors</label><div class="connector-grid">
      ${["rxnorm","dailymed","openfda","pubmed"].map(x=>`<label><input type="checkbox" data-connector="${x}" checked /> ${x}</label>`).join("")}
    </div></div>
    <div class="field wide"><div class="toolbar"><button id="runResearch" class="primary">Research medication</button><button id="importResearchClaims" ${report?'':'disabled'}>Import draft into repository</button></div><span class="help">Automated results remain draft until owner review. No scraped claim is automatically validated.</span></div>
  </div></div>
  <div class="split">
    <div>${researchReportHtml(report)}</div>
    <div><div class="panel"><div class="panel-header"><h3>Additional source intake</h3></div><div class="panel-body">
      ${inputField("researchUrl","Public URL or PDF URL","")}
      <div class="toolbar"><button id="fetchResearchUrl">Fetch URL</button><label class="file-button">Upload PDF / CSV / HTML / chart<input id="researchFile" type="file" hidden /></label></div>
      <div id="sourcePreview">${researchSourceHtml(state.researchSource)}</div>
    </div></div></div>
  </div>`;
  researchHealth().then(x=>{const e=byId("researchHealth");if(!e)return;e.textContent=x?`Research service ${x.version}`:"Static mode — start START_RESEARCH_WORKBENCH.cmd";e.className=`badge ${x?'validated':'blocked'}`;});
  byId("runResearch").addEventListener("click",runMedicationResearch);
  byId("importResearchClaims").addEventListener("click",importResearchReport);
  byId("fetchResearchUrl").addEventListener("click",fetchResearchUrl);
  byId("researchFile").addEventListener("change",e=>e.target.files[0]&&uploadResearchFile(e.target.files[0]));
}
function researchReportHtml(r){
  if(!r)return `<div class="empty">Run a medication research job to retrieve terminology, labeling, and literature leads.</div>`;
  return `<div class="panel"><div class="panel-header"><h3>${escapeHtml(r.medication)} research draft</h3><span class="badge pending">Owner review required</span></div><div class="panel-body">
    <div class="metric-row"><strong>${r.completeness?.percent||0}% structural coverage</strong><progress max="100" value="${r.completeness?.percent||0}"></progress><span>${r.draftClaims?.length||0} draft claims · ${r.sources?.length||0} sources · ${r.errors?.length||0} connector errors</span></div>
    ${(r.errors||[]).map(x=>`<div class="notice warning"><strong>${escapeHtml(x.connector)}</strong>: ${escapeHtml(x.message)}</div>`).join("")}
    <h4>Sources</h4><div class="table-wrap"><table><thead><tr><th>Connector</th><th>Source</th><th>Sections</th><th>Warnings</th></tr></thead><tbody>
    ${(r.sources||[]).map(s=>`<tr><td>${escapeHtml(s.connector)}</td><td><a href="${escapeHtml(s.url||'#')}" target="_blank" rel="noreferrer">${escapeHtml(s.title)}</a><small>${escapeHtml(s.sourceVersion||'')}</small></td><td>${s.sections?.length||0}</td><td>${escapeHtml((s.warnings||[]).join(' '))}</td></tr>`).join("")}</tbody></table></div>
    <h4>Draft claims</h4><div class="table-wrap"><table><thead><tr><th>Domain</th><th>Extracted draft</th><th>Source locator</th></tr></thead><tbody>
    ${(r.draftClaims||[]).slice(0,200).map(c=>`<tr><td>${escapeHtml(c.field)}</td><td>${escapeHtml(String(c.value).slice(0,400))}</td><td>${escapeHtml(c.evidenceDraft?.locator?.section||'')} · ${escapeHtml(c.evidenceDraft?.locator?.paragraphOrBullet||'')}</td></tr>`).join("")}</tbody></table></div>
    <h4>Unresolved profile domains</h4><p>${escapeHtml((r.gaps||[]).join(', ')||'None')}</p>
  </div></div>`;
}
function researchSourceHtml(s){
  if(!s)return `<div class="empty">Fetched or uploaded source sections will appear here.</div>`;
  return `<div class="notice ${s.warnings?.length?'warning':''}"><strong>${escapeHtml(s.title)}</strong><br>${escapeHtml((s.warnings||[]).join(' '))}</div><div class="table-wrap"><table><thead><tr><th>Section</th><th>Locator</th><th>Extracted text</th></tr></thead><tbody>${(s.sections||[]).slice(0,100).map(x=>`<tr><td>${escapeHtml(x.section)}</td><td>${escapeHtml(x.locator)}</td><td>${escapeHtml(String(x.text||'').slice(0,500))}</td></tr>`).join('')}</tbody></table></div>`;
}
async function runMedicationResearch(){
  const medication=byId("researchMedication").value.trim(); if(!medication){toast("Medication is required");return;}
  const connectors=[...document.querySelectorAll('[data-connector]:checked')].map(x=>x.dataset.connector);
  const b=byId("runResearch");b.disabled=true;b.textContent="Researching…";
  try{state.researchReport=await researchApi('/api/research',{medication,connectors});renderResearchTab();toast("Research draft complete");}
  catch(e){alert(`Research failed: ${e.message}\n\nStart START_RESEARCH_WORKBENCH.cmd and retry.`);}
  finally{if(byId("runResearch")){byId("runResearch").disabled=false;byId("runResearch").textContent="Research medication";}}
}
function ensureMedicationForResearch(r){
  let id=slug(r.medication);let med=state.repository.medications[id];
  if(!med){med=newMedication(r.medication);med.medicationId=id;state.repository.medications[id]=med;}
  med.generic=r.identity?.generic||r.medication;med.fields.generic=med.generic;
  med.fields.brands=[...new Set([...(med.fields.brands||[]),...(r.identity?.brands||[])])];return med;
}
function importResearchReport(){
  const r=state.researchReport;if(!r)return;const med=ensureMedicationForResearch(r);let claims=0,evidence=0;
  for(const draft of r.draftClaims||[]){
    const duplicate=med.claimIds.map(id=>state.repository.claims[id]).filter(Boolean).find(c=>c.field===draft.field&&String(c.value).trim()===String(draft.value).trim());
    if(duplicate)continue;
    const c=newClaim(med.medicationId,draft.field);Object.assign(c,draft,{claimId:uid('claim'),medicationId:med.medicationId,reviewStage:'source_attached',validationStatus:'draft',author:ownerSettings().ownerName||'Automated Research Engine'});
    const ev=newEvidence(c.claimId);Object.assign(ev,draft.evidenceDraft||{}, {evidenceId:uid('evidence'),claimId:c.claimId,verificationStatus:'pending'});ev.locator=Object.assign(newEvidence('').locator,draft.evidenceDraft?.locator||{});
    c.evidenceIds=[ev.evidenceId];state.repository.claims[c.claimId]=c;state.repository.evidence[ev.evidenceId]=ev;med.claimIds.push(c.claimId);claims++;evidence++;
  }
  (r.sources||[]).forEach(src=>{const id=src.sourceId||uid('source');state.repository.sourceIntake[id]=Object.assign({},src,{intakeStatus:'retrieved',assignedReviewer:ownerSettings().ownerName||''});});
  addAudit('automated_research_import',med.medicationId,{researchId:r.researchId,claims,evidence,sources:r.sources?.length||0});state.selectedMedicationId=med.medicationId;saveRepository();renderAll();toast(`${claims} draft claims imported`);
}
async function fetchResearchUrl(){
  const url=byId("researchUrl").value.trim();if(!url){toast("Enter a URL");return;}
  try{state.researchSource=await researchApi('/api/fetch-url',{url});renderResearchTab();toast("Source fetched");}catch(e){alert(`Source fetch failed: ${e.message}`);}
}
function uploadResearchFile(file){
  const reader=new FileReader();reader.onload=async()=>{try{const dataBase64=String(reader.result).split(',')[1]||'';state.researchSource=await researchApi('/api/upload',{filename:file.name,dataBase64});renderResearchTab();toast("Source parsed");}catch(e){alert(`Upload parsing failed: ${e.message}`);}};reader.readAsDataURL(file);
}


function addTop300ReloadControl() {
  const header = document.querySelector("header") || document.querySelector(".topbar") || document.body;
  if (!header || document.getElementById("reloadTop300")) return;
  const btn = document.createElement("button");
  btn.id = "reloadTop300";
  btn.textContent = "Reload Top 300";
  btn.title = "Replace an empty or broken local workbench repository with the bundled Top-300 library.";
  btn.style.marginLeft = "8px";
  btn.addEventListener("click", () => {
    const result = mergeTop300IntoRepository(state.repository || emptyRepository());
    state.repository = result.repo;
    state.selectedMedicationId = Object.keys(state.repository.medications)[0] || null;
    state.selectedClaimId = null;
    state.selectedEvidenceId = null;
    saveRepository();
    renderAll();
    toast(`Top-300 database repaired: ${result.added} medication(s) added; existing owner edits preserved.`);
  });
  header.appendChild(btn);
}

window.addEventListener("DOMContentLoaded", () => {
  setTimeout(addTop300ReloadControl, 0);
});
