/**
 * ImportTab Component
 * Handles import functionality for the selected node
 */
Vue.component('import-tab', {
    props: {
        selectedNode: {
            type: Object,
            default: null
        },
        connection: {
            type: Object,
            default: null
        },
        platformType: {
            type: String,
            default: null
        }
    },
    data() {
        return {
            contentTypes: [],
            inSetup: false,
            targetServers: [],
            targetServer: null,
            targetSite: null,
            targetModules: null,
            targetModule: null,
            targetSource: null,
            targetFields: null,
            AllModules: null,
            targetTypes: null,
            targetType: null,
            moduleDetails: null,
            currentStep: 0,
            mappings: null,
            MappingName: "",
            MappingDescription: "",
            existingMappings: null,
            /** "Immediate" | "Recursive" — stored on mapping and used by import runner */
            childrenSelection: "Immediate",
            /** Same field list as Node Details (GetNodeFields); includes regular + system fields */
            nodeDetailFields: [],
            loadingNodeFields: false,
            /** When set, SaveMapping updates this row (same start node). */
            editingMappingId: null,
            /** Populated by GetTypeDetails — API JSON shape varies (camelCase/PascalCase/nesting). */
            targetFieldsCache: [],
            /** When true, targetModule/targetType watchers skip resets and duplicate fetches during EditMapping restore. */
            restoringImportWizard: false,
            /** Edit only: true while GetImportMappingForEdit runs — hides wizard steps until mappings are ready. */
            editImportLoading: false,
            /** Sitefinity: full list from GetAllContentTypes (same as target “Templates with Fields”). */
            sitefinityAllContentTypes: [],
            sitefinityContentTypesLoadedForId: null,
            loadingSitefinityContentTypes: false,
            /** While GetSiteModules is in flight after a site is chosen. */
            loadingSiteModules: false,
            /** Blog posts: parent blog Id (OData <c>blogs</c>) — saved on mapping as ParentId. */
            blogParentId: null,
            blogParentOptions: [],
            loadingBlogParents: false,
            /** After Next → Mappings: until Sitefinity types/fields are ready. */
            loadingMappingsStep: false,
            /** Import run history dialog (per mapping). */
            importLogVisible: false,
            importLogMapping: null,
            importLogRows: [],
            importLogLoading: false,
            /** 'runs' | 'items' | 'steps' — drill-down from log list → items → step detail */
            importLogView: "runs",
            importLogSelectedRun: null,
            importLogRunDetail: null,
            importLogRunItems: [],
            importLogRunItemsLoading: false,
            importLogSelectedItem: null,
            /** Polling: /api/Import/GetImportMappingStatus per mapping id */
            importMappingStatusById: {},
            /** All DB "running" imports (after stale recovery) — blocks Run globally when non-empty */
            globalRunningImports: [],
            importStatusPollTimer: null,
            /** Sitefinity library providers + default album (per mapping row) for image / related-image fields */
            librarySourcesForSite: [],
            librarySourcesLoading: false,
            libraryFolderDialogOpen: false,
            /** "fieldRow" | "htmlInline" — folder picker for related-image row vs mapping-wide rich-text images */
            libraryFolderDialogMode: "fieldRow",
            libraryFolderDialogRow: null,
            libraryFolderTempProviderKey: "",
            libraryFolderTempAlbumId: "",
            libraryFolderAlbums: [],
            libraryFolderAlbumsLoading: false,
            /** Saved on ChameleonMapping: Sitefinity folder for &lt;img&gt; inside long text / HTML fields */
            htmlInlineImagesLibraryProviderName: "",
            htmlInlineImagesLibraryDataSourceName: "",
            htmlInlineImagesParentAlbumId: "",
            htmlInlineImagesParentAlbumTitle: "",
            htmlInlineImagesParentEntitySet: "albums",
            /** Multi-type plan: assign template per source content type under node */
            useTemplatePlanWizard: true,
            useClassicSingleTypeWizard: false,
            sourceTypesForPlan: [],
            templatePlanRows: [],
            importTemplatesList: [],
            loadingSourceTypesPlan: false,
            templateAssociationsByType: {},
            templateCreateMode: false,
            templateCreateReturnTypeKey: "",
            templatePlanDraftBeforeCreate: null
        };
    },
    computed: {
        /** Mirrors OverviewTab / Node Details: all fields from API, not only tree node cache */
        sourceFields() {
            if (this.nodeDetailFields && this.nodeDetailFields.length > 0) return this.nodeDetailFields;
            if (this.selectedNode && this.selectedNode.fields && this.selectedNode.fields.length > 0) return this.selectedNode.fields;
            return [];
        },
        targetFieldsList() {
            if (this.targetFieldsCache && this.targetFieldsCache.length) {
                return this.targetFieldsCache;
            }
            const md = this.moduleDetails;
            if (!md) return [];
            return this.extractTargetFieldsFromModuleDetailsResponse(md);
        },
        /** Libraries Image/Document/Video: Sitefinity exposes UrlName + Title */
        isLibraryMediaTarget() {
            if (!this.targetType) return false;
            const itemName = (this.targetType.contentTypeItemName || this.targetType.name || "").toLowerCase();
            const full = (this.targetType.contentTypeName || "").toLowerCase();
            if (itemName.includes("image") || full.includes("libraries.model.image")) return true;
            if (itemName.includes("document") || full.includes("libraries.model.document")) return true;
            if (itemName.includes("video") || full.includes("libraries.model.video")) return true;
            return false;
        },
        targetFieldsForMapping() {
            const base = this.targetFieldsList || [];
            if (!this.isLibraryMediaTarget) return base;
            const seen = new Set();
            for (let i = 0; i < base.length; i++) {
                const b = base[i];
                const n = (b.name || b.Name || b.title || b.Title || "").toString().toLowerCase();
                if (n) seen.add(n);
            }
            const head = [];
            if (!seen.has("urlname")) {
                head.push({ title: "UrlName", name: "UrlName", typeName: "ShortText" });
                seen.add("urlname");
            }
            if (!seen.has("title")) {
                head.push({ title: "Title", name: "Title", typeName: "ShortText" });
                seen.add("title");
            }
            return head.length ? head.concat(base) : base;
        },
        /** Alphabetical labels for source field dropdown (mapping step). */
        sourceFieldsSorted() {
            const arr = (this.sourceFields || []).slice();
            const label = (f) => {
                const n = (f.name || f.Name || f.title || f.Title || "").toString();
                const ct = (f.contentType || "").toString();
                const lang = (f.language || "").toString();
                return `${n} ${ct} ${lang}`.trim().toLowerCase();
            };
            arr.sort((a, b) => label(a).localeCompare(label(b), undefined, { sensitivity: "base" }));
            return arr;
        },
        /** Alphabetical labels for target field dropdown (mapping step). */
        targetFieldsForMappingSorted() {
            const arr = (this.targetFieldsForMapping || []).slice();
            arr.sort((a, b) =>
                this.fieldPickName(a).localeCompare(this.fieldPickName(b), undefined, { sensitivity: "base" })
            );
            return arr;
        },
        /** Content types for the selected module (from assessment); pool fallback if <code>targetTypes</code> is empty. */
        moduleContentTypesForDropdown() {
            const raw = this.targetTypes && this.targetTypes.items ? this.targetTypes.items : [];
            if (raw.length) return raw;
            return this._moduleContentTypePoolFallback();
        },
        isUmbracoPlatform() {
            const p = (this.platformType || (this.connection && this.connection.platformType) || "").toString().toLowerCase();
            return p === "umbraco";
        },
        /** Multisite module list (handles PascalCase / camelCase). */
        targetModulesDataSources() {
            const tm = this.targetModules;
            if (!tm) return [];
            const ds = tm.DataSources != null ? tm.DataSources : tm.dataSources;
            return Array.isArray(ds) ? ds : [];
        },
        /** Alphabetical module titles for the Import wizard. */
        targetModulesSortedDataSources() {
            const arr = this.targetModulesDataSources.slice();
            const label = (m) => (m.Title || m.title || m.Name || m.name || "").toString();
            arr.sort((a, b) => label(a).localeCompare(label(b), undefined, { sensitivity: "base" }));
            return arr;
        },
        /** Sorted content-type options for the selected module. */
        moduleContentTypesSorted() {
            const raw = this.moduleContentTypesForDropdown || [];
            return raw.slice().sort((a, b) => {
                const x = (a.contentTypeItemName || a.name || a.Name || "").toString();
                const y = (b.contentTypeItemName || b.name || b.Name || "").toString();
                return x.localeCompare(y, undefined, { sensitivity: "base" });
            });
        },
        /** Module dropdown stays disabled until modules + GetAllContentTypes (init) have finished. */
        sitefinityModuleSelectDisabled() {
            if (!this.targetSite) return true;
            if (this.loadingSiteModules || this.loadingSitefinityContentTypes) return true;
            if (!this.targetModules || !this.targetModulesDataSources.length) return true;
            return false;
        },
        /** True when sfmeta indicates a parent item must be chosen (BlogPost→Blog, Image→Album, hierarchical dynamic types, …). */
        needsParentSelection() {
            const tt = this.targetType;
            if (!tt) return false;
            const req = tt.requiresParentSelection != null ? tt.requiresParentSelection : tt.RequiresParentSelection;
            if (req === true) return true;
            if (req === false) return false;
            return !!this.parentEntitySetFromType(tt);
        },
        /** HTML <option> values are strings — bind connection id, not the server object (avoids <code>[object Object]</code>). */
        targetServerConnectionId: {
            get() {
                if (!this.targetServer) return "";
                const id = this.targetServer.id != null ? this.targetServer.id : this.targetServer.Id;
                return id != null ? String(id) : "";
            },
            set(val) {
                if (val === null || val === undefined || val === "" || !this.targetServers || !this.targetServers.length) {
                    this.targetServer = null;
                    return;
                }
                const id = String(val);
                const found = this.targetServers.find((s) => String(s.id != null ? s.id : s.Id) === id);
                this.targetServer = found || null;
            }
        },
        /** Bind module by stable key (DataSourceName / moduleId / title), not list index — avoids 0/1/2 confusion and reorder bugs. */
        targetModuleSelectKey: {
            get() {
                if (!this.targetModule) return "";
                return this.moduleSelectKey(this.targetModule);
            },
            set(val) {
                if (val === null || val === undefined || val === "") {
                    this.targetModule = null;
                    return;
                }
                const k = String(val);
                const ds = this.targetModulesSortedDataSources;
                const found = ds.find((m) => this.moduleSelectKey(m) === k);
                this.targetModule = found || null;
            }
        },
        /** Bind content type by fullTypeName or id — stable across filtering. */
        targetTypeSelectKey: {
            get() {
                if (!this.targetType) return "";
                return this.contentTypeSelectValue(this.targetType);
            },
            set(val) {
                if (val === null || val === undefined || val === "") {
                    this.targetType = null;
                    return;
                }
                const k = String(val);
                const list = this.moduleContentTypesSorted;
                const found = list.find((t) => this.contentTypeSelectValue(t) === k);
                this.targetType = found || null;
            }
        },
        wizardSteps() {
            return this.useTemplatePlanWizard ? ["Plan & scope", "Assign templates"] : ["Server Info", "Mappings"];
        },
        saveButtonLabel() {
            if (this.templateCreateMode) return "Save template";
            return this.useTemplatePlanWizard ? "Save import plan" : "Save Mappings";
        }
    },
    watch: {
        selectedNode: {
            immediate: true,
            handler(newVal) {
                this.LoadMappings();
                this.loadNodeFieldsForImport();
            }
        },
        connection: {
            immediate: true,
            handler() {
                this.loadNodeFieldsForImport();
                this.LoadMappings();
            }
        },
        existingMappings: {
            handler(val) {
                if (val && val.length) this.startImportStatusPolling();
                else this.stopImportStatusPolling();
            }
        },
        targetServer: {
            handler(newVal, oldVal) {
                const nid = newVal && (newVal.id != null ? newVal.id : newVal.Id);
                const oid = oldVal && (oldVal.id != null ? oldVal.id : oldVal.Id);
                if (nid !== oid) {
                    this.sitefinityAllContentTypes = [];
                    this.sitefinityContentTypesLoadedForId = null;
                    if (!newVal) {
                        this.targetSite = null;
                    } else if (!this.restoringImportWizard && this.serverSites(newVal).length) {
                        const first = this.serverSites(newVal)[0];
                        this.targetSite = first.Id != null ? first.Id : first.id;
                    } else if (!this.restoringImportWizard && !this.serverSites(newVal).length) {
                        this.targetSite = null;
                    }
                }
            }
        },
        targetSite: {
            immediate: true,
            handler(newVal) {
                if (!newVal) {
                    this.targetModules = null;
                    this.targetModule = null;
                    this.blogParentId = null;
                    this.blogParentOptions = [];
                    return;
                }
                if (this.restoringImportWizard) return;
                this.targetModule = null;
                this.targetModules = null;
                this.targetType = null;
                this.targetSource = null;
                this.moduleDetails = null;
                this.targetFieldsCache = [];
                this.blogParentId = null;
                this.blogParentOptions = [];
                this.librarySourcesForSite = [];
                this.GetSiteModules(newVal);
            }
        },
        targetSource: {
            handler() {
                if (this.restoringImportWizard) return;
                if (this.needsParentSelection) this.$nextTick(() => this.loadParentOptions());
            }
        },
        targetModule: {
            immediate: true,
            handler(newVal) {
                if (newVal) {
                    if (this.restoringImportWizard) return;
                    this.targetType = null;
                    this.targetSource = null;
                    this.moduleDetails = null;
                    this.targetFieldsCache = [];
                    this.GetModuleDetails();
                }
            }
        },
        targetType: {
            immediate: true,
            handler(newVal) {
                if (newVal) {
                    if (!this.restoringImportWizard) {
                        this.GetTypeDetails();
                    }
                }
                if (!this.needsParentSelection) {
                    this.blogParentId = null;
                    this.blogParentOptions = [];
                } else if (!this.restoringImportWizard) {
                    this.$nextTick(() => this.loadParentOptions());
                }
            }
        },
        childrenSelection: {
            handler() {
                if (this.isUmbracoPlatform) this.loadNodeFieldsForImport();
            }
        },
        useClassicSingleTypeWizard(val) {
            this.useTemplatePlanWizard = !val;
            // Only reset step when switching *into* classic mode. Turning classic off (e.g. after
            // "Create new mapping" save) must not jump back to step 0 — that hid the Assign templates screen.
            if (this.inSetup && val) {
                this.currentStep = 0;
            }
        }
    },
    destroyed() {
        this.stopImportStatusPolling();
    },
    methods: {
        /** Stable <option> value for a multisite module row (aligns with pickDataSourceNameForModule / filter). */
        moduleSelectKey(m) {
            if (!m) return "";
            const links = m.Links || m.links;
            if (Array.isArray(links) && links.length > 0) {
                const l0 = links[0];
                const ds = l0 && (l0.DataSourceName != null ? l0.DataSourceName : l0.dataSourceName);
                if (ds != null && String(ds).trim() !== "") return "ds:" + String(ds).trim();
            }
            const mid = m.moduleId != null ? m.moduleId : m.ModuleId;
            if (mid != null && String(mid).trim() !== "") return "mid:" + String(mid).trim();
            const title = (m.Title || m.Name || m.title || m.name || "").toString().trim();
            if (title) return "title:" + title.toLowerCase();
            return "";
        },
        contentTypeSelectValue(t) {
            if (!t) return "";
            const fn = t.fullTypeName != null ? t.fullTypeName : t.FullTypeName;
            if (fn != null && String(fn).trim() !== "") return String(fn).trim();
            const id = t.contentTypeId != null ? t.contentTypeId : t.ContentTypeId;
            if (id != null && String(id).trim() !== "") return "id:" + String(id).trim();
            return "";
        },
        moduleLabelFromModule(m) {
            if (!m) return "";
            const s = (m.Title || m.title || m.Name || m.name || "").toString().trim();
            return s || "Module";
        },
        sourceConnectionId() {
            if (!this.connection) return null;
            return this.connection.id != null ? this.connection.id : this.connection.Id;
        },
        sourceNodeId() {
            if (!this.selectedNode) return "";
            return this.selectedNode.id != null ? this.selectedNode.id : this.selectedNode.Id;
        },
        serverSites(server) {
            if (!server) return [];
            const parsed = server.parsedResponse || server.ParsedResponse || null;
            if (!parsed) return [];
            const items = parsed.Items != null ? parsed.Items : parsed.items;
            return Array.isArray(items) ? items : [];
        },
        looksLikeSitefinityFieldObject(o) {
            if (!o || typeof o !== "object") return false;
            return o.name != null || o.Name != null || o.title != null || o.Title != null;
        },
        extractTargetFieldsFromModuleDetailsResponse(md) {
            if (!md || typeof md !== "object") return [];
            var r = md.result != null ? md.result : md.Result;
            if (r && typeof r === "object") {
                var ti = r.typeInfo != null ? r.typeInfo : r.TypeInfo;
                if (ti && typeof ti === "object") {
                    var fields = ti.fields != null ? ti.fields : ti.Fields;
                    if (Array.isArray(fields) && fields.length) return fields;
                    var inner = ti.typeInfo != null ? ti.typeInfo : ti.TypeInfo;
                    if (inner && typeof inner === "object") {
                        fields = inner.fields != null ? inner.fields : inner.Fields;
                        if (Array.isArray(fields) && fields.length) return fields;
                    }
                }
                var rf = r.fields != null ? r.fields : r.Fields;
                if (Array.isArray(rf) && rf.length) return rf;
            }
            var topF = md.fields != null ? md.fields : md.Fields;
            if (Array.isArray(topF) && topF.length) return topF;
            var stack = [md];
            var seen = [];
            while (stack.length) {
                var cur = stack.pop();
                if (!cur || typeof cur !== "object") continue;
                if (seen.indexOf(cur) !== -1) continue;
                seen.push(cur);
                if (Array.isArray(cur)) {
                    if (cur.length && this.looksLikeSitefinityFieldObject(cur[0])) return cur;
                    for (var ai = 0; ai < cur.length; ai++) stack.push(cur[ai]);
                    continue;
                }
                var keys = Object.keys(cur);
                for (var ki = 0; ki < keys.length; ki++) {
                    var v = cur[keys[ki]];
                    if (Array.isArray(v) && v.length && this.looksLikeSitefinityFieldObject(v[0])) return v;
                    if (v && typeof v === "object") stack.push(v);
                }
            }
            return [];
        },
        SetupImport() {
            this.editingMappingId = null;
            this.templateCreateMode = false;
            this.templateCreateReturnTypeKey = "";
            this.templatePlanDraftBeforeCreate = null;
            this.childrenSelection = "Immediate";
            this.targetServer = null;
            this.targetSite = null;
            this.targetModules = null;
            this.targetModule = null;
            this.targetSource = null;
            this.targetFields = null;
            this.AllModules = null;
            this.targetTypes = null;
            this.targetType = null;
            this.moduleDetails = null;
            this.targetFieldsCache = [];
            this.sitefinityAllContentTypes = [];
            this.sitefinityContentTypesLoadedForId = null;
            this.blogParentId = null;
            this.blogParentOptions = [];
            this.mappings = null;
            this.MappingName = "";
            this.MappingDescription = "";
            this.currentStep = 0;
            this.editImportLoading = false;
            this.inSetup = true;
            this.htmlInlineImagesLibraryProviderName = "";
            this.htmlInlineImagesLibraryDataSourceName = "";
            this.htmlInlineImagesParentAlbumId = "";
            this.htmlInlineImagesParentAlbumTitle = "";
            this.htmlInlineImagesParentEntitySet = "albums";
            this.useTemplatePlanWizard = true;
            this.useClassicSingleTypeWizard = false;
            this.templatePlanRows = [];
            this.sourceTypesForPlan = [];
            this.importTemplatesList = [];
            this.templateAssociationsByType = {};
            this.loadNodeFieldsForImport();
            this.GetSitefinityConnections();
        },
        async loadNodeFieldsForImport() {
            if (!this.selectedNode || !this.connection) {
                this.nodeDetailFields = [];
                return;
            }
            const nodeId = this.selectedNode.id || this.selectedNode.Id;
            const connId = this.connection.id || this.connection.Id;
            if (!nodeId || connId == null) {
                this.nodeDetailFields = [];
                return;
            }
            this.loadingNodeFields = true;
            try {
                if (this.isUmbracoPlatform) {
                    const scope = this.childrenSelection === "Recursive" ? "Recursive" : "Immediate";
                    const res = await fetch(
                        `/api/ContentTree/GetUmbracoImportAggregateFields?nodeId=${encodeURIComponent(nodeId)}&childrenScope=${encodeURIComponent(scope)}&connectionId=${encodeURIComponent(connId)}`
                    );
                    const data = await res.json();
                    if (data.success === false) {
                        this.nodeDetailFields = [];
                    } else {
                        this.nodeDetailFields = data.fields || [];
                    }
                    return;
                }
                const platformToUse = this.platformType || this.connection.platformType || "SharePoint";
                const res = await fetch(
                    `/api/ContentTree/GetNodeFields?id=${encodeURIComponent(nodeId)}&platformType=${encodeURIComponent(platformToUse)}&connectionId=${encodeURIComponent(connId)}`
                );
                const data = await res.json();
                if (data.success === false) {
                    this.nodeDetailFields = (this.selectedNode.fields && this.selectedNode.fields.slice()) || [];
                } else {
                    this.nodeDetailFields = data.fields || [];
                }
            } catch (e) {
                console.error("loadNodeFieldsForImport:", e);
                this.nodeDetailFields = (this.selectedNode.fields && this.selectedNode.fields.slice()) || [];
            } finally {
                this.loadingNodeFields = false;
            }
        },
        /** When <code>targetTypes</code> is empty, rebuild from merged assessment list + module filter. */
        _moduleContentTypePoolFallback() {
            if (!this.sitefinityAllContentTypes || !this.sitefinityAllContentTypes.length || !this.targetModule) return [];
            const filtered = this.filterContentTypesForModule(this.sitefinityAllContentTypes, this.targetModule);
            return filtered.map((t) => this.mapCtToDropdownRow(t));
        },
        /** If the module exposes exactly one content type, select it (convenience). */
        ensureSingleContentTypeSelected() {
            if (this.restoringImportWizard) return;
            const list = this.moduleContentTypesSorted;
            if (!list || list.length !== 1) return;
            if (!this.targetType) this.targetType = list[0];
        },
        async goNextStep() {
            if (this.useTemplatePlanWizard && this.currentStep === 0) {
                const connId = this.sourceConnectionId();
                const nodeId = this.sourceNodeId();
                if (connId == null || nodeId === "") {
                    Helpers.showNotification("Connect and select a tree node first.", "error");
                    return;
                }
                if (!this.MappingName || !this.MappingName.trim()) {
                    Helpers.showNotification("Enter a mapping name.", "error");
                    return;
                }
                await this.loadTemplatePlanStepData();
                this.currentStep++;
                if (this.isUmbracoPlatform) {
                    this.$nextTick(() => this.loadNodeFieldsForImport());
                }
                return;
            }
            if (this.currentStep === 0 && this.needsParentSelection) {
                if (!this.targetSource) {
                    Helpers.showNotification("Select a provider for the target content type.", "error");
                    return;
                }
                if (!this.blogParentId) {
                    Helpers.showNotification("Select a parent item for the target content type.", "error");
                    return;
                }
            }
            if (this.currentStep === 0 && this.targetServer && this.targetSite) {
                if (this.loadingSiteModules || this.loadingSitefinityContentTypes) {
                    Helpers.showNotification("Wait for modules and Sitefinity content types to finish loading.", "warning");
                    return;
                }
                if (!this.targetModule) {
                    Helpers.showNotification("Select a Sitefinity module.", "error");
                    return;
                }
                if (this.targetModule && this.moduleContentTypesSorted.length && !this.targetType) {
                    Helpers.showNotification("Select a content type before continuing to mappings.", "error");
                    return;
                }
                this.loadingMappingsStep = true;
                try {
                    await this.prepareSitefinityBeforeMappings();
                } finally {
                    this.loadingMappingsStep = false;
                }
            }
            this.currentStep++;
            if (this.isUmbracoPlatform) {
                this.$nextTick(() => this.loadNodeFieldsForImport());
            }
        },
        /** Ensures assessment cache + module/type field lists before showing the Mappings step. */
        async prepareSitefinityBeforeMappings() {
            await this.ensureSitefinityAllContentTypes();
            if (this.targetModule) {
                await this.GetModuleDetails();
            }
            if (this.targetType) {
                await this.GetTypeDetails();
            }
            if (this.targetSite && this.targetServerId() != null) {
                await this.loadLibrarySourcesForSite();
            }
        },
        async GetSitefinityConnections() {
            try {
                const connections = await fetch("/api/SitefinityConnectivity/GetAvailableConnectionStrings");
                const json = await connections.json();
                
                for (let i = 0; i < json.length; i++) {
                    const serverToProcess = json[i];
                    try {
                        const raw = serverToProcess.rawSiteResponse != null
                            ? serverToProcess.rawSiteResponse
                            : serverToProcess.RawSiteResponse;
                        if (raw) {
                            const jsonResult = JSON.parse(raw);
                            if (jsonResult) {
                                serverToProcess.parsedResponse = jsonResult;
                            }
                        }
                    } catch (err) {
                        console.error('Error parsing site response:', err);
                    }
                }

                this.targetServers = json;
            } catch (error) {
                console.error('Error fetching connections:', error);
                Helpers.showNotification('Error loading connections', 'error');
            }
        },
        async GetSiteModules(siteId) {
            this.loadingSiteModules = true;
            try {
                const connId = this.targetServer && (this.targetServer.id != null ? this.targetServer.id : this.targetServer.Id);
                const modulesJson = await fetch(`/api/SitefinityConnectivity/GetSiteModules?siteId=${siteId}&connectionId=${connId}`).then((r) => r.json());
                this.targetModules = JSON.parse(modulesJson.result);
                await this.fetchSitefinityAllContentTypes();
            } catch (error) {
                console.error('Error fetching site modules:', error);
                Helpers.showNotification('Error loading site modules', 'error');
                this.targetModules = null;
            } finally {
                this.loadingSiteModules = false;
            }
        },
        /** Same shape as GetSiteModuleDetails API response (wrapper with result). */
        applySiteModuleDetailsFromFetchJson(json) {
            const r = json && json.result ? json.result : {};
            const ct = r.contentTypes || r.ContentTypes;
            const md = r.moduleDetails || r.ModuleDetails;
            if (ct) {
                const arr = ct.items || ct.Items;
                const list = Array.isArray(arr) ? arr : [];
                this.targetTypes = { items: list };
            } else {
                this.targetTypes = { items: [] };
            }
            if (md) {
                const modItems = md.items || md.Items;
                this.AllModules = modItems != null ? Object.assign({}, md, { items: modItems }) : md;
            }
        },
        targetServerId() {
            if (!this.targetServer) return null;
            return this.targetServer.id != null ? this.targetServer.id : this.targetServer.Id;
        },
        async fetchSitefinityAllContentTypes() {
            const tsId = this.targetServerId();
            if (tsId == null) return;
            this.loadingSitefinityContentTypes = true;
            try {
                const res = await fetch(`/api/SitefinityConnectivity/GetAllContentTypes?connectionId=${encodeURIComponent(tsId)}`);
                const json = await res.json();
                const raw = json.contentTypes != null ? json.contentTypes : json.ContentTypes;
                this.sitefinityAllContentTypes = Array.isArray(raw) ? raw : [];
                this.sitefinityContentTypesLoadedForId = tsId;
            } catch (e) {
                console.error("fetchSitefinityAllContentTypes", e);
                this.sitefinityAllContentTypes = [];
                Helpers.showNotification("Could not load Sitefinity content types.", "error");
            } finally {
                this.loadingSitefinityContentTypes = false;
            }
        },
        async ensureSitefinityAllContentTypes() {
            const tsId = this.targetServerId();
            if (tsId == null) return;
            if (this.sitefinityContentTypesLoadedForId === tsId && this.sitefinityAllContentTypes.length > 0) return;
            await this.fetchSitefinityAllContentTypes();
        },
        /** OData segment for the parent collection (from metadata <c>Parent</c> binding target). */
        parentEntitySetFromType(tt) {
            if (!tt) return "";
            const pe = tt.parentEntitySet != null ? tt.parentEntitySet : tt.ParentEntitySet;
            return pe != null ? String(pe).trim() : "";
        },
        async loadParentOptions() {
            const pe = this.parentEntitySetFromType(this.targetType);
            if (!pe || !this.targetSite || !this.targetSource) {
                this.blogParentOptions = [];
                return;
            }
            const cid = this.targetServerId();
            if (cid == null) return;
            this.loadingBlogParents = true;
            try {
                const u = "/api/SitefinityConnectivity/GetParentList?connectionId=" + encodeURIComponent(cid)
                    + "&siteId=" + encodeURIComponent(this.targetSite)
                    + "&providerName=" + encodeURIComponent(this.targetSource)
                    + "&entitySet=" + encodeURIComponent(pe);
                const res = await fetch(u);
                if (!res.ok) {
                    this.blogParentOptions = [];
                    return;
                }
                const json = await res.json();
                const raw = json.blogs != null ? json.blogs : json.Blogs;
                const arr = Array.isArray(raw) ? raw : [];
                this.blogParentOptions = arr.map((b) => ({
                    id: b.id != null ? b.id : b.Id,
                    title: (b.title != null ? b.title : b.Title) || ""
                }));
            } catch (e) {
                console.error("loadParentOptions", e);
                this.blogParentOptions = [];
            } finally {
                this.loadingBlogParents = false;
            }
        },
        /** Map assessment row to import dropdown shape (contentTypeId / contentTypeItemName / contentTypeName). */
        mapCtToDropdownRow(ct) {
            const full = (ct.fullTypeName != null ? ct.fullTypeName : ct.FullTypeName) || "";
            const name = (ct.name != null ? ct.name : ct.Name) || "";
            const ctid = ct.contentTypeId != null ? ct.contentTypeId : ct.ContentTypeId;
            const ctin = ct.contentTypeItemName != null ? ct.contentTypeItemName : ct.ContentTypeItemName;
            const label = (ctin && String(ctin).trim() !== "") ? ctin : (name || full);
            const peRaw = ct.parentEntitySet != null ? ct.parentEntitySet : ct.ParentEntitySet;
            const peNorm = peRaw != null && String(peRaw).trim() !== "" ? String(peRaw).trim() : null;
            const pft = ct.parentFullTypeName != null ? ct.parentFullTypeName : ct.ParentFullTypeName;
            const pftNorm = pft != null && String(pft).trim() !== "" ? String(pft).trim() : null;
            const rps = ct.requiresParentSelection != null ? ct.requiresParentSelection : ct.RequiresParentSelection;
            const o = Object.assign({}, ct, {
                contentTypeId: ctid,
                ContentTypeId: ctid,
                contentTypeItemName: label,
                ContentTypeItemName: label,
                contentTypeName: full,
                ContentTypeName: full,
                name: name || full,
                Name: name || full,
                parentEntitySet: peNorm,
                ParentEntitySet: peNorm,
                parentFullTypeName: pftNorm,
                ParentFullTypeName: pftNorm
            });
            if (rps === true || rps === false) {
                o.requiresParentSelection = rps;
                o.RequiresParentSelection = rps;
            }
            return o;
        },
        /**
         * Restrict GetAllContentTypes list to the selected module (Blogs, News, Libraries, …).
         * Prefer same moduleId after matching by OData entity set or title/fullTypeName.
         */
        filterContentTypesForModule(all, targetModule) {
            if (!all || !all.length || !targetModule) return [];
            const title = (targetModule.Title || targetModule.Name || "").trim();
            const titleLower = title.toLowerCase();
            const pick = this.pickDataSourceNameForModule();
            const pickNorm = pick.toLowerCase().replace(/\s+/g, "");

            const byModuleName = all.filter((t) => {
                const mn = String((t.moduleName != null ? t.moduleName : t.ModuleName) ?? "").trim().toLowerCase();
                if (!mn) return false;
                if (pickNorm && mn.replace(/\s+/g, "") === pickNorm) return true;
                if (titleLower && mn === titleLower) return true;
                return false;
            });
            let bucket = byModuleName;
            if (!bucket.length) {
                bucket = all.filter((t) => {
                    const es = String((t.entitySet != null ? t.entitySet : t.EntitySet) ?? "").toLowerCase().replace(/\s+/g, "");
                    if (!pickNorm || !es) return false;
                    if (es === pickNorm) return true;
                    if (es + "s" === pickNorm || pickNorm + "s" === es) return true;
                    return es === pickNorm.replace(/s$/, "") || pickNorm === es.replace(/s$/, "");
                });
            }
            if (!bucket.length && title) {
                const tl = title.toLowerCase();
                bucket = all.filter((t) => {
                    const fn = String((t.fullTypeName != null ? t.fullTypeName : t.FullTypeName) ?? "").toLowerCase();
                    const nm = String((t.name != null ? t.name : t.Name) ?? "").toLowerCase();
                    return fn.includes(tl) || nm.includes(tl);
                });
            }
            if (!bucket.length) return [];

            const mid0 = bucket[0].moduleId != null ? bucket[0].moduleId : bucket[0].ModuleId;
            if (mid0 != null && mid0 !== "") {
                const mid = String(mid0);
                const same = all.filter((t) => String(t.moduleId != null ? t.moduleId : t.ModuleId || "") === mid);
                if (same.length) return same;
            }
            return bucket;
        },
        buildSyntheticAllModulesFromModule() {
            const modName = (this.targetModule && (this.targetModule.Name || this.targetModule.Title) || "").trim();
            const list = this.targetTypes && this.targetTypes.items ? this.targetTypes.items : [];
            let mid = list.length > 0 ? (list[0].moduleId != null ? list[0].moduleId : list[0].ModuleId) : null;
            if (mid == null || mid === "") {
                mid = this.targetModule && (this.targetModule.moduleId != null ? this.targetModule.moduleId : this.targetModule.ModuleId);
            }
            this.AllModules = {
                items: [{
                    contentTypeName: modName,
                    ContentTypeName: modName,
                    moduleId: mid,
                    ModuleId: mid
                }]
            };
        },
        applyTargetTypesFromAllContentTypes() {
            const filtered = this.filterContentTypesForModule(this.sitefinityAllContentTypes, this.targetModule);
            const mapped = filtered.map((t) => this.mapCtToDropdownRow(t));
            this.targetTypes = { items: mapped };
            this.buildSyntheticAllModulesFromModule();
        },
        async GetModuleDetails() {
            try {
                await this.ensureSitefinityAllContentTypes();
                if (!this.sitefinityAllContentTypes.length) {
                    this.targetTypes = { items: [] };
                    this.AllModules = null;
                    Helpers.showNotification("No Sitefinity content types loaded. Connect to the target and wait for assessment, then try again.", "warning");
                    return;
                }
                this.applyTargetTypesFromAllContentTypes();
                this.$nextTick(() => this.ensureSingleContentTypeSelected());
            } catch (error) {
                console.error("GetModuleDetails", error);
                Helpers.showNotification("Error loading module content types", "error");
            }
        },
        /** Same shape as GetTypeDetails API response (full JSON body). */
        applyTypeDetailsFromFetchJson(moduleDetailsJson) {
            this.moduleDetails = moduleDetailsJson;
            this.targetFieldsCache = this.extractTargetFieldsFromModuleDetailsResponse(moduleDetailsJson);
        },
        /** Fields from GetAllContentTypes / assessment cache (Title, Name, TypeName, …). */
        normalizeFieldsFromAssessment(fields) {
            if (!Array.isArray(fields) || !fields.length) return [];
            return fields.map((f) => {
                const name = f.name != null ? f.name : f.Name;
                const title = f.title != null ? f.title : f.Title;
                const typeName = f.typeName != null ? f.typeName : f.TypeName;
                return {
                    name: name,
                    Name: name,
                    title: title || name,
                    Title: title || name,
                    typeName: typeName,
                    TypeName: typeName
                };
            });
        },
        appendUrlNameFieldIfMissing(fields) {
            const arr = Array.isArray(fields) ? fields.slice() : [];
            const seen = new Set();
            for (let i = 0; i < arr.length; i++) {
                const n = (arr[i].name || arr[i].Name || "").toString().toLowerCase();
                if (n) seen.add(n);
            }
            if (!seen.has("urlname")) {
                arr.push({ title: "UrlName", name: "UrlName", Title: "UrlName", Name: "UrlName", typeName: "ShortText", TypeName: "ShortText" });
            }
            return arr;
        },
        async GetTypeDetails() {
            try {
                const t = this.targetType;
                if (!t) {
                    this.targetFieldsCache = [];
                    return;
                }
                const embedded = t.fields != null ? t.fields : t.Fields;
                if (Array.isArray(embedded) && embedded.length > 0) {
                    let norm = this.normalizeFieldsFromAssessment(embedded);
                    norm = this.appendUrlNameFieldIfMissing(norm);
                    this.targetFieldsCache = norm;
                    this.moduleDetails = null;
                    return;
                }

                const tsId = this.targetServerId();
                const modName = this.targetModule && (this.targetModule.Name || this.targetModule.Title);
                const items = this.AllModules ? (this.AllModules.items || this.AllModules.Items || []) : [];
                const moduleRows = modName
                    ? items.filter((i) => {
                        const cn = i.contentTypeName || i.ContentTypeName || "";
                        return cn === modName || (cn && modName && String(cn).toLowerCase() === String(modName).toLowerCase());
                    })
                    : [];
                let mid = null;
                if (moduleRows && moduleRows.length > 0) {
                    mid = moduleRows[0].moduleId != null ? moduleRows[0].moduleId : moduleRows[0].ModuleId;
                }
                if (mid == null || mid === "") {
                    mid = this.pickModuleIdForSave();
                }

                let tid = this.pickTypeIdForSave();

                if (!tid || tid === "") {
                    console.warn("GetTypeDetails: missing typeId", { mid, tid });
                    this.targetFieldsCache = [];
                    return;
                }

                const dsn = this.pickDataSourceNameForModule();
                const dsnQ = dsn ? `&dataSourceName=${encodeURIComponent(dsn)}` : "";
                const midStr = mid != null && mid !== "" ? String(mid) : "";

                this.targetFieldsCache = [];
                const url = `/api/SitefinityConnectivity/GetTypeDetails?connectionId=${tsId}&moduleId=${encodeURIComponent(midStr)}&typeId=${encodeURIComponent(String(tid))}${dsnQ}`;
                const moduleInfo = await fetch(url);
                if (!moduleInfo.ok) {
                    console.error("GetTypeDetails failed", moduleInfo.status);
                    this.targetFieldsCache = [];
                    Helpers.showNotification("Could not load target content type fields from Sitefinity.", "error");
                    return;
                }
                const moduleDetails = await moduleInfo.json();
                this.applyTypeDetailsFromFetchJson(moduleDetails);
            } catch (error) {
                this.targetFieldsCache = [];
                console.error("Error fetching type details:", error);
                Helpers.showNotification("Error loading type details", "error");
            }
        },
        async AddMapping() {
            if (this.mappings == null) {
                this.mappings = [];
            }
            this.mappings.push({
                Id: new Date().getTime(),
                Source: "",
                Target: "",
                libraryProviderName: "",
                libraryDataSourceName: "",
                libraryParentAlbumId: "",
                libraryParentEntitySet: "albums",
                libraryParentAlbumTitle: ""
            });
        },
        fieldPickName(obj) {
            if (obj == null) return "";
            if (typeof obj === "string") return obj;
            if (obj.name != null && obj.name !== "") return obj.name;
            if (obj.Name != null && obj.Name !== "") return obj.Name;
            if (obj.title != null && obj.title !== "") return obj.title;
            if (obj.Title != null && obj.Title !== "") return obj.Title;
            return "";
        },
        /** Sitefinity JSON may use camelCase or PascalCase; module row lives on AllModules.items. */
        pickModuleRowFromAllModules() {
            if (!this.targetModule || !this.AllModules) return null;
            const tmName = (this.targetModule.Name || this.targetModule.Title || "").trim();
            const items = this.AllModules.items || this.AllModules.Items || [];
            for (let i = 0; i < items.length; i++) {
                const row = items[i];
                const cn = (row.contentTypeName || row.ContentTypeName || "").trim();
                if (cn && tmName && cn === tmName) return row;
            }
            const lower = tmName.toLowerCase();
            for (let i = 0; i < items.length; i++) {
                const row = items[i];
                const cn = (row.contentTypeName || row.ContentTypeName || "").trim().toLowerCase();
                if (cn && lower && cn === lower) return row;
            }
            return null;
        },
        pickModuleIdForSave() {
            if (this.targetType) {
                const tid = this.targetType.moduleId != null ? this.targetType.moduleId : this.targetType.ModuleId;
                if (tid != null && tid !== "") return String(tid);
            }
            const row = this.pickModuleRowFromAllModules();
            if (row) {
                const id = row.moduleId != null ? row.moduleId : row.ModuleId;
                if (id != null && id !== "") return String(id);
            }
            const items = this.targetTypes && this.targetTypes.items ? this.targetTypes.items : [];
            if (items.length > 0) {
                const id0 = items[0].moduleId != null ? items[0].moduleId : items[0].ModuleId;
                if (id0 != null && id0 !== "") return String(id0);
            }
            if (this.targetModule) {
                const id = this.targetModule.moduleId != null ? this.targetModule.moduleId : this.targetModule.ModuleId;
                if (id != null && id !== "") return String(id);
            }
            const dsn = this.pickDataSourceNameForModule();
            if (dsn) return dsn;
            const tm = this.targetModule && (this.targetModule.Name || this.targetModule.Title);
            return tm ? String(tm).trim() : "";
        },
        /** Same key as SitefinityInitState / Excel: first link DataSourceName, else module Title (cache is per data source). */
        pickDataSourceNameForModule() {
            if (!this.targetModule) return "";
            const links = this.targetModule.Links || this.targetModule.links;
            if (Array.isArray(links) && links.length > 0) {
                const l0 = links[0];
                const ds = l0 && (l0.DataSourceName != null ? l0.DataSourceName : l0.dataSourceName);
                if (ds != null && String(ds).trim() !== "") return String(ds).trim();
            }
            const t = this.targetModule.Title != null ? this.targetModule.Title : this.targetModule.title;
            if (t != null && String(t).trim() !== "") return String(t).trim();
            const n = this.targetModule.Name != null ? this.targetModule.Name : this.targetModule.name;
            return n != null && String(n).trim() !== "" ? String(n).trim() : "";
        },
        pickTypeIdForSave() {
            if (!this.targetType) return "";
            const t = this.targetType;
            const id =
                t.contentTypeId != null ? t.contentTypeId
                : t.ContentTypeId != null ? t.ContentTypeId
                : t.id != null ? t.id
                : t.Id;
            if (id != null && id !== "") return String(id);
            const full = t.fullTypeName != null ? t.fullTypeName : t.FullTypeName;
            if (full != null && String(full).trim() !== "") return String(full).trim();
            return "";
        },
        /** Vue 2 <select> compares option values by reference; rebind saved JSON to actual option objects. */
        fieldKeyLoose(f) {
            if (!f) return "";
            const n = f.name != null ? f.name : f.Name;
            const t = f.title != null ? f.title : f.Title;
            return String(n || t || "").trim().toLowerCase();
        },
        matchFieldInOptions(options, saved) {
            if (!saved || !options || !options.length) return null;
            const candidates = new Set();
            [saved.name, saved.Name, saved.title, saved.Title, saved.typeName, saved.TypeName].forEach((x) => {
                if (x != null && x !== "") candidates.add(String(x).trim().toLowerCase());
            });
            for (let i = 0; i < options.length; i++) {
                const o = options[i];
                const k = this.fieldKeyLoose(o);
                if (k && candidates.has(k)) return o;
            }
            return null;
        },
        rebindMappingRowSelectionsToDropdowns() {
            const targets = this.targetFieldsForMapping || [];
            const sources = this.sourceFields || [];
            if (!this.mappings || !this.mappings.length) return;
            for (let i = 0; i < this.mappings.length; i++) {
                const row = this.mappings[i];
                if (row.Target) {
                    const m = this.matchFieldInOptions(targets, row.Target);
                    if (m) this.$set(this.mappings[i], "Target", m);
                }
                if (row.Source) {
                    const m = this.matchFieldInOptions(sources, row.Source);
                    if (m) this.$set(this.mappings[i], "Source", m);
                }
            }
        },
        /** OData segment from assessment (<code>entitySet</code>); normalizes e.g. <code>blogpost</code> → <code>blogposts</code>. */
        normalizeODataSegmentForSave(t) {
            if (!t) return "";
            const es = t.entitySet != null ? t.entitySet : t.EntitySet;
            if (es == null || String(es).trim() === "") return "";
            let s = String(es).trim().toLowerCase().replace(/\s+/g, "");
            if (s === "blogpost") s = "blogposts";
            return s;
        },
        /** Sitefinity OData entity set for POST/PATCH — from selected content type <code>entitySet</code>. */
        pickODataSegmentForSave() {
            const tt = this.targetType;
            if (tt) {
                const n = this.normalizeODataSegmentForSave(tt);
                if (n) return n;
            }
            const m = this.targetModule;
            if (m && (m.Name || m.Title)) return String(m.Name || m.Title).trim();
            return "";
        },
        sourceTypeKey(templateId, templateName) {
            const id = (templateId || "").toString().trim().toLowerCase();
            if (id) return "id:" + id;
            const nm = (templateName || "").toString().trim().toLowerCase();
            if (nm) return "name:" + nm;
            return "";
        },
        sourceTypeKeyForRow(row) {
            return this.sourceTypeKey(row && row.sourceTemplateId, row && row.sourceTemplateName);
        },
        async loadTemplateAssociationsForConnection() {
            const connId = this.sourceConnectionId();
            this.templateAssociationsByType = {};
            if (connId == null) return;
            try {
                const u = `/api/SitefinityConnectivity/GetMappings?connectionId=${encodeURIComponent(connId)}`;
                const r = await fetch(u);
                const j = await r.json();
                const all = j.result || [];
                const dict = {};
                for (let i = 0; i < all.length; i++) {
                    const m = all[i] || {};
                    const binds = m.typeBindings || m.TypeBindings || [];
                    for (let bi = 0; bi < binds.length; bi++) {
                        const b = binds[bi] || {};
                        const skipMode = (b.skipMode != null ? b.skipMode : b.SkipMode || "Import").toString();
                        const tid = b.importMappingTemplateId != null ? b.importMappingTemplateId : b.ImportMappingTemplateId;
                        if (skipMode !== "Import" || tid == null) continue;
                        const key = this.sourceTypeKey(
                            b.sourceTemplateId != null ? b.sourceTemplateId : b.SourceTemplateId,
                            b.sourceTemplateName != null ? b.sourceTemplateName : b.SourceTemplateName
                        );
                        if (!key) continue;
                        if (!dict[key]) dict[key] = {};
                        dict[key][String(tid)] = true;
                    }
                }
                this.templateAssociationsByType = dict;
            } catch (e) {
                console.error("loadTemplateAssociationsForConnection", e);
            }
        },
        templateOptionsForRow(row) {
            const all = this.importTemplatesList || [];
            const key = this.sourceTypeKeyForRow(row);
            const idsMap = (key && this.templateAssociationsByType && this.templateAssociationsByType[key]) || null;
            const sel = row && row.assignment != null ? String(row.assignment).trim() : "";
            const skipVals = new Set(["", "skipsubtree", "skipitemchildrenonly"]);
            const selIsTemplateId = sel && !skipVals.has(sel.toLowerCase()) && /^\d+$/.test(sel);
            // List every reusable template for this connection so brand-new templates (not yet on any plan)
            // appear. Optionally bubble templates already used for this source type to the top.
            if (!idsMap || !Object.keys(idsMap).length) return all;
            const pickId = (t) => String(t.id != null ? t.id : t.Id);
            const usedHere = (t) => idsMap[pickId(t)] === true;
            const associated = all.filter(usedHere);
            const rest = all.filter((t) => !usedHere(t));
            const ordered = associated.concat(rest);
            if (selIsTemplateId && !ordered.some((t) => pickId(t) === sel)) {
                const missing = all.find((t) => pickId(t) === sel);
                if (missing) ordered.unshift(missing);
            }
            return ordered;
        },
        beginCreateTemplateForRow(row) {
            const retKey = this.sourceTypeKeyForRow(row);
            if (!retKey) {
                Helpers.showNotification("Cannot determine source type for this row.", "error");
                return;
            }
            this.templatePlanDraftBeforeCreate = {
                editingMappingId: this.editingMappingId || 0,
                MappingName: this.MappingName || "",
                MappingDescription: this.MappingDescription || "",
                childrenSelection: this.childrenSelection || "Immediate",
                templatePlanRows: JSON.parse(JSON.stringify(this.templatePlanRows || []))
            };
            this.templateCreateReturnTypeKey = retKey;
            this.templateCreateMode = true;
            this.useClassicSingleTypeWizard = true;
            this.useTemplatePlanWizard = false;
            this.currentStep = 0;
            this.mappings = [];
            this.editingMappingId = 0;
            this.targetServer = null;
            this.targetSite = null;
            this.targetModules = null;
            this.targetModule = null;
            this.targetSource = null;
            this.targetTypes = null;
            this.targetType = null;
            this.moduleDetails = null;
            this.targetFieldsCache = [];
            this.blogParentId = null;
            this.blogParentOptions = [];
            this.htmlInlineImagesLibraryProviderName = "";
            this.htmlInlineImagesLibraryDataSourceName = "";
            this.htmlInlineImagesParentAlbumId = "";
            this.htmlInlineImagesParentAlbumTitle = "";
            this.htmlInlineImagesParentEntitySet = "albums";
            this.MappingName = "";
            this.MappingDescription = "";
        },
        async saveTemplateFromClassicWizard() {
            if (!this.mappings || !this.mappings.length) {
                Helpers.showNotification("Add at least one field mapping.", "error");
                return;
            }
            const connId = this.sourceConnectionId();
            if (connId == null) {
                Helpers.showNotification("Connect a source before saving template.", "error");
                return;
            }
            const mappingsToSave = this.mappings.map((m) => ({
                Source: this.fieldPickName(m.Source),
                Target: this.fieldPickName(m.Target),
                TargetTypeName: m.Target && m.Target.typeName,
                TargetFullDetails: m.Target,
                SourceFullDetails: m.Source,
                libraryProviderName: m.libraryProviderName || null,
                libraryDataSourceName: m.libraryDataSourceName || null,
                libraryParentAlbumId: m.libraryParentAlbumId || null,
                libraryParentEntitySet: m.libraryParentEntitySet || null,
                libraryParentAlbumTitle: m.libraryParentAlbumTitle || null
            }));
            let moduleIdStr = this.pickModuleIdForSave();
            let typeIdStr = this.pickTypeIdForSave();
            if (!moduleIdStr || !typeIdStr) {
                Helpers.showNotification(
                    "Could not resolve Sitefinity module or content type id. Re-select target server/site/module/type/provider.",
                    "error"
                );
                return;
            }
            const payload = {
                id: 0,
                connectionId: connId,
                name: this.MappingName || "New template",
                description: this.MappingDescription || "",
                platformType: this.platformType || "",
                targetServerConnectionId: this.targetServer && (this.targetServer.id != null ? this.targetServer.id : this.targetServer.Id),
                siteId: this.targetSite || "",
                moduleId: moduleIdStr,
                oData: this.pickODataSegmentForSave(),
                typeId: typeIdStr,
                providerName: this.targetSource || "",
                parentId: this.needsParentSelection ? (this.blogParentId || null) : null,
                libraryMediaKind: null,
                htmlInlineImagesLibraryProviderName: this.htmlInlineImagesLibraryProviderName || null,
                htmlInlineImagesLibraryDataSourceName: this.htmlInlineImagesLibraryDataSourceName || null,
                htmlInlineImagesParentAlbumId: this.htmlInlineImagesParentAlbumId || null,
                htmlInlineImagesParentAlbumTitle: this.htmlInlineImagesParentAlbumTitle || null,
                htmlInlineImagesParentEntitySet: this.htmlInlineImagesParentEntitySet || null,
                mappingsJSON: JSON.stringify(mappingsToSave)
            };
            let newTemplateId = null;
            try {
                const res = await fetch("/api/SitefinityConnectivity/SaveImportMappingTemplate", {
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    body: JSON.stringify(payload)
                });
                const json = await res.json();
                if (!res.ok || !json || json.success === false) {
                    Helpers.showNotification((json && (json.message || json.Message)) || "Error saving template.", "error");
                    return;
                }
                newTemplateId = json.id != null ? json.id : json.Id;
            } catch (e) {
                console.error("saveTemplateFromClassicWizard", e);
                Helpers.showNotification("Error saving template.", "error");
                return;
            }
            const draft = this.templatePlanDraftBeforeCreate || null;
            const draftRowsSnapshot = draft ? JSON.parse(JSON.stringify(draft.templatePlanRows || [])) : [];
            const retKey = this.templateCreateReturnTypeKey || "";
            this.templateCreateMode = false;
            this.templatePlanDraftBeforeCreate = null;
            this.useClassicSingleTypeWizard = false;
            this.useTemplatePlanWizard = true;
            this.currentStep = 1;
            if (draft) {
                this.editingMappingId = draft.editingMappingId || 0;
                this.MappingName = draft.MappingName || "";
                this.MappingDescription = draft.MappingDescription || "";
                this.childrenSelection = draft.childrenSelection || "Immediate";
            }
            await this.loadTemplatePlanStepData();
            const prevByKey = {};
            for (let i = 0; i < draftRowsSnapshot.length; i++) {
                const r = draftRowsSnapshot[i];
                const k = this.sourceTypeKey(r.sourceTemplateId, r.sourceTemplateName);
                const a = r.assignment;
                if (k && a != null && String(a).trim() !== "") prevByKey[k] = String(a);
            }
            for (let i = 0; i < this.templatePlanRows.length; i++) {
                const row = this.templatePlanRows[i];
                const k = this.sourceTypeKeyForRow(row);
                if (k && prevByKey[k] != null) row.assignment = prevByKey[k];
            }
            if (retKey && newTemplateId != null) {
                for (let i = 0; i < this.templatePlanRows.length; i++) {
                    const row = this.templatePlanRows[i];
                    if (this.sourceTypeKeyForRow(row) === retKey) {
                        row.assignment = String(newTemplateId);
                        break;
                    }
                }
            }
            this.templateCreateReturnTypeKey = "";
            Helpers.showNotification("Template saved and selected.", "success");
        },
        async loadTemplatePlanStepData() {
            const connId = this.sourceConnectionId();
            const nodeId = this.sourceNodeId();
            const cs = this.childrenSelection || "Immediate";
            this.loadingSourceTypesPlan = true;
            try {
                const uT = `/api/SitefinityConnectivity/GetImportMappingTemplates?connectionId=${encodeURIComponent(connId)}`;
                const uS = `/api/SitefinityConnectivity/GetDistinctImportSourceTypes?connectionId=${encodeURIComponent(connId)}&nodeId=${encodeURIComponent(nodeId)}&childrenSelection=${encodeURIComponent(cs)}`;
                const [rT, rS] = await Promise.all([fetch(uT), fetch(uS)]);
                const jT = await rT.json();
                const jS = await rS.json();
                this.importTemplatesList = jT.result || jT.Result || [];
                const types = jS.result || jS.Result || [];
                this.sourceTypesForPlan = types;
                this.templatePlanRows = types.map((st) => ({
                    sourceTemplateId: st.sourceTemplateId != null ? st.sourceTemplateId : st.SourceTemplateId,
                    sourceTemplateName: st.sourceTemplateName != null ? st.sourceTemplateName : st.SourceTemplateName,
                    itemCount: st.itemCount != null ? st.itemCount : st.ItemCount,
                    assignment: ""
                }));
                await this.loadTemplateAssociationsForConnection();
            } catch (e) {
                console.error("loadTemplatePlanStepData", e);
                Helpers.showNotification("Could not load source types or templates.", "error");
                this.templatePlanRows = [];
            } finally {
                this.loadingSourceTypesPlan = false;
            }
        },
        async saveTemplatePlanMappings() {
            const connId = this.sourceConnectionId();
            const nodeId = this.sourceNodeId();
            if (connId == null || nodeId === "") {
                Helpers.showNotification("Connect and select a tree node (import start node) before saving.", "error");
                return;
            }
            const typeBindings = [];
            const rows = this.templatePlanRows || [];
            for (let i = 0; i < rows.length; i++) {
                const row = rows[i];
                const a = row.assignment;
                if (a === "" || a == null) {
                    Helpers.showNotification(
                        `Choose an import action for type "${row.sourceTemplateName || row.sourceTemplateId || ""}".`,
                        "error"
                    );
                    return;
                }
                if (a === "SkipSubtree") {
                    typeBindings.push({
                        SourceTemplateId: row.sourceTemplateId || null,
                        SourceTemplateName: row.sourceTemplateName || null,
                        ImportMappingTemplateId: null,
                        SkipMode: "SkipSubtree"
                    });
                    continue;
                }
                if (a === "SkipItemChildrenOnly") {
                    typeBindings.push({
                        SourceTemplateId: row.sourceTemplateId || null,
                        SourceTemplateName: row.sourceTemplateName || null,
                        ImportMappingTemplateId: null,
                        SkipMode: "SkipItemChildrenOnly"
                    });
                    continue;
                }
                const tid = Number(a);
                if (!Number.isFinite(tid) || tid <= 0) {
                    Helpers.showNotification("Invalid template selection.", "error");
                    return;
                }
                typeBindings.push({
                    SourceTemplateId: row.sourceTemplateId || null,
                    SourceTemplateName: row.sourceTemplateName || null,
                    ImportMappingTemplateId: tid,
                    SkipMode: "Import"
                });
            }

            const mappingJSON = {
                id: this.editingMappingId || 0,
                Name: this.MappingName,
                Description: this.MappingDescription,
                ConnectionId: connId,
                NodeId: String(nodeId),
                PlatformType: this.platformType,
                ChildrenSelection: this.childrenSelection,
                ImportMode: "TemplatePlan",
                TypeBindings: typeBindings,
                MappingsJSON: "[]",
                TargetServerConnectionId: 0,
                SiteId: "",
                ModuleId: "",
                OData: "",
                TypeId: "",
                ProviderName: "",
                ParentId: null,
                HtmlInlineImagesLibraryProviderName: null,
                HtmlInlineImagesLibraryDataSourceName: null,
                HtmlInlineImagesParentAlbumId: null,
                HtmlInlineImagesParentAlbumTitle: null,
                HtmlInlineImagesParentEntitySet: null
            };

            try {
                await fetch("/api/SitefinityConnectivity/SaveMapping", {
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    body: JSON.stringify(mappingJSON)
                });
                this.editingMappingId = null;
                await this.LoadMappings();
                this.editImportLoading = false;
                this.inSetup = false;
                Helpers.showNotification("Import plan saved.", "success");
                this.$emit("imports-updated");
            } catch (error) {
                console.error("saveTemplatePlanMappings", error);
                Helpers.showNotification("Error saving import plan", "error");
            }
        },
        async SaveMappings() {
            const connId = this.sourceConnectionId();
            const nodeId = this.sourceNodeId();
            if (connId == null || nodeId === "") {
                Helpers.showNotification("Connect and select a tree node (import start node) before saving.", "error");
                return;
            }
            if (this.useTemplatePlanWizard) {
                await this.saveTemplatePlanMappings();
                return;
            }
            if (this.templateCreateMode) {
                await this.saveTemplateFromClassicWizard();
                return;
            }
            if (!this.mappings || !this.mappings.length) {
                Helpers.showNotification("Add at least one field mapping.", "error");
                return;
            }
            const mappingsToSave = this.mappings.map((m) => {
                return {
                    Source: this.fieldPickName(m.Source),
                    Target: this.fieldPickName(m.Target),
                    TargetTypeName: m.Target && m.Target.typeName,
                    TargetFullDetails: m.Target,
                    SourceFullDetails: m.Source,
                    libraryProviderName: m.libraryProviderName || null,
                    libraryDataSourceName: m.libraryDataSourceName || null,
                    libraryParentAlbumId: m.libraryParentAlbumId || null,
                    libraryParentEntitySet: m.libraryParentEntitySet || null,
                    libraryParentAlbumTitle: m.libraryParentAlbumTitle || null
                };
            });

            let moduleIdStr = this.pickModuleIdForSave();
            let typeIdStr = this.pickTypeIdForSave();
            if (!moduleIdStr || !typeIdStr) {
                Helpers.showNotification(
                    "Could not resolve Sitefinity module or content type id. Re-select Target Server, Site, module, content type, and provider, then save again.",
                    "error"
                );
                return;
            }

            const mappingJSON = {
                id: this.editingMappingId || 0,
                Name: this.MappingName,
                Description: this.MappingDescription,
                ConnectionId: connId,
                SiteId: this.targetSite,
                MappingsJSON: JSON.stringify(mappingsToSave),
                ModuleId: moduleIdStr,
                OData: this.pickODataSegmentForSave(),
                TypeId: typeIdStr,
                NodeId: String(nodeId),
                TargetServerConnectionId: this.targetServer && (this.targetServer.id != null ? this.targetServer.id : this.targetServer.Id),
                ProviderName: this.targetSource,
                PlatformType: this.platformType,
                ChildrenSelection: this.childrenSelection,
                LibraryMediaKind: null,
                ParentId: this.needsParentSelection ? (this.blogParentId || null) : null,
                HtmlInlineImagesLibraryProviderName: this.htmlInlineImagesLibraryProviderName || null,
                HtmlInlineImagesLibraryDataSourceName: this.htmlInlineImagesLibraryDataSourceName || null,
                HtmlInlineImagesParentAlbumId: this.htmlInlineImagesParentAlbumId || null,
                HtmlInlineImagesParentAlbumTitle: this.htmlInlineImagesParentAlbumTitle || null,
                HtmlInlineImagesParentEntitySet: this.htmlInlineImagesParentEntitySet || null
            };

            try {
                await fetch("/api/SitefinityConnectivity/SaveMapping", {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(mappingJSON)
                });
                this.editingMappingId = null;
                await this.LoadMappings();
                this.editImportLoading = false;
                this.inSetup = false;
                Helpers.showNotification('Mappings saved successfully', 'success');
                this.$emit('imports-updated');
            } catch (error) {
                console.error('Error saving mappings:', error);
                Helpers.showNotification('Error saving mappings', 'error');
            }
        },
        async LoadMappings() {
            const connId = this.sourceConnectionId();
            const nodeId = this.sourceNodeId();
            this.existingMappings = [];
            if (connId == null || nodeId === "") {
                return;
            }
            try {
                const url = '/api/SitefinityConnectivity/GetMappings?connectionId=' + encodeURIComponent(connId) + '&nodeId=' + encodeURIComponent(String(nodeId));
                const mappings = await fetch(url);
                const json = await mappings.json();
                this.existingMappings = json.result || [];
            } catch (error) {
                console.error('Error loading mappings:', error);
                Helpers.showNotification('Error loading mappings', 'error');
            }
        },
        /** Match saved ModuleId / OData to a module from GetSiteModules (no indexOf('') — that matches every row). */
        findTargetModuleFromDataSources(ds, mapping) {
            if (!mapping || !ds || !ds.length) return null;
            const midRaw = mapping.moduleId != null ? mapping.moduleId : mapping.ModuleId;
            const mid = midRaw != null && String(midRaw).trim() !== "" ? String(midRaw).trim().toLowerCase() : "";
            const odataRaw = mapping.odata != null ? mapping.odata : mapping.OData;
            const odata = odataRaw != null && String(odataRaw).trim() !== "" ? String(odataRaw).trim().toLowerCase() : "";

            if (mid) {
                const byMid = ds.find((m) => {
                    const id = m.moduleId != null ? m.moduleId : m.ModuleId;
                    return id != null && String(id).trim().toLowerCase() === mid;
                });
                if (byMid) return byMid;
            }
            if (odata) {
                return ds.find((m) => {
                    const n = (m.Name != null ? m.Name : "").toString().trim().toLowerCase();
                    const t = (m.Title != null ? m.Title : "").toString().trim().toLowerCase();
                    return n === odata || t === odata;
                }) || null;
            }
            return null;
        },
        mapRowsFromSaved(rows) {
            if (!Array.isArray(rows)) return [];
            return rows.map((row, i) => {
                let src = row.SourceFullDetails || row.sourceFullDetails;
                let tgt = row.TargetFullDetails || row.targetFullDetails;
                if (!src && row.Source != null) {
                    src = typeof row.Source === 'string' ? { name: row.Source, title: row.Source } : row.Source;
                }
                if (!tgt && row.Target != null) {
                    tgt = typeof row.Target === 'string' ? { name: row.Target, title: row.Target } : row.Target;
                }
                return {
                    Id: Date.now() + i,
                    Source: src || null,
                    Target: tgt || null,
                    libraryProviderName: row.libraryProviderName != null ? row.libraryProviderName : row.LibraryProviderName,
                    libraryDataSourceName: row.libraryDataSourceName != null ? row.libraryDataSourceName : row.LibraryDataSourceName,
                    libraryParentAlbumId: row.libraryParentAlbumId != null ? row.libraryParentAlbumId : row.LibraryParentAlbumId,
                    libraryParentEntitySet: row.libraryParentEntitySet != null ? row.libraryParentEntitySet : row.LibraryParentEntitySet,
                    libraryParentAlbumTitle: row.libraryParentAlbumTitle != null ? row.libraryParentAlbumTitle : row.LibraryParentAlbumTitle
                };
            });
        },
        async EditMapping(mapping) {
            if (!mapping || !this.selectedNode) return;
            const startNodeId = String(mapping.nodeId != null ? mapping.nodeId : mapping.NodeId || '');
            const currentId = String(this.sourceNodeId() || '');
            if (startNodeId !== currentId) {
                Helpers.showNotification('This import is bound to another tree node. Select that node in the tree, then open Imports again.', 'warning');
                return;
            }
            this.inSetup = true;
            this.currentStep = 0;
            this.mappings = null;
            try {
                this.restoringImportWizard = true;
                this.editImportLoading = true;
                await this.GetSitefinityConnections();
                const mappingId = mapping.id != null ? mapping.id : mapping.Id;
                const connId = this.sourceConnectionId();
                if (connId == null) {
                    Helpers.showNotification('Connect a source before editing a mapping.', 'error');
                    return;
                }
                const editUrl = '/api/SitefinityConnectivity/GetImportMappingForEdit?mappingId=' + encodeURIComponent(mappingId)
                    + '&sourceConnectionId=' + encodeURIComponent(connId);
                const res = await fetch(editUrl);
                if (!res.ok) {
                    let msg = 'Could not load mapping for editing.';
                    try {
                        const errBody = await res.json();
                        msg = errBody.message || errBody.Message || msg;
                    } catch (e2) { /* ignore */ }
                    Helpers.showNotification(msg, 'error');
                    return;
                }
                const payload = await res.json();
                if (payload.templatePlanEdit) {
                    this.useClassicSingleTypeWizard = false;
                    this.useTemplatePlanWizard = true;
                    const smPlan = payload.mapping || {};
                    this.editingMappingId = smPlan.id != null ? smPlan.id : smPlan.Id;
                    this.MappingName = smPlan.name || smPlan.Name || '';
                    this.MappingDescription = smPlan.description || smPlan.Description || '';
                    this.childrenSelection = smPlan.childrenSelection || smPlan.ChildrenSelection || 'Immediate';
                    this.importTemplatesList = payload.importTemplates || [];
                    await this.loadTemplatePlanStepData();
                    const binds = smPlan.typeBindings || smPlan.TypeBindings || [];
                    for (let ri = 0; ri < this.templatePlanRows.length; ri++) {
                        const row = this.templatePlanRows[ri];
                        const match = binds.find((b) => {
                            const bid = b.sourceTemplateId != null ? b.sourceTemplateId : b.SourceTemplateId;
                            const sid = row.sourceTemplateId;
                            if (bid && sid && String(bid).trim().toLowerCase() === String(sid).trim().toLowerCase()) return true;
                            const bn = (b.sourceTemplateName || b.SourceTemplateName || "").trim().toLowerCase();
                            const rn = (row.sourceTemplateName || "").trim().toLowerCase();
                            return bn && rn && bn === rn;
                        });
                        if (!match) continue;
                        const smode = (match.skipMode || match.SkipMode || "Import").toString();
                        if (smode === "SkipSubtree") row.assignment = "SkipSubtree";
                        else if (smode === "SkipItemChildrenOnly") row.assignment = "SkipItemChildrenOnly";
                        else {
                            const tid = match.importMappingTemplateId != null ? match.importMappingTemplateId : match.ImportMappingTemplateId;
                            row.assignment = tid != null ? String(tid) : "";
                        }
                    }
                    this.currentStep = 1;
                    this.restoringImportWizard = false;
                    this.editImportLoading = false;
                    return;
                }
                const sm = payload.mapping || {};
                const wantServerId = Number(sm.targetServerConnectionId != null ? sm.targetServerConnectionId : sm.TargetServerConnectionId);
                this.targetServerConnectionId = Number.isFinite(wantServerId) ? String(wantServerId) : "";
                if (!this.targetServer) {
                    Helpers.showNotification('Target Sitefinity server not found for this mapping.', 'error');
                    return;
                }
                this.targetModules = payload.targetModules;
                const idx = payload.resolvedModuleIndex;
                const ds = this.targetModules && (this.targetModules.DataSources || this.targetModules.dataSources || []);
                if (!ds || idx == null || idx < 0 || idx >= ds.length) {
                    Helpers.showNotification('Could not resolve module selection from Sitefinity.', 'error');
                    return;
                }
                this.targetSite = sm.siteId != null ? sm.siteId : sm.SiteId;
                this.targetModule = ds[idx];
                const payloadAll = Array.isArray(payload.allContentTypes) ? payload.allContentTypes : [];
                this.sitefinityAllContentTypes = payloadAll;
                this.sitefinityContentTypesLoadedForId = payloadAll.length ? this.targetServerId() : null;
                this.librarySourcesForSite = Array.isArray(payload.librarySources) ? payload.librarySources : [];
                if (payload.siteModuleDetails) {
                    this.applySiteModuleDetailsFromFetchJson(payload.siteModuleDetails);
                } else {
                    this.applyTargetTypesFromAllContentTypes();
                }

                this.editingMappingId = sm.id != null ? sm.id : sm.Id;
                this.MappingName = sm.name || sm.Name || '';
                this.MappingDescription = sm.description || sm.Description || '';
                this.childrenSelection = sm.childrenSelection || sm.ChildrenSelection || 'Immediate';

                await this.$nextTick();
                const typeId = String(sm.typeId != null ? sm.typeId : sm.TypeId || '');
                const list = this.moduleContentTypesSorted;
                const fullList = (this.targetTypes && this.targetTypes.items) ? this.targetTypes.items : [];
                const matchType = (arr) => {
                    if (!arr || !arr.length) return null;
                    return arr.find((t) => String(t.contentTypeId || '') === typeId)
                        || arr.find((t) => String(t.contentTypeItemName || '') === typeId)
                        || arr.find((t) => String(t.fullTypeName || t.FullTypeName || '') === typeId)
                        || arr.find((t) => String(t.contentTypeName || t.ContentTypeName || '') === typeId)
                        || arr.find((t) => String(t.contentTypeName || t.ContentTypeName || '').toLowerCase() === typeId.toLowerCase());
                };
                this.targetType = matchType(list) || matchType(fullList) || null;
                this.targetSource = sm.providerName != null && sm.providerName !== ''
                    ? sm.providerName
                    : (sm.ProviderName != null && sm.ProviderName !== '' ? sm.ProviderName : null);

                this.blogParentId = sm.parentId != null && sm.parentId !== ""
                    ? sm.parentId
                    : (sm.ParentId != null && sm.ParentId !== "" ? sm.ParentId : null);

                this.htmlInlineImagesLibraryProviderName =
                    sm.htmlInlineImagesLibraryProviderName != null && sm.htmlInlineImagesLibraryProviderName !== ""
                        ? sm.htmlInlineImagesLibraryProviderName
                        : sm.HtmlInlineImagesLibraryProviderName != null && sm.HtmlInlineImagesLibraryProviderName !== ""
                          ? sm.HtmlInlineImagesLibraryProviderName
                          : "";
                this.htmlInlineImagesLibraryDataSourceName =
                    sm.htmlInlineImagesLibraryDataSourceName != null && sm.htmlInlineImagesLibraryDataSourceName !== ""
                        ? sm.htmlInlineImagesLibraryDataSourceName
                        : sm.HtmlInlineImagesLibraryDataSourceName != null && sm.HtmlInlineImagesLibraryDataSourceName !== ""
                          ? sm.HtmlInlineImagesLibraryDataSourceName
                          : "";
                this.htmlInlineImagesParentAlbumId =
                    sm.htmlInlineImagesParentAlbumId != null && sm.htmlInlineImagesParentAlbumId !== ""
                        ? sm.htmlInlineImagesParentAlbumId
                        : sm.HtmlInlineImagesParentAlbumId != null && sm.HtmlInlineImagesParentAlbumId !== ""
                          ? sm.HtmlInlineImagesParentAlbumId
                          : "";
                this.htmlInlineImagesParentAlbumTitle =
                    sm.htmlInlineImagesParentAlbumTitle != null && sm.htmlInlineImagesParentAlbumTitle !== ""
                        ? sm.htmlInlineImagesParentAlbumTitle
                        : sm.HtmlInlineImagesParentAlbumTitle != null && sm.HtmlInlineImagesParentAlbumTitle !== ""
                          ? sm.HtmlInlineImagesParentAlbumTitle
                          : "";
                this.htmlInlineImagesParentEntitySet =
                    sm.htmlInlineImagesParentEntitySet != null && sm.htmlInlineImagesParentEntitySet !== ""
                        ? sm.htmlInlineImagesParentEntitySet
                        : sm.HtmlInlineImagesParentEntitySet != null && sm.HtmlInlineImagesParentEntitySet !== ""
                          ? sm.HtmlInlineImagesParentEntitySet
                          : "albums";

                const emb = this.targetType && (this.targetType.fields != null ? this.targetType.fields : this.targetType.Fields);
                if (Array.isArray(emb) && emb.length > 0) {
                    let norm = this.normalizeFieldsFromAssessment(emb);
                    norm = this.appendUrlNameFieldIfMissing(norm);
                    this.targetFieldsCache = norm;
                    this.moduleDetails = null;
                } else if (payload.typeDetails) {
                    this.applyTypeDetailsFromFetchJson(payload.typeDetails);
                } else {
                    this.targetFieldsCache = [];
                }

                await this.loadNodeFieldsForImport();
                await this.$nextTick();
                let rows = [];
                const mj = sm.mappingsJSON != null ? sm.mappingsJSON : sm.MappingsJSON;
                if (mj) {
                    try { rows = JSON.parse(mj); } catch (e) { rows = []; }
                }
                this.mappings = this.mapRowsFromSaved(rows);
                this.currentStep = 1;
                await this.$nextTick();
                this.rebindMappingRowSelectionsToDropdowns();
                await this.$nextTick();
            } catch (e) {
                console.error('EditMapping', e);
                Helpers.showNotification('Could not open mapping for editing.', 'error');
            } finally {
                this.restoringImportWizard = false;
                this.editImportLoading = false;
                if (this.needsParentSelection) {
                    this.$nextTick(() => this.loadParentOptions());
                }
            }
        },
        startImportStatusPolling() {
            this.stopImportStatusPolling();
            const tick = () => this.pollImportMappingStatuses();
            tick();
            this.importStatusPollTimer = setInterval(tick, 5000);
        },
        stopImportStatusPolling() {
            if (this.importStatusPollTimer) {
                clearInterval(this.importStatusPollTimer);
                this.importStatusPollTimer = null;
            }
        },
        async pollImportMappingStatuses() {
            try {
                const gr = await fetch("/api/Import/GetRunningImports");
                if (gr.ok) {
                    const list = await gr.json();
                    this.globalRunningImports = Array.isArray(list) ? list : [];
                } else {
                    this.globalRunningImports = [];
                }
            } catch (e) {
                this.globalRunningImports = [];
            }
            if (!this.existingMappings || !this.existingMappings.length) return;
            for (let i = 0; i < this.existingMappings.length; i++) {
                const m = this.existingMappings[i];
                const mid = m.id != null ? m.id : m.Id;
                if (mid == null) continue;
                try {
                    const r = await fetch("/api/Import/GetImportMappingStatus?mappingId=" + encodeURIComponent(mid));
                    if (!r.ok) continue;
                    const j = await r.json();
                    this.$set(this.importMappingStatusById, mid, j);
                } catch (e) {
                    /* ignore */
                }
            }
        },
        /** True if this mapping has the active import (global list or per-mapping status). */
        isMappingImportRunning(mapping) {
            if (!mapping) return false;
            const mid = mapping.id != null ? mapping.id : mapping.Id;
            const g = (this.globalRunningImports || []).find(r => (r.mappingId != null ? r.mappingId : r.MappingId) === mid);
            if (g) return true;
            const s = this.importMappingStatusById[mid];
            if (!s) return false;
            const run = s.isRunning !== undefined ? s.isRunning : s.IsRunning;
            return run === true;
        },
        /** Disable Run when any import is running anywhere (single global import lock). */
        isRunImportDisabled(mapping) {
            return (this.globalRunningImports && this.globalRunningImports.length > 0)
                || this.isMappingImportRunning(mapping);
        },
        runImportDisabledTitle(mapping) {
            if (!this.globalRunningImports || !this.globalRunningImports.length) {
                return this.isMappingImportRunning(mapping) ? "An import is already running" : "";
            }
            const mid = mapping && (mapping.id != null ? mapping.id : mapping.Id);
            const gr0 = this.globalRunningImports[0];
            const gMid = gr0 && (gr0.mappingId != null ? gr0.mappingId : gr0.MappingId);
            if (gMid === mid) return "An import is already running for this mapping";
            return "Another import is in progress — use Force stop above or on that mapping.";
        },
        mappingRunningRunId(mapping) {
            if (!mapping) return null;
            const mid = mapping.id != null ? mapping.id : mapping.Id;
            const g = (this.globalRunningImports || []).find(r => (r.mappingId != null ? r.mappingId : r.MappingId) === mid);
            if (g) {
                const id = g.id != null ? g.id : g.Id;
                return id != null ? id : null;
            }
            const s = this.importMappingStatusById[mid];
            if (!s) return null;
            return s.runningRunId != null ? s.runningRunId : s.RunningRunId;
        },
        globalRunningRunIdForBanner() {
            const gr0 = this.globalRunningImports && this.globalRunningImports[0];
            if (!gr0) return null;
            const id = gr0.id != null ? gr0.id : gr0.Id;
            return id != null ? id : null;
        },
        mappingImportStatusLine(mapping) {
            if (!mapping) return "";
            const mid = mapping.id != null ? mapping.id : mapping.Id;
            const s = this.importMappingStatusById[mid];
            if (!s) return "";
            const lr = s.lastRun != null ? s.lastRun : s.LastRun;
            if (!lr) return "";
            const st = lr.status != null ? lr.status : lr.Status;
            const proc = lr.processedItems != null ? lr.processedItems : lr.ProcessedItems;
            const tot = lr.totalItems != null ? lr.totalItems : lr.TotalItems;
            if (this.isMappingImportRunning(mapping)) {
                return "Import running…" + (proc != null && tot != null ? " " + proc + " / " + tot : "");
            }
            if (st) return "Last run: " + st + (proc != null && tot != null ? " (" + proc + "/" + tot + ")" : "");
            return "";
        },
        async forceStopImport(mapping) {
            if (!mapping) return;
            const rid = this.mappingRunningRunId(mapping);
            if (rid == null) {
                Helpers.showNotification("No active run id for this mapping.", "warning");
                return;
            }
            await this.forceStopImportByRunId(rid);
        },
        async forceStopImportByRunId(runId) {
            if (runId == null) return;
            if (!confirm("Force stop this import?")) return;
            try {
                const r = await fetch("/api/Import/ForceStopImport?runId=" + encodeURIComponent(runId), { method: "POST" });
                if (!r.ok) {
                    const t = await r.text();
                    throw new Error(t || r.statusText);
                }
                Helpers.showNotification("Stop requested.", "success");
                await this.pollImportMappingStatuses();
            } catch (e) {
                console.error("ForceStopImport", e);
                Helpers.showNotification("Could not stop import.", "error");
            }
        },
        async RunImport(mapping) {
            if (this.isRunImportDisabled(mapping)) {
                Helpers.showNotification(this.runImportDisabledTitle(mapping) || "There is already a running import.", "warning");
                return;
            }
            const importPayload = {
                connectionId: this.sourceConnectionId(),
                sourceItemId: this.sourceNodeId(),
                mappingId: mapping.id,
                name: `${mapping.name}`,
            };

            try {
                const running = await fetch("/api/Import/StartImport", {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(importPayload)
                });

                if (running.status == 400) {
                    const errMsg = await running.text();
                    Helpers.showNotification(`Error starting import: ${errMsg}`, 'error');
                } else {
                    Helpers.showNotification('Import started successfully', 'success');
                    await this.pollImportMappingStatuses();
                    if (this.importLogVisible && this.importLogMapping && this.importLogMapping.id === mapping.id) {
                        this.loadImportLog(mapping);
                    }
                }
            } catch (error) {
                console.error('Error running import:', error);
                Helpers.showNotification('Error starting import', 'error');
            }
        },
        openImportLog(mapping) {
            this.importLogMapping = mapping;
            this.importLogVisible = true;
            this.importLogView = "runs";
            this.importLogSelectedRun = null;
            this.importLogRunDetail = null;
            this.importLogRunItems = [];
            this.importLogSelectedItem = null;
            this.loadImportLog(mapping);
        },
        closeImportLog() {
            this.importLogVisible = false;
            this.importLogMapping = null;
            this.importLogRows = [];
            this.importLogView = "runs";
            this.importLogSelectedRun = null;
            this.importLogRunDetail = null;
            this.importLogRunItems = [];
            this.importLogSelectedItem = null;
        },
        async loadImportLog(mapping) {
            if (!mapping || !mapping.id) return;
            this.importLogLoading = true;
            try {
                const res = await fetch(`/api/Import/GetImportHistory?mappingId=${encodeURIComponent(mapping.id)}&count=20`);
                if (!res.ok) {
                    const t = await res.text();
                    throw new Error(t || res.statusText);
                }
                this.importLogRows = await res.json();
            } catch (e) {
                console.error('GetImportHistory', e);
                Helpers.showNotification('Could not load import history.', 'error');
                this.importLogRows = [];
            } finally {
                this.importLogLoading = false;
            }
        },
        formatImportLogDate(val) {
            if (val == null || val === '') return '—';
            const d = new Date(val);
            if (isNaN(d.getTime())) return String(val);
            return d.toLocaleString();
        },
        importLogProgress(row) {
            const p = row.processedItems != null ? row.processedItems : row.ProcessedItems;
            const t = row.totalItems != null ? row.totalItems : row.TotalItems;
            if (p == null && t == null) return '—';
            const ps = p != null ? p : '—';
            const ts = t != null ? t : '—';
            return `${ps} / ${ts}`;
        },
        /** User-facing outcome: Success, Failed, With errors, or raw status (e.g. Running). */
        importLogOutcome(row) {
            const st = (row.status != null ? row.status : row.Status || '').toString();
            const err = (row.errorCount != null ? row.errorCount : row.ErrorCount);
            const errNum = err != null ? Number(err) : 0;
            if (st === 'Failed') return 'Failed';
            if (st === 'CompletedWithErrors' || errNum > 0) return 'With errors';
            if (st === 'Running') return 'Running';
            if (st === 'Completed' || st === 'Finished') return 'Success';
            return st !== '' ? st : '—';
        },
        importLogErrCount(row) {
            const n = row.errorCount != null ? row.errorCount : row.ErrorCount;
            if (n === null || n === undefined) return '—';
            return n;
        },
        importLogWarnCount(row) {
            const n = row.warningCount != null ? row.warningCount : row.WarningCount;
            if (n === null || n === undefined) return '—';
            return n;
        },
        importLogMessage(row) {
            const m = row.finishReason != null ? row.finishReason : row.FinishReason;
            return m != null && String(m).trim() !== '' ? String(m).trim() : '—';
        },
        openImportRunDetail(row) {
            if (!row) return;
            const rid = row.id != null ? row.id : row.Id;
            if (rid == null) return;
            this.importLogSelectedRun = row;
            this.importLogView = "items";
            this.importLogSelectedItem = null;
            this.importLogRunDetail = null;
            this.importLogRunItems = [];
            this.loadImportRunDetail(rid);
        },
        async loadImportRunDetail(runId) {
            this.importLogRunItemsLoading = true;
            try {
                const res = await fetch(`/api/Import/GetImportRunDetail?runId=${encodeURIComponent(runId)}&maxItems=3000`);
                if (!res.ok) {
                    const t = await res.text();
                    throw new Error(t || res.statusText);
                }
                const json = await res.json();
                this.importLogRunDetail = json;
                const items = json.items != null ? json.items : json.Items;
                this.importLogRunItems = Array.isArray(items) ? items : [];
            } catch (e) {
                console.error("GetImportRunDetail", e);
                Helpers.showNotification("Could not load import run detail.", "error");
                this.importLogRunItems = [];
            } finally {
                this.importLogRunItemsLoading = false;
            }
        },
        openImportItemDetail(item) {
            this.importLogSelectedItem = item;
            this.importLogView = "steps";
        },
        importLogBackToRuns() {
            this.importLogView = "runs";
            this.importLogSelectedRun = null;
            this.importLogRunDetail = null;
            this.importLogRunItems = [];
            this.importLogSelectedItem = null;
        },
        importLogBackToItems() {
            this.importLogView = "items";
            this.importLogSelectedItem = null;
        },
        importLogItemSteps(item) {
            if (!item) return [];
            const s = item.steps != null ? item.steps : item.Steps;
            return Array.isArray(s) ? s : [];
        },
        importLogItemOutcome(item) {
            if (!item) return "—";
            const ok = item.success != null ? item.success : item.Success;
            const sec = item.hasSecondaryErrors != null ? item.hasSecondaryErrors : item.HasSecondaryErrors;
            if (ok === true) return "OK";
            if (sec === true) return "Succeeded with errors";
            if (ok === false) return "Failed";
            return "—";
        },
        importLogItemOutcomeStyle(item) {
            if (!item) return {};
            const ok = item.success != null ? item.success : item.Success;
            const sec = item.hasSecondaryErrors != null ? item.hasSecondaryErrors : item.HasSecondaryErrors;
            if (sec === true && ok === false) return { color: "#e8a04a" };
            if (ok === false) return { color: "#f88" };
            if (ok === true) return { color: "var(--accent-bright)" };
            return {};
        },
        importLogStepIsError(st) {
            return st && (st.isError === true || st.IsError === true);
        },
        importLogStepCategoryStyle(st) {
            const base = { fontSize: "0.72rem", textTransform: "uppercase", letterSpacing: "0.04em" };
            return this.importLogStepIsError(st) ? { ...base, color: "#f88" } : { ...base, color: "var(--accent-bright)" };
        },
        importLogStepSummaryStyle(st) {
            return this.importLogStepIsError(st) ? { color: "#f88", fontWeight: "500" } : { color: "var(--text-primary)" };
        },
        importLogStepDetailStyle(st) {
            return this.importLogStepIsError(st) ? { color: "#f99" } : { color: "var(--text-secondary)" };
        },
        async DeleteMapping(mapping) {
            if (confirm("Are you sure you want to delete this mapping?")) {
                try {
                    await fetch(`/api/SitefinityConnectivity/DeleteMapping?mappingId=${mapping.id}`);
                    this.LoadMappings();
                    Helpers.showNotification('Mapping deleted successfully', 'success');
                    this.$emit('imports-updated');
                } catch (error) {
                    console.error('Error deleting mapping:', error);
                    Helpers.showNotification('Error deleting mapping', 'error');
                }
            }
        },
        async ClearMemory(mapping) {
            if (confirm("This operation is intended if you have imported items and then deleted them.\nAre you sure you want to proceed?")) {
                try {
                    const res = await fetch(`/api/Import/ClearMemoryMappings?mappingId=${mapping.id}`);
                    const jsonRes = await res.json();
                    if (jsonRes && jsonRes.NumberOfItemsRemoved && jsonRes.Succeess) {
                        Helpers.showNotification(`Successfully removed ${jsonRes.NumberOfItemsRemoved} items from memory`, 'success');
                        this.LoadMappings();
                    }
                } catch (error) {
                    console.error('Error clearing memory:', error);
                    Helpers.showNotification('Error clearing memory', 'error');
                }
            }
        },
        PopulateItemFieldMapping(mapping) {
            if (!mapping || !mapping.Target || !this.targetRowIsImageField(mapping)) {
                if (mapping) {
                    this.$delete(mapping, "libraryProviderName");
                    this.$delete(mapping, "libraryDataSourceName");
                    this.$delete(mapping, "libraryParentAlbumId");
                    this.$delete(mapping, "libraryParentEntitySet");
                    this.$delete(mapping, "libraryParentAlbumTitle");
                }
            } else if (mapping && !mapping.libraryParentEntitySet) {
                this.$set(mapping, "libraryParentEntitySet", "albums");
            }
        },
        isTargetFieldImageLike(f) {
            if (!f) return false;
            const t = ((f.typeName != null ? f.typeName : f.TypeName) || "") + " " + ((f.typeUIName != null ? f.typeUIName : f.TypeUIName) || "");
            const lower = t.toLowerCase();
            if (lower.includes("image") && (lower.includes("library") || lower.includes("media") || lower.includes("related") || lower.includes("single"))) return true;
            if (lower.includes("related image") || lower.includes("single-image") || lower.includes("singleimage")) return true;
            if (f.allowMultipleImages === true || f.AllowMultipleImages === true) return true;
            if (f.allowImageLibrary === true || f.AllowImageLibrary === true) return true;
            return false;
        },
        targetRowIsImageField(row) {
            return !!(row && row.Target && this.isTargetFieldImageLike(row.Target));
        },
        libraryProviderKey(s) {
            if (!s) return "";
            const pn = s.providerName != null ? s.providerName : s.ProviderName;
            const ds = s.dataSourceName != null ? s.dataSourceName : s.DataSourceName;
            return String(pn || "") + "::" + String(ds || "");
        },
        libraryProviderLabel(s) {
            if (!s) return "";
            const pt = s.providerTitle != null ? s.providerTitle : s.ProviderTitle;
            const ds = s.dataSourceName != null ? s.dataSourceName : s.DataSourceName;
            if (pt != null && String(pt).trim() !== "") return String(pt).trim();
            if (ds != null && String(ds).trim() !== "") return String(ds).trim();
            return this.libraryProviderKey(s);
        },
        async loadLibrarySourcesForSite() {
            const cid = this.targetServerId();
            if (cid == null || !this.targetSite) return;
            this.librarySourcesLoading = true;
            try {
                const res = await fetch(
                    "/api/SitefinityConnectivity/GetLibraryProviderSources?connectionId=" +
                        encodeURIComponent(cid) +
                        "&siteId=" +
                        encodeURIComponent(this.targetSite)
                );
                const json = await res.json();
                const raw = json.sources != null ? json.sources : json.Sources;
                this.librarySourcesForSite = Array.isArray(raw) ? raw : [];
            } catch (e) {
                console.error("loadLibrarySourcesForSite", e);
                this.librarySourcesForSite = [];
                Helpers.showNotification("Could not load library providers for this site.", "error");
            } finally {
                this.librarySourcesLoading = false;
            }
        },
        async loadLibraryAlbumsForDialog(providerName, entitySet) {
            const cid = this.targetServerId();
            if (cid == null || !this.targetSite || !providerName) {
                this.libraryFolderAlbums = [];
                return;
            }
            const seg = entitySet && String(entitySet).trim() !== "" ? String(entitySet).trim() : "albums";
            this.libraryFolderAlbumsLoading = true;
            try {
                const u =
                    "/api/SitefinityConnectivity/GetParentList?connectionId=" +
                    encodeURIComponent(cid) +
                    "&siteId=" +
                    encodeURIComponent(this.targetSite) +
                    "&providerName=" +
                    encodeURIComponent(providerName) +
                    "&entitySet=" +
                    encodeURIComponent(seg);
                const res = await fetch(u);
                if (!res.ok) {
                    this.libraryFolderAlbums = [];
                    return;
                }
                const json = await res.json();
                const raw = json.blogs != null ? json.blogs : json.Blogs;
                const arr = Array.isArray(raw) ? raw : [];
                this.libraryFolderAlbums = arr.map((b) => ({
                    id: b.id != null ? b.id : b.Id,
                    title: (b.title != null ? b.title : b.Title) || ""
                }));
            } catch (e) {
                console.error("loadLibraryAlbumsForDialog", e);
                this.libraryFolderAlbums = [];
            } finally {
                this.libraryFolderAlbumsLoading = false;
            }
        },
        async openLibraryFolderDialog(row) {
            this.libraryFolderDialogMode = "fieldRow";
            this.libraryFolderDialogRow = row;
            this.libraryFolderTempAlbumId = row.libraryParentAlbumId || "";
            const key =
                row.libraryProviderName && row.libraryDataSourceName
                    ? row.libraryProviderName + "::" + row.libraryDataSourceName
                    : "";
            this.libraryFolderTempProviderKey = key;
            this.libraryFolderDialogOpen = true;
            if (!this.librarySourcesForSite.length) await this.loadLibrarySourcesForSite();
            const src = this.librarySourcesForSite.find((s) => this.libraryProviderKey(s) === this.libraryFolderTempProviderKey);
            const pn = src ? (src.providerName != null ? src.providerName : src.ProviderName) : "";
            const pe = src
                ? src.suggestedParentEntitySet != null
                    ? src.suggestedParentEntitySet
                    : src.SuggestedParentEntitySet
                : "albums";
            if (pn) await this.loadLibraryAlbumsForDialog(pn, pe || "albums");
            else this.libraryFolderAlbums = [];
        },
        async openHtmlInlineImagesFolderDialog() {
            this.libraryFolderDialogMode = "htmlInline";
            this.libraryFolderDialogRow = null;
            this.libraryFolderTempAlbumId = this.htmlInlineImagesParentAlbumId || "";
            const key =
                this.htmlInlineImagesLibraryProviderName && this.htmlInlineImagesLibraryDataSourceName
                    ? this.htmlInlineImagesLibraryProviderName + "::" + this.htmlInlineImagesLibraryDataSourceName
                    : "";
            this.libraryFolderTempProviderKey = key;
            this.libraryFolderDialogOpen = true;
            if (!this.librarySourcesForSite.length) await this.loadLibrarySourcesForSite();
            const src = this.librarySourcesForSite.find((s) => this.libraryProviderKey(s) === this.libraryFolderTempProviderKey);
            const pn = src ? (src.providerName != null ? src.providerName : src.ProviderName) : "";
            const pe = src
                ? src.suggestedParentEntitySet != null
                    ? src.suggestedParentEntitySet
                    : src.SuggestedParentEntitySet
                : this.htmlInlineImagesParentEntitySet || "albums";
            if (pn) await this.loadLibraryAlbumsForDialog(pn, pe || "albums");
            else this.libraryFolderAlbums = [];
        },
        closeLibraryFolderDialog() {
            this.libraryFolderDialogOpen = false;
            this.libraryFolderDialogRow = null;
            this.libraryFolderDialogMode = "fieldRow";
            this.libraryFolderTempProviderKey = "";
            this.libraryFolderTempAlbumId = "";
            this.libraryFolderAlbums = [];
        },
        async onLibraryFolderProviderKeyChange() {
            const src = this.librarySourcesForSite.find((s) => this.libraryProviderKey(s) === this.libraryFolderTempProviderKey);
            const pn = src ? (src.providerName != null ? src.providerName : src.ProviderName) : "";
            const pe = src
                ? src.suggestedParentEntitySet != null
                    ? src.suggestedParentEntitySet
                    : src.SuggestedParentEntitySet
                : "albums";
            this.libraryFolderTempAlbumId = "";
            if (pn) await this.loadLibraryAlbumsForDialog(pn, pe || "albums");
            else this.libraryFolderAlbums = [];
        },
        applyLibraryFolderDialog() {
            if (this.libraryFolderDialogMode === "htmlInline") {
                const src = this.librarySourcesForSite.find((s) => this.libraryProviderKey(s) === this.libraryFolderTempProviderKey);
                if (src) {
                    this.htmlInlineImagesLibraryProviderName = src.providerName != null ? src.providerName : src.ProviderName;
                    this.htmlInlineImagesLibraryDataSourceName = src.dataSourceName != null ? src.dataSourceName : src.DataSourceName;
                    const pe = src.suggestedParentEntitySet != null ? src.suggestedParentEntitySet : src.SuggestedParentEntitySet;
                    this.htmlInlineImagesParentEntitySet = pe || "albums";
                } else {
                    this.htmlInlineImagesLibraryProviderName = "";
                    this.htmlInlineImagesLibraryDataSourceName = "";
                    this.htmlInlineImagesParentEntitySet = "albums";
                }
                const albumTitle = (this.libraryFolderAlbums.find((x) => x.id === (this.libraryFolderTempAlbumId || "")) || {}).title;
                this.htmlInlineImagesParentAlbumId = this.libraryFolderTempAlbumId || "";
                this.htmlInlineImagesParentAlbumTitle = albumTitle || "";
                this.closeLibraryFolderDialog();
                return;
            }
            const row = this.libraryFolderDialogRow;
            if (!row) {
                this.closeLibraryFolderDialog();
                return;
            }
            const src = this.librarySourcesForSite.find((s) => this.libraryProviderKey(s) === this.libraryFolderTempProviderKey);
            if (src) {
                this.$set(row, "libraryProviderName", src.providerName != null ? src.providerName : src.ProviderName);
                this.$set(row, "libraryDataSourceName", src.dataSourceName != null ? src.dataSourceName : src.DataSourceName);
                const pe = src.suggestedParentEntitySet != null ? src.suggestedParentEntitySet : src.SuggestedParentEntitySet;
                this.$set(row, "libraryParentEntitySet", pe || "albums");
            } else {
                this.$set(row, "libraryProviderName", "");
                this.$set(row, "libraryDataSourceName", "");
                this.$set(row, "libraryParentEntitySet", "albums");
            }
            const albumTitle = (this.libraryFolderAlbums.find((x) => x.id === (this.libraryFolderTempAlbumId || "")) || {}).title;
            this.$set(row, "libraryParentAlbumId", this.libraryFolderTempAlbumId || "");
            this.$set(row, "libraryParentAlbumTitle", albumTitle || "");
            this.closeLibraryFolderDialog();
        },
        libraryFolderSummary(row) {
            if (!row || !row.libraryParentAlbumId) return "—";
            const t = row.libraryParentAlbumTitle || row.LibraryParentAlbumTitle;
            if (t) return t;
            return row.libraryParentAlbumId;
        },
        htmlInlineImagesFolderSummary() {
            if (!this.htmlInlineImagesParentAlbumId) return "—";
            if (this.htmlInlineImagesParentAlbumTitle) return this.htmlInlineImagesParentAlbumTitle;
            return this.htmlInlineImagesParentAlbumId;
        }
    },
    template: `
        <div class="import-tab">
            <div v-show="inSetup" class="connection-overlay import-wizard-overlay" @click.self="inSetup = false; editImportLoading = false">
                <div class="connect-panel connection-dialog-panel import-wizard-panel" style="position:relative;">
                    <div class="connection-dialog-header">
                        <h2>{{ editingMappingId ? 'Edit import' : 'Import Wizard' }}</h2>
                        <button type="button" class="connection-dialog-close" @click="inSetup = false; editImportLoading = false" aria-label="Close">
                            <i class="bi bi-x-lg"></i>
                        </button>
                    </div>
                    <template v-if="editImportLoading">
                        <div class="dashboard-loading-overlay import-edit-loading" style="min-height:220px; padding:2.5rem 1.5rem;">
                            <div style="font-size:2rem; line-height:1; color:var(--accent-bright); margin-bottom:0.5rem;">
                                <i class="bi bi-hourglass-split"></i>
                            </div>
                            <div class="dashboard-loading-text">This could take a while…</div>
                            <div class="dashboard-loading-sub" style="max-width:30rem; text-align:center;">
                                Loading mapping and preparing Sitefinity target data (modules, content types, fields, and libraries). If this target Sitefinity has not been initialized yet, the first load is slower; after that, opening Edit is usually much faster.
                            </div>
                        </div>
                    </template>
                    <template v-else>
                    <div v-if="loadingMappingsStep" class="dashboard-loading-overlay import-wizard-preparing-overlay" style="position:absolute; inset:0; z-index:5; border-radius:inherit; min-height:200px; display:flex; flex-direction:column; align-items:center; justify-content:center; background:rgba(12,14,18,0.72); backdrop-filter:blur(4px);">
                        <div class="spinner-ring"></div>
                        <div class="dashboard-loading-text" style="margin-top:1rem;">Loading target fields…</div>
                        <div class="dashboard-loading-sub" style="max-width:22rem; text-align:center;">Preparing Sitefinity content types and field definitions for mappings.</div>
                    </div>
                    <p style="color:var(--text-secondary); font-size:0.88rem; margin:0 0 1.25rem 0;">
                        Map source content to a Sitefinity target. Current platform: <strong style="color:var(--accent-bright);">{{ platformType }}</strong>
                    </p>

                    <div class="connection-dialog-section" style="margin-top:0; padding-top:0; border-top:none;">
                        <div class="detail-field-label">Mapping Name</div>
                        <input class="form-control-dark" v-model="MappingName" type="text" placeholder="e.g. News to Blog"
                            style="width:100%; margin-bottom:1rem; font-family:'IBM Plex Sans',sans-serif;" />
                        <div class="detail-field-label">Description</div>
                        <textarea class="form-control-dark" v-model="MappingDescription" placeholder="Optional description"
                            style="width:100%; min-height:60px; margin-bottom:1rem; font-family:'IBM Plex Sans',sans-serif; resize:vertical;"></textarea>
                        <label style="display:flex; align-items:center; gap:0.5rem; margin-bottom:1rem; cursor:pointer; font-size:0.84rem; color:var(--text-secondary);">
                            <input type="checkbox" v-model="useClassicSingleTypeWizard" />
                            <span>Use classic single-type mapping wizard (pick Sitefinity target and map fields on this screen)</span>
                        </label>
                    </div>

                    <!-- Step 0 (template plan): scope only -->
                    <div v-if="currentStep === 0 && useTemplatePlanWizard" class="connection-dialog-section">
                        <table class="table-dark-custom import-wizard-table">
                            <tbody>
                                <tr>
                                    <td style="width:180px; color:var(--text-muted); font-weight:500;">Import scope</td>
                                    <td>
                                        <select class="form-control-dark select-dark" v-model="childrenSelection" style="width:100%;">
                                            <option value="Immediate">Only immediate children of the selected tree node</option>
                                            <option value="Recursive">In depth — all descendants under the selected node (recursive)</option>
                                        </select>
                                    </td>
                                </tr>
                            </tbody>
                        </table>
                        <p style="color:var(--text-muted); font-size:0.82rem; margin:0.75rem 0 0 0; line-height:1.45;">
                            Next: assign a <strong>reusable template</strong> or skip behavior to each source content type under this node.
                            Templates belong to this source connection; create them via <code style="font-size:0.78rem;">POST /api/SitefinityConnectivity/SaveImportMappingTemplate</code> or tooling, then refresh this wizard.
                        </p>
                    </div>

                    <!-- Step 0: Server Info (classic) -->
                    <div v-if="currentStep === 0 && !useTemplatePlanWizard" class="connection-dialog-section">
                        <table class="table-dark-custom import-wizard-table">
                            <tbody>
                                <tr>
                                    <td style="width:180px; color:var(--text-muted); font-weight:500;">Import scope</td>
                                    <td>
                                        <select class="form-control-dark select-dark" v-model="childrenSelection" style="width:100%;">
                                            <option value="Immediate">Only immediate children of the selected tree node</option>
                                            <option value="Recursive">In depth — all descendants under the selected node (recursive)</option>
                                        </select>
                                    </td>
                                </tr>
                                <tr>
                                    <td style="width:140px; color:var(--text-muted); font-weight:500;">Target Server</td>
                                    <td>
                                        <select class="form-control-dark select-dark" v-model="targetServerConnectionId" style="width:100%;">
                                            <option value="">Select target server...</option>
                                            <option v-for="server in targetServers" :key="server.id || server.Id" :value="String(server.id != null ? server.id : server.Id)">{{ server.name || server.Name }}</option>
                                        </select>
                                    </td>
                                </tr>
                                <tr v-if="targetServer && serverSites(targetServer).length">
                                    <td style="color:var(--text-muted); font-weight:500;">Site</td>
                                    <td>
                                        <select class="form-control-dark select-dark" v-model="targetSite" style="width:100%;">
                                            <option value="">Select site...</option>
                                            <option v-for="site in serverSites(targetServer)" :key="site.Id || site.id" :value="site.Id != null ? site.Id : site.id">{{ site.Name || site.name }}</option>
                                        </select>
                                    </td>
                                </tr>
                                <tr v-if="targetSite">
                                    <td style="color:var(--text-muted); font-weight:500;">Module</td>
                                    <td>
                                        <div v-if="loadingSiteModules" style="display:flex; align-items:center; gap:0.5rem;">
                                            <div class="spinner-ring" style="width:18px;height:18px;"></div>
                                            <span style="font-size:0.82rem; color:var(--text-muted);">Loading modules…</span>
                                        </div>
                                        <template v-else>
                                            <select class="form-control-dark select-dark" v-model="targetModuleSelectKey" style="width:100%;" :disabled="sitefinityModuleSelectDisabled">
                                                <option value="">{{ loadingSitefinityContentTypes ? 'Preparing content types…' : 'Select module…' }}</option>
                                                <option v-for="(module, idx) in targetModulesSortedDataSources" :key="moduleSelectKey(module) + '_' + idx" :value="moduleSelectKey(module)">{{ moduleLabelFromModule(module) }}</option>
                                            </select>
                                            <p v-if="!loadingSitefinityContentTypes && targetModules && !targetModulesDataSources.length" style="font-size:0.82rem; color:var(--text-muted); margin:0.35rem 0 0 0;">No modules returned for this site.</p>
                                            <p v-if="loadingSitefinityContentTypes" style="font-size:0.82rem; color:var(--text-muted); margin:0.35rem 0 0 0;">Preparing Sitefinity content types (assessment)…</p>
                                        </template>
                                    </td>
                                </tr>
                                <tr v-if="targetModule && targetTypes && moduleContentTypesSorted.length">
                                    <td style="color:var(--text-muted); font-weight:500;">Content Type</td>
                                    <td>
                                        <select class="form-control-dark select-dark" v-model="targetTypeSelectKey" style="width:100%;">
                                            <option value="">Select content type...</option>
                                            <option v-for="(t, ctIdx) in moduleContentTypesSorted" :key="contentTypeSelectValue(t) + '_' + ctIdx" :value="contentTypeSelectValue(t)">{{ t.contentTypeItemName || t.name }}</option>
                                        </select>
                                    </td>
                                </tr>
                                <tr v-if="targetModule && targetTypes && !moduleContentTypesSorted.length">
                                    <td colspan="2" style="padding-top:0.5rem;">
                                        <p style="color:var(--text-muted); font-size:0.82rem; margin:0;">No content types matched this module. Run Sitefinity assessment (content types init) on the target connection, or check multisite module / DataSourceName vs assessment <code>moduleName</code>.</p>
                                    </td>
                                </tr>
                                <tr v-if="targetModule && targetModule.Links && targetType">
                                    <td style="color:var(--text-muted); font-weight:500;">Provider</td>
                                    <td>
                                        <select class="form-control-dark select-dark" v-model="targetSource" style="width:100%;">
                                            <option value="">Select provider...</option>
                                            <option v-for="link in targetModule.Links" :key="link.ProviderName" :value="link.ProviderName">{{ link.ProviderTitle || link.ProviderName }}</option>
                                        </select>
                                    </td>
                                </tr>
                                <tr v-if="needsParentSelection && targetSite && targetSource">
                                    <td style="color:var(--text-muted); font-weight:500;">Parent</td>
                                    <td>
                                        <div v-if="loadingBlogParents" style="display:flex; align-items:center; gap:0.5rem;">
                                            <div class="spinner-ring" style="width:18px;height:18px;"></div>
                                            <span style="font-size:0.82rem; color:var(--text-muted);">Loading parents…</span>
                                        </div>
                                        <select v-else class="form-control-dark select-dark" v-model="blogParentId" style="width:100%;">
                                            <option value="">Select parent…</option>
                                            <option v-for="b in blogParentOptions" :key="b.id" :value="b.id">{{ b.title || b.id }}</option>
                                        </select>
                                    </td>
                                </tr>
                            </tbody>
                        </table>
                    </div>

                    <!-- Step 1: Template assignments -->
                    <div v-if="useTemplatePlanWizard && currentStep === 1" class="connection-dialog-section">
                        <div class="detail-field-label">Import scope (children)</div>
                        <select class="form-control-dark select-dark" v-model="childrenSelection" style="width:100%; max-width:480px; margin-bottom:1.25rem;">
                            <option value="Immediate">Only immediate children of the selected tree node</option>
                            <option value="Recursive">In depth — all descendants under the selected node (recursive)</option>
                        </select>
                        <div class="detail-field-label">Assign template per source content type</div>
                        <p v-if="loadingSourceTypesPlan" style="color:var(--text-muted); font-size:0.82rem;">Loading types under this node…</p>
                        <table v-else class="table-dark-custom import-wizard-table">
                            <thead>
                                <tr>
                                    <th>Source type</th>
                                    <th style="width:6rem;">Items</th>
                                    <th style="min-width:14rem;">Import using</th>
                                </tr>
                            </thead>
                            <tbody>
                                <tr v-for="(row, idx) in templatePlanRows" :key="'tpl-' + idx">
                                    <td>
                                        <strong style="color:var(--text-primary);">{{ row.sourceTemplateName || '—' }}</strong>
                                        <div v-if="row.sourceTemplateId" style="font-size:0.76rem; color:var(--text-muted); margin-top:0.15rem;">{{ row.sourceTemplateId }}</div>
                                    </td>
                                    <td>{{ row.itemCount }}</td>
                                    <td>
                                        <div style="display:flex; align-items:center; gap:0.5rem;">
                                        <select class="form-control-dark select-dark" v-model="row.assignment" style="width:100%;">
                                            <option value="">— Choose —</option>
                                            <option v-for="t in templateOptionsForRow(row)" :key="'opt-' + (t.id != null ? t.id : t.Id)" :value="String(t.id != null ? t.id : t.Id)">{{ t.name || t.Name }}</option>
                                            <option value="SkipSubtree">Skip item and subtree</option>
                                            <option value="SkipItemChildrenOnly">Skip item, process children</option>
                                        </select>
                                        <a href="javascript:void(0);" class="import-action-link" style="white-space:nowrap;" @click.prevent="beginCreateTemplateForRow(row)">Create new mapping</a>
                                        </div>
                                    </td>
                                </tr>
                            </tbody>
                        </table>
                        <p v-if="!loadingSourceTypesPlan && templatePlanRows.length === 0" style="color:var(--text-muted); font-size:0.82rem;">No content types found for this scope. Adjust the tree node or import scope.</p>
                    </div>

                    <!-- Step 1: Field Mappings (classic) -->
                    <div v-if="!useTemplatePlanWizard && currentStep === 1" class="connection-dialog-section" style="position:relative;">
                        <div class="detail-field-label">Import scope (children)</div>
                        <select class="form-control-dark select-dark" v-model="childrenSelection" style="width:100%; max-width:480px; margin-bottom:1.25rem;">
                            <option value="Immediate">Only immediate children of the selected tree node</option>
                            <option value="Recursive">In depth — all descendants under the selected node (recursive)</option>
                        </select>
                        <div class="detail-field-label" style="margin-bottom:0.75rem;">Map source fields to target fields</div>
                        <p v-if="loadingNodeFields" style="color:var(--text-muted); font-size:0.82rem; margin:0 0 0.75rem 0;">
                            <template v-if="isUmbracoPlatform">Loading fields for all content types in the import scope…</template>
                            <template v-else>Loading fields from Node Details…</template>
                        </p>
                        <template v-else>
                            <p v-if="isUmbracoPlatform" style="color:var(--text-muted); font-size:0.82rem; margin:0 0 0.75rem 0; line-height:1.45;">
                                Source lists every cms property alias for each content type that appears under this node
                                (<strong>{{ childrenSelection === 'Recursive' ? 'recursive: all descendants' : 'immediate children only' }}</strong>).
                                Adjust <strong>Import scope</strong> on this step or step 1 to refresh.
                            </p>
                            <p v-if="isUmbracoPlatform && sourceFields.length === 0" style="color:var(--text-muted); font-size:0.82rem; margin:0 0 0.75rem 0;">No fields found for this scope. Try another tree node or a different import scope.</p>
                            <p v-if="!isUmbracoPlatform && sourceFields.length === 0" style="color:var(--text-muted); font-size:0.82rem; margin:0 0 0.75rem 0;">No source fields loaded. Select a node with fields, or open Node Details once.</p>
                            <p v-if="!isUmbracoPlatform && sourceFields.length > 0" style="color:var(--text-muted); font-size:0.82rem; margin:0 0 0.75rem 0;">Fields match the Node Details tab for the selected item.</p>
                        </template>
                        <table class="table-dark-custom import-wizard-table">
                            <thead>
                                <tr>
                                    <th>Source Field</th>
                                    <th>Target Field</th>
                                    <th v-if="targetSite" style="min-width:11rem;">Media library</th>
                                    <th style="width:80px;"></th>
                                </tr>
                            </thead>
                            <tbody>
                                <tr v-for="row in mappings" :key="row.Id">
                                    <td>
                                        <select class="form-control-dark select-dark" v-model="row.Source" style="width:100%;">
                                            <option :value="null">— Select —</option>
                                            <option v-for="f in sourceFieldsSorted" :key="(f.name || '') + '_' + (f.language || '') + '_' + (f.contentType || '')" :value="f">{{ f.name || f.title }}<template v-if="f.contentType"> ({{ f.contentType }})</template><template v-if="f.language"> ({{ f.language }})</template></option>
                                        </select>
                                    </td>
                                    <td>
                                        <select class="form-control-dark select-dark" v-model="row.Target" @change="PopulateItemFieldMapping(row)" style="width:100%;">
                                            <option :value="null">— Select —</option>
                                            <option v-for="f in targetFieldsForMappingSorted" :key="fieldPickName(f) + '_' + (f.typeName || f.TypeName || '')" :value="f">{{ fieldPickName(f) }}</option>
                                        </select>
                                    </td>
                                    <td v-if="targetSite">
                                        <template v-if="targetRowIsImageField(row)">
                                            <button type="button" class="btn-outline-subtle" style="padding:0.25rem 0.5rem; font-size:0.78rem; white-space:normal; text-align:left; max-width:100%;" :title="'Default Sitefinity library folder for images imported into this field'"
                                                @click="openLibraryFolderDialog(row)">
                                                <i class="bi bi-folder-symlink"></i> {{ libraryFolderSummary(row) }}
                                            </button>
                                        </template>
                                        <span v-else style="color:var(--text-muted); font-size:0.78rem;">—</span>
                                    </td>
                                    <td>
                                        <a href="javascript:void(0);" class="import-action-link text-danger" @click.prevent="mappings = mappings.filter(function(m) { return m !== row; })">Remove</a>
                                    </td>
                                </tr>
                            </tbody>
                        </table>
                        <button class="btn-outline-subtle" style="margin-top:0.75rem;" @click="AddMapping"><i class="bi bi-plus"></i> Add mapping</button>

                        <div v-if="targetSite" style="margin-top:1.25rem; padding-top:1rem; border-top:1px solid var(--border-subtle);">
                            <div class="detail-field-label">Rich text / HTML inline images</div>
                            <p style="font-size:0.82rem; color:var(--text-muted); margin:0 0 0.65rem 0; line-height:1.45;">
                                Sitefinity library folder for images referenced inside long text fields (<code style="font-size:0.78rem;">&lt;img src&gt;</code>). Applies to this import mapping. If unset, <code style="font-size:0.78rem;">Chameleon:ImportHtmlImages</code> defaults are used.
                            </p>
                            <button type="button" class="btn-outline-subtle" style="padding:0.25rem 0.5rem; font-size:0.78rem; white-space:normal; text-align:left; max-width:100%;" title="Library folder for inline images in rich text"
                                @click="openHtmlInlineImagesFolderDialog">
                                <i class="bi bi-folder-symlink"></i> {{ htmlInlineImagesFolderSummary() }}
                            </button>
                        </div>

                        <div v-if="libraryFolderDialogOpen" class="connection-overlay" style="position:absolute; inset:0; z-index:20; border-radius:inherit; align-items:center; justify-content:center; display:flex; background:rgba(0,0,0,0.5);" @click.self="closeLibraryFolderDialog">
                            <div class="connect-panel connection-dialog-panel" style="max-width:24rem; width:min(96vw, 24rem); margin:1rem;" @click.stop>
                                <div class="connection-dialog-header">
                                    <h4 style="margin:0;">{{ libraryFolderDialogMode === 'htmlInline' ? 'Rich text inline images folder' : 'Media library folder' }}</h4>
                                    <button type="button" class="connection-dialog-close" @click="closeLibraryFolderDialog" aria-label="Close"><i class="bi bi-x-lg"></i></button>
                                </div>
                                <p v-if="libraryFolderDialogMode === 'htmlInline'" style="font-size:0.82rem; color:var(--text-muted); margin:0 0 0.75rem 0; line-height:1.45;">Pick the Sitefinity <strong>library source</strong> and <strong>album</strong> where images from mapped long text / HTML fields will be uploaded during import.</p>
                                <p v-else style="font-size:0.82rem; color:var(--text-muted); margin:0 0 0.75rem 0; line-height:1.45;">Pick the Sitefinity <strong>library source</strong> (provider) and <strong>default parent album</strong> for inline / related images for this field. Import wiring uses these values later.</p>
                                <div v-if="librarySourcesLoading" style="font-size:0.82rem; color:var(--text-muted); margin-bottom:0.75rem;">Loading library sources…</div>
                                <select v-else class="form-control-dark select-dark" v-model="libraryFolderTempProviderKey" @change="onLibraryFolderProviderKeyChange" style="width:100%; margin-bottom:0.75rem;">
                                    <option value="">— Select library source —</option>
                                    <option v-for="s in librarySourcesForSite" :key="libraryProviderKey(s)" :value="libraryProviderKey(s)">{{ libraryProviderLabel(s) }}</option>
                                </select>
                                <div v-if="libraryFolderAlbumsLoading" style="font-size:0.82rem; color:var(--text-muted); margin-bottom:0.75rem;">Loading albums…</div>
                                <select v-else class="form-control-dark select-dark" v-model="libraryFolderTempAlbumId" style="width:100%; margin-bottom:1rem;">
                                    <option value="">— Default album (folder) —</option>
                                    <option v-for="a in libraryFolderAlbums" :key="a.id" :value="a.id">{{ a.title || a.id }}</option>
                                </select>
                                <div style="display:flex; justify-content:flex-end; gap:0.5rem;">
                                    <button type="button" class="btn-outline-subtle" @click="closeLibraryFolderDialog">Cancel</button>
                                    <button type="button" class="btn-accent" @click="applyLibraryFolderDialog">Apply</button>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div class="connection-dialog-section" style="display:flex; justify-content:space-between; align-items:center; margin-top:1.5rem; padding-top:1rem; border-top:1px solid var(--border-subtle);">
                        <button class="btn-outline-subtle" @click="currentStep = currentStep - 1" v-if="currentStep > 0">
                            <i class="bi bi-chevron-left"></i> {{ wizardSteps[currentStep - 1] }}
                        </button>
                        <div v-else></div>
                        <div>
                            <button class="btn-outline-subtle" @click="goNextStep" v-if="currentStep < wizardSteps.length - 1" style="margin-right:0.5rem;"
                                :disabled="loadingMappingsStep || loadingSourceTypesPlan || (currentStep === 0 && !useTemplatePlanWizard && targetSite && (loadingSiteModules || loadingSitefinityContentTypes))">
                                {{ wizardSteps[currentStep + 1] }} <i class="bi bi-chevron-right"></i>
                            </button>
                            <button class="btn-accent" v-if="currentStep === 1" @click="SaveMappings">
                                <i class="bi bi-check-lg"></i> {{ saveButtonLabel }}
                            </button>
                        </div>
                    </div>
                    </template>
                </div>
            </div>

            <div v-show="importLogVisible" class="connection-overlay import-wizard-overlay" @click.self="closeImportLog">
                <div class="connect-panel connection-dialog-panel import-wizard-panel" style="position:relative; max-width:72rem; width:min(98vw, 72rem);">
                    <div class="connection-dialog-header">
                        <h2>Import log</h2>
                        <button type="button" class="connection-dialog-close" @click="closeImportLog" aria-label="Close">
                            <i class="bi bi-x-lg"></i>
                        </button>
                    </div>
                    <p v-if="importLogMapping" style="color:var(--text-secondary); font-size:0.88rem; margin:0 0 1rem 0;">
                        <strong style="color:var(--text-primary);">{{ importLogMapping.name }}</strong>
                        <span style="color:var(--text-muted);"> — {{ importLogView === 'runs' ? 'last 20 runs (click a row for items)' : '' }}{{ importLogView === 'items' ? 'items in this run' : '' }}{{ importLogView === 'steps' ? 'import steps' : '' }}</span>
                    </p>
                    <div v-if="importLogLoading" class="dashboard-loading-overlay import-edit-loading" style="min-height:160px; padding:2rem 1rem; position:relative;">
                        <div class="spinner-ring"></div>
                        <div class="dashboard-loading-text">Loading history…</div>
                    </div>
                    <template v-else-if="importLogView === 'runs'">
                        <div style="overflow-x:auto;">
                            <table class="table-dark-custom" style="margin:0;">
                                <thead>
                                    <tr>
                                        <th>Started</th>
                                        <th>Finished</th>
                                        <th>Outcome</th>
                                        <th style="text-align:right;">Err</th>
                                        <th style="text-align:right;">Warn</th>
                                        <th>Progress</th>
                                        <th>Summary</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <tr v-for="row in importLogRows" :key="row.id || row.Id" @click="openImportRunDetail(row)" title="View items in this run" style="cursor:pointer;" :style="{ background: 'transparent' }" @mouseenter="$event.currentTarget.style.background='rgba(255,255,255,0.04)'" @mouseleave="$event.currentTarget.style.background='transparent'">
                                        <td style="white-space:nowrap; font-size:0.82rem;">{{ formatImportLogDate(row.startedOn != null ? row.startedOn : row.StartedOn) }}</td>
                                        <td style="white-space:nowrap; font-size:0.82rem;">{{ formatImportLogDate(row.finisedOn != null ? row.finisedOn : row.FinisedOn) }}</td>
                                        <td style="font-size:0.82rem;">{{ importLogOutcome(row) }}</td>
                                        <td style="font-size:0.82rem; text-align:right;">{{ importLogErrCount(row) }}</td>
                                        <td style="font-size:0.82rem; text-align:right;">{{ importLogWarnCount(row) }}</td>
                                        <td style="font-size:0.82rem; white-space:nowrap;">{{ importLogProgress(row) }}</td>
                                        <td style="font-size:0.78rem; color:var(--text-secondary); max-width:18rem; overflow:hidden; text-overflow:ellipsis;" :title="importLogMessage(row)">{{ importLogMessage(row) }}</td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                        <p v-if="!importLogRows || importLogRows.length === 0" style="color:var(--text-muted); font-size:0.85rem; margin:0.75rem 0 0 0;">
                            No import runs recorded for this mapping yet.
                        </p>
                    </template>
                    <template v-else-if="importLogView === 'items'">
                        <div style="margin-bottom:0.75rem;">
                            <button type="button" class="btn-outline-subtle" style="padding:0.35rem 0.65rem; font-size:0.82rem;" @click="importLogBackToRuns">
                                <i class="bi bi-arrow-left"></i> Back to runs
                            </button>
                        </div>
                        <p v-if="importLogSelectedRun" style="font-size:0.82rem; color:var(--text-muted); margin:0 0 0.75rem 0;">
                            Run #{{ importLogSelectedRun.id != null ? importLogSelectedRun.id : importLogSelectedRun.Id }}
                            <span v-if="importLogRunDetail && importLogRunDetail.run"> — {{ formatImportLogDate(importLogRunDetail.run.startedOn != null ? importLogRunDetail.run.startedOn : importLogRunDetail.run.StartedOn) }}</span>
                        </p>
                        <div v-if="importLogRunItemsLoading" class="dashboard-loading-overlay import-edit-loading" style="min-height:120px; padding:1.5rem 1rem; position:relative;">
                            <div class="spinner-ring"></div>
                            <div class="dashboard-loading-text">Loading items…</div>
                        </div>
                        <div v-else style="overflow-x:auto; max-height:min(60vh, 28rem); overflow-y:auto;">
                            <table class="table-dark-custom" style="margin:0;">
                                <thead>
                                    <tr>
                                        <th>#</th>
                                        <th>Source item</th>
                                        <th>Name</th>
                                        <th>Outcome</th>
                                        <th>Sitefinity Id</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <tr v-for="it in importLogRunItems" :key="it.id || it.Id" @click="openImportItemDetail(it)" title="View steps" style="cursor:pointer;" @mouseenter="$event.currentTarget.style.background='rgba(255,255,255,0.04)'" @mouseleave="$event.currentTarget.style.background='transparent'">
                                        <td style="font-size:0.82rem;">{{ it.ordinal != null ? it.ordinal : it.Ordinal }}</td>
                                        <td style="font-size:0.78rem; word-break:break-all; max-width:10rem;">{{ it.sourceItemId != null ? it.sourceItemId : it.SourceItemId }}</td>
                                        <td style="font-size:0.82rem;">{{ (it.itemName != null ? it.itemName : it.ItemName) || '—' }}</td>
                                        <td :style="{ fontSize: '0.82rem', ...importLogItemOutcomeStyle(it) }">{{ importLogItemOutcome(it) }}</td>
                                        <td style="font-size:0.78rem; word-break:break-all; max-width:12rem;">{{ (it.targetSitefinityId != null ? it.targetSitefinityId : it.TargetSitefinityId) || '—' }}</td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                        <p v-if="!importLogRunItemsLoading && (!importLogRunItems || importLogRunItems.length === 0)" style="color:var(--text-muted); font-size:0.85rem; margin:0.75rem 0 0 0;">
                            No per-item log for this run (imports started before this feature have no detail).
                        </p>
                    </template>
                    <template v-else-if="importLogView === 'steps' && importLogSelectedItem">
                        <div style="margin-bottom:0.75rem;">
                            <button type="button" class="btn-outline-subtle" style="padding:0.35rem 0.65rem; font-size:0.82rem;" @click="importLogBackToItems">
                                <i class="bi bi-arrow-left"></i> Back to items
                            </button>
                        </div>
                        <p style="font-size:0.9rem; color:var(--text-primary); font-weight:500; margin:0 0 0.25rem 0;">
                            {{ (importLogSelectedItem.itemName != null ? importLogSelectedItem.itemName : importLogSelectedItem.ItemName) || 'Item' }}
                        </p>
                        <p style="font-size:0.78rem; color:var(--text-muted); margin:0 0 1rem 0; word-break:break-all;">
                            Source: {{ importLogSelectedItem.sourceItemId != null ? importLogSelectedItem.sourceItemId : importLogSelectedItem.SourceItemId }}
                            <span v-if="(importLogSelectedItem.targetSitefinityId != null ? importLogSelectedItem.targetSitefinityId : importLogSelectedItem.TargetSitefinityId)"> → Sitefinity: {{ importLogSelectedItem.targetSitefinityId != null ? importLogSelectedItem.targetSitefinityId : importLogSelectedItem.TargetSitefinityId }}</span>
                        </p>
                        <p v-if="(importLogSelectedItem.errorMessage != null ? importLogSelectedItem.errorMessage : importLogSelectedItem.ErrorMessage)" style="font-size:0.82rem; margin:0 0 0.75rem 0;" :style="{ color: (importLogSelectedItem.hasSecondaryErrors != null ? importLogSelectedItem.hasSecondaryErrors : importLogSelectedItem.HasSecondaryErrors) ? '#e8a04a' : '#f88' }">
                            {{ importLogSelectedItem.errorMessage != null ? importLogSelectedItem.errorMessage : importLogSelectedItem.ErrorMessage }}
                        </p>
                        <div style="max-height:min(55vh, 26rem); overflow-y:auto; border:1px solid var(--border-subtle); border-radius:6px; padding:0.5rem 0.75rem;">
                            <div v-for="st in importLogItemSteps(importLogSelectedItem)" :key="(st.stepOrder != null ? st.stepOrder : st.StepOrder) + '-' + (st.category || st.Category)" style="padding:0.5rem 0; border-bottom:1px solid var(--border-subtle); font-size:0.82rem;">
                                <div style="display:flex; align-items:baseline; gap:0.5rem; flex-wrap:wrap;">
                                    <span :style="importLogStepCategoryStyle(st)">{{ st.category != null ? st.category : st.Category }}</span>
                                    <span :style="importLogStepSummaryStyle(st)">{{ st.summary != null ? st.summary : st.Summary }}</span>
                                </div>
                                <pre v-if="(st.detail != null ? st.detail : st.Detail)" style="margin:0.35rem 0 0 0; font-size:0.76rem; white-space:pre-wrap; word-break:break-word; font-family:ui-monospace,monospace;" :style="importLogStepDetailStyle(st)">{{ st.detail != null ? st.detail : st.Detail }}</pre>
                            </div>
                        </div>
                        <p v-if="importLogItemSteps(importLogSelectedItem).length === 0" style="color:var(--text-muted); font-size:0.85rem;">No steps recorded.</p>
                    </template>
                </div>
            </div>

            <!-- Existing Mappings Table -->
            <div v-if="existingMappings && existingMappings.length > 0" class="section-card" style="margin-top:1.5rem;">
                <div class="section-card-header">
                    <h6><i class="bi bi-link-45deg" style="color:var(--accent-bright);"></i> Saved Mappings</h6>
                </div>
                <div v-if="globalRunningImports && globalRunningImports.length" class="section-card-body" style="padding:0.75rem 1rem; border-bottom:1px solid var(--border-subtle); background:rgba(255,180,80,0.06);">
                    <div style="display:flex; align-items:center; flex-wrap:wrap; gap:0.75rem; font-size:0.85rem; color:var(--text-secondary);">
                        <span><i class="bi bi-hourglass-split" style="color:var(--accent-bright);"></i> An import is in progress (run #{{ globalRunningRunIdForBanner() }}<span v-if="globalRunningImports[0] && (globalRunningImports[0].name || globalRunningImports[0].Name)"> — {{ globalRunningImports[0].name || globalRunningImports[0].Name }}</span>).</span>
                        <button type="button" class="btn-outline-subtle" style="padding:0.25rem 0.55rem; font-size:0.78rem; color:#f88; border-color:rgba(255,136,136,0.45);" @click="forceStopImportByRunId(globalRunningRunIdForBanner())" title="Cancel the running import">
                            <i class="bi bi-stop-circle"></i> Force stop
                        </button>
                    </div>
                </div>
                <div class="section-card-body" style="padding:0;">
                    <table class="table-dark-custom">
                        <thead>
                            <tr>
                                <th>Name</th>
                                <th>Description</th>
                                <th>Actions</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr v-for="mapping in existingMappings" :key="mapping.id">
                                <td style="color:var(--text-primary); font-weight:500;">
                                    {{ mapping.name }}
                                    <div v-if="mappingImportStatusLine(mapping)" style="font-size:0.75rem; color:var(--accent-bright); font-weight:400; margin-top:0.25rem;">{{ mappingImportStatusLine(mapping) }}</div>
                                </td>
                                <td style="color:var(--text-secondary); font-size:0.85rem;">{{ mapping.description || '—' }}</td>
                                <td>
                                    <button class="btn-outline-subtle" style="padding:0.3rem 0.6rem; font-size:0.78rem; margin-right:0.4rem;" @click="EditMapping(mapping)">
                                        <i class="bi bi-pencil"></i> Edit
                                    </button>
                                    <button class="btn-outline-subtle" style="padding:0.3rem 0.6rem; font-size:0.78rem; margin-right:0.4rem;" @click="RunImport(mapping)" :disabled="isRunImportDisabled(mapping)" :title="runImportDisabledTitle(mapping)">
                                        <i class="bi bi-play-fill"></i> Run
                                    </button>
                                    <button v-if="isMappingImportRunning(mapping)" type="button" class="btn-outline-subtle" style="padding:0.3rem 0.6rem; font-size:0.78rem; margin-right:0.4rem; color:#f88; border-color:rgba(255,136,136,0.4);" @click="forceStopImport(mapping)" title="Request cancellation">
                                        <i class="bi bi-stop-circle"></i> Force stop
                                    </button>
                                    <button class="btn-outline-subtle" style="padding:0.3rem 0.6rem; font-size:0.78rem; margin-right:0.4rem;" @click="openImportLog(mapping)" title="View recent import runs">
                                        <i class="bi bi-journal-text"></i> Log
                                    </button>
                                    <a href="javascript:void(0);" class="import-action-link" style="margin-right:0.5rem;" @click.prevent="DeleteMapping(mapping)">Delete</a>
                                    <a href="javascript:void(0);" class="import-action-link text-danger" @click.prevent="ClearMemory(mapping)">Clear memory</a>
                                </td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
            <div v-else-if="connection && !selectedNode" class="empty-tab-placeholder" style="padding:2rem;">
                <i class="bi bi-diagram-3" style="font-size:2rem; color:var(--text-muted);"></i>
                <p style="color:var(--text-secondary); margin-top:1rem;">Select a node in the content tree to view or create imports for that <strong>start node</strong>.</p>
            </div>
            <div v-else-if="connection && selectedNode" class="empty-tab-placeholder" style="padding:2rem; min-height:60vh;">
                <i class="bi bi-arrow-left-right" style="font-size:2rem; color:var(--text-muted);"></i>
                <p style="color:var(--text-secondary); margin-top:1rem; margin-bottom:0.35rem;">
                    Selected node: <strong style="color:var(--text-primary);">{{ selectedNode.name || selectedNode.Name }}</strong>
                    <span style="color:var(--text-muted);"> ({{ sourceNodeId() }})</span>
                </p>
                <p style="color:var(--text-secondary); margin-top:0; margin-bottom:1rem;">No mappings yet for this node.</p>
                <button type="button" class="btn-accent" @click="SetupImport">
                    <i class="bi bi-plus-circle"></i> Setup Import
                </button>
            </div>
        </div>
    `
}); 