(() => {
  const catalog = window.SFP_SKILL_CATALOG || { packs: [], skill_count: 0 };
  const skillFiles = window.SFP_SKILL_FILES || {};
  const builderConfig = window.SFP_LOCAL_BUILDER_CONFIG || {};
  const apiDefaults = builderConfig.api_defaults || {};
  const localRendererUrl = apiDefaults.local_renderer_url || "http://127.0.0.1:8787/v1/skill-packs/render";
  const productionApiUrl = apiDefaults.production_api_url || "https://api.serviceflowpro.com";
  const profilePresets = catalog.profile_presets || [];
  const packSurfaceBundles = catalog.pack_surface_bundles || {};
  const profileContextDefaults = builderConfig.profile_context_defaults || {};
  const businessTypeTree = Array.isArray(builderConfig.business_type_tree)
    ? builderConfig.business_type_tree
    : [];
  const coreBusinessProcesses = Array.isArray(builderConfig.core_business_processes)
    ? builderConfig.core_business_processes
    : [];
  const coreBusinessSkillTemplates = Array.isArray(builderConfig.core_business_skill_templates)
    ? builderConfig.core_business_skill_templates
    : coreBusinessProcesses.flatMap((process) => process.skill_templates || []);
  const uiCopy = builderConfig.ui_copy || {};
  const uiFlow = builderConfig.ui_flow || {};
  const flowSteps = Array.isArray(uiFlow.steps) && uiFlow.steps.length
    ? uiFlow.steps
    : [
        { id: 1, label: "Business Type", short_label: "Business Type", next_label: "Next: Basics" },
        { id: 2, label: "Business Basics", short_label: "Basics", next_label: "Next: Systems" },
        { id: 3, label: "Systems & Work", short_label: "Systems & Work", next_label: "Next: Guardrails" },
        { id: 4, label: "Guardrails", short_label: "Guardrails", next_label: "Next: Pack" },
        { id: 5, label: "Recommended Pack", short_label: "Recommended Pack", next_label: "Next: Download" },
        { id: 6, label: "Download", short_label: "Download", next_label: "Download" },
      ];
  const fieldRelevanceLabels = builderConfig.field_relevance_labels || {};
  const decisionSummaryLabels = {
    progress: "Progress",
    profile: "Profile",
    selected: "Selected",
    output: "Output",
    ...builderConfig.decision_summary_labels,
  };
  const visualTheme = builderConfig.visual_theme || {};
  const businessTypeSprite = safeAssetPath(visualTheme.business_type_sprite || "assets/cloud-elephants-business-types-cave-sprite.png");
  const stepSprite = safeAssetPath(visualTheme.step_sprite || "assets/cloud-elephants-builder-steps-cave-sprite.png");
  const categoryBoundarySprite = safeAssetPath(visualTheme.category_boundary_sprite || "assets/cloud-elephants-category-boundaries-cave-sprite.png");
  const spriteSheets = normalizeSpriteSheets(visualTheme.sprite_sheets || {});
  const stepVisuals = visualTheme.steps || {};
  const branchVisuals = visualTheme.branches || {};
  const profileVisuals = visualTheme.profiles || {};
  const coreProcessVisuals = visualTheme.core_processes || {};
  const boundaryVisuals = visualTheme.boundaries || {};
  const summaryLabels = {
    contextMetric: "business details",
    personalizedSettings: "Personalized settings",
    packageSummary: "Package summary",
    selected: "Selected",
    dependencies: "Dependencies",
    packageType: "Package type",
    ...uiCopy.summary_labels,
  };
  const packageSafetyCopy = uiCopy.package_safety_copy ||
    "No API tokens, webhook secrets, or KG runtime credentials are included in this ZIP or request JSON.";
  const kgRuntimeCopy = uiCopy.kg_runtime_copy ||
    "KG runtime activates only after local ENV is configured.";
  const recommendationCopy = uiCopy.recommendation_reasons || {};
  const omittedSecretFields = uiCopy.omitted_secret_fields || [
    "rendererApiKey",
    "CLOUD_ELEPHANTS_KG_API_TOKEN",
    "CLOUD_ELEPHANTS_KG_WEBHOOK_URL",
  ];
  const brandVoiceOptions = builderConfig.brand_voice_options || [
    { value: "practical", label: "Practical" },
    { value: "friendly", label: "Friendly" },
    { value: "direct", label: "Direct" },
    { value: "executive", label: "Executive" },
    { value: "warm", label: "Warm" },
  ];
  const industryOptions = builderConfig.industry_options || [
    "home services",
    "finance",
    "sales growth",
    "admin operations",
  ];
  const allSkills = flattenSkills(catalog);
  const pickerSkills = allSkills.filter((skill) => !["dependency_only", "hidden_review"].includes(skill.picker_eligibility));
  const skillByName = new Map(allSkills.map((skill) => [skill.name, skill]));
  let activeProfileId = profilePresets[0]?.id || "";
  let activeStep = 1;
  let activeSkillView = "recommended";
  let profileExplicitUnlocked = false;
  let completeBusinessOperations = false;
  let contextCompletedEmitted = false;
  const appliedProfileDefaults = new Set();
  let selectedProfileIds = new Set(activeProfileId ? [activeProfileId] : []);
  const selected = new Set(initialProfileSelection());

  const form = document.querySelector(".form-panel");
  const skillGrid = document.querySelector("[data-skill-grid]");
  const selectedList = document.querySelector("[data-selected-list]");
  const status = document.querySelector("[data-status]");
  const localServerStatus = document.querySelector("[data-local-server-status]");
  const searchInput = document.querySelector('[name="search"]');
  const packFilter = document.querySelector('[name="packFilter"]');
  const profilePicks = document.querySelector("[data-profile-picks]");
  const profileSummary = document.querySelector("[data-profile-summary]");
  const packagePreview = document.querySelector("[data-package-preview]");
  const recommendedPack = document.querySelector("[data-recommended-pack]");
  const catalogPanel = document.querySelector("[data-catalog-panel]");
  const skillViewButtons = Array.from(document.querySelectorAll("[data-skill-view]"));
  const wizardSteps = Array.from(document.querySelectorAll("[data-wizard-step]"));
  const wizardTargets = Array.from(document.querySelectorAll("[data-wizard-target]"));
  const wizardPrev = document.querySelector("[data-wizard-prev]");
  const wizardNext = document.querySelector("[data-wizard-next]");
  const stepProgress = document.querySelector("[data-step-progress]");
  const activeProfileMetric = document.querySelector("[data-active-profile]");
  const selectedCount = document.querySelector("[data-selected-count]");
  const outputType = document.querySelector("[data-output-type]");
  const packageTrust = document.querySelector("[data-package-trust]");
  const kgRuntimeNotice = document.querySelector("[data-kg-runtime-copy]");
  const packageTrustPanel = document.querySelector(".package-trust");
  const requestPreview = document.querySelector("[data-request-preview]");
  const mobileDock = document.querySelector("[data-mobile-package-dock]");
  const mobileSelectedCount = document.querySelector("[data-mobile-selected-count]");
  const mobilePersonalizedCount = document.querySelector("[data-mobile-personalized-count]");
  const mobileOutputType = document.querySelector("[data-mobile-output-type]");

  init();

  function init() {
    populateFlowLabels();
    populateStepVisuals();
    populateBoundaryVisuals();
    populateFieldRelevanceLabels();
    populateDecisionSummaryLabels();
    populatePackFilter();
    populateBuilderOptions();
    renderProfilePicks();
    bindEvents();
    applyProfileDefaults({ silent: true });
    updateDerivedFields();
    showWizardStep(activeStep);
    loadLocalServerStatus();
    emitJourney("profile_started", { profile_id: activeProfileId || "custom" });
    render();
  }

  function flattenSkills(publicCatalog) {
    return publicCatalog.packs.flatMap((pack) =>
      pack.skills
        .filter((skill) => skill.visibility !== "internal")
        .map((skill) => ({
          ...skill,
          picker_eligibility: skill.picker_eligibility || "hidden_review",
          pack_id: pack.id,
          pack_display_name: pack.display_name,
          searchText: [
            skill.title,
            skill.display_name,
            skill.buyer_display_name,
            skill.buyer_summary,
            skill.buyer_category,
            skill.picker_eligibility,
            skill.agent_invocation,
            pack.display_name,
          ]
            .filter(Boolean)
            .join(" ")
            .toLowerCase(),
        }))
    );
  }

  function populatePackFilter() {
    const packs = [...new Map(pickerSkills.map((skill) => [skill.pack_id, skill.pack_display_name])).entries()]
      .sort((a, b) => a[1].localeCompare(b[1]));
    packs.forEach(([id, name]) => {
      const option = document.createElement("option");
      option.value = id;
      option.textContent = name;
      packFilter.append(option);
    });
  }

  function populateBuilderOptions() {
    const voiceField = form.querySelector('[name="brandVoice"]');
    if (voiceField && brandVoiceOptions.length) {
      const selectedVoice = voiceField.value;
      voiceField.textContent = "";
      brandVoiceOptions.forEach((option) => {
        const element = document.createElement("option");
        element.value = option.value;
        element.textContent = option.label || option.value;
        voiceField.append(element);
      });
      voiceField.value = selectedVoice || brandVoiceOptions[0]?.value || "";
    }

    const industryList = document.querySelector("#industry-options");
    if (industryList && industryOptions.length) {
      industryList.textContent = "";
      industryOptions.forEach((industry) => {
        const option = document.createElement("option");
        option.value = industry;
        industryList.append(option);
      });
    }
  }

  function initialProfileSelection() {
    const names = selectedProfileSkillNames();
    const fallback = [
      "smb-service-job-intake-triage",
      "smb-service-estimate-work-order-builder",
      "smb-service-dispatch-route-planner",
      "smb-ar-collections",
      "smb-crm-pipeline-review",
      "smb-ai-automation-discovery-map",
    ];
    return (names.length ? names : fallback).filter((name) => canSelectProfileSkill(name, false));
  }

  function profileById(profileId) {
    return profilePresets.find((profile) => profile.id === profileId) || null;
  }

  function activeProfile() {
    return profileById(activeProfileId);
  }

  function activeBranch() {
    return businessTypeTree.find((branch) =>
      Array.isArray(branch.profile_ids) && branch.profile_ids.includes(activeProfileId)
    ) || null;
  }

  function selectedProfiles() {
    const profiles = [...selectedProfileIds]
      .map(profileById)
      .filter(Boolean);
    const active = activeProfile();
    if (active && !profiles.some((profile) => profile.id === active.id)) {
      profiles.unshift(active);
    }
    return profiles;
  }

  function selectedBranches() {
    const branches = [];
    selectedProfiles().forEach((profile) => {
      const branch = businessTypeTree.find((item) =>
        Array.isArray(item.profile_ids) && item.profile_ids.includes(profile.id)
      );
      if (branch && !branches.some((item) => item.id === branch.id)) {
        branches.push(branch);
      }
    });
    return branches;
  }

  function profileSkillNames(profile = activeProfile()) {
    if (!profile) {
      return [];
    }
    const branch = businessTypeTree.find((item) =>
      Array.isArray(item.profile_ids) && item.profile_ids.includes(profile.id)
    );
    return dedupe([
      ...(completeBusinessOperations ? coreBusinessSkillTemplates : []),
      ...((branch && branch.skill_templates) || []),
      ...(profile.recommended_skill_templates || []),
    ]);
  }

  function selectedProfileSkillNames() {
    return dedupe(selectedProfiles().flatMap((profile) => profileSkillNames(profile)));
  }

  function selectedExplicitSkillNames() {
    return dedupe(selectedProfiles().flatMap((profile) => profile.explicit_skill_templates || []));
  }

  function syncSelectedSkillsFromProfiles() {
    selected.clear();
    initialProfileSelection().forEach((name) => selected.add(name));
  }

  function canSelectProfileSkill(name, includeExplicit) {
    const skill = skillByName.get(name);
    if (!skill) {
      return false;
    }
    if (skill.picker_eligibility === "default") {
      return true;
    }
    if (includeExplicit && skill.picker_eligibility === "profile_explicit") {
      return true;
    }
    return false;
  }

  function renderProfilePicks() {
    if (!profilePicks) {
      return;
    }
    profilePicks.textContent = "";
    profilePicks.classList.toggle("business-type-tree", Boolean(businessTypeTree.length));
    if (businessTypeTree.length) {
      businessTypeTree.forEach((branch) => {
        const article = document.createElement("article");
        const branchSelected = (branch.profile_ids || []).some((profileId) => selectedProfileIds.has(profileId));
        article.className = `branch-card${branchSelected ? " is-active" : ""}`;
        article.innerHTML = `
          ${renderBranchImageVisual(branch)}
          <div class="branch-heading">
            <div>
              <span class="tag">Branch</span>
              <h3>${escapeHtml(branch.label || branch.id)}</h3>
              <p>${escapeHtml(branch.summary || "")}</p>
            </div>
            <span class="branch-count">${(branch.skill_templates || []).length} add-ons</span>
          </div>
          <div class="branch-options"></div>
        `;
        const options = article.querySelector(".branch-options");
        (branch.profile_ids || []).forEach((profileId) => {
          const profile = profilePresets.find((candidate) => candidate.id === profileId);
          if (!profile) {
            return;
          }
          const button = profileButton(profile);
          options.append(button);
        });
        profilePicks.append(article);
      });
      return;
    }
    profilePresets.forEach((profile) => {
      profilePicks.append(profileButton(profile));
    });
  }

  function profileButton(profile) {
      const isPrimary = profile.id === activeProfileId;
      const isSelected = selectedProfileIds.has(profile.id);
      const wrapper = document.createElement("div");
      wrapper.className = `profile-option${isPrimary ? " is-primary" : ""}${isSelected ? " is-selected" : ""}`;

      const button = document.createElement("button");
      button.type = "button";
      button.dataset.profileId = profile.id;
      button.className = `profile-primary${isPrimary ? " is-active" : ""}`;
      button.setAttribute("aria-pressed", isPrimary ? "true" : "false");
      const skillCount = profileSkillNames(profile).filter((name) => canSelectProfileSkill(name, false)).length;
      const roleCopy = isPrimary ? "Primary defaults" : isSelected ? "Adds skills" : "Can add skills";
      button.innerHTML = `
        ${renderProfileVisual(profile)}
        <span class="profile-title">${escapeHtml(profile.label)}</span>
        <span class="profile-copy">${escapeHtml(profile.summary || "")}</span>
        <span class="profile-meta">${skillCount} skills · ${roleCopy}</span>
      `;
      button.addEventListener("click", () => selectProfile(profile.id));

      const toggle = document.createElement("button");
      toggle.type = "button";
      toggle.dataset.profileToggle = profile.id;
      toggle.className = "profile-toggle";
      toggle.disabled = isPrimary;
      toggle.setAttribute("aria-pressed", isSelected ? "true" : "false");
      toggle.textContent = isPrimary
        ? "Primary"
        : isSelected
          ? "Remove add-on"
          : "Add skills";
      toggle.addEventListener("click", () => toggleProfileSelection(profile.id));

      wrapper.append(button, toggle);
      return wrapper;
  }

  function selectProfile(profileId) {
    activeProfileId = profileId;
    selectedProfileIds = new Set(profileId ? [profileId] : []);
    activeSkillView = "recommended";
    profileExplicitUnlocked = false;
    syncSelectedSkillsFromProfiles();
    applyProfileDefaults({ silent: true });
    emitJourney("profile_selected", {
      profile_id: profileId,
      selected_profile_count: selectedProfileIds.size,
      selected_count: selected.size,
    });
    renderProfilePicks();
    render();
  }

  function toggleProfileSelection(profileId) {
    if (!profileId || profileId === activeProfileId) {
      return;
    }
    const selectedBefore = selectedProfileIds.has(profileId);
    if (selectedBefore) {
      selectedProfileIds.delete(profileId);
    } else {
      selectedProfileIds.add(profileId);
    }
    activeSkillView = "recommended";
    profileExplicitUnlocked = false;
    syncSelectedSkillsFromProfiles();
    emitJourney("profile_multi_selected", {
      profile_id: profileId,
      selected: !selectedBefore,
      selected_profile_count: selectedProfileIds.size,
      selected_count: selected.size,
    });
    renderProfilePicks();
    render();
  }

  function renderProfileSummary() {
    if (!profileSummary) {
      return;
    }
    const profile = activeProfile();
    if (!profile) {
      profileSummary.innerHTML = "<strong>Custom profile.</strong> Search default-ready skills or choose a profile above.";
      return;
    }
    const branch = activeBranch();
    const explicitCount = selectedExplicitSkillNames()
      .filter((name) => skillByName.get(name)?.picker_eligibility === "profile_explicit")
      .length;
    const explicitCopy = explicitCount && !profileExplicitUnlocked
      ? `<button class="button" type="button" data-confirm-profile-explicit>Show ${explicitCount} profile-specific add-ons</button>`
      : explicitCount
        ? `<span><strong>${explicitCount}</strong> profile-specific add-ons are visible for explicit selection.</span>`
        : `<span>Only default-ready skills are recommended for this profile.</span>`;
    const coreCopy = coreBusinessProcesses.length
      ? `<div class="core-process-list">
          ${coreBusinessProcesses.map((process) => `
            <span class="core-process-card">
              ${renderCoreProcessVisual(process)}
              <span>
                <strong>${escapeHtml(process.label || process.id)}</strong>
                <small>${escapeHtml(process.summary || "Always included")}</small>
              </span>
            </span>
          `).join("")}
        </div>`
      : "";
    profileSummary.innerHTML = `
      <strong>${escapeHtml(branch?.label ? `${branch.label} -> ${profile.label}` : `${profile.label} profile`)}.</strong>
      <span>${escapeHtml(profile.summary || "")}</span>
      <span>The focused profile workflow is included first. Complete business operations is available as an explicit addition.</span>
      <span><strong>${selectedProfileIds.size}</strong> business type${selectedProfileIds.size === 1 ? "" : "s"} selected for this pack.</span>
      ${coreCopy}
      ${explicitCopy}
    `;
    const unlock = profileSummary.querySelector("[data-confirm-profile-explicit]");
    if (unlock) {
      unlock.addEventListener("click", () => {
        profileExplicitUnlocked = true;
        emitJourney("profile_explicit_unlocked", { profile_id: profile.id, explicit_count: explicitCount });
        render();
      });
    }
  }

  function bindEvents() {
    wizardTargets.forEach((button) => {
      button.addEventListener("click", () => showWizardStep(Number(button.dataset.wizardTarget || 1)));
    });
    if (wizardPrev) {
      wizardPrev.addEventListener("click", () => showWizardStep(activeStep - 1));
    }
    if (wizardNext) {
      wizardNext.addEventListener("click", () => {
        if (activeStep < totalWizardSteps()) {
          showWizardStep(activeStep + 1);
        }
      });
    }
    const defaultsButton = document.querySelector("[data-apply-profile-defaults]");
    if (defaultsButton) {
      defaultsButton.addEventListener("click", () => {
        applyProfileDefaults({ silent: false });
        updateDerivedFields();
        handleContextInput();
        render();
      });
    }
    document.querySelectorAll("[data-download-package]").forEach((button) => {
      button.addEventListener("click", downloadBrowserPackage);
    });
    document.querySelectorAll("[data-download-request]").forEach((button) => {
      button.addEventListener("click", downloadRequestJson);
    });
    const rendererDownload = document.querySelector("[data-renderer-download]");
    if (rendererDownload) {
      rendererDownload.addEventListener("click", downloadFromRenderer);
    }
    const localDirectoryButton = document.querySelector("[data-create-local-directories]");
    if (localDirectoryButton) {
      localDirectoryButton.addEventListener("click", createLocalDirectories);
    }
    const resetButton = document.querySelector("[data-reset-profile-selection]");
    if (resetButton) {
      resetButton.addEventListener("click", () => {
        resetProfileSelection();
      });
    }
    skillViewButtons.forEach((button) => {
      button.addEventListener("click", () => {
        activeSkillView = button.dataset.skillView || "recommended";
        renderSkillView();
        if (activeSkillView === "catalog") {
          renderSkillCards();
        }
      });
    });
    searchInput.addEventListener("input", renderSkillCards);
    packFilter.addEventListener("change", renderSkillCards);
    form.querySelector('[name="businessName"]').addEventListener("input", () => {
      form.querySelector('[name="businessName"]').dataset.userEdited = "true";
      updateDerivedFields();
      handleContextInput();
    });
    form.querySelectorAll("input, textarea, select").forEach((field) => {
      if (field.name === "businessName") {
        return;
      }
      field.addEventListener("input", () => {
        field.dataset.userEdited = "true";
        appliedProfileDefaults.delete(field.name);
        handleContextInput();
      });
      field.addEventListener("change", () => {
        field.dataset.userEdited = "true";
        appliedProfileDefaults.delete(field.name);
        if (field.name === "completeBusinessOperations") {
          completeBusinessOperations = Boolean(field.checked);
          syncSelectedSkillsFromProfiles();
        }
        handleContextInput();
        render();
      });
    });
    form.querySelector('[name="apiMode"]').addEventListener("change", updateApiModeDefaults);
    markEditedOnInput(form.querySelector('[name="skillPrefix"]'));
    markEditedOnInput(form.querySelector('[name="localOutputName"]'));
    markEditedOnInput(form.querySelector('[name="rendererUrl"]'));
  }

  function resetProfileSelection() {
    selectedProfileIds = new Set(activeProfileId ? [activeProfileId] : []);
    selected.clear();
    initialProfileSelection().forEach((name) => selected.add(name));
    activeSkillView = "recommended";
    profileExplicitUnlocked = false;
    emitJourney("profile_recommendations_reset", {
      profile_id: activeProfileId || "custom",
      selected_profile_count: selectedProfileIds.size,
      selected_count: selected.size,
    });
    renderProfilePicks();
    render();
  }

  function showWizardStep(step) {
    const previousStep = activeStep;
    activeStep = Math.min(totalWizardSteps(), Math.max(1, Number(step) || 1));
    if (activeStep === 5 && previousStep !== 5) {
      activeSkillView = "recommended";
    }
    wizardSteps.forEach((panel) => {
      const isActive = Number(panel.dataset.wizardStep || 0) === activeStep;
      panel.classList.toggle("is-active", isActive);
      panel.hidden = !isActive;
    });
    wizardTargets.forEach((button) => {
      const isActive = Number(button.dataset.wizardTarget || 0) === activeStep;
      button.classList.toggle("is-active", isActive);
      button.setAttribute("aria-current", isActive ? "step" : "false");
    });
    if (wizardPrev) {
      wizardPrev.disabled = activeStep === 1;
    }
    if (wizardNext) {
      wizardNext.hidden = activeStep >= totalWizardSteps();
      wizardNext.textContent = stepDefinition(activeStep).next_label ||
        `Next: ${stepDefinition(activeStep + 1).label || "Download"}`;
    }
    renderSkillView();
    renderSummaryStrip();
    renderMobileDock();
  }

  function applyProfileDefaults({ silent = false } = {}) {
    const defaults = {
      ...(activeBranch()?.defaults || {}),
      ...(profileContextDefaults[activeProfileId] || {}),
    };
    Object.entries(defaults).forEach(([name, value]) => setProfileField(name, value));
    if (!silent) {
      setStatus(`${activeProfile()?.label || "Profile"} defaults applied.`);
      emitJourney("context_defaults_applied", { profile_id: activeProfileId || "custom" });
    }
  }

  function setProfileField(name, value) {
    const field = form.querySelector(`[name="${name}"]`);
    if (!field || field.dataset.userEdited) {
      return;
    }
    field.value = value;
    if (String(value || "").trim()) {
      appliedProfileDefaults.add(name);
    }
  }

  function handleContextInput() {
    renderPackagePreview();
    renderRecommendedPack();
    renderRequestPreview();
    renderSummaryStrip();
    renderMobileDock();
    if (!contextCompletedEmitted && completedContextFieldCount() >= 7) {
      contextCompletedEmitted = true;
      emitJourney("context_completed", {
        profile_id: activeProfileId || "custom",
        filled_fields: completedContextFieldCount(),
      });
    }
  }

  function completedContextFieldCount() {
    return [
      "businessName",
      "industry",
      "locations",
      "serviceArea",
      "owner",
      "systems",
      "brandVoice",
      "serviceLines",
      "offers",
      "personas",
      "reviewPolicy",
    ].filter((name) => fieldValue(name)).length;
  }

  function updateDerivedFields() {
    const prefix = slugify(fieldValue("businessName")).split("-").filter(Boolean)[0] || "local";
    const prefixField = form.querySelector('[name="skillPrefix"]');
    if (!prefixField.dataset.edited) {
      prefixField.value = prefix;
    }

    const outputField = form.querySelector('[name="localOutputName"]');
    if (outputField && !outputField.dataset.edited) {
      outputField.value = `${slugify(fieldValue("businessName")) || "local-business"}-skills`;
    }
  }

  function markEditedOnInput(field) {
    if (!field) {
      return;
    }
    field.addEventListener("input", () => {
      field.dataset.edited = "true";
    }, { once: true });
  }

  function updateApiModeDefaults() {
    const mode = fieldValue("apiMode");
    const urlField = form.querySelector('[name="rendererUrl"]');
    if (!urlField || urlField.dataset.edited) {
      return;
    }
    urlField.value = mode === "production" ? productionApiUrl : localRendererUrl;
  }

  function render() {
    renderProfileSummary();
    renderSkillView();
    renderRecommendedPack();
    renderSkillCards();
    renderSelectedList();
    renderPackagePreview();
    renderSummaryStrip();
    renderRequestPreview();
    renderMobileDock();
    renderFinanceContextVisibility();
    if (selectedCount) {
      selectedCount.textContent = `${selected.size} ${selected.size === 1 ? "skill" : "skills"}`;
    }
    if (packageTrust) {
      packageTrust.textContent = packageSafetyCopy;
    }
    if (kgRuntimeNotice) {
      kgRuntimeNotice.textContent = kgRuntimeCopy;
    }
  }

  function renderFinanceContextVisibility() {
    const financeSelected = activeProfileId === "finance" || [...selected].some((name) => {
      const skill = skillByName.get(name);
      return skill?.pack_id?.includes("finance") || /month-end|financial|bank-card|transaction|cash-flow|budget|payroll|tax|audit-evidence|ap-controls|ar-collections/.test(name);
    });
    document.querySelectorAll("[data-finance-context]").forEach((field) => {
      field.hidden = !financeSelected;
      field.querySelectorAll("input").forEach((input) => { input.disabled = !financeSelected; });
    });
  }

  function renderSummaryStrip() {
    if (stepProgress) {
      stepProgress.textContent = `Step ${activeStep} of ${totalWizardSteps()}`;
    }
    if (activeProfileMetric) {
      const profileLabel = activeProfile()?.label || "Custom";
      const addOnCount = Math.max(0, selectedProfileIds.size - 1);
      activeProfileMetric.textContent = addOnCount ? `${profileLabel} + ${addOnCount}` : profileLabel;
    }
    if (selectedCount) {
      selectedCount.textContent = `${selected.size} ${selected.size === 1 ? "skill" : "skills"}`;
    }
    if (outputType) {
      outputType.textContent = fieldValue("apiMode") === "production"
        ? "Verified ZIP request"
        : "Preview ZIP";
    }
  }

  function renderSkillView() {
    skillViewButtons.forEach((button) => {
      const isActive = button.dataset.skillView === activeSkillView;
      button.classList.toggle("is-active", isActive);
      button.setAttribute("aria-selected", isActive ? "true" : "false");
    });
    if (recommendedPack) {
      recommendedPack.hidden = activeSkillView !== "recommended";
    }
    if (catalogPanel) {
      catalogPanel.hidden = activeSkillView !== "catalog";
    }
  }

  function renderRecommendedPack() {
    if (!recommendedPack) {
      return;
    }
    const summary = packageSummaryData();
    if (!selected.size) {
      recommendedPack.innerHTML = `
        <div class="empty-state">
          <strong>No skills selected.</strong>
          <span>Reset to profile recommendations or add skills from the catalog.</span>
        </div>
      `;
      return;
    }
    const profile = activeProfile();
    recommendedPack.textContent = "";
    const header = document.createElement("div");
    header.className = "recommended-pack-header";
    header.innerHTML = `
      <div>
        <p class="eyebrow">${escapeHtml(profile?.label || "Custom")} pack</p>
        <h3>Recommended work to include</h3>
        <p>${escapeHtml(profile?.summary || "Selected skills are ready to package with your captured business context.")}</p>
      </div>
      <div class="preview-metrics compact">
        <div><span>${escapeHtml(summaryLabels.selected)}</span><strong>${selected.size}</strong></div>
        <div><span>${escapeHtml(summaryLabels.dependencies)}</span><strong>${summary.dependencyCount}</strong></div>
        <div><span>${escapeHtml(summaryLabels.personalizedSettings)}</span><strong>${summary.personalizedSettingsCount}</strong></div>
        <div><span>${escapeHtml(summaryLabels.packageType)}</span><strong>${escapeHtml(summary.packageType)}</strong></div>
      </div>
    `;
    recommendedPack.append(header);

    const contextState = document.createElement("div");
    contextState.className = "context-state";
    contextState.innerHTML = `
      <span><strong>Missing context:</strong> ${escapeHtml(summary.missing.join(", ") || "None detected in the local wizard")}</span>
      <span><strong>Defaults used:</strong> ${escapeHtml(summary.defaulted.join(", ") || "None")}</span>
    `;
    recommendedPack.append(contextState);

    const list = document.createElement("div");
    list.className = "recommended-skill-list";
    const focusedNames = new Set(dedupe(selectedProfiles().flatMap((selectedProfile) => {
      const branch = businessTypeTree.find((item) => item.profile_ids?.includes(selectedProfile.id));
      return [...(branch?.skill_templates || []), ...(selectedProfile.recommended_skill_templates || [])];
    })));
    const selectedSkills = selectedSkillsInDisplayOrder();
    const groups = [
      ["Primary workflow", selectedSkills.filter((skill) => focusedNames.has(skill.name))],
      ["Supporting dependencies", summary.resolvedNames.filter((name) => !selected.has(name)).map((name) => skillByName.get(name)).filter(Boolean)],
      ["Optional additions", selectedSkills.filter((skill) => !focusedNames.has(skill.name))],
    ];
    groups.forEach(([label, skills]) => {
      if (!skills.length) return;
      const heading = document.createElement("h3");
      heading.textContent = label;
      list.append(heading);
      skills.forEach((skill) => {
      const item = document.createElement("article");
      item.className = "recommended-skill-card";
      const isDependency = !selected.has(skill.name);
      item.innerHTML = `
        <div>
          <div class="skill-card-header">
            <h3>${escapeHtml(skill.display_name)}</h3>
            <span class="tag">${isDependency ? "Included support" : "Selected"}</span>
          </div>
          <div class="tag-row">
            <span class="tag">${escapeHtml(skill.buyer_category || "Business workflow")}</span>
            <span class="tag">${escapeHtml(skill.pack_display_name)}</span>
          </div>
          <p>${escapeHtml(skill.buyer_summary || skill.description || "")}</p>
          ${isDependency ? "" : `<p class="why-selected">${escapeHtml(recommendationReason(skill, summary.context))}</p>`}
          <details class="context-details"><summary>Technical details</summary><span class="selector">${escapeHtml(skill.agent_invocation)}</span></details>
        </div>
        ${isDependency ? "" : `<button class="button" type="button" data-remove-skill="${escapeHtml(skill.name)}">Remove</button>`}
      `;
      list.append(item);
      });
    });
    recommendedPack.append(list);
    recommendedPack.querySelectorAll("[data-remove-skill]").forEach((button) => {
      button.addEventListener("click", () => toggleSkill(button.dataset.removeSkill));
    });
  }

  function renderSkillCards() {
    const term = searchInput.value.trim().toLowerCase();
    const pack = packFilter.value;
    const visible = pickerSkills
      .filter(canShowSkill)
      .filter((skill) => !pack || skill.pack_id === pack)
      .filter((skill) => !term || skill.searchText.includes(term))
      .slice(0, 80);

    skillGrid.textContent = "";
    if (!visible.length) {
      skillGrid.innerHTML = '<p class="small-copy">No matching picker-ready skills. Clear filters or search another workflow.</p>';
      return;
    }
    visible.forEach((skill) => {
      const card = document.createElement("article");
      card.className = `skill-card${selected.has(skill.name) ? " is-selected" : ""}`;
      card.innerHTML = `
        <div class="skill-card-header">
          <h3>${escapeHtml(skill.display_name)}</h3>
          <span class="toggle" aria-hidden="true">${selected.has(skill.name) ? "✓" : ""}</span>
        </div>
        <div class="tag-row">
          <span class="tag">${escapeHtml(skill.buyer_category || "Business workflow")}</span>
          <span class="tag">${escapeHtml(skill.pack_display_name)}</span>
          ${skill.picker_eligibility === "profile_explicit" ? '<span class="tag">Profile add-on</span>' : ""}
        </div>
        <p>${escapeHtml(skill.buyer_summary || skill.description || "")}</p>
        <details class="context-details"><summary>Technical details</summary><span class="selector">${escapeHtml(skill.agent_invocation)}</span></details>
      `;
      card.tabIndex = 0;
      card.setAttribute("role", "button");
      card.setAttribute("aria-pressed", selected.has(skill.name) ? "true" : "false");
      card.addEventListener("click", () => toggleSkill(skill.name));
      card.addEventListener("keydown", (event) => {
        if (event.key === "Enter" || event.key === " ") {
          event.preventDefault();
          toggleSkill(skill.name);
        }
      });
      skillGrid.append(card);
    });
  }

  function canShowSkill(skill) {
    if (skill.picker_eligibility === "default") {
      return true;
    }
    if (skill.picker_eligibility !== "profile_explicit" || !profileExplicitUnlocked) {
      return false;
    }
    return selectedExplicitSkillNames().includes(skill.name);
  }

  function renderSelectedList() {
    selectedList.textContent = "";
    if (!selected.size) {
      selectedList.innerHTML = '<p class="small-copy">Choose at least one skill to build a package.</p>';
      return;
    }
    selectedSkillsInDisplayOrder().forEach((skill) => {
      const item = document.createElement("div");
      item.className = "selected-item";
      item.innerHTML = `
        <strong>${escapeHtml(skill.display_name)}</strong>
        <span>${escapeHtml(skill.agent_invocation)}</span>
      `;
      selectedList.append(item);
    });
  }

  function renderPackagePreview() {
    if (!packagePreview) {
      return;
    }
    if (!selected.size) {
      packagePreview.innerHTML = "<strong>Preview package.</strong><span>Choose skills to see the ZIP summary before download.</span>";
      return;
    }
    const summary = packageSummaryData();
    packagePreview.innerHTML = `
      <strong>${escapeHtml(summaryLabels.packageSummary)}</strong>
      <div class="preview-metrics">
        <div><span>${escapeHtml(summaryLabels.selected)}</span><strong>${selected.size}</strong></div>
        <div><span>${escapeHtml(summaryLabels.dependencies)}</span><strong>${summary.dependencyCount}</strong></div>
        <div><span>${escapeHtml(summaryLabels.personalizedSettings)}</span><strong>${summary.personalizedSettingsCount}</strong></div>
        <div><span>${escapeHtml(summaryLabels.packageType)}</span><strong>${escapeHtml(summary.packageType)}</strong></div>
      </div>
      <span><strong>Missing context:</strong> ${escapeHtml(summary.missing.join(", ") || "None detected in the local wizard")}</span>
      <span><strong>Defaults used:</strong> ${escapeHtml(summary.defaulted.join(", ") || "None")}</span>
    `;
  }

  function packageSummaryData() {
    const resolvedNames = resolveSkillClosure([...selected]);
    const context = businessContext();
    return {
      resolvedNames,
      context,
      dependencyCount: resolvedNames.filter((name) => !selected.has(name)).length,
      personalizedSettingsCount: Object.keys(context.personalization_overrides || {}).length,
      defaulted: previewDefaults(context),
      missing: previewMissingContext(context),
      packageType: fieldValue("apiMode") === "production" ? "Verified ZIP request" : "Preview",
    };
  }

  function selectedSkillsInDisplayOrder() {
    const profileOrder = new Map();
    selectedProfiles().forEach((profile, profileIndex) => {
      (profile.recommended_skill_templates || []).forEach((name, index) => {
        if (!profileOrder.has(name)) {
          profileOrder.set(name, (profileIndex * 1000) + index);
        }
      });
    });
    const branchOrder = new Map();
    selectedBranches().forEach((branch, branchIndex) => {
      (branch.skill_templates || []).forEach((name, index) => {
        if (!branchOrder.has(name)) {
          branchOrder.set(name, (branchIndex * 1000) + index);
        }
      });
    });
    const coreOrder = new Map(coreBusinessSkillTemplates.map((name, index) => [name, index]));
    return [...selected]
      .map((name) => skillByName.get(name))
      .filter(Boolean)
      .sort((a, b) => {
        const aCore = coreOrder.has(a.name) ? 0 : 1;
        const bCore = coreOrder.has(b.name) ? 0 : 1;
        if (aCore !== bCore) {
          return aCore - bCore;
        }
        if (aCore === 0) {
          return coreOrder.get(a.name) - coreOrder.get(b.name);
        }
        const aOrder = profileOrder.has(a.name) ? profileOrder.get(a.name) : Number.MAX_SAFE_INTEGER;
        const bOrder = profileOrder.has(b.name) ? profileOrder.get(b.name) : Number.MAX_SAFE_INTEGER;
        if (aOrder !== bOrder) {
          return aOrder - bOrder;
        }
        const aBranch = branchOrder.has(a.name) ? branchOrder.get(a.name) : Number.MAX_SAFE_INTEGER;
        const bBranch = branchOrder.has(b.name) ? branchOrder.get(b.name) : Number.MAX_SAFE_INTEGER;
        if (aBranch !== bBranch) {
          return aBranch - bBranch;
        }
        return a.display_name.localeCompare(b.display_name);
      });
  }

  function recommendationReason(skill, context) {
    const coreProcess = coreBusinessProcesses.find((process) =>
      (process.skill_templates || []).includes(skill.name)
    );
    if (coreProcess) {
      return `Always included for ${coreProcess.label.toLowerCase()} because every pack should cover core business operations.`;
    }
    const branch = selectedBranches().find((item) => item.skill_templates?.includes(skill.name));
    if (branch?.skill_templates?.includes(skill.name)) {
      return `Added by the ${branch.label} branch before the ${activeProfile()?.label || "selected"} profile adds domain skills.`;
    }
    const matchingProfiles = selectedProfiles().filter((profile) =>
      profile.recommended_skill_templates?.includes(skill.name)
    );
    const profileLabel = matchingProfiles.length
      ? formatList(matchingProfiles.map((profile) => profile.label))
      : activeProfile()?.label || "your profile";
    const values = personalizationSurfaceValues(context.systems, context.owners);
    const surfaceLabels = [
      ...(packSurfaceBundles[skill.pack_id] || []),
      ...(skill.recommended_personalization_surfaces || []),
    ]
      .map(normalizedSurfaceKey)
      .filter((key, index, keys) => keys.indexOf(key) === index)
      .filter((key) => values[key])
      .map(surfaceLabel)
      .slice(0, recommendationCopy.surface_limit || 5);
    if (!surfaceLabels.length) {
      return recommendationCopy.fallback ||
        `Selected for ${profileLabel} because it is part of this profile's recommended operating pack.`;
    }
    return `Selected for ${profileLabel} because your context includes ${formatList(surfaceLabels)}.`;
  }

  function previewMissingContext(context) {
    const surfaceKeys = selectedSurfaceKeys();
    const values = personalizationSurfaceValues(context.systems, context.owners);
    const requiredPreviewSurfaces = [
      "offers",
      "personas",
      "review_rules",
      "systems",
      "service_area",
      "consent_policy",
      "period_context",
      "chart_of_accounts",
      "materiality_thresholds",
      "approval_policy",
      "operating_cadence",
      "document_system",
      "artifact_format",
      "warranty_policy",
    ];
    const missing = requiredPreviewSurfaces
      .filter((key) => surfaceKeys.has(key) && !values[key])
      .map(surfaceLabel);
    return missing;
  }

  function previewDefaults(context) {
    const surfaceKeys = selectedSurfaceKeys();
    const defaults = [];
    if (!fieldValue("brandVoice")) defaults.push("brand voice=practical");
    if (!fieldValue("owner")) defaults.push("owner=Operations lead");
    if (!context.business.locations.length) defaults.push("locations=not provided");
    if (surfaceKeys.has("service_area") && !fieldValue("serviceArea") && fieldValue("locations")) {
      defaults.push("service area=locations");
    }
    appliedProfileDefaults.forEach((name) => defaults.push(`${surfaceLabel(name)}=profile suggestion`));
    return defaults;
  }

  function renderRequestPreview() {
    if (!requestPreview) {
      return;
    }
    const request = renderRequest();
    const preview = {
      package_label: fieldValue("requestLabel") || `${request.business.legal_name} skill pack`,
      api_mode: fieldValue("apiMode") || "renderer",
      renderer_target: fieldValue("rendererUrl") || localRendererUrl,
      route_environment: fieldValue("routeEnvironment") || "qa",
      primary_profile: activeProfileId || "custom",
      selected_business_types: selectedProfiles().map((profile) => profile.id),
      core_business_processes: coreBusinessProcesses.map((process) => process.id),
      selected_skill_templates: request.skill_templates,
      business_config_sections: [
        "business",
        "owners",
        "systems",
        "review_rules",
        "brand_context",
        "context_graph",
        "personalization_overrides",
      ],
      omitted_secret_fields: omittedSecretFields,
    };
    requestPreview.innerHTML = `
      <strong>Sanitized request preview</strong>
      <p class="small-copy">This preview shows routing and skill selection only. Secret fields are intentionally omitted.</p>
      <pre>${escapeHtml(JSON.stringify(preview, null, 2))}</pre>
    `;
  }

  function renderMobileDock() {
    if (!mobileDock) {
      return;
    }
    const summary = packageSummaryData();
    mobileDock.hidden = !(selected.size && activeStep === totalWizardSteps());
    document.body.classList.toggle("mobile-dock-open", !mobileDock.hidden);
    if (mobileSelectedCount) {
      mobileSelectedCount.textContent = String(selected.size);
    }
    if (mobilePersonalizedCount) {
      mobilePersonalizedCount.textContent = String(summary.personalizedSettingsCount);
    }
    if (mobileOutputType) {
      mobileOutputType.textContent = summary.packageType;
    }
  }

  function toggleSkill(name) {
    if (!name) {
      return;
    }
    const wasSelected = selected.has(name);
    if (selected.has(name)) {
      selected.delete(name);
    } else {
      selected.add(name);
    }
    emitJourney("skill_selected", {
      skill_name: name,
      selected: !wasSelected,
      selected_count: selected.size,
      profile_id: activeProfileId || "custom",
    });
    render();
  }

  function totalWizardSteps() {
    return Math.max(wizardSteps.length, flowSteps.length, 6);
  }

  function stepDefinition(step) {
    return flowSteps.find((definition) => Number(definition.id) === Number(step)) || {};
  }

  function populateFlowLabels() {
    wizardTargets.forEach((button) => {
      const step = Number(button.dataset.wizardTarget || 0);
      const definition = stepDefinition(step);
      const label = definition.short_label || definition.label;
      if (!label) {
        return;
      }
      button.innerHTML = `<span>${step}</span>${escapeHtml(label)}`;
    });
  }

  function populateStepVisuals() {
    wizardSteps.forEach((panel) => {
      const stepId = String(panel.dataset.wizardStep || "");
      const spec = stepVisuals[stepId];
      const heading = panel.querySelector(".panel-heading");
      if (!spec || !heading || panel.querySelector(".step-visual-banner")) {
        return;
      }
      heading.insertAdjacentHTML(
        "afterend",
        renderGeneratedVisual(spec, "step-visual-banner", stepDefinition(stepId).label || spec.label || "")
      );
    });
  }

  function populateBoundaryVisuals() {
    if (!packageTrustPanel || packageTrustPanel.querySelector(".boundary-trust-visual")) {
      return;
    }
    const spec = boundaryVisuals["kg-runtime"];
    if (!spec?.sprite) {
      return;
    }
    packageTrustPanel.insertAdjacentHTML(
      "afterbegin",
      `<div class="boundary-trust-visual">${renderSpriteVisual(spec, "boundary-sprite-cell", spec.label || "No secrets boundary", "category_boundary")}</div>`
    );
  }

  function populateFieldRelevanceLabels() {
    document.querySelectorAll("[data-field-relevance]").forEach((element) => {
      const label = fieldRelevanceLabels[element.dataset.fieldRelevance];
      if (label) {
        element.textContent = label;
      }
    });
  }

  function populateDecisionSummaryLabels() {
    document.querySelectorAll("[data-decision-summary-label]").forEach((element) => {
      const label = decisionSummaryLabels[element.dataset.decisionSummaryLabel];
      if (label) {
        element.textContent = label;
      }
    });
  }

  function dedupe(items) {
    return items.filter((item, index) => item && items.indexOf(item) === index);
  }

  function renderProfileVisual(profile) {
    const spec = {
      ...(profileVisuals[profile.id] || {}),
      label: profile.label,
      soft: profileVisuals[profile.id]?.soft || "#edf1ef",
    };
    return renderSpriteVisual(spec, "profile-mini-visual", profile.label || profile.id);
  }

  function renderBranchImageVisual(branch) {
    const spec = branchVisuals[branch.id] || {};
    const profiles = (branch.profile_ids || [])
      .map(profileById)
      .filter(Boolean);
    const branchSprites = Array.isArray(spec.sprites) && spec.sprites.length
      ? spec.sprites
      : spec.sprite
        ? [spec.sprite]
        : [];
    const cells = (branchSprites.length
      ? branchSprites.map((sprite, index) => ({
          label: `${spec.label || branch.label || branch.id} ${index + 1}`,
          sprite,
        }))
      : profiles.slice(0, 3).map((profile) => ({
          ...(profileVisuals[profile.id] || {}),
          label: profile.label || profile.id,
        })))
      .map((item) => renderSpriteVisual(item, "branch-sprite-cell", item.label || branch.label || branch.id, branchSprites.length ? "category_boundary" : "business_type"))
      .join("");
    const chips = Array.isArray(spec.chips) ? spec.chips.slice(0, 3) : [];
    return `
      <div class="branch-image-visual" role="img" aria-label="${escapeHtml(spec.label || branch.label || "Business type illustrations")}">
        <div class="branch-sprite-row">${cells}</div>
        <div class="visual-copy">
          <strong>${escapeHtml(spec.label || branch.label || branch.id)}</strong>
          ${spec.cue ? `<span>${escapeHtml(spec.cue)}</span>` : ""}
          ${chips.length ? `<div class="visual-chip-row">${chips.map((chip) => `<span>${escapeHtml(chip)}</span>`).join("")}</div>` : ""}
        </div>
      </div>
    `;
  }

  function renderCoreProcessVisual(process) {
    const spec = coreProcessVisuals[process.id] || {};
    if (spec.sprite) {
      return renderSpriteVisual(spec, "core-process-sprite", spec.label || process.label || process.id, "category_boundary");
    }
    return "";
  }

  function renderSpriteVisual(spec, className, fallbackLabel, defaultSheetId = "business_type") {
    const sprite = spec.sprite || {};
    const sheetId = sprite.sheet || spec.sheet || defaultSheetId;
    const sheet = spriteSheets[sheetId] || spriteSheets[defaultSheetId] || spriteSheets.business_type;
    const cols = clampSpriteGrid(sheet.cols, 3);
    const rows = clampSpriteGrid(sheet.rows, 3);
    const row = clampSpriteIndex(sprite.row, rows);
    const col = clampSpriteIndex(sprite.col, cols);
    const x = cols <= 1 ? 0 : (col * 100) / (cols - 1);
    const y = rows <= 1 ? 0 : (row * 100) / (rows - 1);
    const label = spec.label || fallbackLabel || "Business type illustration";
    return `
      <span class="${escapeHtml(className)} imagegen-sprite imagegen-sprite-${escapeHtml(sheetId)}" style="--sprite-bg-width: ${cols * 100}%; --sprite-bg-height: ${rows * 100}%; --sprite-x: ${x}%; --sprite-y: ${y}%; background-image: url('${sheet.asset}');" aria-label="${escapeHtml(label)}"></span>
    `;
  }

  function renderGeneratedVisual(spec, className, fallbackLabel) {
    if (!spec) {
      return "";
    }
    const label = spec.label || fallbackLabel || "Visual guide";
    const cue = spec.cue || "";
    const chips = Array.isArray(spec.chips) ? spec.chips.slice(0, 3) : [];
    const accent = safeHexColor(spec.accent, "#21525b");
    const soft = safeHexColor(spec.soft, "#e9eeee");
    const visual = spec.sprite
      ? renderSpriteVisual(spec, "step-sprite-cell", label, "step")
      : renderVisualSvg(spec.layout || "flow");
    return `
      <div class="generated-visual ${escapeHtml(className || "")}" style="--visual-accent: ${accent}; --visual-soft: ${soft};" role="img" aria-label="${escapeHtml(label)}">
        ${visual}
        <div class="visual-copy">
          <strong>${escapeHtml(label)}</strong>
          ${cue ? `<span>${escapeHtml(cue)}</span>` : ""}
          ${chips.length ? `<div class="visual-chip-row">${chips.map((chip) => `<span>${escapeHtml(chip)}</span>`).join("")}</div>` : ""}
        </div>
      </div>
    `;
  }

  function renderMiniVisual(spec, fallbackLabel) {
    const accent = safeHexColor(spec.accent, "#21525b");
    const label = spec.label || fallbackLabel || "Profile visual";
    return `
      <span class="profile-mini-visual" style="--visual-accent: ${accent};" aria-label="${escapeHtml(label)}">
        ${renderMiniSvg(spec.layout || "flow")}
      </span>
    `;
  }

  function renderVisualSvg(layout) {
    return `
      <svg viewBox="0 0 236 132" aria-hidden="true" focusable="false">
        <rect class="visual-bg" x="8" y="8" width="220" height="116" rx="14"></rect>
        <g class="visual-lines">${renderMascotScene(layout || "flow")}</g>
      </svg>
    `;
  }

  function renderMascotScene(layout) {
    return `
      <ellipse class="mascot-shadow" cx="90" cy="113" rx="54" ry="8"></ellipse>
      <circle class="mascot-ear" cx="58" cy="61" r="28"></circle>
      <circle class="mascot-ear" cx="113" cy="61" r="28"></circle>
      <rect class="mascot-body" x="64" y="72" width="60" height="42" rx="16"></rect>
      <circle class="mascot-face" cx="86" cy="58" r="37"></circle>
      <path class="mascot-trunk" d="M92 68c14 21 4 40-14 36-9-2-9-13-1-16"></path>
      <circle class="mascot-eye" cx="76" cy="52" r="3"></circle>
      <circle class="mascot-eye" cx="96" cy="52" r="3"></circle>
      <path class="mascot-smile" d="M76 69c7 7 22 7 29 0"></path>
      <path class="mascot-tusk" d="M108 65l17-7"></path>
      ${renderMascotProp(layout)}
    `;
  }

  function renderMascotProp(layout) {
    const props = {
      selector: `
        <rect class="prop-fill" x="146" y="30" width="28" height="34" rx="7"></rect>
        <rect class="prop-fill" x="180" y="42" width="28" height="34" rx="7"></rect>
        <rect class="prop-fill" x="151" y="76" width="52" height="20" rx="7"></rect>
        <path class="prop-line" d="M156 42h9M156 52h13M188 54h12M161 86h31"></path>
      `,
      basics: `
        <path class="prop-fill" d="M174 30c16 0 27 11 27 26 0 18-27 43-27 43s-27-25-27-43c0-15 11-26 27-26z"></path>
        <circle class="prop-dot" cx="174" cy="56" r="8"></circle>
        <rect class="prop-fill" x="137" y="82" width="48" height="22" rx="7"></rect>
        <path class="prop-line" d="M146 94h29"></path>
      `,
      systems: `
        <rect class="prop-fill" x="142" y="32" width="70" height="54" rx="10"></rect>
        <path class="prop-line" d="M154 48h46M154 62h26M154 76h38"></path>
        <circle class="prop-dot" cx="198" cy="74" r="7"></circle>
        <path class="prop-line" d="M124 70h18"></path>
      `,
      guardrails: `
        <path class="prop-fill" d="M176 26l42 15v26c0 24-18 38-42 48-24-10-42-24-42-48V41z"></path>
        <path class="prop-line" d="M157 67l14 14 28-34"></path>
      `,
      pack: `
        <path class="prop-fill" d="M137 49h35l8 10h39v48h-82z"></path>
        <path class="prop-line" d="M151 74h48M151 88h36"></path>
        <rect class="prop-fill" x="150" y="31" width="44" height="20" rx="6"></rect>
      `,
      download: `
        <rect class="prop-fill" x="142" y="62" width="68" height="42" rx="9"></rect>
        <path class="prop-line" d="M176 28v45M158 56l18 18 18-18M157 84h38"></path>
      `,
      route: `
        <rect class="prop-fill" x="142" y="64" width="66" height="30" rx="7"></rect>
        <path class="prop-line" d="M151 64l12-18h28l14 18"></path>
        <circle class="prop-dot" cx="158" cy="96" r="6"></circle>
        <circle class="prop-dot" cx="194" cy="96" r="6"></circle>
        <path class="prop-line" d="M147 43l25-16M165 31l7-4 4 8"></path>
      `,
      pipeline: `
        <path class="prop-fill" d="M145 43h56l-18 26v31h-20V69z"></path>
        <path class="prop-line" d="M141 101h66M154 54h36"></path>
        <circle class="prop-dot" cx="203" cy="37" r="8"></circle>
      `,
      community: `
        <path class="prop-fill" d="M176 101s-36-20-36-45c0-13 9-22 21-22 7 0 12 3 15 8 4-5 9-8 16-8 12 0 21 9 21 22 0 25-37 45-37 45z"></path>
        <path class="prop-line" d="M158 59c8 7 28 7 36 0"></path>
      `,
      functions: `
        <rect class="prop-fill" x="144" y="29" width="58" height="76" rx="10"></rect>
        <path class="prop-line" d="M156 48h34M156 64h34M156 80h22"></path>
        <circle class="prop-dot" cx="193" cy="79" r="7"></circle>
        <path class="prop-line" d="M188 80l4 5 9-13"></path>
      `,
      flow: `
        <rect class="prop-fill" x="146" y="43" width="54" height="44" rx="10"></rect>
        <path class="prop-line" d="M158 58h30M158 72h20"></path>
      `,
    };
    return props[layout] || props.flow;
  }

  function renderMiniSvg(layout) {
    return `
      <svg viewBox="0 0 64 64" aria-hidden="true" focusable="false">
        <rect class="mini-bg" x="4" y="4" width="56" height="56" rx="14"></rect>
        <g class="mini-mascot">
          <circle class="mini-ear" cx="22" cy="31" r="12"></circle>
          <circle class="mini-ear" cx="42" cy="31" r="12"></circle>
          <circle class="mini-face" cx="32" cy="30" r="16"></circle>
          <path class="mini-trunk" d="M34 36c7 10 2 19-7 17-4-1-4-6 0-8"></path>
          <circle class="mini-eye" cx="27" cy="27" r="2"></circle>
          <circle class="mini-eye" cx="37" cy="27" r="2"></circle>
          ${renderMiniMascotProp(layout)}
        </g>
      </svg>
    `;
  }

  function renderMiniMascotProp(layout) {
    const props = {
      route: '<path class="mini-prop" d="M9 48h44M45 43l7 5-7 5"></path>',
      pipeline: '<path class="mini-prop" d="M48 16h10l-7 10v11h-6V26z"></path>',
      community: '<path class="mini-prop" d="M50 48s-9-6-9-13c0-4 3-7 6-7 2 0 3 1 4 3 1-2 3-3 5-3 4 0 6 3 6 7 0 7-12 13-12 13z"></path>',
      functions: '<rect class="mini-prop-fill" x="45" y="41" width="13" height="15" rx="3"></rect><path class="mini-prop" d="M48 47h7M48 52h5"></path>',
      selector: '<rect class="mini-prop-fill" x="45" y="13" width="12" height="10" rx="3"></rect><rect class="mini-prop-fill" x="45" y="27" width="12" height="10" rx="3"></rect>',
      basics: '<path class="mini-prop" d="M50 12c5 0 8 3 8 8 0 6-8 13-8 13s-8-7-8-13c0-5 3-8 8-8z"></path>',
      systems: '<rect class="mini-prop-fill" x="45" y="14" width="14" height="18" rx="3"></rect><path class="mini-prop" d="M48 21h8M48 27h5"></path>',
      guardrails: '<path class="mini-prop" d="M51 12l9 4v8c0 6-4 10-9 13-6-3-10-7-10-13v-8z"></path>',
      pack: '<path class="mini-prop" d="M43 43h9l3 4h8v10H43z"></path>',
      download: '<path class="mini-prop" d="M52 11v19M45 23l7 7 7-7"></path>',
      flow: '<path class="mini-prop" d="M45 46h13M45 52h9"></path>',
    };
    return props[layout] || props.flow;
  }

  function safeHexColor(value, fallback) {
    const color = String(value || "").trim();
    return /^#[0-9a-fA-F]{6}$/.test(color) ? color : fallback;
  }

  function safeAssetPath(value) {
    const path = String(value || "").trim();
    if (/^[a-zA-Z0-9_./-]+$/.test(path) && !path.includes("..") && !path.startsWith("/")) {
      return path;
    }
    return "assets/cloud-elephants-business-types-cave-sprite.png";
  }

  function normalizeSpriteSheets(config) {
    const sheets = {
      business_type: {
        asset: businessTypeSprite,
        cols: 3,
        rows: 3,
      },
      step: {
        asset: stepSprite,
        cols: 3,
        rows: 2,
      },
      category_boundary: {
        asset: categoryBoundarySprite,
        cols: 4,
        rows: 2,
      },
    };
    Object.entries(config || {}).forEach(([id, sheet]) => {
      const safeId = String(id || "").trim();
      if (!/^[a-zA-Z0-9_-]+$/.test(safeId)) {
        return;
      }
      sheets[safeId] = {
        asset: safeAssetPath(sheet?.asset || sheets[safeId]?.asset || businessTypeSprite),
        cols: clampSpriteGrid(sheet?.cols, sheets[safeId]?.cols || 3),
        rows: clampSpriteGrid(sheet?.rows, sheets[safeId]?.rows || 3),
      };
    });
    return sheets;
  }

  function clampSpriteGrid(value, fallback) {
    const number = Number(value);
    if (!Number.isFinite(number)) {
      return fallback;
    }
    return Math.min(8, Math.max(1, Math.round(number)));
  }

  function clampSpriteIndex(value, size = 3) {
    const number = Number(value);
    if (!Number.isFinite(number)) {
      return 0;
    }
    return Math.min(Math.max(0, size - 1), Math.max(0, Math.round(number)));
  }

  function businessContext() {
    const businessName = fieldValue("businessName") || "Local Business";
    const systems = splitList(fieldValue("systems"));
    const locations = splitList(fieldValue("locations"));
    const owner = fieldValue("owner") || "Operations lead";
    const notes = fieldValue("notes");
    const brandContext = buildBrandContext(businessName, notes);
    const systemMap = systemContext(systems);
    const ownerMap = {
      admin: owner,
      finance: owner,
      hr: owner,
      revenue: owner,
      legal_review: owner,
    };
    const reviewPolicy = fieldValue("reviewPolicy") || "Human review required before operational use";
    const business = {
      slug: slugify(businessName),
      legal_name: businessName,
      industry: fieldValue("industry"),
      locations,
      jurisdictions: locations,
    };
    return {
      business,
      owners: ownerMap,
      systems: systemMap,
      integrations: {},
      review_rules: {
        required_reviewers: {
          operator: owner,
          legal_review: owner,
        },
        escalation_triggers: [
          "Sensitive customer, employee, financial, health, credential, access, or payment information",
          "Missing owner, missing source evidence, or low-confidence assumption",
        ],
        prohibited_actions: [
          "Do not call live CRM, finance, HR, payroll, banking, messaging, publishing, or government systems",
          "Do not make legal, tax, HR, safety, privacy, compliance, medical, or financial decisions without human review",
        ],
        output_requirements: [
          reviewPolicy,
          "Include source files, assumptions, exceptions, confidence, and human_review_required",
          "Route required review actions to the named owner or reviewer",
        ],
      },
      brand_context: brandContext,
      context_graph: buildContextGraph(business, ownerMap, systemMap, brandContext),
      personalization_overrides: buildPersonalizationOverrides(systemMap, ownerMap),
      skill_prefix: slugify(fieldValue("skillPrefix") || "local"),
    };
  }

  function buildBrandContext(businessName, notes) {
    return {
      brand_name: businessName,
      voice: fieldValue("brandVoice") || "practical",
      tone: "clear",
      positioning: truncateText(notes, 480),
      audiences: splitList(fieldValue("personas")),
      personas: splitList(fieldValue("personas")),
      offers: splitList(fieldValue("offers")),
      approved_claims: splitList(fieldValue("approvedClaims")),
      restricted_claims: splitList(fieldValue("restrictedClaims")),
      examples: [],
      terminology: terminologyMap(fieldValue("terminology")),
    };
  }

  function systemContext(systems) {
    const mapped = {};
    systems.forEach((system, index) => {
      const key = systemKey(system, mapped) || `system_${index + 1}`;
      mapped[key] = system;
    });
    return mapped;
  }

  function systemKey(system, existing) {
    const value = system.toLowerCase();
    const candidates = [
      [/quickbooks|xero|accounting|bookkeeping|ledger/, "accounting"],
      [/drive|dropbox|box|document|docs|sharepoint|storage/, "documents"],
      [/asana|monday|basecamp|project|workfront|clickup/, "project_management"],
      [/ats|greenhouse|lever|candidate|recruiting/, "ats"],
      [/bamboohr|hris|human resources|employee records/, "hris"],
      [/appfolio|buildium|property management|leasing/, "property_management"],
      [/maintenance portal|maintenance request|work order/, "maintenance_portal"],
      [/bloomerang|donor crm|donor/, "donor_crm"],
      [/instrumentl|grant tracker|grant management|grant/, "grant_management"],
      [/crm|go high level|gohighlevel|hubspot|salesforce|pipeline/, "crm"],
      [/gusto|payroll|adp|paychex|rippling/, "payroll"],
      [/cms|wordpress|webflow|shopify|content/, "cms"],
      [/mailchimp|constant contact|email marketing/, "email_marketing"],
      [/slack|teams|email|gmail|outlook|messaging/, "communications"],
    ];
    const match = candidates.find(([pattern, key]) => pattern.test(value) && !existing[key]);
    return match ? match[1] : "";
  }

  function buildContextGraph(business, owners, systems, brandContext) {
    const nodes = [];
    const edges = [];
    const businessId = contextId("business", business.slug || business.legal_name);
    const brandId = contextId("brand", brandContext.brand_name || business.legal_name);
    addNode(nodes, businessId, "business", business.legal_name, {
      industry: business.industry,
      primary_profile: activeProfileId || "custom",
      selected_profiles: selectedProfiles().map((profile) => profile.id),
      selected_branches: selectedBranches().map((branch) => branch.id),
    }, "local_builder.business");
    addNode(nodes, brandId, "brand", brandContext.brand_name, {
      voice: brandContext.voice,
      tone: brandContext.tone,
    }, "local_builder.brand_context");
    addEdge(edges, "owned_by", brandId, businessId, "Brand belongs to business");

    selectedProfiles().forEach((profile) => {
      const nodeId = contextId("business-profile", profile.id);
      addNode(nodes, nodeId, "business_profile", profile.label, {
        primary: profile.id === activeProfileId,
        skill_count: profileSkillNames(profile).length,
      }, "local_builder.selected_profiles");
      addEdge(
        edges,
        "configures_pack",
        businessId,
        nodeId,
        profile.id === activeProfileId ? "Primary business type" : "Additional skill source"
      );
    });

    selectedBranches().forEach((branch) => {
      const nodeId = contextId("business-branch", branch.id);
      addNode(nodes, nodeId, "business_branch", branch.label || branch.id, {
        skill_templates: branch.skill_templates || [],
      }, "local_builder.business_type_tree");
      addEdge(edges, "uses_profile_branch", businessId, nodeId, "Selected business type branch");
    });

    Object.entries(systems).forEach(([key, value]) => {
      const nodeId = contextId("system", key);
      addNode(nodes, nodeId, "system", value, { key }, "local_builder.systems");
      addEdge(edges, "uses_system", businessId, nodeId, `${business.legal_name} uses ${value}`);
    });

    Object.entries(owners).forEach(([key, value]) => {
      const nodeId = contextId("owner", key);
      addNode(nodes, nodeId, "owner", value, { role: key }, "local_builder.owners");
      addEdge(edges, "owned_by", businessId, nodeId, `${key} owner`);
    });

    business.jurisdictions.forEach((location, index) => {
      const nodeId = contextId("jurisdiction", location || `location-${index + 1}`);
      addNode(nodes, nodeId, "jurisdiction", location, {}, "local_builder.locations");
      addEdge(edges, "governed_by", businessId, nodeId, "Operating location");
    });

    brandContext.offers.forEach((offer, index) => {
      const nodeId = contextId("offer", offer || `offer-${index + 1}`);
      addNode(nodes, nodeId, "offer", offer, {}, "local_builder.brand_context.offers");
      addEdge(edges, "applies_to_offer", businessId, nodeId, "Business offer");
    });

    brandContext.personas.forEach((persona, index) => {
      const nodeId = contextId("persona", persona || `persona-${index + 1}`);
      addNode(nodes, nodeId, "persona", persona, {}, "local_builder.brand_context.personas");
      addEdge(edges, "serves_persona", businessId, nodeId, "Target persona");
    });

    splitList(fieldValue("serviceLines")).forEach((serviceLine, index) => {
      const nodeId = contextId("workflow", serviceLine || `service-line-${index + 1}`);
      addNode(nodes, nodeId, "workflow", serviceLine, { kind: "service_line" }, "local_builder.service_lines");
      addEdge(edges, "supports_workflow", businessId, nodeId, "Service line");
    });

    [
      ["review", fieldValue("reviewPolicy")],
      ["approval", fieldValue("approvalPolicy")],
      ["consent", fieldValue("consentPolicy")],
      ["warranty", fieldValue("warrantyPolicy")],
    ].forEach(([key, value]) => {
      if (!value) {
        return;
      }
      const nodeId = contextId("policy", key);
      addNode(nodes, nodeId, "policy", value, { key }, "local_builder.policies");
      addEdge(edges, "governed_by", businessId, nodeId, `${key} policy`);
      addEdge(edges, "requires_review", brandId, nodeId, `${key} review`);
    });

    return { nodes, edges };
  }

  function addNode(nodes, id, type, label, attributes, source) {
    if (!label || nodes.some((node) => node.id === id)) {
      return;
    }
    nodes.push({
      id,
      type,
      label: truncateText(label, 240),
      attributes: compactMap(attributes),
      source,
      confidence: 0.82,
    });
  }

  function addEdge(edges, type, from, to, label) {
    if (!from || !to || edges.some((edge) => edge.type === type && edge.from === from && edge.to === to)) {
      return;
    }
    edges.push({
      id: contextId("edge", `${type}-${from}-${to}`),
      type,
      from,
      to,
      label: truncateText(label, 160),
      attributes: {},
      source: "local_builder",
      confidence: 0.82,
    });
  }

  function buildPersonalizationOverrides(systems, owners) {
    const values = personalizationSurfaceValues(systems, owners);
    const overrides = {};
    [...selected].sort().forEach((name) => {
      const skill = skillByName.get(name);
      if (!skill) {
        return;
      }
      const surfaceKeys = new Set([
        ...(packSurfaceBundles[skill.pack_id] || []),
        ...(skill.recommended_personalization_surfaces || []),
      ]);
      const surfaceValues = {};
      surfaceKeys.forEach((surface) => {
        const key = normalizedSurfaceKey(surface);
        if (values[key]) {
          surfaceValues[key] = values[key];
        }
      });
      if (Object.keys(surfaceValues).length) {
        overrides[name] = {
          surface_values: surfaceValues,
          configuration: {
            primary_profile_preset: activeProfileId || "custom",
            selected_profile_presets: selectedProfiles().map((profile) => profile.id),
          },
          pinned_node_ids: [],
          excluded_node_ids: [],
          notes: "Captured by the Service Flow Pro local builder personalization wizard.",
        };
      }
    });
    return overrides;
  }

  function personalizationSurfaceValues(systems, owners) {
    const systemText = Object.values(systems).join(", ");
    const ownerText = Object.values(owners).filter(Boolean).join(", ");
    return compactMap({
      brand_name: fieldValue("businessName"),
      business_profile: `${fieldValue("businessName")} is a ${fieldValue("industry")} business serving ${fieldValue("locations")}.`,
      review_rules: fieldValue("reviewPolicy"),
      brand_voice: fieldValue("brandVoice"),
      positioning: fieldValue("notes"),
      systems: systemText,
      owners: ownerText,
      jurisdictions: fieldValue("locations"),
      service_lines: fieldValue("serviceLines"),
      service_area: fieldValue("serviceArea") || fieldValue("locations"),
      offers: fieldValue("offers"),
      personas: fieldValue("personas"),
      approved_claims: fieldValue("approvedClaims"),
      restricted_claims: fieldValue("restrictedClaims"),
      terminology: fieldValue("terminology"),
      consent_policy: fieldValue("consentPolicy"),
      pricing_context: fieldValue("pricingContext"),
      capacity_context: fieldValue("capacityContext"),
      warranty_policy: fieldValue("warrantyPolicy"),
      period_context: fieldValue("periodContext"),
      chart_of_accounts: fieldValue("chartOfAccounts"),
      materiality_thresholds: fieldValue("materialityThresholds"),
      approval_policy: fieldValue("approvalPolicy"),
      operating_cadence: fieldValue("operatingCadence"),
      document_system: fieldValue("documentSystem"),
      artifact_format: fieldValue("artifactFormat"),
      customer_segments: fieldValue("personas"),
      policy_context: [
        fieldValue("reviewPolicy"),
        fieldValue("approvalPolicy"),
        fieldValue("consentPolicy"),
      ].filter(Boolean).join(" "),
      compliance_boundaries: [
        fieldValue("restrictedClaims"),
        fieldValue("approvalPolicy"),
      ].filter(Boolean).join(" "),
      human_review_routing: `${ownerText} ${fieldValue("reviewPolicy")}`.trim(),
      jurisdictional_scope: fieldValue("locations"),
      integration_context: systemText,
      output_artifacts: fieldValue("artifactFormat"),
      brand_context: "Captured in business_config.brand_context",
      context_graph: "Captured in business_config.context_graph",
      selector_policy: selectedProfileIds.size
        ? `Primary profile: ${activeProfileId}; selected profiles: ${[...selectedProfileIds].join(", ")}`
        : "Custom skill selection",
      missing_context: "Preview before renderer plan",
      defaults_used: "Preview before renderer plan",
    });
  }

  function normalizedSurfaceKey(surface) {
    if (surface.startsWith("systems.")) {
      return "systems";
    }
    if (surface.startsWith("owners.")) {
      return "owners";
    }
    return surface.replace(/[^a-z0-9_-]+/g, "_");
  }

  function selectedSurfaceKeys() {
    const keys = new Set();
    [...selected].forEach((name) => {
      const skill = skillByName.get(name);
      if (!skill) {
        return;
      }
      [
        ...(packSurfaceBundles[skill.pack_id] || []),
        ...(skill.recommended_personalization_surfaces || []),
      ].forEach((surface) => keys.add(normalizedSurfaceKey(surface)));
    });
    return keys;
  }

  function surfaceLabel(key) {
    return {
      offers: "offers",
      personas: "personas",
      review_rules: "review policy",
      systems: "systems",
      service_area: "service area",
      consent_policy: "consent policy",
      period_context: "finance period",
      chart_of_accounts: "chart of accounts",
      materiality_thresholds: "materiality thresholds",
      approval_policy: "approval policy",
      operating_cadence: "operating cadence",
      document_system: "document system",
      artifact_format: "artifact format",
      warranty_policy: "warranty policy",
      customer_segments: "customer segments",
      policy_context: "policy context",
      compliance_boundaries: "compliance boundaries",
      human_review_routing: "review routing",
      jurisdictional_scope: "jurisdictional scope",
      integration_context: "integration context",
      output_artifacts: "output artifacts",
    }[key] || key.replace(/_/g, " ");
  }

  function renderRequest() {
    return {
      ...businessContext(),
      packs: [],
      workflow_groups: [],
      workflow_categories: [],
      skill_templates: [...selected].sort(),
    };
  }

  function packageManifest(resolvedNames) {
    const selectedSkills = [...selected].sort().map((name) => skillByName.get(name)).filter(Boolean);
    const dependencySkills = resolvedNames
      .filter((name) => !selected.has(name))
      .map((name) => skillByName.get(name) || { name, display_name: name, agent_invocation: `$${name}` });
    return {
      package: "service-flow-pro-local-skill-pack",
      generated_at: new Date().toISOString(),
      business: businessContext().business,
      selected_skill_count: selectedSkills.length,
      included_dependency_count: dependencySkills.length,
      selected_skills: selectedSkills.map(skillSummary),
      included_dependencies: dependencySkills.map(skillSummary),
      render_request_file: "render-request.json",
      note: "This browser-generated preview package preserves original skill folders and adds business context files. Use render-request.json with skill-render-service for the verified personalized ZIP.",
    };
  }

  function skillSummary(skill) {
    return {
      name: skill.name,
      display_name: skill.display_name,
      buyer_category: skill.buyer_category || "",
      agent_invocation: skill.agent_invocation || `$${skill.name}`,
    };
  }

  async function downloadBrowserPackage() {
    if (!selected.size) {
      setStatus("Choose at least one skill first.");
      return;
    }
    const resolvedNames = resolveSkillClosure([...selected]);
    const files = packageFiles(resolvedNames);
    const zip = createZip(files);
    const businessSlug = businessContext().business.slug || "local-business";
    downloadBlob(zip, `${businessSlug}-service-flow-pro-preview-skills.zip`);
    emitJourney("preview_zip_downloaded", {
      profile_id: activeProfileId || "custom",
      selected_count: selected.size,
      resolved_count: resolvedNames.length,
    });
    setStatus(`Downloaded preview ZIP with ${resolvedNames.length} skill folders, including dependencies.`);
  }

  function downloadRequestJson() {
    const request = JSON.stringify(renderRequest(), null, 2);
    const businessSlug = businessContext().business.slug || "local-business";
    downloadBlob(new Blob([request], { type: "application/json" }), `${businessSlug}-render-request.json`);
    emitJourney("request_json_downloaded", {
      profile_id: activeProfileId || "custom",
      selected_count: selected.size,
    });
    setStatus("Downloaded renderer-ready request JSON.");
  }

  async function downloadFromRenderer() {
    if (!selected.size) {
      setStatus("Choose at least one skill first.");
      return;
    }
    if (fieldValue("apiMode") !== "renderer") {
      setStatus("Use Create Local Directories for the production API. Direct download is only for the local renderer.");
      return;
    }
    const url = fieldValue("rendererUrl");
    const apiKey = fieldValue("rendererApiKey");
    if (!url || !apiKey) {
      setStatus("Enter a renderer URL and API key, or use the browser-only ZIP.");
      return;
    }
    setStatus("Generating verified personalized ZIP from local renderer...");
    emitJourney("renderer_started", {
      profile_id: activeProfileId || "custom",
      selected_count: selected.size,
      mode: "local_renderer",
    });
    try {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${apiKey}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(renderRequest()),
      });
      if (!response.ok) {
        const text = await response.text();
        throw new Error(text || `Renderer returned HTTP ${response.status}`);
      }
      const blob = await response.blob();
      const businessSlug = businessContext().business.slug || "local-business";
      downloadBlob(blob, `${businessSlug}-renderer-skills.zip`);
      emitJourney("verified_zip_created", {
        profile_id: activeProfileId || "custom",
        selected_count: selected.size,
        mode: "local_renderer",
      });
      setStatus("Downloaded verified personalized ZIP.");
    } catch (error) {
      emitJourney("renderer_failed", {
        profile_id: activeProfileId || "custom",
        mode: "local_renderer",
      });
      setStatus(`Renderer download failed: ${error.message}`);
    }
  }

  async function createLocalDirectories() {
    if (!selected.size) {
      setStatus("Choose at least one skill first.");
      return;
    }
    const mode = fieldValue("apiMode");
    emitJourney("renderer_started", {
      profile_id: activeProfileId || "custom",
      selected_count: selected.size,
      mode,
    });
    setStatus(mode === "production"
      ? "Creating a production bundle job and waiting for the ZIP..."
      : "Generating verified personalized ZIP and creating local directories...");
    try {
      const response = await fetch("/api/render-directories", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          api_mode: mode,
          renderer_url: fieldValue("rendererUrl"),
          api_key: fieldValue("rendererApiKey"),
          tenant_id: fieldValue("tenantId"),
          customer_id: fieldValue("customerId"),
          route_environment: fieldValue("routeEnvironment"),
          request_label: fieldValue("requestLabel"),
          output_dir_name: fieldValue("localOutputName"),
          render_request: renderRequest(),
        }),
      });
      const payload = await response.json().catch(() => ({}));
      if (!response.ok || !payload.ok) {
        throw new Error(payload.error || `Local server returned HTTP ${response.status}`);
      }
      emitJourney("verified_zip_created", {
        profile_id: activeProfileId || "custom",
        selected_count: selected.size,
        mode,
      });
      setStatus(`Created ${payload.skill_count} verified personalized skill directories at ${payload.output_dir}.`);
    } catch (error) {
      emitJourney("renderer_failed", {
        profile_id: activeProfileId || "custom",
        mode,
      });
      setStatus(`Local directory creation failed: ${error.message}`);
    }
  }

  function packageFiles(resolvedNames) {
    const context = businessContext();
    const manifest = packageManifest(resolvedNames);
    const files = [
      ["README.md", packageReadme(context, manifest)],
      ["business-context.json", JSON.stringify(context, null, 2)],
      ["manifest.json", JSON.stringify(manifest, null, 2)],
      ["render-request.json", JSON.stringify(renderRequest(), null, 2)],
      ["selected-skills.json", JSON.stringify(manifest.selected_skills, null, 2)],
      ["install-notes.md", installNotes(context)],
      ["cloud-elephants-kg-runtime.md", cloudElephantsKgRuntimeMarkdown("", context)],
    ];

    resolvedNames.forEach((skillName) => {
      const sourceFiles = skillFiles[skillName] || [];
      sourceFiles.forEach((file) => {
        files.push([`skills/${skillName}/${file.path}`, file.content]);
      });
      files.push([
        `skills/${skillName}/references/service-flow-pro-business-context.md`,
        skillContextMarkdown(skillName, context),
      ]);
      files.push([
        `skills/${skillName}/references/cloud-elephants-kg-runtime.md`,
        cloudElephantsKgRuntimeMarkdown(skillName, context),
      ]);
    });

    return files;
  }

  function resolveSkillClosure(startingNames) {
    const resolved = new Set();
    const queue = [...startingNames];
    while (queue.length) {
      const name = queue.shift();
      if (!name || resolved.has(name)) {
        continue;
      }
      resolved.add(name);
      const files = skillFiles[name] || [];
      files
        .filter((file) => file.path === "SKILL.md")
        .flatMap((file) => [...file.content.matchAll(/\$smb-[a-z0-9-]+/g)].map((match) => match[0].slice(1)))
        .forEach((dependency) => {
          if (skillFiles[dependency] && !resolved.has(dependency)) {
            queue.push(dependency);
          }
        });
    }
    return [...resolved].sort();
  }

  function packageReadme(context, manifest) {
    return `# ${context.business.legal_name} Service Flow Pro Preview Skill Pack

This preview ZIP was generated locally in a browser using the Service Flow Pro Local Skill Pack Builder.

## Contents

- \`skills/\`: selected source skill folders plus dependency skills referenced by those instructions.
- \`business-context.json\`: business context entered in the builder.
- \`render-request.json\`: request JSON compatible with \`skill-render-service render --config\`.
- \`manifest.json\`: selected skills, included dependencies, and generation details.
- \`cloud-elephants-kg-runtime.md\`: optional local ENV-backed KG pull/push runbook.

Selected skills: ${manifest.selected_skill_count}
Included dependencies: ${manifest.included_dependency_count}

Review all outputs before operational use. These skills are designed to support human decisions, not replace legal, tax, HR, safety, privacy, compliance, or financial judgment.

For the verified personalized artifact, submit \`render-request.json\` to the Service Flow Pro renderer or production bundle API.
`;
  }

  function installNotes(context) {
    return `# Renderer Command

From the Service Flow Pro repository root, save \`render-request.json\` and run:

\`\`\`bash
cargo run --manifest-path skill-render-service/Cargo.toml -- --source-root . render \\
  --config render-request.json \\
  --output ${context.business.slug || "local-business"}-renderer-skills.zip
\`\`\`

The browser-generated \`skills/\` folder is useful for review, local handoff, and lightweight installation. The renderer command produces the verified personalized package with rendered context files.

# Optional Cloud Elephants KG Runtime

Set these environment variables in the local agent shell only. Do not add them to \`render-request.json\`, \`business-context.json\`, skill sidecars, manifests, or browser payloads.

\`\`\`bash
export CLOUD_ELEPHANTS_LOCAL_USER_ID=<local-user-id>
export CLOUD_ELEPHANTS_KG_WEBHOOK_URL=<cloud-elephants-kg-webhook-url>
export CLOUD_ELEPHANTS_KG_API_TOKEN=<cloud-elephants-kg-token>
export CLOUD_ELEPHANTS_KG_ENVIRONMENT=production
python3 scripts/cloud_elephants_kg_connector.py status
\`\`\`

Before skill work, retrieve context with \`pull-context\`. After skill work, record run metadata with \`record-run\`. Preserve declared artifacts with \`record-artifact\`; full artifact body sync is opt-in only.
`;
  }

  function cloudElephantsKgRuntimeMarkdown(skillName, context) {
    const selector = skillName ? `$${skillName}` : "selected skills";
    return `# Cloud Elephants KG Runtime

This optional local runtime lets an agent associate ${selector} work with Cloud Elephants knowledge graph resources.

## Environment

Set these in the local shell only:

\`\`\`bash
export CLOUD_ELEPHANTS_LOCAL_USER_ID=<local-user-id>
export CLOUD_ELEPHANTS_KG_WEBHOOK_URL=<cloud-elephants-kg-webhook-url>
export CLOUD_ELEPHANTS_KG_API_TOKEN=<cloud-elephants-kg-token>
export CLOUD_ELEPHANTS_KG_ENVIRONMENT=production
\`\`\`

Never copy those values into \`business-context.json\`, \`render-request.json\`, manifests, sidecars, skill instructions, chat transcripts, or customer-visible logs.

## Pull Before Work

\`\`\`bash
python3 scripts/cloud_elephants_kg_connector.py pull-context \\
  --skill ${skillName || "<skill-name>"} \\
  --artifact-root .
\`\`\`

Use retrieved context as hints only. Confirm source files, owners, systems, CRM or finance records, and review boundaries before relying on them.

## Record After Work

\`\`\`bash
python3 scripts/cloud_elephants_kg_connector.py record-run \\
  --skill ${skillName || "<skill-name>"} \\
  --artifact-root . \\
  --output-summary summary.json
\`\`\`

Record resource links, review flags, missing context, defaults used, and suggested next actions. If resource matches are ambiguous, mark them for human review.

## Declared Artifacts

When this skill produces a durable artifact, record artifact metadata explicitly:

\`\`\`bash
python3 scripts/cloud_elephants_kg_connector.py record-artifact \\
  --skill ${skillName || "<skill-name>"} \\
  --artifact ./path/to/artifact.md
\`\`\`

Full artifact content is not pushed by default. Use \`--include-content\` or \`CLOUD_ELEPHANTS_KG_PUSH_ARTIFACT_CONTENT=1\` only when the user intentionally wants the artifact body preserved in the knowledge graph.

Business context for this package: ${context.business.legal_name || "Not provided"}.
`;
  }

  function skillContextMarkdown(skillName, context) {
    const skill = skillByName.get(skillName);
    return `# Service Flow Pro Business Context

Business: ${context.business.legal_name}
Industry: ${context.business.industry || "Not provided"}
Locations: ${context.business.locations.join(", ") || "Not provided"}
Owner: ${context.owners.admin || "Not provided"}
Systems: ${Object.values(context.systems).join(", ") || "Not provided"}

Selected skill: ${skill ? skill.display_name : skillName}
Selector: $${skillName}

Notes:
${context.brand_context.positioning || "No notes provided."}

Use this context to ask better intake questions, tailor examples, and flag missing source data. Keep final decisions with a human operator.
`;
  }

  function fieldValue(name) {
    const field = form.querySelector(`[name="${name}"]`);
    return field ? field.value.trim() : "";
  }

  function splitList(value) {
    return value
      .split(",")
      .map((item) => item.trim())
      .filter(Boolean);
  }

  function formatList(items) {
    if (items.length <= 1) {
      return items.join("");
    }
    if (items.length === 2) {
      return `${items[0]} and ${items[1]}`;
    }
    return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
  }

  function terminologyMap(value) {
    const terms = {};
    splitList(value).forEach((item) => {
      const [rawKey, ...rest] = item.split(":");
      const key = safeMapKey(rawKey || "");
      const mapped = rest.join(":").trim();
      if (key && mapped) {
        terms[key] = truncateText(mapped, 240);
      }
    });
    return terms;
  }

  function compactMap(values) {
    const compacted = {};
    Object.entries(values || {}).forEach(([key, value]) => {
      const safeKey = safeMapKey(key);
      const text = Array.isArray(value) ? value.join(", ") : String(value || "").trim();
      if (safeKey && text) {
        compacted[safeKey] = truncateText(text, 480);
      }
    });
    return compacted;
  }

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

  function contextId(prefix, value) {
    const safeValue = slugify(value || prefix).replace(/-/g, "_") || "item";
    return `${prefix}.${safeValue}`.slice(0, 96);
  }

  function slugify(value) {
    return String(value)
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, "-")
      .replace(/^-+|-+$/g, "")
      .slice(0, 64);
  }

  function truncateText(value, maxLength) {
    const text = String(value || "").trim();
    return text.length > maxLength ? text.slice(0, maxLength - 1).trimEnd() : text;
  }

  function escapeHtml(value) {
    return String(value)
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;");
  }

  function setStatus(message) {
    status.textContent = message;
  }

  function emitJourney(eventName, detail = {}) {
    const payload = {
      event: eventName,
      source: "service-flow-pro-local-builder",
      ...detail,
    };
    if (Array.isArray(window.dataLayer)) {
      window.dataLayer.push(payload);
    }
    window.dispatchEvent(new CustomEvent("sfp:journey", { detail: payload }));
  }

  async function loadLocalServerStatus() {
    if (!localServerStatus) {
      return;
    }
    try {
      const response = await fetch("/api/status");
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      const payload = await response.json();
      const kgStatus = payload.kg_connector_configured
        ? " KG connector configured."
        : " KG connector not configured.";
      localServerStatus.textContent = payload.has_api_key
        ? `Local server ready. Output root: ${payload.output_root}`
        : `Local server ready. Output root: ${payload.output_root}. Enter a renderer API key or restart with one configured.`;
      localServerStatus.textContent += kgStatus;
      const rendererField = form.querySelector('[name="rendererUrl"]');
      if (payload.renderer_url && rendererField && !rendererField.dataset.edited && fieldValue("apiMode") !== "production") {
        rendererField.value = payload.renderer_url;
      }
    } catch (_error) {
      localServerStatus.textContent = "Start the Python server to create directories locally.";
    }
  }

  function downloadBlob(blob, filename) {
    const url = URL.createObjectURL(blob);
    const link = document.createElement("a");
    link.href = url;
    link.download = filename;
    document.body.append(link);
    link.click();
    link.remove();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  }

  function createZip(files) {
    const encoder = new TextEncoder();
    const localParts = [];
    const centralParts = [];
    let offset = 0;

    files.forEach(([rawName, content]) => {
      const name = rawName.replace(/^\/+/, "");
      const nameBytes = encoder.encode(name);
      const data = typeof content === "string" ? encoder.encode(content) : content;
      const crc = crc32(data);
      const localHeader = new Uint8Array(30 + nameBytes.length);
      const local = new DataView(localHeader.buffer);
      local.setUint32(0, 0x04034b50, true);
      local.setUint16(4, 20, true);
      local.setUint16(6, 0x0800, true);
      local.setUint16(8, 0, true);
      local.setUint16(10, dosTime(), true);
      local.setUint16(12, dosDate(), true);
      local.setUint32(14, crc, true);
      local.setUint32(18, data.length, true);
      local.setUint32(22, data.length, true);
      local.setUint16(26, nameBytes.length, true);
      localHeader.set(nameBytes, 30);
      localParts.push(localHeader, data);

      const centralHeader = new Uint8Array(46 + nameBytes.length);
      const central = new DataView(centralHeader.buffer);
      central.setUint32(0, 0x02014b50, true);
      central.setUint16(4, 20, true);
      central.setUint16(6, 20, true);
      central.setUint16(8, 0x0800, true);
      central.setUint16(10, 0, true);
      central.setUint16(12, dosTime(), true);
      central.setUint16(14, dosDate(), true);
      central.setUint32(16, crc, true);
      central.setUint32(20, data.length, true);
      central.setUint32(24, data.length, true);
      central.setUint16(28, nameBytes.length, true);
      central.setUint32(38, 0o100644 << 16, true);
      central.setUint32(42, offset, true);
      centralHeader.set(nameBytes, 46);
      centralParts.push(centralHeader);

      offset += localHeader.length + data.length;
    });

    const centralSize = centralParts.reduce((sum, part) => sum + part.length, 0);
    const end = new Uint8Array(22);
    const endView = new DataView(end.buffer);
    endView.setUint32(0, 0x06054b50, true);
    endView.setUint16(8, files.length, true);
    endView.setUint16(10, files.length, true);
    endView.setUint32(12, centralSize, true);
    endView.setUint32(16, offset, true);
    return new Blob([...localParts, ...centralParts, end], { type: "application/zip" });
  }

  function dosTime() {
    return (12 << 11);
  }

  function dosDate() {
    return ((2026 - 1980) << 9) | (1 << 5) | 1;
  }

  function crc32(bytes) {
    let crc = -1;
    for (let index = 0; index < bytes.length; index += 1) {
      crc = (crc >>> 8) ^ crcTable[(crc ^ bytes[index]) & 0xff];
    }
    return (crc ^ -1) >>> 0;
  }

  const crcTable = (() => {
    const table = new Uint32Array(256);
    for (let index = 0; index < 256; index += 1) {
      let value = index;
      for (let bit = 0; bit < 8; bit += 1) {
        value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
      }
      table[index] = value >>> 0;
    }
    return table;
  })();
})();
