/**
 * @fileoverview Classify · Dry-run / disposition PREVIEW dialog.
 *
 * Honest, client-side, in-memory preview of how a source's EXISTING extracted
 * ContentItemTags would be dispositioned under its current taxonomy mode +
 * thresholds. This is NOT a fresh LLM run and NOTHING is persisted — it samples
 * up to N existing tags, resolves each against the cached taxonomy
 * (Tags / TagSynonyms) client-side, and replays the deterministic routing via the
 * pure `previewDispositions` helper.
 *
 * It is hosted by the Sources tab (which already hosts its own dialogs): data
 * DOWN via `[SourceCard]` / `[Show]`, intent UP via `(Closed)`. On open it loads
 * the sample itself (read-only RunView, `ResultType: 'simple'`).
 *
 * NOTE: the server also has a semantic/embedding match tier not reproducible
 * client-side, so borderline rows may resolve differently in a real run. The
 * template states this explicitly.
 */
import { Component, ChangeDetectorRef, EventEmitter, Input, Output, inject } from '@angular/core';
import { RunView } from '@memberjunction/core';
import { UUIDsEqual } from '@memberjunction/global';
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
import { TagEngineBase } from '@memberjunction/tag-engine-base';
import { KnowledgeHubMetadataEngine } from '@memberjunction/core-entities';
import { previewDispositions, } from '../shared/classify.dryrun';
import * as i0 from "@angular/core";
import * as i1 from "@memberjunction/ng-shared-generic";
import * as i2 from "@memberjunction/ng-ui-components";
function ClassifyDryRunPreviewDialogComponent_Conditional_0_Conditional_9_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 7);
    i0.ɵɵelement(1, "i");
    i0.ɵɵelementStart(2, "span", 17);
    i0.ɵɵtext(3);
    i0.ɵɵelementEnd()();
} if (rf & 2) {
    const ctx_r1 = i0.ɵɵnextContext(2);
    i0.ɵɵadvance();
    i0.ɵɵclassMap(ctx_r1.SourceCard.Icon);
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate(ctx_r1.SourceCard.Name);
} }
function ClassifyDryRunPreviewDialogComponent_Conditional_0_Conditional_36_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelement(0, "mj-loading", 13);
} }
function ClassifyDryRunPreviewDialogComponent_Conditional_0_Conditional_37_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 14);
    i0.ɵɵelement(1, "i", 18);
    i0.ɵɵelementStart(2, "p");
    i0.ɵɵtext(3, "No extracted tags found for this source yet. Run the pipeline first, then preview how its tags would be dispositioned.");
    i0.ɵɵelementEnd()();
} }
function ClassifyDryRunPreviewDialogComponent_Conditional_0_Conditional_38_Conditional_13_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "span", 26);
    i0.ɵɵtext(1);
    i0.ɵɵelementEnd();
} if (rf & 2) {
    const ctx_r1 = i0.ɵɵnextContext(3);
    i0.ɵɵadvance();
    i0.ɵɵtextInterpolate1("", ctx_r1.Counts.Reject, " reject");
} }
function ClassifyDryRunPreviewDialogComponent_Conditional_0_Conditional_38_For_27_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "tr")(1, "td", 31);
    i0.ɵɵtext(2);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(3, "td", 32);
    i0.ɵɵtext(4);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(5, "td", 33);
    i0.ɵɵtext(6);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(7, "td")(8, "span", 34);
    i0.ɵɵtext(9);
    i0.ɵɵelementEnd()()();
} if (rf & 2) {
    const row_r3 = ctx.$implicit;
    const ctx_r1 = i0.ɵɵnextContext(3);
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate(row_r3.tag);
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate(row_r3.matchedTag ?? "\u2014");
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate(ctx_r1.FormatScore(row_r3.score));
    i0.ɵɵadvance(2);
    i0.ɵɵclassMap(ctx_r1.DispositionClass(row_r3.disposition));
    i0.ɵɵproperty("title", row_r3.reason);
    i0.ɵɵadvance();
    i0.ɵɵtextInterpolate1(" ", ctx_r1.DispositionLabel(row_r3.disposition), " ");
} }
function ClassifyDryRunPreviewDialogComponent_Conditional_0_Conditional_38_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 19);
    i0.ɵɵelement(1, "i", 20);
    i0.ɵɵelementStart(2, "span");
    i0.ɵɵtext(3);
    i0.ɵɵelementStart(4, "span", 21);
    i0.ɵɵtext(5, "(rough estimate)");
    i0.ɵɵelementEnd()()();
    i0.ɵɵelementStart(6, "div", 22)(7, "span", 23);
    i0.ɵɵtext(8);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(9, "span", 24);
    i0.ɵɵtext(10);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(11, "span", 25);
    i0.ɵɵtext(12);
    i0.ɵɵelementEnd();
    i0.ɵɵconditionalCreate(13, ClassifyDryRunPreviewDialogComponent_Conditional_0_Conditional_38_Conditional_13_Template, 2, 1, "span", 26);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(14, "table", 27)(15, "thead")(16, "tr")(17, "th");
    i0.ɵɵtext(18, "Tag");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(19, "th");
    i0.ɵɵtext(20, "Matched");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(21, "th", 28);
    i0.ɵɵtext(22, "Score");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(23, "th");
    i0.ɵɵtext(24, "What would happen");
    i0.ɵɵelementEnd()()();
    i0.ɵɵelementStart(25, "tbody");
    i0.ɵɵrepeaterCreate(26, ClassifyDryRunPreviewDialogComponent_Conditional_0_Conditional_38_For_27_Template, 10, 7, "tr", null, i0.ɵɵrepeaterTrackByIndex);
    i0.ɵɵelementEnd()();
    i0.ɵɵelementStart(28, "div", 29);
    i0.ɵɵelement(29, "i", 30);
    i0.ɵɵtext(30, " A real run also uses a semantic / embedding match tier not reproducible here, so borderline rows may resolve differently. ");
    i0.ɵɵelementEnd();
} if (rf & 2) {
    const ctx_r1 = i0.ɵɵnextContext(2);
    i0.ɵɵadvance(3);
    i0.ɵɵtextInterpolate4(" ", ctx_r1.Estimate.ItemsSampled, " sampled tag", ctx_r1.Estimate.ItemsSampled === 1 ? "" : "s", " \u00B7 est. ", ctx_r1.FormatTokens(ctx_r1.Estimate.EstimatedTokens), " tokens \u00B7 ~", ctx_r1.FormatCost(ctx_r1.Estimate.EstimatedCost), " ");
    i0.ɵɵadvance(5);
    i0.ɵɵtextInterpolate1("", ctx_r1.Counts.AutoApply, " auto-apply");
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate1("", ctx_r1.Counts.RouteToInbox, " \u2192 inbox");
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate1("", ctx_r1.Counts.CreateNew, " create");
    i0.ɵɵadvance();
    i0.ɵɵconditional(ctx_r1.Counts.Reject > 0 ? 13 : -1);
    i0.ɵɵadvance(13);
    i0.ɵɵrepeater(ctx_r1.Rows);
} }
function ClassifyDryRunPreviewDialogComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
    const _r1 = i0.ɵɵgetCurrentView();
    i0.ɵɵelementStart(0, "div", 0);
    i0.ɵɵlistener("click", function ClassifyDryRunPreviewDialogComponent_Conditional_0_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.close()); });
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(1, "div", 1)(2, "div", 2)(3, "h3");
    i0.ɵɵelement(4, "i", 3);
    i0.ɵɵtext(5, " Dry-run preview");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(6, "button", 4);
    i0.ɵɵlistener("click", function ClassifyDryRunPreviewDialogComponent_Conditional_0_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.close()); });
    i0.ɵɵelement(7, "i", 5);
    i0.ɵɵelementEnd()();
    i0.ɵɵelementStart(8, "div", 6);
    i0.ɵɵconditionalCreate(9, ClassifyDryRunPreviewDialogComponent_Conditional_0_Conditional_9_Template, 4, 3, "div", 7);
    i0.ɵɵelementStart(10, "div", 8);
    i0.ɵɵelement(11, "i", 9);
    i0.ɵɵelementStart(12, "span");
    i0.ɵɵtext(13, " Preview of how this source's sampled tags resolve under the current ");
    i0.ɵɵelementStart(14, "b");
    i0.ɵɵtext(15, "mode & thresholds");
    i0.ɵɵelementEnd();
    i0.ɵɵtext(16, " \u2014 ");
    i0.ɵɵelementStart(17, "b");
    i0.ɵɵtext(18, "no LLM call, nothing persisted");
    i0.ɵɵelementEnd();
    i0.ɵɵtext(19, ". ");
    i0.ɵɵelementEnd()();
    i0.ɵɵelementStart(20, "div", 10)(21, "span", 11);
    i0.ɵɵelement(22, "i", 3);
    i0.ɵɵtext(23, " PREVIEW");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(24, "span", 12);
    i0.ɵɵtext(25, "Mode ");
    i0.ɵɵelementStart(26, "b");
    i0.ɵɵtext(27);
    i0.ɵɵelementEnd()();
    i0.ɵɵelementStart(28, "span", 12);
    i0.ɵɵtext(29, "Match ");
    i0.ɵɵelementStart(30, "b");
    i0.ɵɵtext(31);
    i0.ɵɵelementEnd()();
    i0.ɵɵelementStart(32, "span", 12);
    i0.ɵɵtext(33, "Suggest ");
    i0.ɵɵelementStart(34, "b");
    i0.ɵɵtext(35);
    i0.ɵɵelementEnd()()();
    i0.ɵɵconditionalCreate(36, ClassifyDryRunPreviewDialogComponent_Conditional_0_Conditional_36_Template, 1, 0, "mj-loading", 13)(37, ClassifyDryRunPreviewDialogComponent_Conditional_0_Conditional_37_Template, 4, 0, "div", 14)(38, ClassifyDryRunPreviewDialogComponent_Conditional_0_Conditional_38_Template, 31, 8);
    i0.ɵɵelementStart(39, "div", 15)(40, "button", 16);
    i0.ɵɵlistener("click", function ClassifyDryRunPreviewDialogComponent_Conditional_0_Template_button_click_40_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.close()); });
    i0.ɵɵtext(41, "Close");
    i0.ɵɵelementEnd()()()();
} if (rf & 2) {
    const ctx_r1 = i0.ɵɵnextContext();
    i0.ɵɵadvance(9);
    i0.ɵɵconditional(ctx_r1.SourceCard ? 9 : -1);
    i0.ɵɵadvance(18);
    i0.ɵɵtextInterpolate(ctx_r1.ModeLabel(ctx_r1.EffectiveMode));
    i0.ɵɵadvance(4);
    i0.ɵɵtextInterpolate(ctx_r1.FormatScore(ctx_r1.EffectiveMatchThreshold));
    i0.ɵɵadvance(4);
    i0.ɵɵtextInterpolate(ctx_r1.FormatScore(ctx_r1.EffectiveSuggestThreshold));
    i0.ɵɵadvance();
    i0.ɵɵconditional(ctx_r1.Loading ? 36 : ctx_r1.NoTagsSampled ? 37 : 38);
} }
/** Default routing config when a source has no explicit Configuration blob. */
const DEFAULT_MODE = 'auto-grow';
const DEFAULT_MATCH_THRESHOLD = 0.85;
/** Illustrative per-item token estimate for the (non-binding) cost line. */
const EST_TOKENS_PER_ITEM = 840;
/** Illustrative blended $/1K-token rate for the cost estimate. */
const EST_COST_PER_1K_TOKENS = 0.01;
/** Max existing tags to sample for the preview. */
const SAMPLE_SIZE = 15;
export class ClassifyDryRunPreviewDialogComponent extends BaseAngularComponent {
    cdr = inject(ChangeDetectorRef);
    /** Whether the dialog is visible. */
    _show = false;
    set Show(value) {
        const opening = value && !this._show;
        this._show = value;
        if (opening && this._sourceCard) {
            void this.runPreview(this._sourceCard);
        }
    }
    get Show() {
        return this._show;
    }
    /** The source being previewed; supplied by the Sources tab when opening. */
    _sourceCard = null;
    set SourceCard(value) {
        this._sourceCard = value;
        if (value && this._show) {
            void this.runPreview(value);
        }
    }
    get SourceCard() {
        return this._sourceCard;
    }
    /** Emitted when the user dismisses the dialog. */
    Closed = new EventEmitter();
    // ── View state ──
    Loading = false;
    Rows = [];
    Counts = { AutoApply: 0, RouteToInbox: 0, CreateNew: 0, Reject: 0 };
    Estimate = { ItemsSampled: 0, EstimatedTokens: 0, EstimatedCost: 0 };
    /** Effective config used for this preview (mirrored for the "effective values" line). */
    EffectiveMode = DEFAULT_MODE;
    EffectiveMatchThreshold = DEFAULT_MATCH_THRESHOLD;
    EffectiveSuggestThreshold = DEFAULT_MATCH_THRESHOLD - 0.05;
    /** True when the sampled source has no extracted tags yet. */
    NoTagsSampled = false;
    close() {
        this.Closed.emit();
    }
    /**
     * Load a sample of the source's existing extracted tags, resolve each against
     * the cached taxonomy, and produce the preview rows + summary. Read-only —
     * nothing is written.
     */
    async runPreview(card) {
        this.Loading = true;
        this.Rows = [];
        this.NoTagsSampled = false;
        this.cdr.detectChanges();
        try {
            const p = this.ProviderToUse;
            await TagEngineBase.Instance.Config(false, p.CurrentUser, p);
            await KnowledgeHubMetadataEngine.Instance.Config(false, p.CurrentUser, p);
            const cfg = this.resolveSourceConfig(card.ID);
            this.EffectiveMode = cfg.mode;
            this.EffectiveMatchThreshold = cfg.matchThreshold;
            this.EffectiveSuggestThreshold = cfg.suggestThreshold;
            const inputs = await this.loadSampleTags(card.ID);
            if (inputs.length === 0) {
                this.NoTagsSampled = true;
                this.Estimate = { ItemsSampled: 0, EstimatedTokens: 0, EstimatedCost: 0 };
                return;
            }
            const resolve = this.buildResolver();
            this.Rows = previewDispositions(inputs, cfg, resolve);
            this.Counts = this.tally(this.Rows);
            this.Estimate = this.estimate(inputs.length);
        }
        catch (error) {
            console.error('[Classify] Dry-run preview error:', error);
        }
        finally {
            this.Loading = false;
            this.cdr.detectChanges();
        }
    }
    /**
     * Sample up to SAMPLE_SIZE existing ContentItemTags for the source. We first
     * pull a handful of the source's items, then their tags — the simplest path
     * that avoids depending on a direct source-level filter on ContentItemTags.
     */
    async loadSampleTags(sourceID) {
        const rv = RunView.FromMetadataProvider(this.ProviderToUse);
        const itemResult = await rv.RunView({
            EntityName: 'MJ: Content Items',
            ExtraFilter: `ContentSourceID='${sourceID}'`,
            OrderBy: '__mj_UpdatedAt DESC',
            Fields: ['ID'],
            MaxRows: SAMPLE_SIZE,
            ResultType: 'simple',
        });
        if (!itemResult.Success || itemResult.Results.length === 0)
            return [];
        const itemIds = itemResult.Results.map(i => i.ID);
        const inClause = itemIds.map(id => `'${id}'`).join(',');
        const tagResult = await rv.RunView({
            EntityName: 'MJ: Content Item Tags',
            ExtraFilter: `ItemID IN (${inClause})`,
            Fields: ['Tag', 'TagID', 'Weight'],
            MaxRows: SAMPLE_SIZE,
            ResultType: 'simple',
        });
        if (!tagResult.Success)
            return [];
        return tagResult.Results
            .filter(t => !!t.Tag)
            .map(t => ({
            tag: t.Tag,
            resolvedTagId: t.TagID ?? null,
            weight: Number(t.Weight ?? 1),
        }));
    }
    /**
     * Read the source's effective taxonomy config from its ConfigurationObject,
     * falling back to documented runtime defaults. SuggestThreshold defaults to
     * matchThreshold − 0.05 (matches the server's runtime default).
     */
    resolveSourceConfig(sourceID) {
        const source = KnowledgeHubMetadataEngine.Instance.ContentSources.find(cs => UUIDsEqual(cs.ID, sourceID));
        const config = source?.ConfigurationObject ?? null;
        const mode = config?.TagTaxonomyMode ?? DEFAULT_MODE;
        const matchThreshold = config?.TagMatchThreshold ?? DEFAULT_MATCH_THRESHOLD;
        const suggestThreshold = config?.SuggestThreshold ?? matchThreshold - 0.05;
        return { mode, matchThreshold, suggestThreshold };
    }
    /**
     * Build the client-side resolver closure over the cached taxonomy.
     * tier 'synonym' if the tag matches a TagSynonym (case-insensitive),
     * 'exact' if it matches a Tag Name (case-insensitive), 'fuzzy' if a
     * normalized match (lowercase, strip plurals/hyphens) hits, else 'none'.
     */
    buildResolver() {
        const engine = TagEngineBase.Instance;
        const activeTags = engine.Tags.filter(t => t.Status === 'Active');
        // synonym text (lower) → owning Tag
        const synonymMap = new Map();
        const tagById = new Map();
        for (const t of activeTags) {
            tagById.set(t.ID.toLowerCase(), { id: t.ID, name: t.Name });
        }
        for (const syn of engine.TagSynonyms) {
            // Only Active synonyms resolve — Pending (proposed, unapproved) and
            // Rejected ones are not live, matching the server-side resolver.
            if (syn.Status !== 'Active')
                continue;
            const owner = tagById.get((syn.TagID ?? '').toLowerCase());
            if (owner && syn.Synonym) {
                synonymMap.set(syn.Synonym.trim().toLowerCase(), owner);
            }
        }
        // exact name (lower) → Tag; normalized name (lower) → Tag (for fuzzy tier)
        const exactMap = new Map();
        const normalizedMap = new Map();
        for (const t of activeTags) {
            const entry = { id: t.ID, name: t.Name };
            exactMap.set(t.Name.trim().toLowerCase(), entry);
            normalizedMap.set(this.normalize(t.Name), entry);
        }
        return (tag) => {
            const lower = tag.trim().toLowerCase();
            const syn = synonymMap.get(lower);
            if (syn)
                return { tagId: syn.id, tagName: syn.name, score: 1.0, tier: 'synonym' };
            const exact = exactMap.get(lower);
            if (exact)
                return { tagId: exact.id, tagName: exact.name, score: 1.0, tier: 'exact' };
            const fuzzy = normalizedMap.get(this.normalize(tag));
            if (fuzzy)
                return { tagId: fuzzy.id, tagName: fuzzy.name, score: 0.8, tier: 'fuzzy' };
            return { tagId: null, tagName: null, score: null, tier: 'none' };
        };
    }
    /** Lowercase, strip hyphens/underscores/whitespace and a trailing plural 's'. */
    normalize(value) {
        let v = value.trim().toLowerCase().replace(/[-_\s]+/g, '');
        if (v.endsWith('s') && v.length > 3)
            v = v.slice(0, -1);
        return v;
    }
    tally(rows) {
        const counts = { AutoApply: 0, RouteToInbox: 0, CreateNew: 0, Reject: 0 };
        for (const r of rows) {
            switch (r.disposition) {
                case 'auto-apply':
                    counts.AutoApply++;
                    break;
                case 'route-to-inbox':
                    counts.RouteToInbox++;
                    break;
                case 'create-new':
                    counts.CreateNew++;
                    break;
                case 'reject':
                    counts.Reject++;
                    break;
            }
        }
        return counts;
    }
    estimate(itemsSampled) {
        const tokens = itemsSampled * EST_TOKENS_PER_ITEM;
        const cost = (tokens / 1000) * EST_COST_PER_1K_TOKENS;
        return { ItemsSampled: itemsSampled, EstimatedTokens: tokens, EstimatedCost: cost };
    }
    // ── Template helpers ──
    /** Status-token-backed CSS class for a disposition pill. */
    DispositionClass(d) {
        switch (d) {
            case 'auto-apply': return 'disp disp-apply';
            case 'route-to-inbox': return 'disp disp-inbox';
            case 'create-new': return 'disp disp-create';
            case 'reject': return 'disp disp-reject';
        }
    }
    /** Human label for a disposition pill. */
    DispositionLabel(d) {
        switch (d) {
            case 'auto-apply': return 'AUTO-APPLY';
            case 'route-to-inbox': return '→ INBOX';
            case 'create-new': return this.EffectiveMode === 'free-flow' ? 'CREATE (free-flow)' : 'CREATE (auto-grow)';
            case 'reject': return 'REJECT';
        }
    }
    FormatScore(score) {
        return score == null ? '—' : score.toFixed(2);
    }
    FormatTokens(tokens) {
        return tokens.toLocaleString();
    }
    FormatCost(cost) {
        return `$${cost.toFixed(2)}`;
    }
    ModeLabel(mode) {
        switch (mode) {
            case 'constrained': return 'Constrained';
            case 'auto-grow': return 'Auto-grow';
            case 'free-flow': return 'Free-flow';
        }
    }
    static ɵfac = /*@__PURE__*/ (() => { let ɵClassifyDryRunPreviewDialogComponent_BaseFactory; return function ClassifyDryRunPreviewDialogComponent_Factory(__ngFactoryType__) { return (ɵClassifyDryRunPreviewDialogComponent_BaseFactory || (ɵClassifyDryRunPreviewDialogComponent_BaseFactory = i0.ɵɵgetInheritedFactory(ClassifyDryRunPreviewDialogComponent)))(__ngFactoryType__ || ClassifyDryRunPreviewDialogComponent); }; })();
    static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ClassifyDryRunPreviewDialogComponent, selectors: [["classify-dry-run-preview"]], inputs: { Show: "Show", SourceCard: "SourceCard" }, outputs: { Closed: "Closed" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 1, vars: 1, consts: [[1, "at-slide-overlay", 3, "click"], [1, "at-slide-panel", "dry-run-panel"], [1, "at-slide-header"], [1, "fa-solid", "fa-flask"], ["aria-label", "Close dry-run preview", 1, "at-slide-close", 3, "click"], [1, "fa-solid", "fa-times"], [1, "at-slide-body"], [1, "dry-source-line"], [1, "dry-banner"], [1, "fa-solid", "fa-circle-info"], [1, "dry-effective"], [1, "pill", "purple"], [1, "dry-eff-item"], ["text", "Sampling existing tags\u2026"], [1, "dry-empty"], [1, "dry-footer"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click"], [1, "dry-source-name"], [1, "fa-solid", "fa-tags"], [1, "dry-estimate"], [1, "fa-solid", "fa-calculator"], [1, "dry-est-note"], [1, "dry-summary"], [1, "disp", "disp-apply"], [1, "disp", "disp-inbox"], [1, "disp", "disp-create"], [1, "disp", "disp-reject"], [1, "dry-table"], [1, "dry-col-score"], [1, "dry-disclaimer"], [1, "fa-solid", "fa-triangle-exclamation"], [1, "dry-cell-tag"], [1, "dry-cell-matched"], [1, "dry-col-score", "dry-mono"], [3, "title"]], template: function ClassifyDryRunPreviewDialogComponent_Template(rf, ctx) { if (rf & 1) {
            i0.ɵɵconditionalCreate(0, ClassifyDryRunPreviewDialogComponent_Conditional_0_Template, 42, 5);
        } if (rf & 2) {
            i0.ɵɵconditional(ctx.Show ? 0 : -1);
        } }, dependencies: [i1.LoadingComponent, i2.MJButtonDirective], styles: ["\n\n\n\n\n\n\n.dry-run-panel[_ngcontent-%COMP%] {\n    width: 640px;\n    max-width: 92vw;\n}\n\n.dry-source-line[_ngcontent-%COMP%] {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    font-size: 0.95rem;\n    color: var(--mj-text-primary);\n    margin-bottom: 12px;\n}\n.dry-source-line[_ngcontent-%COMP%]   i[_ngcontent-%COMP%] {\n    color: var(--mj-text-secondary);\n}\n.dry-source-name[_ngcontent-%COMP%] {\n    font-weight: 600;\n}\n\n\n\n.dry-banner[_ngcontent-%COMP%] {\n    display: flex;\n    align-items: flex-start;\n    gap: 10px;\n    padding: 10px 12px;\n    border-radius: 8px;\n    background: color-mix(in srgb, var(--mj-status-info) 8%, var(--mj-bg-surface));\n    border: 1px solid var(--mj-status-info-border);\n    color: var(--mj-status-info-text);\n    font-size: 0.85rem;\n    line-height: 1.4;\n    margin-bottom: 14px;\n}\n.dry-banner[_ngcontent-%COMP%]   i[_ngcontent-%COMP%] {\n    margin-top: 2px;\n}\n\n\n\n.dry-effective[_ngcontent-%COMP%] {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: center;\n    gap: 10px;\n    margin-bottom: 14px;\n}\n.dry-eff-item[_ngcontent-%COMP%] {\n    font-size: 0.8rem;\n    color: var(--mj-text-secondary);\n}\n.dry-eff-item[_ngcontent-%COMP%]   b[_ngcontent-%COMP%] {\n    color: var(--mj-text-primary);\n}\n\n.pill[_ngcontent-%COMP%] {\n    display: inline-flex;\n    align-items: center;\n    gap: 5px;\n    padding: 3px 9px;\n    border-radius: 999px;\n    font-size: 0.72rem;\n    font-weight: 700;\n    letter-spacing: 0.03em;\n}\n.pill.purple[_ngcontent-%COMP%] {\n    background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface));\n    color: var(--mj-brand-primary);\n}\n\n\n\n.dry-estimate[_ngcontent-%COMP%] {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    font-size: 0.82rem;\n    color: var(--mj-text-secondary);\n    margin-bottom: 12px;\n}\n.dry-est-note[_ngcontent-%COMP%] {\n    font-style: italic;\n    color: var(--mj-text-muted);\n}\n\n\n\n.dry-summary[_ngcontent-%COMP%] {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 8px;\n    margin-bottom: 14px;\n}\n\n\n\n.disp[_ngcontent-%COMP%] {\n    display: inline-flex;\n    align-items: center;\n    padding: 3px 9px;\n    border-radius: 6px;\n    font-size: 0.72rem;\n    font-weight: 700;\n    letter-spacing: 0.02em;\n    white-space: nowrap;\n}\n.disp-apply[_ngcontent-%COMP%] {\n    background: var(--mj-status-success-bg);\n    color: var(--mj-status-success-text);\n    border: 1px solid var(--mj-status-success-border);\n}\n.disp-inbox[_ngcontent-%COMP%] {\n    background: var(--mj-status-warning-bg);\n    color: var(--mj-status-warning-text);\n    border: 1px solid var(--mj-status-warning-border);\n}\n.disp-create[_ngcontent-%COMP%] {\n    background: var(--mj-status-info-bg);\n    color: var(--mj-status-info-text);\n    border: 1px solid var(--mj-status-info-border);\n}\n.disp-reject[_ngcontent-%COMP%] {\n    background: var(--mj-status-error-bg);\n    color: var(--mj-status-error-text);\n    border: 1px solid var(--mj-status-error-border);\n}\n\n\n\n.dry-table[_ngcontent-%COMP%] {\n    width: 100%;\n    border-collapse: collapse;\n    font-size: 0.84rem;\n    margin-bottom: 14px;\n}\n.dry-table[_ngcontent-%COMP%]   thead[_ngcontent-%COMP%]   th[_ngcontent-%COMP%] {\n    text-align: left;\n    font-weight: 600;\n    color: var(--mj-text-secondary);\n    padding: 6px 8px;\n    border-bottom: 1px solid var(--mj-border-default);\n}\n.dry-table[_ngcontent-%COMP%]   tbody[_ngcontent-%COMP%]   td[_ngcontent-%COMP%] {\n    padding: 7px 8px;\n    border-bottom: 1px solid var(--mj-border-subtle);\n    color: var(--mj-text-primary);\n    vertical-align: middle;\n}\n.dry-table[_ngcontent-%COMP%]   tbody[_ngcontent-%COMP%]   tr[_ngcontent-%COMP%]:hover {\n    background: var(--mj-bg-surface-hover);\n}\n.dry-cell-tag[_ngcontent-%COMP%] {\n    font-weight: 500;\n}\n.dry-cell-matched[_ngcontent-%COMP%] {\n    color: var(--mj-text-secondary);\n}\n.dry-col-score[_ngcontent-%COMP%] {\n    text-align: right;\n    width: 64px;\n}\n.dry-mono[_ngcontent-%COMP%] {\n    font-family: var(--mj-font-mono, monospace);\n    color: var(--mj-text-secondary);\n}\n\n.dry-disclaimer[_ngcontent-%COMP%] {\n    display: flex;\n    align-items: flex-start;\n    gap: 8px;\n    font-size: 0.78rem;\n    color: var(--mj-text-muted);\n    line-height: 1.4;\n    margin-bottom: 16px;\n}\n.dry-disclaimer[_ngcontent-%COMP%]   i[_ngcontent-%COMP%] {\n    margin-top: 2px;\n    color: var(--mj-status-warning);\n}\n\n.dry-empty[_ngcontent-%COMP%] {\n    text-align: center;\n    padding: 32px 16px;\n    color: var(--mj-text-muted);\n}\n.dry-empty[_ngcontent-%COMP%]   i[_ngcontent-%COMP%] {\n    font-size: 1.8rem;\n    margin-bottom: 10px;\n    color: var(--mj-text-disabled);\n}\n.dry-empty[_ngcontent-%COMP%]   p[_ngcontent-%COMP%] {\n    font-size: 0.88rem;\n    line-height: 1.5;\n    max-width: 420px;\n    margin: 0 auto;\n}\n\n.dry-footer[_ngcontent-%COMP%] {\n    display: flex;\n    gap: 8px;\n    margin-top: 4px;\n}"] });
}
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ClassifyDryRunPreviewDialogComponent, [{
        type: Component,
        args: [{ standalone: false, selector: 'classify-dry-run-preview', template: "<!-- \u2550\u2550\u2550\u2550\u2550\u2550 DRY-RUN / DISPOSITION PREVIEW SLIDE-IN \u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (Show) {\n    <div class=\"at-slide-overlay\" (click)=\"close()\"></div>\n    <div class=\"at-slide-panel dry-run-panel\">\n        <div class=\"at-slide-header\">\n            <h3><i class=\"fa-solid fa-flask\"></i> Dry-run preview</h3>\n            <button class=\"at-slide-close\" aria-label=\"Close dry-run preview\" (click)=\"close()\"><i class=\"fa-solid fa-times\"></i></button>\n        </div>\n\n        <div class=\"at-slide-body\">\n            <!-- Source + intent line -->\n            @if (SourceCard) {\n                <div class=\"dry-source-line\">\n                    <i [class]=\"SourceCard.Icon\"></i>\n                    <span class=\"dry-source-name\">{{ SourceCard.Name }}</span>\n                </div>\n            }\n\n            <!-- \"nothing persisted\" banner -->\n            <div class=\"dry-banner\">\n                <i class=\"fa-solid fa-circle-info\"></i>\n                <span>\n                    Preview of how this source's sampled tags resolve under the current\n                    <b>mode &amp; thresholds</b> \u2014 <b>no LLM call, nothing persisted</b>.\n                </span>\n            </div>\n\n            <!-- Effective config -->\n            <div class=\"dry-effective\">\n                <span class=\"pill purple\"><i class=\"fa-solid fa-flask\"></i> PREVIEW</span>\n                <span class=\"dry-eff-item\">Mode <b>{{ ModeLabel(EffectiveMode) }}</b></span>\n                <span class=\"dry-eff-item\">Match <b>{{ FormatScore(EffectiveMatchThreshold) }}</b></span>\n                <span class=\"dry-eff-item\">Suggest <b>{{ FormatScore(EffectiveSuggestThreshold) }}</b></span>\n            </div>\n\n            @if (Loading) {\n                <mj-loading text=\"Sampling existing tags\u2026\"></mj-loading>\n            } @else if (NoTagsSampled) {\n                <div class=\"dry-empty\">\n                    <i class=\"fa-solid fa-tags\"></i>\n                    <p>No extracted tags found for this source yet. Run the pipeline first, then preview how its tags would be dispositioned.</p>\n                </div>\n            } @else {\n                <!-- Estimate line (illustrative) -->\n                <div class=\"dry-estimate\">\n                    <i class=\"fa-solid fa-calculator\"></i>\n                    <span>\n                        {{ Estimate.ItemsSampled }} sampled tag{{ Estimate.ItemsSampled === 1 ? '' : 's' }} \u00B7\n                        est. {{ FormatTokens(Estimate.EstimatedTokens) }} tokens \u00B7\n                        ~{{ FormatCost(Estimate.EstimatedCost) }}\n                        <span class=\"dry-est-note\">(rough estimate)</span>\n                    </span>\n                </div>\n\n                <!-- Summary counts -->\n                <div class=\"dry-summary\">\n                    <span class=\"disp disp-apply\">{{ Counts.AutoApply }} auto-apply</span>\n                    <span class=\"disp disp-inbox\">{{ Counts.RouteToInbox }} \u2192 inbox</span>\n                    <span class=\"disp disp-create\">{{ Counts.CreateNew }} create</span>\n                    @if (Counts.Reject > 0) {\n                        <span class=\"disp disp-reject\">{{ Counts.Reject }} reject</span>\n                    }\n                </div>\n\n                <!-- Disposition table -->\n                <table class=\"dry-table\">\n                    <thead>\n                        <tr>\n                            <th>Tag</th>\n                            <th>Matched</th>\n                            <th class=\"dry-col-score\">Score</th>\n                            <th>What would happen</th>\n                        </tr>\n                    </thead>\n                    <tbody>\n                        @for (row of Rows; track $index) {\n                            <tr>\n                                <td class=\"dry-cell-tag\">{{ row.tag }}</td>\n                                <td class=\"dry-cell-matched\">{{ row.matchedTag ?? '\u2014' }}</td>\n                                <td class=\"dry-col-score dry-mono\">{{ FormatScore(row.score) }}</td>\n                                <td>\n                                    <span [class]=\"DispositionClass(row.disposition)\" [title]=\"row.reason\">\n                                        {{ DispositionLabel(row.disposition) }}\n                                    </span>\n                                </td>\n                            </tr>\n                        }\n                    </tbody>\n                </table>\n\n                <div class=\"dry-disclaimer\">\n                    <i class=\"fa-solid fa-triangle-exclamation\"></i>\n                    A real run also uses a semantic / embedding match tier not reproducible here,\n                    so borderline rows may resolve differently.\n                </div>\n            }\n\n            <!-- Footer -->\n            <div class=\"dry-footer\">\n                <button mjButton variant=\"primary\" size=\"sm\" (click)=\"close()\">Close</button>\n            </div>\n        </div>\n    </div>\n}\n", styles: ["/**\n * Dry-run preview dialog \u2014 net-new rules only. The slide-in shell (.at-slide-*)\n * is owned by the host (ViewEncapsulation.None) and already styles the panel.\n * Disposition pills use semantic status tokens so they adapt to dark mode.\n */\n\n.dry-run-panel {\n    width: 640px;\n    max-width: 92vw;\n}\n\n.dry-source-line {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    font-size: 0.95rem;\n    color: var(--mj-text-primary);\n    margin-bottom: 12px;\n}\n.dry-source-line i {\n    color: var(--mj-text-secondary);\n}\n.dry-source-name {\n    font-weight: 600;\n}\n\n/* \"nothing persisted\" banner */\n.dry-banner {\n    display: flex;\n    align-items: flex-start;\n    gap: 10px;\n    padding: 10px 12px;\n    border-radius: 8px;\n    background: color-mix(in srgb, var(--mj-status-info) 8%, var(--mj-bg-surface));\n    border: 1px solid var(--mj-status-info-border);\n    color: var(--mj-status-info-text);\n    font-size: 0.85rem;\n    line-height: 1.4;\n    margin-bottom: 14px;\n}\n.dry-banner i {\n    margin-top: 2px;\n}\n\n/* effective config row */\n.dry-effective {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: center;\n    gap: 10px;\n    margin-bottom: 14px;\n}\n.dry-eff-item {\n    font-size: 0.8rem;\n    color: var(--mj-text-secondary);\n}\n.dry-eff-item b {\n    color: var(--mj-text-primary);\n}\n\n.pill {\n    display: inline-flex;\n    align-items: center;\n    gap: 5px;\n    padding: 3px 9px;\n    border-radius: 999px;\n    font-size: 0.72rem;\n    font-weight: 700;\n    letter-spacing: 0.03em;\n}\n.pill.purple {\n    background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface));\n    color: var(--mj-brand-primary);\n}\n\n/* estimate line */\n.dry-estimate {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    font-size: 0.82rem;\n    color: var(--mj-text-secondary);\n    margin-bottom: 12px;\n}\n.dry-est-note {\n    font-style: italic;\n    color: var(--mj-text-muted);\n}\n\n/* summary counts */\n.dry-summary {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 8px;\n    margin-bottom: 14px;\n}\n\n/* disposition pills (status-token backed) */\n.disp {\n    display: inline-flex;\n    align-items: center;\n    padding: 3px 9px;\n    border-radius: 6px;\n    font-size: 0.72rem;\n    font-weight: 700;\n    letter-spacing: 0.02em;\n    white-space: nowrap;\n}\n.disp-apply {\n    background: var(--mj-status-success-bg);\n    color: var(--mj-status-success-text);\n    border: 1px solid var(--mj-status-success-border);\n}\n.disp-inbox {\n    background: var(--mj-status-warning-bg);\n    color: var(--mj-status-warning-text);\n    border: 1px solid var(--mj-status-warning-border);\n}\n.disp-create {\n    background: var(--mj-status-info-bg);\n    color: var(--mj-status-info-text);\n    border: 1px solid var(--mj-status-info-border);\n}\n.disp-reject {\n    background: var(--mj-status-error-bg);\n    color: var(--mj-status-error-text);\n    border: 1px solid var(--mj-status-error-border);\n}\n\n/* table */\n.dry-table {\n    width: 100%;\n    border-collapse: collapse;\n    font-size: 0.84rem;\n    margin-bottom: 14px;\n}\n.dry-table thead th {\n    text-align: left;\n    font-weight: 600;\n    color: var(--mj-text-secondary);\n    padding: 6px 8px;\n    border-bottom: 1px solid var(--mj-border-default);\n}\n.dry-table tbody td {\n    padding: 7px 8px;\n    border-bottom: 1px solid var(--mj-border-subtle);\n    color: var(--mj-text-primary);\n    vertical-align: middle;\n}\n.dry-table tbody tr:hover {\n    background: var(--mj-bg-surface-hover);\n}\n.dry-cell-tag {\n    font-weight: 500;\n}\n.dry-cell-matched {\n    color: var(--mj-text-secondary);\n}\n.dry-col-score {\n    text-align: right;\n    width: 64px;\n}\n.dry-mono {\n    font-family: var(--mj-font-mono, monospace);\n    color: var(--mj-text-secondary);\n}\n\n.dry-disclaimer {\n    display: flex;\n    align-items: flex-start;\n    gap: 8px;\n    font-size: 0.78rem;\n    color: var(--mj-text-muted);\n    line-height: 1.4;\n    margin-bottom: 16px;\n}\n.dry-disclaimer i {\n    margin-top: 2px;\n    color: var(--mj-status-warning);\n}\n\n.dry-empty {\n    text-align: center;\n    padding: 32px 16px;\n    color: var(--mj-text-muted);\n}\n.dry-empty i {\n    font-size: 1.8rem;\n    margin-bottom: 10px;\n    color: var(--mj-text-disabled);\n}\n.dry-empty p {\n    font-size: 0.88rem;\n    line-height: 1.5;\n    max-width: 420px;\n    margin: 0 auto;\n}\n\n.dry-footer {\n    display: flex;\n    gap: 8px;\n    margin-top: 4px;\n}\n"] }]
    }], null, { Show: [{
            type: Input
        }], SourceCard: [{
            type: Input
        }], Closed: [{
            type: Output
        }] }); })();
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ClassifyDryRunPreviewDialogComponent, { className: "ClassifyDryRunPreviewDialogComponent", filePath: "src/AI/components/autotagging/dialogs/dry-run-preview.dialog.component.ts", lineNumber: 46 }); })();
//# sourceMappingURL=dry-run-preview.dialog.component.js.map