/* D2C uses only published, immutable snapshots. No production fixture or demo fallback. */
const D2C_DICT = {
  de: {providerUsageUnknown:'Der Provider hat keinen vollständigen Tokenverbrauch gemeldet. Die Budgetprüfung stoppt den Lauf. Abrechnung prüfen; erneutes Fortsetzen behebt die fehlende Messung nicht.',providerTimeout:'Der Modellaufruf hat das Zeitlimit erreicht. Verbrauch und gespeicherten Checkpoint prüfen, bevor der Lauf fortgesetzt wird.',providerUsageJournalFailed:'Die lokale Verbrauchsabrechnung konnte nicht sicher gespeichert werden. Speicherzugriff des Workers prüfen, bevor weitere Modellaufrufe starten.',publicationCapacity:'Die Ergebnisprüfung überschreitet ein Serverlimit. Den Worker-Prüfpfad prüfen; der Report ist noch nicht veröffentlicht.',publicationVersion:'Worker und Server verwenden unterschiedliche Prüfversionen. Beide aktualisieren, bevor die Publikation fortgesetzt wird.',providerMismatch:'Worker und Lauf verwenden unterschiedliche Provider. Den Worker auf den beim Laufstart festgelegten Provider einstellen oder für einen anderen Provider einen neuen Lauf starten. Der aktive Lauf behält seine Provider-Auswahl.',anonymousErasureMigration:'Die Review-Quelle muss migriert werden. Neue Läufe bleiben gesperrt, um historische Löschungen zu schützen. Quellenmigration prüfen, bevor ein neuer Lauf startet.',claudeAuthIsolation:'Claude kann mit seiner nativen Anmeldung in dieser Umgebung nicht sicher starten. Für den Pilot Codex wählen, bis Runtime und Anmeldung gemeinsam geprüft sind.',title:'D2C-Analyse',overview:'Übersicht',runs:'Analyse-Läufe',settings:'Quellen & Worker',budget:'Provider & Limits',budgetHint:'Neue Limits gelten für den nächsten Lauf. Fortsetzen behält die beim Start gebundenen Limits und den bisherigen Verbrauch.',sourceHint:'Ein laufender Run behält seine Quellenkonfiguration. Nach einer Quellenänderung den alten Lauf abbrechen und neu starten.',retention:'Aufbewahrung (Tage)',maxRequests:'Quellen-Anfragen',maxProducts:'Max. Produkte',maxReviews:'Max. Reviews',maxBatchReviews:'Reviews pro Batch',maxRuntimeMs:'Max. Laufzeit (Millisekunden)',maxProviderRequests:'Provider-Anfragen',maxProviderTokens:'Provider-Token',configSaved:'Limits und Aufbewahrung gespeichert.',deleteReview:'Review löschen',deleteSource:'Quelle löschen',purge:'Shop und Analysedaten löschen',deleteTitle:'Löschung bestätigen',deleteAck:'Dieses Objekt und zugehörige historische Belege dauerhaft löschen. Betroffene Snapshots werden widerrufen.',deleteWord:'Zur Bestätigung DELETE eingeben',deleted:'Gelöscht. Betroffene Ergebnisstände sind widerrufen.',sourceInvalid:'Import ist nicht mehr gültig. Einen bereinigten vollständigen Export neu einspielen.',globalControl:'Analyse-Betrieb',enabled:'Aktiviert',disabled:'Deaktiviert',controlReason:'Grund für die Betriebsänderung',enable:'Analysen aktivieren',disable:'Alle Analysen stoppen',controlSaved:'Betriebsstatus geändert.',newAnalysis:'Neue Analyse',newShop:'Shop hinzufügen',shop:'Shop',customer:'CRM-Kunde',chooseCustomer:'Kunde auswählen',shopUrl:'Shop-URL',products:'Produkte',segments:'Segmente',status:'Status',reviewText:'Originaltext',unusable:'Nicht analysierbar',extracted:'Analysiert',raw:'Noch nicht analysiert',productUrls:'Direkte Produktseiten',productHint:'Eine HTTPS-URL pro Zeile. Im Shop-Scope priorisierte Startpunkte; im Produktscope die feste Auswahl.',scope:'Analyseumfang',wholeShop:'Gesamten Shop analysieren',selectedProducts:'Nur diese Produkte',source:'Review-Zugang',public:'Öffentliche Produktseiten',api:'Judge.me API',import:'CSV / JSON importieren',permission:'Erlaubter Datenzugang',permissionHint:'Quelle und Erlaubnis konkret benennen. Der Worker prüft Zugang und Robots.',permissionCheck:'Ich bin autorisiert, diese Quelle für die Analyse zu verwenden.',apiDomain:'Judge.me Shop-Domain',apiDomainHint:'Originale Shopify-Domain aus Judge.me, z. B. brand.myshopify.com. Kann von der Shop-URL abweichen.',token:'Privater Judge.me Token',tokenHint:'Wird serverseitig gespeichert. Nie im Report oder Browser-Speicher.',importTooLarge:'Import überschreitet 8 MB. Review-API nutzen oder den ausgewählten Produktscope mit passendem Export begrenzen.',importData:'Exportdatei',importHint:'Vollständiger Kundenexport mit stabilen Produkt-/Review-IDs. JSON oder CSV wählen.',declaredComplete:'Export deckt den gewählten Scope vollständig ab',expectedCount:'Erwartete Review-Anzahl',start:'Quelle prüfen & Analyse starten',cancel:'Abbrechen',back:'Zurück',close:'Schließen',loading:'Daten werden geladen.',refresh:'Neu laden',retry:'Erneut prüfen',save:'Speichern',noShop:'Noch kein Shop.',noShopText:'CRM-Kunden verknüpfen, Shop und erlaubte Review-Quelle eintragen. Der Worker erfasst Produkte und Reviews bis zur Ergebnisübersicht.',noSnapshot:'Noch kein geprüftes Ergebnis.',noSnapshotText:'Ein veröffentlichter Snapshot erscheint nach Quellenprüfung, Erfassung und Analyse. Worker-Status und Datenlücken stehen unter Analyse-Läufe.',noProfiles:'Keine belegten Persona-Profile in dieser Basis.',noReviews:'Keine Textreviews in dieser Basis.',noReviewsText:'Reine Sterne bleiben in der Rating-Basis. Persona-Profile benötigen Originaltext und validierte Belege.',noMatches:'Keine Treffer für diese Filter.',noMatchesText:'Filter zurücksetzen oder Scope erweitern. Fehlende Merkmale sind kein negativer Beleg.',profiles:'Kaufgründe als Persona-Profile',profileHint:'Wiederkehrende Situationen aus Review-Belegen. Demografie bleibt unbekannt, solange sie nicht ausdrücklich belegt ist.',reviewShare:'der gefilterten Textreviews',evidence:'Belege',details:'Produktfit & Belege',rating:'Sterne',all:'Alle',supported:'Gestützt',mixed:'Gemischte Belege',insufficient:'Kleine Basis',unknown:'Unbekannt',complete:'Vollständig',partial:'Unvollständig',unreviewed:'Ungeprüft',reviewed:'Geprüft',footnote:'Review-Anteile sind keine Käuferanteile. Persona-Profile können sich überschneiden. Gruppen zählen Review-IDs als Vereinigung.',findings:'Priorisierte Findings',noFindings:'Keine belegten Findings in diesem Scope.',productFit:'Produktfit',matrix:'Produktvergleich',matrixHint:'Segmentbelege / analysierte Textreviews je Produkt. Überschneidungen sind möglich.',coverage:'Abdeckung',pdpCoverage:'Produktseiten geprüft',reviewCoverage:'Feed-Zuordnungen',physicalReviews:'Physische Reviews im Scope',analysisCoverage:'Textreviews analysiert',filteredBase:'Aktuelle Filterbasis',filter:'Filter',reset:'Zurücksetzen',need:'Bedarf',occasion:'Kaufanlass',usage:'Nutzung',preference:'Vorlieben',interest:'Interessen',objection:'Einwände',aspect:'Produktaspekte',experience:'Erfahrung',context:'Expliziter Kontext',language:'Review-Sprache',dateFrom:'Review-Datum ab',dateTo:'Review-Datum bis',unknownAge:'Reviewer-Alter unbekannt',explicitAges:'Explizite Altersbelege',contradicting:'Widersprechende Originalbelege',supporting:'Weitere Originalbelege',advanced:'Erweiterte Regel',rule:'Ausführbare Regel',ruleHint:'OR innerhalb eines Filters; AND zwischen Filtern. Unknown wird nicht als Gegenteil interpretiert.',savedViews:'Gespeicherte Ansichten',viewName:'Ansichtsname',saveView:'Ansicht speichern',pinned:'Fester Snapshot',latest:'Neuester fertiger Snapshot',snapshot:'Snapshot',knowledge:'Methodikversion',newSnapshot:'Ein neuer geprüfter Stand ist verfügbar.',useSnapshot:'Neuen Stand öffnen',oldSnapshot:'Vorheriger Ergebnisstand bleibt sichtbar, während ein neuer Lauf arbeitet.',export:'Export',print:'CI-Report / PDF',printHint:'Druckdialog öffnen. Als PDF speichern.',json:'JSON',csv:'CSV',recommendations:'Konkrete nächste Handlung',method:'Mesper-Methodik',original:'Original-Reviewbeleg',testPlan:'Testplan',why:'Herleitung',required:'Fehlende Inputs',hypothesis:'Hypothese',ready_to_test:'Bereit zum Test',needs_input:'Input fehlt',assets:'Benötigte Assets',measurement:'Messung',deliverable:'Deliverable',notEstablished:'Keine passende Mesper-Regel. Als Analystenhypothese prüfen.',noRecommendations:'Keine validierte Empfehlung für diesen Kontext.',moreEvidence:'Weitere Belege laden',noEvidence:'Keine Belege für diesen Selector. Datenlücke bleibt sichtbar.',sourceLink:'Originalquelle öffnen',subject:'Sprecherrolle',age:'Alter',interests:'Interessen',notProven:'Nicht belegt',correction:'Fakt korrigieren',reason:'Grund für die Korrektur',corrected:'Korrektur gespeichert. Neu berechneten Snapshot bewusst öffnen.',polarity:'Aspekt-Sentiment',tag:'Tag-ID',reportInclude:'In Briefing / Report übernehmen',selectedReport:'Ausgewählte Empfehlungen',copyBrief:'Briefing kopieren',copied:'Briefing kopiert.',workerOffline:'Worker offline. Den gepairten Rechner einschalten und Runner starten.',workerOnline:'Worker online',workerUnknown:'Worker-Status unbekannt. Kein Fortschritt wird simuliert.',pair:'Worker pairen',pairName:'Worker-Name',deviceId:'Worker-ID',revoke:'Worker-Zugang widerrufen',revoked:'Worker-Zugang widerrufen. Aktive Leases sind ungültig.',correctionNeedsRun:'Korrektur gespeichert. Neuanalyse starten, damit ein neuer Snapshot entsteht.',chooseSource:'Aktive Quelle für die nächste Analyse',provider:'Provider',pairHint:'Ein laufender Rechner ist erforderlich. Codex und Claude werden im Preflight geprüft. Quota blockiert den Lauf.',credential:'Worker-Zugang einmalig sichern',credentialHint:'Dieses Secret wird nur jetzt angezeigt. Für den Runner verwenden und sicher verwahren.',dismissSecret:'Secret ausblenden',sourceSaved:'Quelle gespeichert.',sourceChange:'Quelle speichern',pause:'Pausieren',resume:'Fortsetzen',full:'Vollständig',incremental:'Neue / geänderte Reviews',mode:'Neuanalyse',queued:'Wartet auf Worker',running:'Läuft',paused:'Pausiert',blocked:'Blockiert',completed:'Abgeschlossen',failed:'Fehlgeschlagen',cancelled:'Abgebrochen',preflight:'Quelle prüfen',discovery:'Produkte entdecken',reviews:'Reviews',reviewStage:'Reviews erfassen',taxonomy_ready:'Taxonomie prüfen',extraction:'Reviews analysieren',segmentation:'Segmente bilden',validation:'Ergebnis prüfen',recommendationsStage:'Empfehlungen prüfen',snapshotStage:'Snapshot publizieren',noRuns:'Noch kein Analyse-Lauf.',startAgain:'Neu analysieren',showResult:'Ergebnis öffnen',warnings:'Datenlücken',cancelRun:'Lauf abbrechen',invalid:'Eingaben prüfen.',network:'Verbindung fehlt. Erneut laden, sobald du online bist.',auth:'Session fehlt oder Zugriff verweigert. Erneut anmelden.',schema:'D2C ist für diese Umgebung noch nicht eingerichtet.',noTask:'Das Briefing erzeugt keine Tasks automatisch.',reviewDate:'Review-Datum',absentDate:'Datum fehlt',reportScope:'Berichtsumfang',sourcePermission:'Zugangsgrundlage',scopeConfirmed:'Abgeleiteter Shop-Host',starsOnly:'Sterne ohne Text',addCondition:'Bedingung',addGroup:'Gruppe',remove:'Entfernen',and:'ALLE (AND)',or:'EINE (OR)',not:'NICHT (NOT)',factAny:'Gleicher Kontext-Fakt',jsonRule:'JSON-Regel bearbeiten',applyRule:'Regel anwenden',runId:'Run-ID',runScope:'Run-Scope',outOfDate:'Snapshot wurde widerrufen. Einen neuen Stand öffnen.',overrideHelp:'Korrektur wird protokolliert. Historischer Snapshot bleibt unverändert.',positive:'Positiv',negative:'Negativ',neutral:'Neutral',pendingReport:'Report wird aus dem gebundenen Snapshot erstellt.',exportError:'Export konnte nicht geladen werden.',published:'Veröffentlicht',noCompany:'Zuerst einen CRM-Kunden anlegen.',sameShop:'Produktseiten müssen eindeutig zum selben Shop gehören.',permissionRequired:'Autorisierung und Zugangsgrundlage fehlen.',urlRequired:'Shop-URL oder Produktseite fehlt.',productsRequired:'Für „Nur diese Produkte“ mindestens eine Produktseite angeben.',date:'Datenstand',count:'Anzahl',productWidth:'Produktbreite',selectRun:'Historischer Lauf',allGroups:'Gesamter Shop',groups:'Produktgruppen',canonical:'Produktseite',pdpStatus:'PDP-Prüfung',price:'Preis',variants:'Varianten',sourceOrigin:'Gruppenherkunft',unassigned:'Nicht zugeordnet',saveSuccess:'Ansicht gespeichert.',selected:'ausgewählte Produkte',discovered:'entdeckte Produkte',providerAuth:'Provider-Auth / Quota im Preflight prüfen',roleDenied:'D2C ist nur für das interne Team verfügbar.',briefTitle:'Creative- / Shop-Briefing'},
  en: {providerUsageUnknown:'The provider did not report complete token usage. The budget check has stopped this run. Check accounting; resuming again does not recover the missing measurement.',providerTimeout:'The model request reached its time limit. Check usage and the saved checkpoint before resuming.',providerUsageJournalFailed:'Local usage accounting could not be saved safely. Check worker storage access before starting more model requests.',publicationCapacity:'Result validation exceeds a server limit. Check the worker validation path; the report has not been published.',publicationVersion:'The worker and server use different validation versions. Update both before resuming publication.',providerMismatch:'The worker and run use different providers. Align the worker with the provider fixed at run start, or start a new run for another provider. The active run keeps its provider selection.',anonymousErasureMigration:'The review source needs a migration. New runs remain blocked to protect historical deletions. Validate the source migration before starting another run.',claudeAuthIsolation:'Claude cannot start safely with its native authentication in this environment. Choose Codex for the pilot until the runtime and authentication are validated together.',title:'D2C analysis',overview:'Overview',runs:'Analysis runs',settings:'Sources & worker',budget:'Provider & limits',budgetHint:'New limits apply to the next run. Resume retains the limits bound at start and usage already consumed.',sourceHint:'An active run keeps its source configuration. After changing a source, cancel the old run and start a new one.',retention:'Retention (days)',maxRequests:'Source requests',maxProducts:'Max products',maxReviews:'Max reviews',maxBatchReviews:'Reviews per batch',maxRuntimeMs:'Max runtime (milliseconds)',maxProviderRequests:'Provider requests',maxProviderTokens:'Provider tokens',configSaved:'Limits and retention saved.',deleteReview:'Delete review',deleteSource:'Delete source',purge:'Delete shop and analysis data',deleteTitle:'Confirm deletion',deleteAck:'Permanently delete this object and its historic evidence. Affected snapshots will be revoked.',deleteWord:'Type DELETE to confirm',deleted:'Deleted. Affected snapshots are revoked.',sourceInvalid:'Import is no longer valid. Upload a cleaned, complete export again.',globalControl:'Analysis operation',enabled:'Enabled',disabled:'Disabled',controlReason:'Reason for operation change',enable:'Enable analyses',disable:'Stop all analyses',controlSaved:'Operation status changed.',newAnalysis:'New analysis',newShop:'Add shop',shop:'Shop',customer:'CRM customer',chooseCustomer:'Choose customer',shopUrl:'Shop URL',products:'Products',segments:'Segments',status:'Status',reviewText:'Original text',unusable:'Unusable',extracted:'Analyzed',raw:'Not analyzed yet',productUrls:'Direct product pages',productHint:'One HTTPS URL per line. Prioritized seeds for shop scope; the fixed selection for product scope.',scope:'Analysis scope',wholeShop:'Analyze whole shop',selectedProducts:'Only these products',source:'Review access',public:'Public product pages',api:'Judge.me API',import:'Import CSV / JSON',permission:'Authorized data access',permissionHint:'Name the source and authorization. The worker checks access and robots.',permissionCheck:'I am authorized to use this source for analysis.',apiDomain:'Judge.me shop domain',apiDomainHint:'Original Shopify domain from Judge.me, e.g. brand.myshopify.com. May differ from the storefront URL.',token:'Private Judge.me token',tokenHint:'Stored on the server. Never in reports or browser storage.',importTooLarge:'Import exceeds 8 MB. Use the review API or narrow the product scope with a matching export.',importData:'Export file',importHint:'Customer export with stable product / review IDs. Choose JSON or CSV.',declaredComplete:'Export covers the selected scope completely',expectedCount:'Expected review count',start:'Check source & start analysis',cancel:'Cancel',back:'Back',close:'Close',loading:'Loading data.',refresh:'Refresh',retry:'Check again',save:'Save',noShop:'No shop yet.',noShopText:'Link a CRM customer, add the shop and authorized review source. The worker captures products and reviews through to the results overview.',noSnapshot:'No validated result yet.',noSnapshotText:'A published snapshot appears after source checks, collection and analysis. Worker status and data gaps appear under Analysis runs.',noProfiles:'No evidenced persona profiles in this base.',noReviews:'No text reviews in this base.',noReviewsText:'Star-only reviews stay in the rating base. Persona profiles require original text and validated evidence.',noMatches:'No reviews match these filters.',noMatchesText:'Reset filters or expand the scope. Missing attributes do not prove a negative.',profiles:'Buying reasons as persona profiles',profileHint:'Recurring situations from review evidence. Demographics remain unknown until explicitly stated.',reviewShare:'of filtered text reviews',evidence:'Evidence',details:'Product fit & evidence',rating:'Rating',all:'All',supported:'Supported',mixed:'Mixed evidence',insufficient:'Small base',unknown:'Unknown',complete:'Complete',partial:'Partial',unreviewed:'Unreviewed',reviewed:'Reviewed',footnote:'Review shares are not buyer shares. Profiles can overlap. Groups use the union of review IDs.',findings:'Prioritized findings',noFindings:'No evidenced findings in this scope.',productFit:'Product fit',matrix:'Product comparison',matrixHint:'Segment evidence / analyzed text reviews for each product. Overlap is possible.',coverage:'Coverage',pdpCoverage:'Product pages checked',reviewCoverage:'Feed assignments',physicalReviews:'Physical reviews in scope',analysisCoverage:'Text reviews analyzed',filteredBase:'Current filter base',filter:'Filters',reset:'Reset',need:'Need',occasion:'Purchase occasion',usage:'Usage',preference:'Preferences',interest:'Interests',objection:'Objections',aspect:'Product aspects',experience:'Experience',context:'Explicit context',language:'Review language',dateFrom:'Review date from',dateTo:'Review date to',unknownAge:'Reviewer age unknown',explicitAges:'Explicit age evidence',contradicting:'Contradicting original evidence',supporting:'Other original evidence',advanced:'Advanced rule',rule:'Executable rule',ruleHint:'OR within a filter; AND between filters. Unknown is not interpreted as the opposite.',savedViews:'Saved views',viewName:'View name',saveView:'Save view',pinned:'Fixed snapshot',latest:'Latest completed snapshot',snapshot:'Snapshot',knowledge:'Method version',newSnapshot:'A new validated snapshot is available.',useSnapshot:'Open new snapshot',oldSnapshot:'The previous result stays visible while a new run is working.',export:'Export',print:'CI report / PDF',printHint:'Open print dialog. Save as PDF.',json:'JSON',csv:'CSV',recommendations:'Concrete next action',method:'Mesper method',original:'Original review evidence',testPlan:'Validation plan',why:'Reasoning',required:'Missing inputs',hypothesis:'Hypothesis',ready_to_test:'Ready to test',needs_input:'Needs input',assets:'Required assets',measurement:'Measurement',deliverable:'Deliverable',notEstablished:'No matching Mesper rule. Validate as an analyst hypothesis.',noRecommendations:'No validated recommendation for this context.',moreEvidence:'Load more evidence',noEvidence:'No evidence for this selector. The data gap stays visible.',sourceLink:'Open original source',subject:'Speaker role',age:'Age',interests:'Interests',notProven:'Not evidenced',correction:'Correct fact',reason:'Reason for correction',corrected:'Correction saved. Open the rebuilt snapshot explicitly.',polarity:'Aspect sentiment',tag:'Tag ID',reportInclude:'Include in briefing / report',selectedReport:'Selected recommendations',copyBrief:'Copy briefing',copied:'Briefing copied.',workerOffline:'Worker offline. Power on the paired computer and start the runner.',workerOnline:'Worker online',workerUnknown:'Worker status unknown. Progress is never simulated.',pair:'Pair worker',pairName:'Worker name',deviceId:'Worker ID',revoke:'Revoke worker access',revoked:'Worker access revoked. Active leases are invalid.',correctionNeedsRun:'Correction saved. Start a new analysis to publish a revised snapshot.',chooseSource:'Active source for the next analysis',provider:'Provider',pairHint:'A running computer is required. Codex and Claude are checked in preflight. Quota blocks the run.',credential:'Save worker credentials once',credentialHint:'This secret is shown only now. Use it for the runner and store it securely.',dismissSecret:'Hide secret',sourceSaved:'Source saved.',sourceChange:'Save source',pause:'Pause',resume:'Resume',full:'Full',incremental:'New / changed reviews',mode:'New analysis',queued:'Waiting for worker',running:'Running',paused:'Paused',blocked:'Blocked',completed:'Completed',failed:'Failed',cancelled:'Cancelled',preflight:'Check source',discovery:'Discover products',reviews:'Reviews',reviewStage:'Collect reviews',taxonomy_ready:'Review taxonomy',extraction:'Analyze reviews',segmentation:'Build segments',validation:'Validate result',recommendationsStage:'Validate recommendations',snapshotStage:'Publish snapshot',noRuns:'No analysis run yet.',startAgain:'Analyze again',showResult:'Open result',warnings:'Data gaps',cancelRun:'Cancel run',invalid:'Check inputs.',network:'Connection lost. Refresh when you are online.',auth:'Session expired or access denied. Sign in again.',schema:'D2C is not configured for this environment yet.',noTask:'This briefing does not create tasks automatically.',reviewDate:'Review date',absentDate:'Date missing',reportScope:'Report scope',sourcePermission:'Access basis',scopeConfirmed:'Derived shop host',starsOnly:'Stars without text',addCondition:'Condition',addGroup:'Group',remove:'Remove',and:'ALL (AND)',or:'ONE (OR)',not:'NOT',factAny:'Same context fact',jsonRule:'Edit JSON rule',applyRule:'Apply rule',runId:'Run ID',runScope:'Run scope',outOfDate:'Snapshot revoked. Open a new snapshot.',overrideHelp:'Corrections are audited. The historical snapshot stays unchanged.',positive:'Positive',negative:'Negative',neutral:'Neutral',pendingReport:'Creating report from the pinned snapshot.',exportError:'Export could not be loaded.',published:'Published',noCompany:'Create a CRM customer first.',sameShop:'Product pages must belong to the same shop.',permissionRequired:'Authorization and access basis are required.',urlRequired:'Shop URL or product page is required.',productsRequired:'At least one product page is required for selected products.',date:'Data as of',count:'Count',productWidth:'Product breadth',selectRun:'Historical run',allGroups:'Whole shop',groups:'Product groups',canonical:'Product page',pdpStatus:'PDP check',price:'Price',variants:'Variants',sourceOrigin:'Group origin',unassigned:'Unassigned',saveSuccess:'View saved.',selected:'selected products',discovered:'discovered products',providerAuth:'Provider auth / quota checked in preflight',roleDenied:'D2C is available to the internal team only.',briefTitle:'Creative / shop briefing'}
};
const D2C_STAGES = ['preflight','discovery','products','reviews','taxonomy_ready','extraction','segmentation','recommendations','validation','snapshot'];
const D2C_TONES = ['#4340c4','#185ea6','#623b9d','#156748','#8b451a','#544477'];
function d2cText(value,lang) { return typeof value==='string' ? value : value && typeof value==='object' ? (value[lang] || value.de || value.en || '') : value==null ? '' : String(value); }
// Presentation overlays never replace IDs, rules, evidence or methodology text.
function d2cLocalized(item,lang) {if(!item||lang!=='en'||!item.localization?.en)return item;const fields=item.recommendationId?['title','action','why','deliverable','validationPlan','requiredInputs']:item.findingId?['title','claim','dataGaps']:item.segmentVersionId?['label','description']:[];const view={...item};for(const key of fields)if(Object.prototype.hasOwnProperty.call(item.localization.en,key))view[key]=item.localization.en[key];return view;}
function d2cField(item,key,lang) {return d2cLocalized(item,lang)?.[key];}
// Source rows retain every native family member. A scoped family basis is built afterwards.
function d2cNativeReviews(dashboard) {
  if(Array.isArray(dashboard?.sourceReviews))return dashboard.sourceReviews;
  const rows=new Map();for(const row of dashboard?.reviews||[])for(const member of row._members||[row])if(!rows.has(member.reviewVersionId))rows.set(member.reviewVersionId,member);
  return [...rows.values()];
}
function d2cPhysicalReviewCount(dashboard) {
  const count=dashboard?.metrics?.sourceReviews;
  if(Number.isSafeInteger(count)&&count>=0)return count;
  if(!Array.isArray(dashboard?.sourceReviews)&&!Array.isArray(dashboard?.reviews))return null;
  return new Set(d2cNativeReviews(dashboard).map(row=>JSON.stringify([row.sourceId,row.productId,row.nativeReviewId||row.reviewVersionId]))).size;
}
function d2cAnalysisSnapshot(dashboard,shopId) {return dashboard?{...dashboard,shopId,products:dashboard.catalogProducts||dashboard.products||[],reviews:d2cNativeReviews(dashboard)}:null;}
function d2cProductAnalysis(snapshot,products,filter) {
  const rowsByProduct=new Map();for(const row of snapshot?.reviews||[]){const rows=rowsByProduct.get(row.productId)||[];rows.push(row);rowsByProduct.set(row.productId,rows);}
  return new Map((products||[]).map(product=>{const data=D2CEngine.aggregateSnapshot({...snapshot,products:[product],reviews:rowsByProduct.get(product.id)||[]},{scope:{type:'product',id:product.id},filter});return[product.id,{metrics:data.metrics,segments:new Map(data.segments.map(segment=>[segment.segmentVersionId,segment.metrics]))}];}));
}
function d2cPrintView(dashboard,shopId,scope,filter) {
  const reviews=d2cNativeReviews(dashboard),reviewIds=new Set(reviews.map(row=>row.reviewVersionId));
  const evidence=(dashboard.evidence||[]).filter(item=>reviewIds.has(item.reviewVersionId)),evidenceIds=new Set(evidence.map(item=>item.evidenceId));
  const trim=item=>({...item,reviewEvidenceIds:(item.reviewEvidenceIds||[]).filter(id=>evidenceIds.has(id))});
  return {...dashboard,reviews,sourceReviews:reviews,analysedReviews:dashboard.reviews,evidence,findings:(dashboard.findings||[]).map(trim),recommendations:(dashboard.recommendations||[]).map(trim),segments:(dashboard.segments||[]).map(segment=>({...segment,claimEvidence:segment.claimEvidence?.map(claim=>({...claim,reviewVersionIds:claim.reviewVersionIds.filter(id=>reviewIds.has(id)),reviewEvidenceIds:claim.reviewEvidenceIds?.filter(id=>evidenceIds.has(id))})).filter(claim=>claim.reviewVersionIds.length)})),exportScope:scope.type==='shop'?{type:'shop',id:shopId}:scope,exportFilter:filter};
}
function d2cDashboardBinding({shopId,snapshotId,scope,filter}) {return JSON.stringify([shopId,snapshotId,{type:scope.type,id:scope.type==='shop'?shopId:scope.id},filter]);}
function d2cDate(value,lang) { if (!value) return ''; const d=new Date(value); return Number.isNaN(d.getTime()) ? '' : d.toLocaleString(lang==='de'?'de-DE':'en-GB',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Europe/Berlin'}); }
function d2cNumber(value,lang,digits=0) { return value==null ? '?' : new Intl.NumberFormat(lang==='de'?'de-DE':'en-GB',{maximumFractionDigits:digits}).format(value); }
function d2cSafeUrl(value) { try { const u=new URL(value); return u.protocol==='https:' && !u.username && !u.password ? u.href : null; } catch (_) { return null; } }
function d2cError(e,t) { const actionable={TOKEN_USAGE_UNAVAILABLE:'providerUsageUnknown',TIMEOUT:'providerTimeout',PROVIDER_USAGE_JOURNAL_FAILED:'providerUsageJournalFailed',WORKER_RESOURCE_LIMIT:'publicationCapacity',PUBLICATION_PHASE_INVALID:'publicationVersion',PROVIDER_MISMATCH:'providerMismatch',ANONYMOUS_ERASURE_MIGRATION_REQUIRED:'anonymousErasureMigration',CLAUDE_AUTH_ISOLATION_UNAVAILABLE:'claudeAuthIsolation'};if(actionable[e?.code])return t(actionable[e.code]);return ['UNAUTHORIZED','FORBIDDEN'].includes(e.code)?t('auth'):['NOT_CONFIGURED','D2C_DISABLED'].includes(e.code)?t('schema'):['NETWORK','FETCH_ERROR'].includes(e.code)?t('network'):['SNAPSHOT_REVOKED'].includes(e.code)?t('outOfDate'):(e.message || t('invalid')); }
function d2cRunCanResume(run) { return ['paused','blocked','failed'].includes(run.state) && run.error?.code!=='TOKEN_USAGE_UNAVAILABLE'; }
function D2CSymbol({index=0,size=24}) { const paths=[<React.Fragment><circle cx="12" cy="12" r="9"/><path d="M12 7v5l4 2"/></React.Fragment>,<React.Fragment><path d="M17 3l4 4-4 4M3 11V9a2 2 0 0 1 2-2h16M7 21l-4-4 4-4M21 13v2a2 2 0 0 1-2 2H3"/></React.Fragment>,<React.Fragment><path d="M4 4h16v16H4zM8 4v4M12 4v2M16 4v4M4 8h4M4 12h2M4 16h4"/></React.Fragment>,<React.Fragment><path d="M12 3l8 5v8l-8 5-8-5V8zM4 8l8 5 8-5M12 13v8"/></React.Fragment>,<React.Fragment><path d="M4 12h16M12 4v16M6 6l12 12M6 18L18 6"/></React.Fragment>,<React.Fragment><path d="M4 6h5l3 6 7 2a3 3 0 0 1 2 3v2H3V9zM3 16h18M10 10l3-1"/></React.Fragment>]; return <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">{paths[index%paths.length]}</svg>; }
function d2cProfileIndex(segment,dashboard) {return Math.max(0,(dashboard?.segments||[]).findIndex(s=>s.segmentVersionId===segment.segmentVersionId));}
function D2CStatus({value,t}) { return <span className={'d2c-status '+(value||'unknown')}>{t(value||'unknown')}</span>; }
function D2CDrawer({title,onClose,children,closeLabel}) { const ref=React.useRef(null),prior=React.useRef(document.activeElement); React.useEffect(()=>{const old=document.body.style.overflow;document.body.style.overflow='hidden';ref.current?.querySelector('button')?.focus();const key=e=>{if(e.key==='Escape'){e.preventDefault();onClose();}if(e.key==='Tab'){const all=[...ref.current.querySelectorAll('button:not(:disabled),a[href],input,select,textarea,[tabindex="0"]')].filter(el=>el.getClientRects().length);const a=all[0],b=all[all.length-1];if(!a)return;if(e.shiftKey&&document.activeElement===a){e.preventDefault();b.focus();}else if(!e.shiftKey&&document.activeElement===b){e.preventDefault();a.focus();}}};document.addEventListener('keydown',key);return()=>{document.body.style.overflow=old;document.removeEventListener('keydown',key);prior.current?.isConnected&&prior.current.focus();};},[]);return <><div className="d2c-backdrop" onClick={onClose}/><section ref={ref} className="d2c-drawer" role="dialog" aria-modal="true" aria-labelledby="d2c-detail-title"><div className="d2c-drawer-head"><strong id="d2c-detail-title">{title}</strong><button className="d2c-button" onClick={onClose}>{closeLabel}</button></div><div className="d2c-drawer-body">{children}</div></section></>; }
function D2CSourceFields({form,set,t,canManageApi=false}) { const [inputError,setInputError]=React.useState('');return <><label className="d2c-field">{t('source')}<select value={form.type} onChange={e=>set('type',e.target.value)}><option value="public_structured">{t('public')}</option><option value="judge_me" disabled={!canManageApi}>{t('api')}</option><option value="import">{t('import')}</option></select></label><label className="d2c-field">{t('permission')}<input required value={form.permissionBasis} onChange={e=>set('permissionBasis',e.target.value)} placeholder={t('sourcePermission')}/><small>{t('permissionHint')}</small></label>{form.type==='judge_me'&&<><label className="d2c-field">{t('apiDomain')}<input required inputMode="url" value={form.shopDomain||''} pattern="[A-Za-z0-9.-]+" placeholder="brand.myshopify.com" onChange={e=>set('shopDomain',e.target.value)}/><small>{t('apiDomainHint')}</small></label><label className="d2c-field">{t('token')}<input type="password" required autoComplete="off" value={form.apiToken||''} onChange={e=>set('apiToken',e.target.value)}/><small>{t('tokenHint')}</small></label></>}{form.type==='import'&&<><label className="d2c-field wide">{t('importData')}<input type="file" accept=".csv,.json,text/csv,application/json" onChange={async e=>{const f=e.target.files[0];set('data','');setInputError('');if(f){if(f.size>8_000_000){setInputError(t('importTooLarge'));return;}set('data',await f.text());set('format',f.name.toLowerCase().endsWith('.csv')?'csv':'json');}}}/><small>{t('importHint')}</small>{inputError&&<span className="d2c-notice error" role="alert">{inputError}</span>}</label><label className="d2c-field">{t('expectedCount')}<input type="number" min="0" value={form.expectedCount??''} onChange={e=>set('expectedCount',e.target.value)}/></label><label className="d2c-check"><input type="checkbox" checked={!!form.declaredComplete} onChange={e=>set('declaredComplete',e.target.checked)}/>{t('declaredComplete')}</label></>}<label className="d2c-check wide"><input type="checkbox" required checked={!!form.authorized} onChange={e=>set('authorized',e.target.checked)}/>{t('permissionCheck')}</label></>; }
function d2cSourceInput(form,shopId,shopUrl) { const settings=form.type==='judge_me'?{shopDomain:(form.shopDomain||'').trim().toLowerCase(),apiToken:form.apiToken}:form.type==='import'?{format:form.format||'json',data:form.data,declaredComplete:!!form.declaredComplete,...(form.expectedCount===''||form.expectedCount==null?{}:{expectedCount:Number(form.expectedCount)})}:{};return {shopId,type:form.type,permissionBasis:form.permissionBasis.trim(),settings}; }
function D2CStartForm({companies,onStart,onClose,t,busy,lang,userRole}) { const [form,setForm]=React.useState({companyId:'',shopUrl:'',productText:'',scanScope:'shop',type:'public_structured',permissionBasis:'',authorized:false,expectedCount:'',mode:'full'}),[error,setError]=React.useState('');const set=(key,value)=>setForm(f=>({...f,[key]:value})); const urls=D2CDBFactory.parseUrls(form.productText);let inferred='';try{if(!form.shopUrl&&urls.length)inferred=new URL(urls[0]).hostname;}catch(_){};async function submit(e){e.preventDefault();setError('');try{if(!form.authorized||!form.permissionBasis.trim())throw new Error(t('permissionRequired'));if(!form.shopUrl&&!urls.length)throw new Error(t('urlRequired'));if(form.scanScope==='selected_products'&&!urls.length)throw new Error(t('productsRequired'));if(form.type==='import'&&!form.data)throw new Error(t('importHint'));const input=D2CDBFactory.normalizeInput({companyId:form.companyId,shopUrl:form.shopUrl||null,productUrls:urls,scanScope:form.scanScope});await onStart(input,form);}catch(err){setError(d2cError(err,t));}} return <form onSubmit={submit} className="d2c-panel"><h2>{t('newAnalysis')}</h2>{error&&<div className="d2c-notice error" role="alert">{error}</div>}<div className="d2c-form-grid"><label className="d2c-field">{t('customer')}<select required value={form.companyId} onChange={e=>set('companyId',e.target.value)}><option value="">{t('chooseCustomer')}</option>{companies.map(c=><option key={c.id} value={c.id}>{c.name}</option>)}</select>{!companies.length&&<small>{t('noCompany')}</small>}</label><label className="d2c-field">{t('shopUrl')}<input type="url" inputMode="url" value={form.shopUrl} onChange={e=>set('shopUrl',e.target.value)} placeholder="https://shop.example"/></label><label className="d2c-field wide">{t('productUrls')}<textarea value={form.productText} onChange={e=>set('productText',e.target.value)} placeholder="https://shop.example/products/…"/><small>{t('productHint')}</small></label>{inferred&&<div className="d2c-notice wide">{t('scopeConfirmed')}: <strong>{inferred}</strong></div>}<fieldset className="d2c-scope-choice wide"><legend>{t('scope')}</legend>{['shop','selected_products'].map(v=><label key={v}><input type="radio" name="d2c-scan-scope" checked={form.scanScope===v} onChange={()=>set('scanScope',v)}/>{t(v==='shop'?'wholeShop':'selectedProducts')}</label>)}</fieldset><D2CSourceFields form={form} set={set} t={t} canManageApi={userRole==='ceo'}/></div><div className="d2c-form-footer"><button type="button" className="d2c-button" onClick={onClose}>{t('cancel')}</button><button type="submit" className="d2c-button primary" disabled={busy||!companies.length}>{busy?t('loading'):t('start')}</button></div></form>; }
function D2CRuleNode({node,onChange,onRemove,t,fact=false}) { const group=node.all?'all':node.any?'any':node.not?'not':null;const leaf={field:'rating',op:'gte',value:4};if(node.factAny)return <div className="d2c-builder-node"><div className="d2c-builder-controls"><strong>{t('factAny')}</strong><input aria-label="facetId" value={node.factAny.facetId} onChange={e=>onChange({factAny:{...node.factAny,facetId:e.target.value}})}/>{onRemove&&<button type="button" className="d2c-button" onClick={onRemove}>{t('remove')}</button>}</div><D2CRuleNode node={node.factAny.expression} fact onChange={v=>onChange({factAny:{...node.factAny,expression:v}})} t={t}/></div>;if(group){const children=group==='not'?[node.not]:node[group];return <div className="d2c-builder-node"><div className="d2c-builder-controls"><select aria-label={t('addGroup')} value={group} onChange={e=>onChange(e.target.value==='not'?{not:children[0]||leaf}:{[e.target.value]:children})}><option value="all">{t('and')}</option><option value="any">{t('or')}</option><option value="not">{t('not')}</option></select>{group!=='not'&&<><button type="button" className="d2c-button" onClick={()=>onChange({[group]:[...children,fact?{field:'subject',op:'eq',value:'reviewer'}:leaf]})}>+ {t('addCondition')}</button><button type="button" className="d2c-button" onClick={()=>onChange({[group]:[...children,{all:[leaf]}]})}>+ {t('addGroup')}</button>{!fact&&<button type="button" className="d2c-button" onClick={()=>onChange({[group]:[...children,{factAny:{facetId:'context.age',expression:{all:[{field:'subject',op:'exists',value:true},{field:'subject',op:'eq',value:'reviewer'},{field:'explicitAge',op:'gte',value:60}]}}}]})}>+ {t('factAny')}</button>}</>}{onRemove&&<button type="button" className="d2c-button" onClick={onRemove}>{t('remove')}</button>}</div>{children.map((child,i)=><D2CRuleNode key={i} node={child} fact={fact} t={t} onChange={v=>{const n=[...children];n[i]=v;onChange(group==='not'?{not:v}:{[group]:n});}} onRemove={group==='not'?undefined:()=>onChange({[group]:children.filter((_,j)=>i!==j)})}/>)}</div>;}const fields=fact?['subject','explicitAge','tagId']:['productId','groupId','rating','reviewDate','language','sourceId','evidenceStatus','reviewStatus','tagId'];const value=node.value;return <div className="d2c-builder-node d2c-builder-controls"><select aria-label="Field" value={node.field} onChange={e=>onChange({field:e.target.value,op:'eq',value:e.target.value==='rating'||e.target.value==='explicitAge'?0:''})}>{fields.map(f=><option key={f}>{f}</option>)}</select><select aria-label="Operator" value={node.op} onChange={e=>onChange({...node,op:e.target.value,value:e.target.value==='in'?[value].flat():e.target.value==='exists'?true:Array.isArray(value)?value[0]:value})}>{['eq','in','gte','lte','exists'].map(op=><option key={op}>{op}</option>)}</select>{node.op==='exists'?<select aria-label="Value" value={String(value)} onChange={e=>onChange({...node,value:e.target.value==='true'})}><option value="true">true</option><option value="false">false</option></select>:<input aria-label="Value" type={['rating','explicitAge'].includes(node.field)&&node.op!=='in'?'number':node.field==='reviewDate'?'date':'text'} value={Array.isArray(value)?value.join(','):value??''} onChange={e=>{const numeric=['rating','explicitAge'].includes(node.field);onChange({...node,value:node.op==='in'?e.target.value.split(',').map(s=>numeric?Number(s.trim()):s.trim()):numeric?Number(e.target.value):e.target.value});}}/>}{onRemove&&<button type="button" className="d2c-button" onClick={onRemove}>{t('remove')}</button>}</div>; }
function D2CAdvancedRule({filter,t,onApply}) { const [draft,setDraft]=React.useState(filter),[json,setJson]=React.useState(JSON.stringify(filter,null,2)),[error,setError]=React.useState('');const apply=rule=>{try{D2CEngine.validateRule(rule);onApply(rule);setError('');}catch(e){setError(e.message);}};return <div className="d2c-panel"><h3>{t('advanced')}</h3><p className="d2c-muted">{t('ruleHint')}</p><D2CRuleNode node={draft.expression} onChange={expression=>{const n={...draft,expression};setDraft(n);setJson(JSON.stringify(n,null,2));}} t={t}/><div className="d2c-actions"><button className="d2c-button primary" onClick={()=>apply(draft)}>{t('applyRule')}</button></div><details><summary className="d2c-rule-summary">{t('jsonRule')}</summary><label className="d2c-field">{t('rule')}<textarea rows="10" value={json} onChange={e=>setJson(e.target.value)}/></label><button className="d2c-button" onClick={()=>{try{const n=JSON.parse(json);apply(n);setDraft(n);}catch(e){setError(e.message);}}}>{t('applyRule')}</button></details>{error&&<div className="d2c-notice error" role="alert">{error}</div>}</div>; }
function D2CCoverage({dashboard,scopeLabel,t,lang}) {const c=dashboard.coverage||{},m=dashboard.metrics||{},physical=d2cPhysicalReviewCount(dashboard);const pdp=c.products||c.pdp||{},rev=c.reviews||{},analysis=c.analysis||{};const checked=pdp.checked??c.checkedProducts??c.manifest?.checked??c.productsChecked,discovered=pdp.discovered??c.discoveredProducts??c.manifest?.discovered??c.productsDiscovered;const loaded=rev.loaded??c.loadedReviews??(c.sources?.length?c.sources.reduce((n,s)=>n+(s.loaded||0),0):null)??c.reviewsLoaded,expected=rev.expected??c.expectedReviews??(c.sources?.length&&c.sources.every(s=>s.expectedCount!=null)?c.sources.reduce((n,s)=>n+s.expectedCount,0):null)??c.reviewsExpected;const analyzed=analysis.extracted??analysis.analyzed??c.extractedReviews??m.analysedTextReviews??m.analyzedTextCount,analyzable=analysis.eligible??analysis.analyzable??c.analyzableReviews??m.analyzableTextReviews??m.textReviewCount;return <div className="d2c-coverage" aria-label={t('coverage')}><div><span>{t('pdpCoverage')}</span><strong>{d2cNumber(checked,lang)} / {d2cNumber(discovered,lang)}</strong><small>{dashboard.scanScope==='selected_products'?t('selected'):t('discovered')}</small></div><div><span>{t('reviewCoverage')}</span><strong>{d2cNumber(loaded,lang)} / {d2cNumber(expected,lang)}</strong><small>{t(c.status||c.reviewStatus||'unknown')}</small><small>{t('physicalReviews')}: {d2cNumber(physical,lang)}</small></div><div><span>{t('analysisCoverage')}</span><strong>{d2cNumber(analyzed,lang)} / {d2cNumber(analyzable,lang)}</strong><small>{t('starsOnly')}: {d2cNumber(m.starOnlyCount??(m.analysisFamilies!=null&&m.analyzableTextReviews!=null?m.analysisFamilies-m.analyzableTextReviews:null),lang)}</small></div><div><span>{t('filteredBase')}</span><strong>{d2cNumber(m.denominator??m.analysedTextReviews??m.analyzedTextCount??m.textReviewCount,lang)}</strong><small>{scopeLabel}</small><small>{t('all')} {t('rating')}: {d2cNumber(m.ratingAll?.mean,lang,1)} / 5 · n={d2cNumber(m.ratingAll?.n,lang)}</small></div></div>; }
function D2CRecommendation({item:canonicalItem,t,lang,inReport,onToggle}) {const item=d2cLocalized(canonicalItem,lang);const action=d2cText(item.action,lang),vp=item.validationPlan;return <article className="d2c-detail-recommendation"><div className="d2c-row"><h3>{item.priority} · {d2cText(item.title,lang)}</h3><D2CStatus value={item.status} t={t}/></div><p>{action}</p><p><strong>{t('why')}: </strong>{d2cText(item.why,lang)}</p><p><strong>{t('deliverable')}: </strong>{d2cText(item.deliverable,lang)}</p><h4>{t('method')}</h4>{item.knowledgeRuleRefs?.length?item.knowledgeRuleRefs.map((r,i)=><div key={i} className="d2c-rule"><strong>{r.methodId}</strong><p>{d2cText(r.passage||r.rulePassage||r.claim,lang)}</p><small>{r.knowledgeVersion} · {r.sourceHash}</small></div>):<p>{t('notEstablished')}</p>}<h4>{t('testPlan')}</h4>{typeof vp==='object'&&vp?<dl>{Object.entries(vp).map(([k,v])=><React.Fragment key={k}><dt><strong>{t(k)}</strong></dt><dd>{Array.isArray(v)?v.map(x=>d2cText(x,lang)).join(' · '):d2cText(v,lang)}</dd></React.Fragment>)}</dl>:<p>{d2cText(vp,lang)}</p>}{item.requiredInputs?.length>0&&<><h4>{t('required')}</h4><ul>{item.requiredInputs.map((x,i)=><li key={i}>{d2cText(x,lang)}</li>)}</ul></>}<label className="d2c-report-selection"><input type="checkbox" checked={inReport} onChange={()=>onToggle(item.recommendationId)}/>{t('reportInclude')}</label></article>; }
function D2CEvidenceItem({item,t,lang,onCorrection,busy}) {const [edit,setEdit]=React.useState(false),[reason,setReason]=React.useState(''),[tag,setTag]=React.useState(item.tagId||item.fact?.tagId||''),[polarity,setPolarity]=React.useState(item.polarity||item.fact?.polarity||'neutral');const quote=item.evidenceQuote||item.quote||item.fact?.evidenceQuote,original=item.originalText||item.text||item.review?.text,href=d2cSafeUrl(item.sourceUrl||item.sourceURL||item.review?.sourceUrl);return <article className="d2c-evidence"><strong>{t('original')}</strong><blockquote>„{quote||original||t('noEvidence')}“</blockquote><div className="d2c-evidence-meta"><span>{item.productTitle||item.product?.title||item.productId||item.review?.productId}</span><span>{item.rating!=null?item.rating+'/5 '+t('rating'):t('unknown')}</span><span>{item.subject||item.fact?.subject||'unknown'}</span><span>{t(item.polarity||item.fact?.polarity||'unknown')}</span><span>{d2cDate(item.reviewDate||item.review?.reviewDate,lang)||t('absentDate')}</span><span>{item.nativeReviewId||item.reviewVersionId}</span></div>{href&&<a href={href} target="_blank" rel="noopener noreferrer">{t('sourceLink')}</a>}{original&&quote&&<details><summary>{t('original')}</summary><p>{original}</p></details>}{(item.factId||item.id||item.evidenceId||item.fact?.factId)&&item.reviewVersionId&&<><button className="d2c-button" onClick={()=>setEdit(v=>!v)} aria-expanded={edit}>{t('correction')}</button>{edit&&<form onSubmit={async e=>{e.preventDefault();try{await onCorrection(item,{tagId:tag,polarity},reason);setEdit(false);}catch(_){}}}><p className="d2c-footnote">{t('overrideHelp')}</p><label className="d2c-field">{t('tag')}<input value={tag} required onChange={e=>setTag(e.target.value)}/></label><label className="d2c-field">{t('polarity')}<select value={polarity} onChange={e=>setPolarity(e.target.value)}>{['positive','neutral','negative','mixed'].map(p=><option key={p} value={p}>{t(p)}</option>)}</select></label><label className="d2c-field">{t('reason')}<textarea required value={reason} onChange={e=>setReason(e.target.value)}/></label><button className="d2c-button primary" disabled={busy||!reason.trim()}>{t('save')}</button></form>}</>}</article>; }
function D2CDeleteDialog({target,t,busy,onClose,onConfirm,error}) {const [ack,setAck]=React.useState(false),[word,setWord]=React.useState('');return <D2CDrawer title={t('deleteTitle')} onClose={onClose} closeLabel={t('close')}>{error&&<div className="d2c-notice error" role="alert">{d2cError(error,t)}</div>}<h2>{t(target.action==='delete_review'?'deleteReview':target.action==='delete_source'?'deleteSource':'purge')}</h2><p>{target.label}</p><p className="d2c-footnote">{target.id}</p><form onSubmit={e=>{e.preventDefault();if(ack&&word==='DELETE')onConfirm();}}><label className="d2c-check"><input required type="checkbox" checked={ack} onChange={e=>setAck(e.target.checked)}/>{t('deleteAck')}</label><label className="d2c-field">{t('deleteWord')}<input value={word} required onChange={e=>setWord(e.target.value)} autoComplete="off"/></label><div className="d2c-form-footer"><button type="button" className="d2c-button" onClick={onClose}>{t('cancel')}</button><button className="d2c-button danger" disabled={busy||!ack||word!=='DELETE'}>{t('deleteTitle')}</button></div></form></D2CDrawer>;}
const D2C_LIMIT_MAXIMUMS={maxBatchReviews:80,maxRuntimeMs:86400000,maxProviderTokens:100000000};
const D2C_DEFAULT_LIMITS={maxRequests:5000,maxProducts:1000,maxReviews:50000,maxBatchReviews:20,maxRuntimeMs:7200000,maxProviderRequests:3000,maxProviderTokens:3000000};
function d2cParseRoute(path) { const parts=String(path||'/d2c').split('?')[0].split('/').filter(Boolean);if(parts.length===1&&parts[0]==='d2c')return{};if(parts[0]!=='d2c'||parts[1]!=='shops'||!(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i).test(parts[2]||''))return{invalid:true};const shopId=parts[2],section=parts[3]||'overview';if(!['overview','products','groups','segments','reviews','runs','settings'].includes(section)||parts.length>5)return{invalid:true};let id;try{id=parts[4]?decodeURIComponent(parts[4]):null;}catch(_){return{invalid:true};}if(id&&(id.length>128||/[\x00-\x1f]/.test(id)))return{invalid:true};return{shopId,section,...(id&&section==='products'?{scope:{type:'product',id}}:id&&section==='groups'?{scope:{type:'group',id}}:id&&section==='segments'?{segmentVersionId:id}:{})}; }
function D2CApp({user,onHome,path,navigate}) {
  const api=window.D2CDB;
  const [lang,setLangState]=React.useState(()=>{try{return localStorage.getItem('mesper.lang')==='en'?'en':'de';}catch(_){return 'de';}});
  const t=k=>D2C_DICT[lang][k]||k;
  const setLang=l=>{setLangState(l);try{localStorage.setItem('mesper.lang',l);}catch(_){};document.documentElement.lang=l;};
  const lastPath=React.useRef(path);const initialRoute=React.useRef(d2cParseRoute(path||window.location.pathname));const initial=React.useRef(new URLSearchParams(window.location.search));if(initialRoute.current.shopId&&!initial.current.get('shop')){initial.current.set('shop',initialRoute.current.shopId);if(initialRoute.current.scope){initial.current.set('scope',initialRoute.current.scope.type);initial.current.set('scopeId',initialRoute.current.scope.id);}}
  const [shops,setShops]=React.useState([]),[companies,setCompanies]=React.useState([]),[shopId,setShopId]=React.useState(initial.current.get('shop')||''),[runs,setRuns]=React.useState([]),[worker,setWorker]=React.useState(null),[views,setViews]=React.useState([]);
  const [navOpen,setNavOpen]=React.useState(false),[reviewRows,setReviewRows]=React.useState({items:[],nextCursor:null,total:0}),[reviewsLoading,setReviewsLoading]=React.useState(false);
  const [tab,setTab]=React.useState(['runs','settings','reviews'].includes(initialRoute.current.section)?initialRoute.current.section:'overview'),[formOpen,setFormOpen]=React.useState(false),[phase,setPhase]=React.useState('loading'),[error,setError]=React.useState(initialRoute.current.invalid?Object.assign(new Error('Invalid D2C route'),{code:'VALIDATION'}):null),[notice,setNotice]=React.useState(''),[busy,setBusy]=React.useState(false);
  const [dashboard,setDashboard]=React.useState(null),[snapshotId,setSnapshotId]=React.useState(initial.current.get('snapshot')||null),[newSnapshot,setNewSnapshot]=React.useState(null),[scope,setScope]=React.useState({type:initial.current.get('scope')||'shop',id:initial.current.get('scopeId')||null});
  const emptyFilter=version=>({schemaVersion:1,taxonomyVersion:version||1,matchUnknown:false,expression:{field:'productId',op:'exists',value:true}});
  const [filter,setFilter]=React.useState(emptyFilter(1)),[simple,setSimple]=React.useState({}),[filtersOpen,setFiltersOpen]=React.useState(false),[advancedOpen,setAdvancedOpen]=React.useState(false),[viewName,setViewName]=React.useState(''),[viewPinned,setViewPinned]=React.useState(true);
  const [detail,setDetail]=React.useState(null),[detailData,setDetailData]=React.useState({items:[],recommendations:[],segments:[],nextCursor:null,dataGaps:[]}),[detailLoading,setDetailLoading]=React.useState(false),[detailError,setDetailError]=React.useState(null),[selectedReport,setSelectedReport]=React.useState([]),[reportData,setReportData]=React.useState(null);
  const [deleteTarget,setDeleteTarget]=React.useState(null),[configForm,setConfigForm]=React.useState({provider:'codex',retentionDays:90,limits:D2C_DEFAULT_LIMITS}),[controlEnabled,setControlEnabled]=React.useState(null),[controlReason,setControlReason]=React.useState('');
  const [sources,setSources]=React.useState([]),[selectedSourceId,setSelectedSourceId]=React.useState(''),[revokeId,setRevokeId]=React.useState('');
  const [sourceForm,setSourceForm]=React.useState({type:'public_structured',permissionBasis:'',authorized:false,expectedCount:''}),[pairName,setPairName]=React.useState(''),[provider,setProvider]=React.useState('codex'),[credential,setCredential]=React.useState(null);
  const request=React.useRef(0),dashboardAbort=React.useRef(null),detailRequest=React.useRef(0),reviewRequest=React.useRef(0),loadedDashboard=React.useRef(null),current=React.useRef({});const [dashboardBinding,setDashboardBinding]=React.useState(null);current.current={shopId,snapshotId,scope,filter,dashboard,reportData};
  const analysisSnapshot=React.useMemo(()=>d2cAnalysisSnapshot(dashboard,shopId),[dashboard,shopId]);
  React.useEffect(()=>()=>{request.current++;dashboardAbort.current?.abort();},[]);
  React.useEffect(()=>{setReportData(null);},[dashboard,scope,filter]);
  React.useEffect(()=>{
    const before=()=>{const state=current.current,loaded=loadedDashboard.current;const show=value=>{const commit=()=>setReportData(value);if(typeof ReactDOM!=='undefined'&&ReactDOM.flushSync)ReactDOM.flushSync(commit);else commit();};
      if(!loaded||loaded.dashboard!==state.dashboard||loaded.binding!==d2cDashboardBinding(state)){show(null);return;}const cached=state.reportData,printScope=state.scope.type==='shop'?{type:'shop',id:state.shopId}:state.scope;
      if(cached?.snapshotId===state.snapshotId&&JSON.stringify(cached.exportScope||cached.scope)===JSON.stringify(printScope)&&JSON.stringify(cached.exportFilter)===JSON.stringify(state.filter))return;
      show(d2cPrintView(state.dashboard,state.shopId,state.scope,state.filter));
    },after=()=>setReportData(null);
    window.addEventListener('beforeprint',before);window.addEventListener('afterprint',after);return()=>{window.removeEventListener('beforeprint',before);window.removeEventListener('afterprint',after);};
  },[]);
  const shop=shops.find(s=>s.id===shopId), scopeLabel=scope.type==='product'?(dashboard?.products||[]).find(p=>p.id===scope.id)?.title||t('products'):scope.type==='group'?(dashboard?.groups||[]).find(g=>g.id===scope.id)?.title||t('scope'):t('allGroups');
  React.useEffect(()=>{setConfigForm({provider:shop?.settings?.provider||'codex',retentionDays:shop?.settings?.retentionDays??90,limits:{...D2C_DEFAULT_LIMITS,...shop?.settings?.limits}});},[shopId,shop?.settings]);
  React.useEffect(()=>{document.documentElement.lang=lang;},[lang]);
  React.useEffect(()=>{let live=true;(async()=>{try{const [list,co]=await Promise.all([api.command('list_shops'),api.companies()]);if(!live)return;setShops(list.items||[]);setControlEnabled(list.enabled??null);setCompanies(co||[]);if(!shopId&&list.items?.length)setShopId(list.items[0].id);setPhase('ready');}catch(e){if(live){setError(e);setPhase('error');}}})();return()=>{live=false;};},[]);
  function syncUrl(next={}) {const state={...current.current,...next};const q=new URLSearchParams();if(state.shopId)q.set('shop',state.shopId);if(state.snapshotId)q.set('snapshot',state.snapshotId);if(state.scope.type!=='shop'){q.set('scope',state.scope.type);q.set('scopeId',state.scope.id);}const route=state.shopId?'/d2c/shops/'+state.shopId+(state.scope.type==='product'?'/products/'+encodeURIComponent(state.scope.id):state.scope.type==='group'?'/groups/'+encodeURIComponent(state.scope.id):''):'/d2c';window.history.replaceState(null,'',route+(q.size?'?'+q.toString():''));}
  async function loadDashboard(options={}) {const state={...current.current,...options};if(!state.shopId)return;const ticket=++request.current;dashboardAbort.current?.abort();const controller=new AbortController();dashboardAbort.current=controller;const startingBinding=d2cDashboardBinding(current.current),requestedBinding=d2cDashboardBinding(state);let enteredRequested=startingBinding===requestedBinding;const valid=()=>{if(controller.signal.aborted||ticket!==request.current||current.current.shopId!==state.shopId)return false;const now=d2cDashboardBinding(current.current);if(now===requestedBinding){enteredRequested=true;return true;}return !enteredRequested&&now===startingBinding;};loadedDashboard.current=null;setDashboardBinding(null);setReportData(null);setError(null);try{const data=await api.command('get_dashboard',{shopId:state.shopId,...(state.snapshotId?{snapshotId:state.snapshotId}:options.runId?{runId:options.runId}:{}),scope:state.scope.type==='shop'?{type:'shop',id:state.shopId}:state.scope,filter:state.filter},{signal:controller.signal,valid});if(!valid())return;const binding=d2cDashboardBinding({...state,snapshotId:data.snapshotId});loadedDashboard.current={dashboard:data,binding};setDashboardBinding(binding);setDashboard(data);setSnapshotId(data.snapshotId);if(data.taxonomyVersion!==state.filter.taxonomyVersion&&state.filter.expression.field==='productId'&&state.filter.expression.op==='exists')setFilter(emptyFilter(data.taxonomyVersion));syncUrl({snapshotId:data.snapshotId,shopId:state.shopId,scope:state.scope});setPhase('ready');if(initialRoute.current.segmentVersionId){const segment=data.segments.find(s=>s.segmentVersionId===initialRoute.current.segmentVersionId);initialRoute.current.segmentVersionId=null;if(segment)setTimeout(()=>openDetail({kind:'segment',item:segment,index:d2cProfileIndex(segment,data),dashboard:data,snapshotId:data.snapshotId}),0);else setError(Object.assign(new Error('Segment not found'),{code:'NOT_FOUND'}));}else if(['products','groups','segments'].includes(initialRoute.current.section)){const anchor=initialRoute.current.section;initialRoute.current.section=null;setTimeout(()=>document.getElementById('d2c-'+anchor)?.scrollIntoView({block:'start'}),0);}}catch(e){if(!valid())return;if(['NO_SNAPSHOT','SNAPSHOT_NOT_FOUND','NOT_FOUND','SNAPSHOT_NOT_READY'].includes(e.code)){setDashboard(null);setPhase('ready');}else {if(!revokedSnapshot(e))setError(e);}}}
  async function loadRuns(id=shopId) {if(!id)return;const [r,v,src]=await Promise.all([api.command('list_runs',{shopId:id}),api.command('list_views',{shopId:id}),api.command('get_sources',{shopId:id})]);if(current.current.shopId!==id)return;setRuns(r.items||[]);setWorker(r.worker||null);setViews(v.items||[]);setSources(src.items||[]);setSelectedSourceId(old=>{const usable=(src.items||[]).filter(s=>s.configured!==false);return usable.some(s=>s.id===old)?old:((r.items||[])[0]?.sourceIds||[]).find(id=>usable.some(s=>s.id===id))||usable[0]?.id||'';});const published=(r.items||[]).find(x=>x.snapshotId&&['completed','partial'].includes(x.state));if(published&&current.current.snapshotId&&published.snapshotId!==current.current.snapshotId)setNewSnapshot(published.snapshotId);if(published&&!current.current.snapshotId&&!current.current.dashboard)loadDashboard({snapshotId:published.snapshotId});}
  React.useEffect(()=>{if(!shopId)return;detailRequest.current++;reviewRequest.current++;setDetail(null);setReviewRows({items:[],nextCursor:null,total:0});loadedDashboard.current=null;setDashboardBinding(null);setDashboard(null);setReportData(null);setDetailData({items:[],recommendations:[],segments:[],nextCursor:null,dataGaps:[]});setDeleteTarget(null);setSourceForm({type:'public_structured',permissionBasis:'',authorized:false,expectedCount:''});setNewSnapshot(null);setSelectedReport([]);setCredential(null);setSelectedSourceId('');setSimple({});const routeScope=initial.current.get('shop')===shopId?{type:initial.current.get('scope')||'shop',id:initial.current.get('scopeId')||shopId}:{type:'shop',id:shopId};setScope(routeScope);setFilter(emptyFilter(1));setSnapshotId(null);setPhase('loading');current.current={...current.current,shopId,snapshotId:null,scope:routeScope,filter:emptyFilter(1),dashboard:null};loadDashboard({shopId,snapshotId:initial.current.get('shop')===shopId?initial.current.get('snapshot'):null,scope:routeScope});initial.current=new URLSearchParams();loadRuns(shopId).catch(setError);const timer=setInterval(()=>loadRuns(shopId).catch(setError),15000);return()=>clearInterval(timer);},[shopId]);
  React.useEffect(()=>{if(shopId&&dashboard)loadDashboard();},[filter,scope]);
  function revokedSnapshot(e) {if(e.code!=='SNAPSHOT_REVOKED')return false;request.current++;detailRequest.current++;reviewRequest.current++;loadedDashboard.current=null;setDashboardBinding(null);setDashboard(null);setReportData(null);setDetail(null);setReviewRows({items:[],nextCursor:null,total:0});setDetailData({items:[],recommendations:[],segments:[],nextCursor:null,dataGaps:[]});setSelectedReport([]);setError(e);return true;}
  async function mutate(action,input) {setBusy(true);setError(null);try{return await api.command(action,input);}catch(e){setError(e);throw e;}finally{setBusy(false);}}
  async function start(input,source) {const normalized=await mutate('upsert_shop',input);const savedSource=await mutate('set_source',d2cSourceInput(source,normalized.shopId,normalized.normalizedShopUrl));await mutate('start_run',{shopId:normalized.shopId,sourceIds:[savedSource.sourceId],mode:'full',scanScope:input.scanScope,productUrls:normalized.canonicalProductUrls||input.productUrls});const list=await api.command('list_shops');setShops(list.items||[]);setShopId(normalized.shopId);setFormOpen(false);setTab('runs');if(normalized.shopId===shopId)await loadRuns();}
  async function runAction(action,run) {try{await mutate(action,{shopId,runId:run.runId||run.id,expectedRevision:run.revision});await loadRuns();}catch(_){};}
  async function rerun(mode='incremental') {try{const previous=runs[0];if(!previous){setFormOpen(true);return;}const chosenSources=selectedSourceId?[selectedSourceId]:(previous.sourceIds||[]);if(!chosenSources.length||chosenSources.some(id=>!sources.some(s=>s.id===id&&s.configured!==false)))throw new Error(t('sourceInvalid'));await mutate('start_run',{shopId,sourceIds:chosenSources,mode,scanScope:previous.scanScope,productUrls:previous.productUrls||[]});setTab('runs');await loadRuns();}catch(e){setError(e);};}
  function changeSimple(key,value) {const values={...simple,[key]:value};setSimple(values);const nodes=[];Object.entries(values).forEach(([k,v])=>{if(!v)return;if(k==='rating')nodes.push({field:'rating',op:v==='low'?'lte':'gte',value:v==='low'?3:4});else if(k==='dateFrom'||k==='dateTo')nodes.push({field:'reviewDate',op:k==='dateFrom'?'gte':'lte',value:v});else if(k==='unknownAge')nodes.push({not:{factAny:{facetId:'context.age',expression:{all:[{field:'subject',op:'exists',value:true},{field:'subject',op:'eq',value:'reviewer'},{field:'explicitAge',op:'exists',value:true}]}}}});else if(k==='language'||k==='sourceId'||k==='evidenceStatus'||k==='reviewStatus')nodes.push({field:k,op:'in',value:v.split(',')});else nodes.push({field:'tagId',op:'in',value:v.split(',')});});setFilter({...emptyFilter(dashboard?.taxonomyVersion),expression:nodes.length?{all:nodes}:{field:'productId',op:'exists',value:true}});}
  async function saveView() {try{await mutate('save_view',{shopId,name:viewName.trim(),scope,filter,runSelection:viewPinned?{kind:'fixed',snapshotId,runId:dashboard.runId}:{kind:'latest'}});setViewName('');setNotice(t('saveSuccess'));await loadRuns();}catch(_){};}
  async function useView(view) {setScope(view.scope||{type:'shop',id:null});setFilter(view.filter||emptyFilter(dashboard?.taxonomyVersion));setSimple({});const bound=view.runSelection?.kind==='fixed'?view.runSelection.snapshotId:null;setSnapshotId(bound||null);await loadDashboard({snapshotId:bound||null,scope:view.scope,filter:view.filter});}
  const tags=dashboard?.taxonomy?.tags||dashboard?.tags||[...new Map((dashboard?.reviews||[]).flatMap(r=>r.facts||[]).map(f=>[f.tagId,{id:f.tagId,facetId:f.facetId,label:f.tagId}])).values()];
  const facets=React.useMemo(()=>{const out={};for(const tag of tags){const key=tag.facetId||tag.facet_id||String(tag.id).split('.')[0];(out[key]||(out[key]=[])).push(tag);}return out;},[dashboard]);
  const applyScope=next=>{setScope(next);syncUrl({scope:next});};
  async function openDetail(value) {const boundSnapshot=value.snapshotId||value.dashboard?.snapshotId||current.current.snapshotId;const boundShop=current.current.shopId;const boundFilter=current.current.filter;const ticket=++detailRequest.current;setDetail(value);setDetailData({items:[],recommendations:[],segments:[],nextCursor:null,dataGaps:[]});setDetailLoading(true);setDetailError(null);try{let activeScope=value.scope||current.current.scope,activeDashboard=value.dashboard||current.current.dashboard;if(value.kind==='scope'){activeScope=value.scope;activeDashboard=await api.command('get_dashboard',{shopId:boundShop,snapshotId:boundSnapshot,scope:activeScope,filter:boundFilter});}
      let selector=value.selector;if(!selector&&value.kind==='segment'){const claim=value.item.claimEvidence?.[0];selector=claim?{kind:'claim',segmentVersionId:value.item.segmentVersionId,claimId:claim.claimId}:{kind:'evidence_ids',evidenceIds:value.item.reviewEvidenceIds||[]};}if(!selector&&value.kind==='scope'){selector={kind:'scope'};}
      const [e,r]=await Promise.all([api.command('get_evidence',{shopId:boundShop,snapshotId:boundSnapshot,scope:activeScope,filter:boundFilter,selector:selector||{kind:'evidence_ids',evidenceIds:[]},cursor:null,pageSize:20}),api.command('get_recommendations',{shopId:boundShop,snapshotId:boundSnapshot,scope:activeScope,...(value.kind==='segment'?{segmentVersionId:value.item.segmentVersionId}:value.kind==='finding'?{findingId:value.item.findingId}:{})})]);if(ticket!==detailRequest.current)return;setDetailData({...e,analysisSnapshot:activeDashboard,snapshotId:boundSnapshot,filter:boundFilter,recommendations:value.kind==='recommendation'?(r.items||[]).filter(x=>x.recommendationId===value.item.recommendationId):r.items||[],scope:activeScope,selector,segments:(activeDashboard.segments||[]).filter(s=>(s.metrics?.n??s.metrics?.count??0)>0),missingInputs:r.missingInputs||[]});}catch(e){if(ticket===detailRequest.current&&!revokedSnapshot(e))setDetailError(e);}finally{if(ticket===detailRequest.current)setDetailLoading(false);}}
  async function moreEvidence() {const bound=detailRequest.current;setDetailLoading(true);try{const e=await api.command('get_evidence',{shopId,snapshotId:detailData.snapshotId,scope:detailData.scope,filter:detailData.filter,selector:detailData.selector,cursor:detailData.nextCursor,pageSize:20});if(bound===detailRequest.current)setDetailData(d=>({...d,items:[...d.items,...e.items],nextCursor:e.nextCursor}));}catch(e){if(!revokedSnapshot(e))setDetailError(e);}finally{setDetailLoading(false);}}
  function closeDetail(){detailRequest.current++;setDetail(null);}
  async function correction(item,correction,reason) {try{const result=await mutate('save_override',{shopId,reviewVersionId:item.reviewVersionId,factId:item.factId||item.fact?.factId||item.evidenceId||item.id,correction:{facetId:item.facetId||item.fact?.facetId,tagId:item.tagId||item.fact?.tagId,subject:item.subject||item.fact?.subject,value:item.value??item.fact?.value,evidenceQuote:item.evidenceQuote||item.fact?.evidenceQuote,...correction},reason,expectedRevision:item.revision||0});setDetailData(d=>({...d,items:d.items.map(e=>e.evidenceId===item.evidenceId?{...e,revision:result.revision}:e)}));setNotice(t(result.rebuildState==='new_run_required'?'correctionNeedsRun':'corrected'));await loadRuns();}catch(e){setDetailError(e);throw e;}}
  const toggleReport=id=>setSelectedReport(old=>old.includes(id)?old.filter(x=>x!==id):[...old,id]);
  function download(content,type,name) {const url=URL.createObjectURL(new Blob([content],{type}));const a=document.createElement('a');a.href=url;a.download=name;document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),1000);}
  async function exportSnapshot(format) {const bound={...current.current},binding=d2cDashboardBinding(bound),mounted=document.querySelector(".d2c-app");const valid=()=>mounted?.isConnected&&loadedDashboard.current?.binding===binding&&loadedDashboard.current?.dashboard===current.current.dashboard&&d2cDashboardBinding(current.current)===binding;if(!valid())return;try{setBusy(true);let result=await api.command('export_snapshot',{shopId:bound.shopId,snapshotId:bound.snapshotId,scope:bound.scope.type==='shop'?{type:'shop',id:bound.shopId}:bound.scope,filter:bound.filter,format});if(!valid())return;if(result.transfer){if(!window.D2CExportClient)throw new Error(t('exportError'));result=await window.D2CExportClient.materialize(result,{command:api.command,valid,expectedBinding:{shopId:bound.shopId,snapshotId:bound.snapshotId,snapshotVersion:bound.dashboard.snapshotVersion,knowledgeVersion:bound.dashboard.knowledgeVersion,scope:bound.scope.type==='shop'?{type:'shop',id:bound.shopId}:bound.scope,filter:bound.filter??null,format}});if(!valid())return;}if(format==='print'){const payload=result.report||result.snapshot||result.data||result.content;if(payload&&typeof payload==='object'){setReportData({...payload,exportScope:payload.exportScope||(bound.scope.type==='shop'?{type:'shop',id:bound.shopId}:bound.scope),exportFilter:payload.exportFilter??bound.filter});await new Promise(r=>setTimeout(r,100));if(valid())window.print();}else if(result.url||result.downloadUrl){window.open(result.url||result.downloadUrl,'_blank','noopener');}else throw new Error(t('exportError'));}else if(result.content!=null)download(typeof result.content==='string'?result.content:JSON.stringify(result.content,null,2),format==='csv'?'text/csv;charset=utf-8':'application/json',`mesper-d2c-${bound.snapshotId}.${format}`);else if(result.url||result.downloadUrl){const r=await fetch(result.url||result.downloadUrl,{credentials:'omit'});if(!r.ok)throw new Error(t('exportError'));const text=await r.text();if(valid())download(text,format==='csv'?'text/csv;charset=utf-8':'application/json',`mesper-d2c-${bound.snapshotId}.${format}`);}else if(result.snapshot||result.data)download(JSON.stringify(result.snapshot||result.data,null,2),'application/json',`mesper-d2c-${bound.snapshotId}.${format}`);else throw new Error(t('exportError'));}catch(e){if(!['EXPORT_CANCELLED','EXPORT_STALE'].includes(e.code)&&valid()&&!revokedSnapshot(e))setError(e);}finally{setBusy(false);}}
  async function copyBrief() {const items=[...new Map([...(dashboard?.recommendations||[]),...detailData.recommendations].map(r=>[r.recommendationId,r])).values()].filter(r=>selectedReport.includes(r.recommendationId)).map(r=>d2cLocalized(r,lang));const body=[t('briefTitle'),`${t('snapshot')}: ${snapshotId}`,`${t('scope')}: ${scopeLabel}`,`Knowledge: ${dashboard.knowledgeVersion}`, ...items.map(r=>`${r.priority} · ${d2cText(d2cField(r,'title',lang),lang)}\n${d2cText(d2cField(r,'action',lang),lang)}\n${t('why')}: ${d2cText(d2cField(r,'why',lang),lang)}\n${t('testPlan')}: ${typeof r.validationPlan==='object'?JSON.stringify(r.validationPlan):d2cText(r.validationPlan,lang)}\nEvidence: ${(r.reviewEvidenceIds||[]).join(', ')}\nMethod: ${(r.knowledgeRuleRefs||[]).map(k=>k.methodId+'@'+k.knowledgeVersion).join(', ')}`)].join('\n\n');try{await navigator.clipboard.writeText(body);setNotice(t('copied'));}catch(e){download(body,'text/plain;charset=utf-8',`mesper-briefing-${snapshotId}.txt`);}}
  const contextRows=React.useMemo(()=>{if(detail?.kind!=='segment')return[];const active=detailData.analysisSnapshot||detail.dashboard||dashboard;if(!active)return[];return D2CEngine.aggregateSnapshot(d2cAnalysisSnapshot(active,shopId),{scope:detail.scope||detailData.scope||scope,segmentVersionId:detail.item.segmentVersionId,filter:detailData.filter||filter}).reviews;},[detail,detailData.analysisSnapshot,detailData.scope,detailData.filter,dashboard,shopId,scope,filter]);const contextFacts=contextRows.flatMap(row=>row._members||[row]).flatMap(row=>row.facts||[]);const ageFacts=contextFacts.filter(f=>f.facetId==='context.age');const interestFacts=contextFacts.filter(f=>f.facetId?.startsWith('interest'));
  const metrics=dashboard?.metrics||{},segments=(dashboard?.segments||[]).filter(s=>(s.metrics?.n??s.metrics?.count??0)>0),products=dashboard?.products||[],groups=dashboard?.groups||[],running=runs.find(r=>['queued','running','blocked','paused'].includes(r.state));
  const dashboardReady=!!dashboard&&dashboardBinding===d2cDashboardBinding(current.current)&&loadedDashboard.current?.dashboard===dashboard;
  const productAnalysis=React.useMemo(()=>analysisSnapshot&&(Array.isArray(dashboard.sourceReviews)||Array.isArray(dashboard.reviews))?d2cProductAnalysis(analysisSnapshot,dashboard.products,filter):new Map(),[analysisSnapshot,filter]);
  async function confirmDelete() {const target=deleteTarget;try{await mutate(target.action,{shopId,...(target.action==='delete_review'?{reviewVersionId:target.id}:target.action==='delete_source'?{sourceId:target.id}:{})});setDeleteTarget(null);revokedSnapshot({code:'SNAPSHOT_REVOKED',message:t('outOfDate')});setSnapshotId(null);setNewSnapshot(null);syncUrl({snapshotId:null});setNotice(t('deleted'));if(target.action==='purge_shop'){const result=await api.command('list_shops');setShops(result.items||[]);setShopId('');syncUrl({shopId:'',snapshotId:null,scope:{type:'shop',id:null}});setError(null);}else await loadRuns();}catch(_){};}
  async function loadReviews(cursor=null) {if(!snapshotId)return;const ticket=++reviewRequest.current,boundSnapshot=snapshotId,boundShop=shopId;setReviewsLoading(true);try{const result=await api.command('get_reviews',{shopId,snapshotId,scope,filter,cursor,pageSize:25});if(ticket!==reviewRequest.current||current.current.snapshotId!==boundSnapshot||current.current.shopId!==boundShop)return;setReviewRows(old=>({...result,items:cursor?[...old.items,...result.items]:result.items}));}catch(e){if(!revokedSnapshot(e))setError(e);}finally{if(ticket===reviewRequest.current)setReviewsLoading(false);}}
  function goSection(section) {setNavOpen(false);setFormOpen(false);const view=['runs','settings','reviews'].includes(section)?section:'overview';setTab(view);const q=new URLSearchParams();if(snapshotId)q.set('snapshot',snapshotId);window.history.replaceState(null,'','/d2c/shops/'+shopId+(section==='overview'?'':'/'+section)+(q.size?'?'+q.toString():''));if(['products','groups','segments'].includes(section))setTimeout(()=>document.getElementById('d2c-'+section)?.scrollIntoView({block:'start'}),0);}
  React.useEffect(()=>{if(path===lastPath.current)return;lastPath.current=path;const route=d2cParseRoute(path);if(route.invalid){setError(Object.assign(new Error('Invalid D2C route'),{code:'VALIDATION'}));return;}if(route.shopId&&route.shopId!==current.current.shopId){initialRoute.current=route;initial.current=new URLSearchParams(window.location.search);initial.current.set('shop',route.shopId);if(route.scope){initial.current.set('scope',route.scope.type);initial.current.set('scopeId',route.scope.id);}setShopId(route.shopId);}else if(route.scope)setScope(route.scope);const section=route.section||'overview';setTab(['reviews','runs','settings'].includes(section)?section:'overview');if(route.segmentVersionId){const item=current.current.dashboard?.segments?.find(s=>s.segmentVersionId===route.segmentVersionId);if(item)openDetail({kind:'segment',item,index:d2cProfileIndex(item,current.current.dashboard)});else initialRoute.current.segmentVersionId=route.segmentVersionId;}if(['products','groups','segments'].includes(section))setTimeout(()=>document.getElementById('d2c-'+section)?.scrollIntoView({block:'start'}),0);},[path]);
  React.useEffect(()=>{if(tab==='reviews'&&snapshotId){setReviewRows({items:[],nextCursor:null,total:0});loadReviews();}},[tab,snapshotId,scope,filter]);
  const sidebar=<><label htmlFor="d2c-shop-select">{t('shop')}</label><select id="d2c-shop-select" value={shopId} onChange={e=>{setShopId(e.target.value);setNavOpen(false);}}><option value="">{t('noShop')}</option>{shops.map(s=><option key={s.id} value={s.id}>{companies.find(c=>c.id===s.companyId)?.name||s.domain} · {s.domain}</option>)}</select><nav aria-label={t('title')}>{['overview','products','groups','segments','reviews','runs','settings'].map(v=><button key={v} aria-current={tab===v?'page':undefined} onClick={()=>goSection(v)}>{t(v)}</button>)}<button onClick={()=>{setFormOpen(true);setTab('overview');setNavOpen(false);}}>{t('newShop')}</button></nav>{shop&&<p className="d2c-footnote">{companies.find(c=>c.id===shop.companyId)?.name}<br/>{shop.domain}<br/><a href={'/crm/c/'+shop.companyId} onClick={e=>{if(navigate){e.preventDefault();navigate('/crm/c/'+shop.companyId);}}}>{t('customer')}</a></p>}</>;
  if(user?.isGuest||!['ceo','employee'].includes(user?.role))return <div className="d2c-app"><div className="d2c-empty"><h1>{t('roleDenied')}</h1><button className="d2c-button" onClick={onHome}>{t('back')}</button></div></div>;
  return <div className="d2c-app"><header className="d2c-header" inert={detail||navOpen||deleteTarget?'':undefined}><button type="button" className="d2c-button d2c-nav-toggle" onClick={()=>setNavOpen(true)} aria-label={lang==='de'?'Navigation öffnen':'Open navigation'} aria-expanded={navOpen}><Icon name="menu" size={20}/></button><button className="d2c-home" onClick={onHome} title={t('back')}><Icon name="arrowLeft" size={18}/><BrandLockup height={22} sub="D2C"/></button><div className="d2c-header-actions">{running&&<span className="d2c-header-status"><D2CStatus value={running.state} t={t}/></span>}<button className="d2c-language" onClick={()=>setLang(lang==='de'?'en':'de')} aria-label={lang==='de'?'Switch to English':'Auf Deutsch wechseln'}>{lang.toUpperCase()}</button>{typeof NotificationBell==='function'&&<NotificationBell navigate={navigate}/>}<AccountMenu user={user} onLogout={()=>MesperDB.signOut()}/></div></header><div className="d2c-layout" inert={detail||navOpen||deleteTarget?'':undefined}><aside className="d2c-sidebar d2c-desktop-sidebar">{sidebar}</aside><main className="d2c-main"><span className="d2c-sr-only" role="status" aria-live="polite">{running?t(running.state)+' · '+t(running.stage):''}</span><div className="d2c-title-row"><div><h1>{t('title')}{scope.type!=='shop'?' · '+scopeLabel:''}</h1>{dashboard&&<p className="d2c-subtitle">{t('snapshot')} {dashboard.snapshotVersion} · {d2cDate(dashboard.createdAt||dashboard.publishedAt,lang)} · {t('knowledge')}: {dashboard.knowledgeVersion}</p>}</div><div className="d2c-actions">{shopId&&!formOpen&&<button className="d2c-button primary" onClick={()=>rerun()} disabled={busy}>{t('startAgain')}</button>}</div></div>{error&&<div className="d2c-notice error" role="alert"><span>{d2cError(error,t)} <small>({error.code||'ERROR'})</small></span><button className="d2c-button" onClick={()=>{setError(null);if(shopId){loadDashboard();loadRuns().catch(setError);}else window.location.reload();}}>{t('retry')}</button></div>}{notice&&<div className="d2c-notice success" role="status">{notice}<button className="d2c-button" onClick={()=>setNotice('')}>{t('close')}</button></div>}{user.role==='ceo'&&controlEnabled===false&&<section className="d2c-notice warning"><form onSubmit={async e=>{e.preventDefault();try{const result=await mutate('set_control',{enabled:true,reason:controlReason.trim()});setControlEnabled(result.enabled);setNotice(t('controlSaved'));}catch(_){}}}><strong>{t('globalControl')}: {t('disabled')}</strong><label className="d2c-field">{t('controlReason')}<input required value={controlReason} onChange={e=>setControlReason(e.target.value)}/></label><button className="d2c-button primary" disabled={busy||!controlReason.trim()}>{t('enable')}</button></form></section>}{phase==='loading'&&!formOpen&&<p className="d2c-loading" role="status">{t('loading')}</p>}{formOpen?<D2CStartForm companies={companies} onStart={start} onClose={()=>setFormOpen(false)} t={t} busy={busy} lang={lang} userRole={user.role}/>:!shopId&&phase!=='loading'?<div className="d2c-empty"><h2>{t('noShop')}</h2><p>{t('noShopText')}</p><button className="d2c-button primary" onClick={()=>setFormOpen(true)}>{t('newAnalysis')}</button></div>:<>
  {tab==='overview'&&<>{running&&worker&&!worker.online&&<div className="d2c-notice warning" role="status">{t('workerOffline')}<button className="d2c-button" onClick={()=>setTab('runs')}>{t('runs')}</button></div>}{newSnapshot&&<div className="d2c-notice"><span>{t('newSnapshot')}</span><button className="d2c-button primary" onClick={()=>{setSnapshotId(newSnapshot);loadDashboard({snapshotId:newSnapshot});setNewSnapshot(null);setSelectedReport([]);}}>{t('useSnapshot')}</button></div>}{dashboard&&running&&<div className="d2c-notice" role="status">{t('oldSnapshot')} <D2CStatus value={running.state} t={t}/></div>}{!dashboard&&phase!=='loading'?<div className="d2c-empty"><h2>{t('noSnapshot')}</h2><p>{t('noSnapshotText')}</p><button className="d2c-button" onClick={()=>setTab('runs')}>{t('runs')}</button></div>:dashboard&&<>
    <div className="d2c-row" style={{marginBottom:16}}><span className="d2c-chip">{t('runScope')}: {t(dashboard.scanScope==='selected_products'?'selectedProducts':'wholeShop')}</span><div className="d2c-actions">{['csv','json','print'].map(f=><button key={f} className="d2c-button" disabled={busy||!dashboardReady} onClick={()=>exportSnapshot(f)}>{t(f)}</button>)}</div></div>
    <D2CCoverage dashboard={dashboard} scopeLabel={scopeLabel} t={t} lang={lang}/>
    {(dashboard.warnings||[]).map((w,i)=><div key={i} className="d2c-notice warning">{d2cText(w.message||w,lang)}</div>)}
    <div className="d2c-filters"><label className="d2c-field">{t('scope')}<select value={scope.type==='shop'?'shop':scope.type+':'+scope.id} onChange={e=>{const [type,id]=e.target.value.split(':');applyScope({type,id:id||null});}}><option value="shop">{t('allGroups')}</option>{groups.map(g=><option key={g.id} value={'group:'+g.id}>{g.title||g.name}</option>)}{(dashboard.catalogProducts||products).map(p=><option key={p.id} value={'product:'+p.id}>{p.title}</option>)}</select></label><label className="d2c-field">{t('selectRun')}<select value={dashboard.runId} onChange={e=>{const r=runs.find(x=>(x.runId||x.id)===e.target.value);if(r?.snapshotId){setSnapshotId(r.snapshotId);loadDashboard({snapshotId:r.snapshotId});setSelectedReport([]);}}}>{runs.filter(r=>r.snapshotId).map(r=><option key={r.runId||r.id} value={r.runId||r.id}>{d2cDate(r.createdAt,lang)} · {t(r.state)}</option>)}</select></label><button className="d2c-button" onClick={()=>setFiltersOpen(v=>!v)} aria-expanded={filtersOpen}>{t('filter')}</button><button className="d2c-button" onClick={()=>{setFilter(emptyFilter(dashboard.taxonomyVersion));setSimple({});}}>{t('reset')}</button>{filtersOpen&&<div className="d2c-filters-expanded"><label className="d2c-field">{t('rating')}<select value={simple.rating||''} onChange={e=>changeSimple('rating',e.target.value)}><option value="">{t('all')}</option><option value="low">1–3</option><option value="high">4–5</option></select></label><label className="d2c-field">{t('language')}<input value={simple.language||''} onChange={e=>changeSimple('language',e.target.value)} placeholder="de,en"/></label><label className="d2c-field">{t('source')}<select value={simple.sourceId||''} onChange={e=>changeSimple('sourceId',e.target.value)}><option value="">{t('all')}</option>{(dashboard.sources||[]).map(s=><option key={s.id} value={s.id}>{s.provider||s.type||s.id}</option>)}</select></label>{['dateFrom','dateTo'].map(k=><label key={k} className="d2c-field">{t(k)}<input type="date" value={simple[k]||''} onChange={e=>changeSimple(k,e.target.value)}/></label>)}{Object.entries(facets).map(([facet,options])=><label className="d2c-field" key={facet}>{t(facet.split('.')[0])}<select multiple size="3" value={(simple[facet]||'').split(',').filter(Boolean)} onChange={e=>changeSimple(facet,[...e.target.selectedOptions].map(o=>o.value).join(','))}>{options.map(tag=><option key={tag.id||tag.tagId} value={tag.id||tag.tagId}>{d2cText(tag.label||tag.id||tag.tagId,lang)}</option>)}</select></label>)}<label className="d2c-check"><input type="checkbox" checked={!!simple.unknownAge} onChange={e=>changeSimple('unknownAge',e.target.checked?'true':'')}/>{t('unknownAge')}</label><button className="d2c-button" onClick={()=>setAdvancedOpen(v=>!v)} aria-expanded={advancedOpen}>{t('advanced')}</button></div>}</div>
    <div className="d2c-filter-rule">{filter.expression.field==='productId'&&filter.expression.op==='exists'?t('all')+' '+t('evidence'):D2CEngine.explainRule(filter,lang)}</div>{advancedOpen&&<D2CAdvancedRule key={dashboard.snapshotId+filter.taxonomyVersion} filter={filter} t={t} onApply={rule=>{setFilter(rule);setSimple({});}}/>}
    <details className="d2c-panel"><summary className="d2c-rule-summary">{t('savedViews')}</summary><div className="d2c-saved-row">{views.map(v=><button key={v.viewId||v.id} className="d2c-button" onClick={()=>useView(v)}>{v.name}</button>)}</div><div className="d2c-saved-row"><label className="d2c-field">{t('viewName')}<input value={viewName} onChange={e=>setViewName(e.target.value)}/></label><label className="d2c-field">{t('snapshot')}<select value={viewPinned?'fixed':'latest'} onChange={e=>setViewPinned(e.target.value==='fixed')}><option value="fixed">{t('pinned')}</option><option value="latest">{t('latest')}</option></select></label><button className="d2c-button" disabled={!viewName.trim()||busy} onClick={saveView}>{t('saveView')}</button></div></details>
    <div id="d2c-segments" className="d2c-section-heading"><h2>{t('profiles')}</h2></div><p className="d2c-muted">{t('profileHint')}</p>{segments.length?<div className="d2c-profile-grid">{segments.map((s,i)=>{const toneIndex=d2cProfileIndex(s,dashboard);const m=s.metrics||{},share=m.share??s.share,n=m.count??m.n??s.count,denominator=m.denominator??metrics.denominator??metrics.analysedTextReviews??metrics.analyzedTextCount;return <button key={s.segmentVersionId} className="d2c-profile" style={{'--profile-tone':D2C_TONES[toneIndex%D2C_TONES.length]}} onClick={()=>openDetail({kind:'segment',item:s,index:toneIndex})} aria-label={d2cText(d2cField(s,'label',lang),lang)+' · '+d2cNumber(n,lang)+' '+t('evidence')}><div className="d2c-profile-art"><D2CSymbol index={toneIndex}/></div><div className="d2c-profile-body"><h3>{d2cText(d2cField(s,'label',lang),lang)}</h3><p className="d2c-profile-description">{d2cText(d2cField(s,'description',lang),lang)}</p><div className="d2c-profile-stat">{d2cNumber(share==null?null:share*100,lang,1)}<small>%</small></div><div className="d2c-profile-denominator">{t('reviewShare')} · n={d2cNumber(n,lang)} / {d2cNumber(denominator,lang)}</div><div className="d2c-profile-base"><D2CStatus value={m.evidenceStatus||s.evidenceStatus||'insufficient'} t={t}/><span>{(m.rating?.mean??m.meanRating)==null?t('unknown'):d2cNumber(m.rating?.mean??m.meanRating,lang,1)+' / 5 · n='+d2cNumber(m.rating?.n,lang)}</span></div><div className="d2c-profile-link">{t('details')}<span aria-hidden="true">→</span></div></div></button>;})}</div>:<div className="d2c-empty" style={{marginTop:16}}><h2>{Object.values(simple).some(Boolean)?t('noMatches'):metrics.analysedTextReviews>0?t('noProfiles'):t('noReviews')}</h2><p>{Object.values(simple).some(Boolean)?t('noMatchesText'):t('noReviewsText')}</p></div>}<p className="d2c-footnote">{t('footnote')}</p>
    {groups.length>0&&<><div id="d2c-groups" className="d2c-section-heading"><h2>{t('groups')}</h2></div><div className="d2c-actions">{groups.map(g=><button className="d2c-button" key={g.id} onClick={()=>openDetail({kind:'scope',scope:{type:'group',id:g.id},title:g.title||g.name,item:g})}>{g.parentId&&groups.find(x=>x.id===g.parentId)?(groups.find(x=>x.id===g.parentId).title||groups.find(x=>x.id===g.parentId).name)+' / ':''}{g.title||g.name} · {(dashboard.catalogProducts||products).filter(p=>p.groupIds?.includes(g.id)).length} {t('products')}</button>)}</div></>}<div id="d2c-products" className="d2c-section-heading"><h2>{t('productFit')}</h2></div><div className="d2c-product-grid">{products.map((p,i)=><button key={p.id} className="d2c-product" onClick={()=>openDetail({kind:'scope',scope:{type:'product',id:p.id},title:p.title,item:p})}>{d2cSafeUrl(p.imageUrl||p.metadata?.images?.[0]?.url||p.metadata?.images?.[0])?<img src={d2cSafeUrl(p.imageUrl||p.metadata?.images?.[0]?.url||p.metadata?.images?.[0])} alt="" loading="lazy"/>:<D2CSymbol index={i+3}/>}<div><strong>{p.title}</strong><small>{(productAnalysis.get(p.id)?.metrics.analysedTextReviews??p.metrics?.analyzedTextCount)!=null?d2cNumber(productAnalysis.get(p.id)?.metrics.analysedTextReviews??p.metrics?.analyzedTextCount,lang)+' '+t('evidence'):(p.groupIds?.length?p.groupIds.map(id=>groups.find(g=>g.id===id)?.title||id).join(' · '):t('unassigned'))}</small></div><span aria-hidden="true">→</span></button>)}</div>
    <div className="d2c-section-heading"><h2>{t('findings')}</h2></div><div className="d2c-findings">{(dashboard.findings||[]).map(f=><button className="d2c-finding" key={f.findingId} onClick={()=>openDetail({kind:'finding',item:f,selector:{kind:'finding',findingId:f.findingId}})}><span className="d2c-priority">{dashboard.recommendations?.find(r=>r.findingIds?.includes(f.findingId))?.priority||t('evidence')}</span><div><h3>{d2cText(d2cField(f,'title',lang),lang)}</h3><p>{d2cText(d2cField(f,'claim',lang),lang)}</p><small>{f.reviewEvidenceIds?.length||0} {t('evidence')} · {(f.scope?.type||'shop')}{f.dataGaps?.length?' · '+t('warnings'):''}</small></div><span aria-hidden="true">→</span></button>)}{!dashboard.findings?.length&&<p className="d2c-muted">{t('noFindings')}</p>}</div>
    {products.length>0&&segments.length>0&&<><div className="d2c-section-heading"><h2>{t('matrix')}</h2></div><div className="d2c-table-wrap"><table className="d2c-table"><caption>{t('matrixHint')}</caption><thead><tr><th scope="col">{t('products')}</th>{segments.map((s,i)=><th key={s.segmentVersionId} scope="col" style={{color:D2C_TONES[d2cProfileIndex(s,dashboard)%D2C_TONES.length]}}>{d2cText(d2cField(s,'label',lang),lang)}</th>)}</tr></thead><tbody>{products.map(p=><tr key={p.id}><th scope="row"><button className="d2c-button" onClick={()=>openDetail({kind:'scope',scope:{type:'product',id:p.id},title:p.title,item:p})}>{p.title}</button></th>{segments.map(s=>{const fit=productAnalysis.get(p.id)?.segments.get(s.segmentVersionId)||s.metrics?.products?.find?.(x=>x.productId===p.id)||s.productMetrics?.find?.(x=>x.productId===p.id)||s.metrics?.productMetrics?.[p.id];return <td key={s.segmentVersionId}>{fit?d2cNumber(fit.count??fit.n,lang)+' / '+d2cNumber(fit.denominator,lang):t('unknown')}</td>;})}</tr>)}</tbody></table></div></>}
    {dashboard.recommendations?.length>0&&<><div className="d2c-section-heading"><h2>{t('recommendations')}</h2></div><div className="d2c-findings">{dashboard.recommendations.map(r=><button key={r.recommendationId} className="d2c-finding" onClick={()=>openDetail({kind:'recommendation',item:r,selector:{kind:'recommendation',recommendationId:r.recommendationId}})}><span className="d2c-priority">{r.priority}</span><div><h3>{d2cText(d2cField(r,'title',lang),lang)}</h3><p>{d2cText(d2cField(r,'action',lang),lang)}</p><small>{t(r.status)} · {t(r.origin==='mesper_knowledge'?'method':'hypothesis')}</small></div></button>)}</div></>}
    {selectedReport.length>0&&<div className="d2c-panel" style={{marginTop:20}}><div className="d2c-row"><h3>{t('selectedReport')} · {selectedReport.length}</h3><button className="d2c-button primary" onClick={copyBrief}>{t('copyBrief')}</button></div><p className="d2c-footnote">{t('noTask')}</p></div>}
  </>}</>}
  {tab==='reviews'&&<section className="d2c-panel"><div className="d2c-row"><h2>{t('reviews')} · {d2cNumber(reviewRows.total,lang)}</h2><button className="d2c-button" disabled={reviewsLoading||!snapshotId} onClick={()=>loadReviews()}>{t('refresh')}</button></div><p className="d2c-footnote">{t('snapshot')}: {snapshotId||t('unknown')} · {scopeLabel}. {t('noReviewsText')}</p>{reviewsLoading&&!reviewRows.items.length&&<p role="status">{t('loading')}</p>}{!reviewsLoading&&!reviewRows.items.length&&<p>{t('noMatches')}</p>}{reviewRows.items.length>0&&<div className="d2c-table-wrap"><table className="d2c-table"><thead><tr><th scope="col">{t('products')}</th><th scope="col">{t('rating')}</th><th scope="col">{t('reviewText')}</th><th scope="col">{t('reviewDate')}</th><th scope="col">{t('status')}</th><th scope="col">{t('source')}</th>{user.role==='ceo'&&<th scope="col">{t('deleteReview')}</th>}</tr></thead><tbody>{reviewRows.items.map(r=><tr key={r.reviewVersionId}><th scope="row">{(dashboard?.catalogProducts||products).find(p=>p.id===r.productId)?.title||r.productId}</th><td>{r.rating==null?t('unknown'):r.rating+'/5'}</td><td className="d2c-review-text">{r.text||t('starsOnly')}{r.reason&&<p className="d2c-footnote">{r.reason}</p>}</td><td>{d2cDate(r.reviewDate,lang)||t('absentDate')}</td><td>{t(r.status||'unknown')}</td><td>{d2cSafeUrl(r.sourceUrl)?<a target="_blank" rel="noopener noreferrer" href={d2cSafeUrl(r.sourceUrl)}>{r.sourceId}</a>:r.sourceId}<br/>{r.nativeReviewId}</td>{user.role==='ceo'&&<td><button className="d2c-button danger" onClick={()=>setDeleteTarget({action:'delete_review',id:r.reviewVersionId,label:r.text||t('starsOnly')})}>{t('deleteReview')}</button></td>}</tr>)}</tbody></table></div>}{reviewRows.nextCursor&&<button className="d2c-button" disabled={reviewsLoading} onClick={()=>loadReviews(reviewRows.nextCursor)}>{t('moreEvidence')}</button>}</section>}
  {tab==='runs'&&<><div className={'d2c-notice '+(worker?.online?'success':'warning')} role="status">{worker?.online?t('workerOnline'):worker?t('workerOffline'):t('workerUnknown')}{worker?.lastSeenAt&&<span> · {d2cDate(worker.lastSeenAt,lang)}</span>}</div><section className="d2c-panel"><div className="d2c-row" style={{marginBottom:20}}><h2>{t('runs')}</h2><button className="d2c-button" onClick={()=>loadRuns().catch(setError)}>{t('refresh')}</button></div>{!runs.length&&<p>{t('noRuns')}</p>}{runs.map(r=><article className="d2c-run" key={r.runId||r.id}><div><div className="d2c-row"><strong>{d2cDate(r.createdAt,lang)}</strong><D2CStatus value={r.state} t={t}/></div><p className="d2c-run-meta">{t('runScope')}: {t(r.scanScope==='selected_products'?'selectedProducts':'wholeShop')} · {r.provider||provider}<br/>{t('runId')}: {r.runId||r.id}<br/>{t('knowledge')}: {r.knowledgeVersion||t('unknown')}</p><ol className="d2c-run-stages" aria-label={t('runs')}>{D2C_STAGES.map(s=><li key={s} className={r.stage===s?'active':''} aria-current={r.stage===s?'step':undefined}>{t(s==='recommendations'?'recommendationsStage':s==='snapshot'?'snapshotStage':s==='reviews'?'reviewStage':s)}</li>)}</ol>{r.counts&&<p className="d2c-run-meta">{Object.entries(r.counts).map(([k,v])=><span key={k} style={{marginRight:10}}>{t(k)}: {typeof v==='object'?JSON.stringify(v):d2cNumber(v,lang)}</span>)}</p>}{r.error&&<div className="d2c-notice warning">{r.error.code?d2cError(r.error,t):d2cText(r.error.message||r.error,lang)}{r.error.code&&<small> ({r.error.code})</small>}</div>}</div><div className="d2c-actions">{['queued','running'].includes(r.state)&&<button className="d2c-button" disabled={busy} onClick={()=>runAction('pause_run',r)}>{t('pause')}</button>}{['paused','blocked','failed'].includes(r.state)&&<button className="d2c-button primary" disabled={busy||!d2cRunCanResume(r)} title={!d2cRunCanResume(r)?t('providerUsageUnknown'):undefined} onClick={()=>runAction('resume_run',r)}>{t('resume')}</button>}{['queued','running','paused','blocked'].includes(r.state)&&<button className="d2c-button danger" disabled={busy} onClick={()=>runAction('cancel_run',r)}>{t('cancelRun')}</button>}{r.snapshotId&&<button className="d2c-button" onClick={()=>{setSnapshotId(r.snapshotId);loadDashboard({snapshotId:r.snapshotId});setTab('overview');}}>{t('showResult')}</button>}</div></article>)}</section></>}
  {tab==='settings'&&<>{user.role==='ceo'&&<><section className="d2c-panel"><h2>{t('budget')}</h2><p className="d2c-footnote">{t('budgetHint')}</p><form onSubmit={async e=>{e.preventDefault();try{const updated=await mutate('configure_shop',{shopId,settings:configForm});setShops(old=>old.map(s=>s.id===shopId?{...s,...updated}:s));setNotice(t('configSaved'));}catch(_){}}}><div className="d2c-form-grid"><label className="d2c-field">{t('provider')}<select value={configForm.provider} onChange={e=>setConfigForm(f=>({...f,provider:e.target.value}))}><option value="codex">Codex</option><option value="claude">Claude Code</option></select></label><label className="d2c-field">{t('retention')}<input type="number" required min="1" max="365" value={configForm.retentionDays} onChange={e=>setConfigForm(f=>({...f,retentionDays:Number(e.target.value)}))}/></label>{Object.keys(D2C_DEFAULT_LIMITS).map(key=><label key={key} className="d2c-field">{t(key)}<input required type="number" min="1" max={D2C_LIMIT_MAXIMUMS[key]||10000000} step="1" value={configForm.limits[key]} onChange={e=>setConfigForm(f=>({...f,limits:{...f.limits,[key]:Number(e.target.value)}}))}/></label>)}</div><div className="d2c-form-footer"><button className="d2c-button primary" disabled={busy}>{t('save')}</button></div></form></section><section className="d2c-panel"><h2>{t('globalControl')} · {controlEnabled===null?t('unknown'):t(controlEnabled?'enabled':'disabled')}</h2><form onSubmit={async e=>{e.preventDefault();try{const result=await mutate('set_control',{enabled:!controlEnabled,reason:controlReason.trim()});setControlEnabled(result.enabled);setNotice(t('controlSaved'));await loadRuns();}catch(_){}}}><label className="d2c-field">{t('controlReason')}<input required value={controlReason} onChange={e=>setControlReason(e.target.value)}/></label><button className={'d2c-button '+(controlEnabled?'danger':'primary')} disabled={busy||!controlReason.trim()}>{t(controlEnabled?'disable':'enable')}</button></form><button className="d2c-button danger" onClick={()=>setDeleteTarget({action:'purge_shop',id:shopId,label:shop?.domain})}>{t('purge')}</button></section></>}
<section className="d2c-panel"><h2>{t('source')}</h2><p className="d2c-footnote">{t('sourceHint')}</p>{sources.length>0&&<label className="d2c-field">{t('chooseSource')}<select value={selectedSourceId} onChange={e=>setSelectedSourceId(e.target.value)}>{sources.map(src=><option key={src.id} value={src.id} disabled={src.configured===false}>{t(src.type==='judge_me'?'api':src.type==='import'?'import':'public')} · {src.permissionBasis}{src.configured===false?' · '+t('sourceInvalid'):''}</option>)}</select></label>}{sources.map(src=><div key={src.sourceId||src.id} className="d2c-notice"><div><strong>{src.provider||src.type||src.mode}</strong><p>{src.permissionBasis||src.permission_basis}</p><small>{d2cDate(src.lastVerifiedAt||src.updatedAt,lang)}</small>{src.configured===false&&<p>{t('sourceInvalid')}</p>}</div>{user.role==='ceo'&&<button className="d2c-button danger" onClick={()=>setDeleteTarget({action:'delete_source',id:src.id,label:src.permissionBasis||src.type})}>{t('deleteSource')}</button>}</div>)}<form onSubmit={async e=>{e.preventDefault();if(!sourceForm.authorized||!sourceForm.permissionBasis.trim()){setError(new Error(t('permissionRequired')));return;}try{const savedSource=await mutate('set_source',d2cSourceInput(sourceForm,shopId,shop.shopUrl||'https://'+shop.domain));setSelectedSourceId(savedSource.sourceId);await loadRuns();setSourceForm({...sourceForm,apiToken:'',data:''});setNotice(t('sourceSaved'));}catch(_){}}}><div className="d2c-form-grid"><D2CSourceFields form={sourceForm} set={(k,v)=>setSourceForm(f=>({...f,[k]:v}))} t={t} canManageApi={user.role==='ceo'}/></div><div className="d2c-form-footer"><button className="d2c-button primary" disabled={busy}>{t('sourceChange')}</button></div></form></section><section className="d2c-panel"><h2>{t('pair')}</h2><p className="d2c-muted">{t('pairHint')}</p><p className="d2c-footnote">{worker?.online?t('workerOnline'):t('workerOffline')} · {t('providerAuth')}</p>{user.role==='ceo'&&<form onSubmit={async e=>{e.preventDefault();try{await mutate('configure_shop',{shopId,settings:{provider}});const result=await mutate('pair_worker',{shopId,name:pairName.trim(),provider,capabilities:['pipeline']});setCredential(result);setRevokeId(result.deviceId);setPairName('');}catch(_){}}}><div className="d2c-form-grid"><label className="d2c-field">{t('pairName')}<input required value={pairName} onChange={e=>setPairName(e.target.value)}/></label><label className="d2c-field">{t('provider')}<select value={provider} onChange={e=>setProvider(e.target.value)}><option value="codex">Codex</option><option value="claude">Claude Code</option></select></label></div><div className="d2c-form-footer"><button className="d2c-button primary" disabled={busy||!pairName.trim()}>{t('pair')}</button></div></form>}{user.role==='ceo'&&<form onSubmit={async e=>{e.preventDefault();try{await mutate('revoke_worker',{shopId,deviceId:revokeId.trim()});setCredential(null);setRevokeId('');setNotice(t('revoked'));await loadRuns();}catch(_){}}}><label className="d2c-field">{t('deviceId')}<input required value={revokeId} onChange={e=>setRevokeId(e.target.value)} pattern="[0-9a-fA-F-]{36}"/></label><button className="d2c-button danger" disabled={busy||!revokeId.trim()}>{t('revoke')}</button></form>}{credential&&<div className="d2c-secret"><h3>{t('credential')}</h3><p>{t('credentialHint')}</p><pre>{JSON.stringify(credential,null,2)}</pre><button className="d2c-button" onClick={()=>setCredential(null)}>{t('dismissSecret')}</button></div>}</section></>}
  </>}</main></div>
  {deleteTarget&&<D2CDeleteDialog target={deleteTarget} t={t} busy={busy} error={error} onClose={()=>setDeleteTarget(null)} onConfirm={confirmDelete}/>}
  {navOpen&&<D2CDrawer title="Navigation" onClose={()=>setNavOpen(false)} closeLabel={t('close')}><div className="d2c-mobile-sidebar">{sidebar}</div></D2CDrawer>}{detail&&<D2CDrawer title={t('details')} onClose={closeDetail} closeLabel={t('close')}>{detailLoading&&!detailData.items.length&&<p className="d2c-loading" role="status">{t('loading')}</p>}{detailError&&<div className="d2c-notice error" role="alert">{d2cError(detailError,t)}</div>}<div style={{'--profile-tone':D2C_TONES[(detail.index||0)%D2C_TONES.length]}}><div className={detail.kind==='segment'?'d2c-persona-heading':undefined}>{detail.kind==='segment'&&<span className="d2c-persona-marker" aria-hidden="true"><D2CSymbol index={detail.index||0} size={28}/></span>}<h2>{d2cText(d2cField(detail.item,'title',lang)||d2cField(detail.item,'label',lang)||detail.title,lang)}</h2></div><p>{d2cText(d2cField(detail.item,'claim',lang)||d2cField(detail.item,'description',lang)||d2cField(detail.item,'action',lang),lang)}</p>{detail.kind==='scope'&&detail.scope.type==='product'&&<div className="d2c-fact-grid"><div><span>{t('pdpStatus')}</span><strong>{detail.item?.pdpStatus||t('unknown')}</strong></div><div><span>{t('price')}</span><strong>{detail.item?.metadata?.price!=null?detail.item.metadata.price+' '+(detail.item.metadata.currency||''):t('unknown')}</strong></div><div><span>{t('variants')}</span><strong>{detail.item?.variants?.length??t('unknown')}</strong></div><div><span>{t('canonical')}</span>{d2cSafeUrl(detail.item?.canonicalUrl)&&<a target="_blank" rel="noopener noreferrer" href={d2cSafeUrl(detail.item.canonicalUrl)}>{detail.item.canonicalUrl}</a>}</div></div>}{detail.kind==='scope'&&detail.scope.type==='group'&&<p className="d2c-footnote">{t('sourceOrigin')}: {detail.item?.origin||t('unknown')}</p>}{detail.kind==='segment'&&<><div className="d2c-fact-grid"><div><span>{t('age')}</span><strong>{ageFacts.length?ageFacts.map(f=>(typeof f.value==='object'?(f.value.min??'?')+'–'+(f.value.max??'?'):d2cText(f.value,lang))+' ('+f.subject+')').join(' · '):t('notProven')}</strong></div><div><span>{t('interests')}</span><strong>{interestFacts.length?[...new Set(interestFacts.map(f=>d2cText(f.value||f.tagId,lang)))].join(' · '):t('notProven')}</strong></div><div><span>{t('evidence')}</span><strong>{d2cNumber(detail.item.metrics?.count??detail.item.metrics?.n,lang)}</strong></div><div><span>{t('productWidth')}</span><strong>{d2cNumber(detail.item.metrics?.productBreadth?.n??detail.item.metrics?.productCount,lang)}</strong></div></div><details><summary className="d2c-rule-summary">{t('rule')}</summary><p>{D2CEngine.explainRule(detail.item.rule,lang)}</p><pre className="d2c-rule">{JSON.stringify(detail.item.rule,null,2)}</pre></details>{detail.item.claimEvidence?.length>1&&<div className="d2c-actions">{detail.item.claimEvidence.map(c=><button key={c.claimId} className="d2c-button" onClick={()=>openDetail({...detail,selector:{kind:'claim',segmentVersionId:detail.item.segmentVersionId,claimId:c.claimId}})}>{d2cText(c.text,lang)}</button>)}</div>}</>}
    <h3>{t('recommendations')}</h3>{detailData.recommendations.length?detailData.recommendations.map(r=><D2CRecommendation key={r.recommendationId} item={r} t={t} lang={lang} inReport={selectedReport.includes(r.recommendationId)} onToggle={toggleReport}/>):!detailLoading&&<p className="d2c-muted">{t('noRecommendations')}</p>}
    {detail.kind==='scope'&&detailData.segments.length>0&&<><h3>{t('profiles')}</h3><div className="d2c-actions">{detailData.segments.map((s,i)=><button className="d2c-button" key={s.segmentVersionId} onClick={()=>openDetail({kind:'segment',item:s,index:d2cProfileIndex(s,dashboard),scope:detailData.scope,dashboard:detailData.analysisSnapshot})}>{d2cText(d2cField(s,'label',lang),lang)} · n={d2cNumber(s.metrics?.count??s.metrics?.n,lang)}</button>)}</div></>}
    <h3>{t('original')} · {d2cNumber(detailData.total,lang)}</h3>{[...detailData.items].sort((a,b)=>(a.polarity==='negative'?-1:1)-(b.polarity==='negative'?-1:1)).map((item,i)=><D2CEvidenceItem key={item.evidenceId||item.id||i} item={item} t={t} lang={lang} onCorrection={correction} busy={busy}/>)}{!detailLoading&&!detailData.items.length&&<p className="d2c-muted">{t('noEvidence')}</p>}{detailData.nextCursor&&<button className="d2c-button" disabled={detailLoading} onClick={moreEvidence}>{t('moreEvidence')}</button>}{[...(detailData.dataGaps||[]),...(d2cField(detail.item,'dataGaps',lang)||[])].map((gap,i)=><div className="d2c-notice warning" key={i}>{d2cText(gap.message||gap,lang)}</div>)}<p className="d2c-footnote">{t('snapshot')}: {snapshotId} · {t('knowledge')}: {dashboard?.knowledgeVersion}<br/>{t('footnote')}</p></div></D2CDrawer>}
  {reportData&&<D2CPrintReport dashboard={reportData} shop={shop} scopeLabel={scopeLabel} filter={filter} t={t} lang={lang} selected={selectedReport}/>}</div>;
}
function D2CPrintReport({dashboard,shop,scopeLabel,filter,t,lang,selected}) {const nativeReviews=React.useMemo(()=>d2cNativeReviews(dashboard),[dashboard]);const reviewIds=React.useMemo(()=>new Set(nativeReviews.map(row=>row.reviewVersionId)),[nativeReviews]);const printEvidence=React.useMemo(()=>(dashboard.evidence||[]).filter(item=>!dashboard.reviews&&!dashboard.sourceReviews||reviewIds.has(item.reviewVersionId)),[dashboard.evidence,dashboard.reviews,dashboard.sourceReviews,reviewIds]);const recs=(selected.length?(dashboard.recommendations||[]).filter(r=>selected.includes(r.recommendationId)):(dashboard.recommendations||[])).map(r=>d2cLocalized(r,lang));return <div className="d2c-report"><header><BrandLockup height={26} sub="D2C"/><span>{shop?.domain} · {d2cDate(dashboard.createdAt||dashboard.publishedAt,lang)}</span></header><h1>{t('title')} · {scopeLabel}</h1><p>{t('reportScope')}: {t(dashboard.scanScope==='selected_products'?'selectedProducts':'wholeShop')}</p><p>{t('snapshot')}: {dashboard.snapshotId} / v{dashboard.snapshotVersion} · {t('runId')}: {dashboard.runId} · {t('knowledge')}: {dashboard.knowledgeVersion}</p><p>{D2CEngine.explainRule(filter,lang)}</p><D2CCoverage dashboard={dashboard} scopeLabel={scopeLabel} t={t} lang={lang}/><h2>{t('profiles')}</h2>{(dashboard.segments||[]).map(s=><article key={s.segmentVersionId}><h3>{d2cText(d2cField(s,'label',lang),lang)}</h3><p>{d2cText(d2cField(s,'description',lang),lang)}</p><p>{t('evidence')}: {s.metrics?.count??s.metrics?.n} / {s.metrics?.denominator} · {t(s.metrics?.evidenceStatus||s.evidenceStatus||'insufficient')}</p><p>{D2CEngine.explainRule(s.rule,lang)}</p></article>)}<h2>{t('findings')}</h2>{(dashboard.findings||[]).map(f=><article key={f.findingId}><h3>{d2cText(d2cField(f,'title',lang),lang)}</h3><p>{d2cText(d2cField(f,'claim',lang),lang)}</p><p>{t('evidence')}: {(f.reviewEvidenceIds||[]).join(', ')}</p></article>)}<h2>{t('recommendations')}</h2>{recs.map(r=><article key={r.recommendationId}><h3>{r.priority} · {d2cText(d2cField(r,'title',lang),lang)}</h3><p>{d2cText(d2cField(r,'action',lang),lang)}</p><p>{t('why')}: {d2cText(d2cField(r,'why',lang),lang)}</p><p>{t('testPlan')}: {typeof r.validationPlan==='object'?Object.entries(r.validationPlan).map(([k,v])=>t(k)+': '+(Array.isArray(v)?v.map(x=>d2cText(x,lang)).join(', '):d2cText(v,lang))).join(' · '):d2cText(r.validationPlan,lang)}</p><p>{t('method')}: {(r.knowledgeRuleRefs||[]).map(k=>k.methodId+'@'+k.knowledgeVersion+' ('+k.sourceHash+')').join(', ')||t('notEstablished')}</p>{(r.knowledgeRuleRefs||[]).map((k,i)=><blockquote key={i}>{d2cText(k.passage||k.rulePassage||k.claim||(dashboard.knowledgeRules||[]).find(rule=>rule.methodId===k.methodId)?.claim,lang)}</blockquote>)}<p>{t('required')}: {(r.requiredInputs||[]).map(x=>d2cText(x,lang)).join(' · ')}</p></article>)}<h2>{t('original')}</h2>{printEvidence.map((e,i)=><article key={e.evidenceId||i}><blockquote>{e.evidenceQuote||e.quote}</blockquote><p>{e.productTitle||e.productId} · {e.sourceUrl} · {e.reviewVersionId} · {e.evidenceId}</p></article>)}<h2>{t('warnings')}</h2>{(dashboard.warnings||[]).map((w,i)=><p key={i}>{d2cText(w.message||w,lang)}</p>)}<p>{t('footnote')}</p></div>;}
