<!--
/**
 * inPIPE UTM Coder Component - Free Version
 *
 * Clean UTM parameter generator with no premium dependencies or view switching.
 * Handles UTM parameter generation, storage, and basic management for free version.
 *
 * @package InPipe
 * @subpackage Free/Shared
 * @since 1.0.0
 * @author Seresa
 * @license GPL-2.0+
 * @link https://seresa.io
 */
-->

<template>
  <div class="utm-coder-container">
    <!-- Security Check Failed Message -->
    <div v-if="securityCheckFailed" class="security-warning">
      <inPipeBaseCard>
        <div class="text-center">
          <inPipeBaseText variant="danger" tag="h2">
            {{ $__("common.access_denied", "Access Denied") }}
          </inPipeBaseText>
          <div class="text-group">
            <inPipeBaseText>
              {{ $__("common.permission_error", "You don't have permission to access this component directly.") }}
            </inPipeBaseText>
            <inPipeBaseText>
              {{ $__("common.use_main_interface", "Please use the main application interface.") }}
            </inPipeBaseText>
          </div>
        </div>
      </inPipeBaseCard>
    </div>

    <div v-else class="utm-coder-content">
      <!-- Header Card -->
      <inPipeBaseCard>
        <div class="text-center">
          <inPipeBaseText variant="title" tag="h2" class="main-title">
            {{ $__("components.utm_coder.title", "UTM Parameter Generator") }}
          </inPipeBaseText>
          <div class="text-group">
            <inPipeBaseText>
              {{ $__("components.utm_coder.description", "Generate and encode your UTM URL tracking parameters.") }}
            </inPipeBaseText>
            <inPipeBaseText>
              {{ $__("components.utm_coder.additional_desc", "Build your marketing URLs with correct UTM parameters for better analytics tracking.") }}
            </inPipeBaseText>
            <inPipeBaseText variant="accent">
              {{ $__("components.utm_coder.tagline", "Optimize your campaign tracking!") }}
            </inPipeBaseText>
          </div>
        </div>
      </inPipeBaseCard>

      <!-- UTM Parameters Card -->
      <inPipeBaseCard ref="utmParamsCard" class="mt-6">
        <div class="card-header">
          <div>
            <inPipeBaseText variant="title" tag="h2">
              {{ $__("components.utm_coder.params_section.title", "UTM Parameters") }}
            </inPipeBaseText>
            <inPipeBaseText variant="description">
              {{ $__("components.utm_coder.params_section.description", "Configure your UTM tracking parameters") }}
            </inPipeBaseText>
          </div>
          <div class="header-actions">
            <inPipeBaseButton
              variant="slate"
              size="small"
              @click="showImportModal = true"
            >
              <div class="button-content">
                <Download class="button-icon" />
                <span>{{ $__("components.utm_coder.params_section.import_utm", "Import UTM") }}</span>
              </div>
            </inPipeBaseButton>
            <inPipeBaseButton
              variant="ghost"
              size="small"
              @click="clearAllParams"
              class="reset-button"
            >
              <div class="button-content">
                <RefreshCcw class="button-icon" />
                <span>{{ $__("components.utm_coder.params_section.reset_all", "Reset All") }}</span>
              </div>
            </inPipeBaseButton>
          </div>
        </div>

        <!-- Editing indicator banner -->
        <div v-if="editingUtmId" class="editing-banner">
          <div class="editing-banner-content">
            <Pencil class="editing-banner-icon" />
            <span>{{ $__("components.utm_coder.params_section.editing_label", "Editing UTM:") }} <code class="editing-banner-id">{{ editingUtmId }}</code></span>
          </div>
          <inPipeBaseButton
            variant="ghost"
            size="small"
            @click="clearAllParams"
            class="editing-banner-cancel"
          >
            {{ $__("components.utm_coder.params_section.cancel_edit", "Cancel") }}
          </inPipeBaseButton>
        </div>

        <!-- Landing Page Path + Live UTM Query Preview -->
        <div class="landing-path-group">
          <inPipeBaseText class="param-label">{{ $__("components.utm_coder.params_section.landing_page", "Landing Page") }}</inPipeBaseText>
          <div
            class="landing-path-input"
            :class="{
              'has-preview': hasUtmParams,
              'is-confirmed': landingPathConfirmed && !hasUtmParams,
              'has-warning': landingPathWarning
            }"
            @click="(landingPathConfirmed || hasUtmParams) && toast.info($__('components.utm_coder.params_section.landing_locked', 'Reset All to edit the landing page path.'))"
          >
            <span class="landing-path-domain">{{ BASE_URL }}</span>
            <input
              v-if="!landingPathConfirmed && !hasUtmParams"
              v-model="landingPath"
              type="text"
              :placeholder="$__('components.utm_coder.params_section.landing_path_placeholder', 'page/subpage')"
              class="landing-path-field"
              @keyup.enter="validateAndConfirmPath"
            />
            <code v-else-if="hasUtmParams" class="landing-path-preview">{{ utmQueryPreview }}</code>
            <span v-else class="landing-path-confirmed">{{ landingPath || '/' }}</span>
            <button
              v-if="!landingPathConfirmed && !hasUtmParams"
              type="button"
              class="landing-path-check"
              :disabled="!landingPath.trim() || !!landingPathWarning"
              @click.stop="validateAndConfirmPath"
              :title="$__('components.utm_coder.params_section.confirm_path', 'Confirm landing page path')"
            >
              ✓
            </button>
          </div>
          <p v-if="landingPathWarning" class="landing-path-warning">{{ landingPathWarning }}</p>
        </div>

        <div class="utm-params-wrapper" :class="{ 'is-locked': pathPendingConfirm }">
          <div v-if="pathPendingConfirm" class="utm-params-lock-overlay" @click="toast.info($__('components.utm_coder.params_section.confirm_path_first', 'Please confirm the landing page path first.'))">
          </div>
          <div class="utm-params">
            <div
              v-for="(value, key) in utmParams"
              :key="key"
              :class="['param-group', { 'has-value': value }]"
            >
              <div class="param-header">
                <inPipeBaseText class="param-label">utm_{{ key }}</inPipeBaseText>
              </div>

              <inPipeBaseSelect
                :id="`utm_${key}`"
                v-model="utmParams[key]"
                :placeholder="`Select ${key}`"
                :options="utmOptions[key].map((opt) => ({ value: opt, label: opt }))"
                :deletableOptions="utmOptions[key] || []"
                :deleteTitle="$__('components.utm_coder.params_section.delete_option', 'Delete this option')"
                :allowCustomInput="true"
                :customInputPlaceholder="$__('components.utm_coder.params_section.custom_input_placeholder', 'Type custom value...')"
                @delete-option="(value) => handleDeleteFromDropdown(key, value)"
                @add-option="(value) => handleAddFromDropdown(key, value)"
                class="param-select"
              />

            </div>
          </div>

        <!-- Custom Parameters (collapsed by default, shown when user adds one) -->
        <div v-if="customParams.length > 0" class="custom-params-inline">
          <div class="custom-params-header">
            <inPipeBaseText variant="description" class="custom-params-label">
              {{ $__("components.utm_coder.custom_params.title", "Custom Parameters") }}
            </inPipeBaseText>
            <inPipeBaseText variant="description" class="param-limit">
              {{ customParams.length }}/{{ maxCustomParams }}
            </inPipeBaseText>
          </div>

          <div
            v-for="(param, index) in customParams"
            :key="index"
            class="param-row"
          >
            <inPipeBaseInput
              :id="`param_${index}_key`"
              v-model="param.key"
              :placeholder="$__('components.utm_coder.custom_params.param_key', 'Parameter key')"
              class="param-key-input"
              @blur="validateCustomKey(param.key, index)"
            />
            <inPipeBaseInput
              :id="`param_${index}_value`"
              v-model="param.value"
              :placeholder="$__('components.utm_coder.custom_params.param_value', 'Parameter value')"
              class="param-value-input"
              @blur="validateCustomValue(param.value, index)"
            />
            <inPipeBaseButton
              variant="ghost"
              size="small"
              @click="removeCustomParam(index)"
              class="delete-button"
            >
              <Trash2 class="button-icon" />
            </inPipeBaseButton>
          </div>
        </div>

        <!-- Card Footer: Add Custom + Save UTM -->
        <div class="utm-card-footer">
          <inPipeBaseButton
            v-if="customParams.length < maxCustomParams"
            variant="ghost"
            size="small"
            @click="addCustomParam"
          >
            <div class="button-content">
              <Plus class="button-icon" />
              <span>{{ $__("components.utm_coder.custom_params.add_button", "Add Custom Parameter") }}</span>
            </div>
          </inPipeBaseButton>
          <span v-else></span>

          <inPipeBaseButton
            variant="primary"
            @click="handleSaveUTM"
            :disabled="!generatedUrl || !hasUnsavedChanges || isSaving"
            class="save-utm-button"
          >
            <div class="button-content">
              <Loader v-if="isSaving" class="button-icon icon-spin" />
              <Pencil v-else-if="editingUtmId" class="button-icon" />
              <Save v-else class="button-icon" />
              <span>
                {{ isSaving
                  ? $__("components.utm_coder.url_section.saving_button", "Saving...")
                  : editingUtmId
                    ? $__("components.utm_coder.url_section.update_button", "Update UTM")
                    : $__("components.utm_coder.url_section.save_button", "Save UTM")
                }}
              </span>
            </div>
          </inPipeBaseButton>
        </div>
        </div> <!-- /utm-params-wrapper -->
      </inPipeBaseCard>

      <!-- Import UTM Modal - added in 1.1.1 -->
      <inPipeBaseModal
        v-if="showImportModal"
        :title="$__('components.utm_coder.import_modal.title', 'Import UTM')"
        size="medium"
        @close="closeImportModal"
      >
        <div class="import-modal-content">
          <inPipeBaseText variant="description">
            {{ $__("components.utm_coder.import_modal.description", "Paste a URL containing UTM parameters") }}
          </inPipeBaseText>

          <textarea
            v-model="importUrl"
            :placeholder="$__('components.utm_coder.import_modal.placeholder', 'https://example.com?utm_source=google&utm_medium=cpc&utm_campaign=summer_sale')"
            class="import-textarea"
            rows="3"
          />

          <p v-if="importError" class="import-error">{{ importError }}</p>

          <div class="import-modal-actions">
            <inPipeBaseButton
              variant="ghost"
              size="small"
              @click="closeImportModal"
            >
              {{ $__("components.utm_coder.import_modal.cancel", "Cancel") }}
            </inPipeBaseButton>
            <inPipeBaseButton
              variant="primary"
              size="small"
              @click="parseAndImport"
              :disabled="!importUrl.trim()"
            >
              <div class="button-content">
                <Zap class="button-icon" />
                <span>{{ $__("components.utm_coder.import_modal.parse", "Parse") }}</span>
              </div>
            </inPipeBaseButton>
          </div>
        </div>
      </inPipeBaseModal>

      <!-- Stored UTMs Card -->
      <inPipeBaseCard class="mt-6">
        <inPipeBaseText variant="title" tag="h2">
          {{ $__("components.utm_coder.stored_utms.title", "Stored UTM URLs") }}
        </inPipeBaseText>
        <inPipeBaseText variant="description">
          {{ $__("components.utm_coder.stored_utms.description", "Previously generated UTM URLs") }}
        </inPipeBaseText>

        <inPipeBaseText
          v-if="storedUtms.length > 0 && !utmProcessingEnabled"
          class="utm-warning"
        >
          <CircleAlert class="warning-icon" />
          {{ $__("components.utm_coder.stored_utms.processing_warning", "To activate UTM processing, switch ON in the Settings tab") }}
        </inPipeBaseText>

        <div class="stored-utms">
          <div v-for="utm in storedUtms" :key="utm.id" :class="['utm-item', { 'is-editing': editingUtmId === utm.id, 'is-highlighted': highlightedUtmId === utm.id }]">
            <div class="utm-urls">
              <div class="utm-url-row">
                <span class="url-label">{{ $__("components.utm_coder.stored_utms.full_url", "Full URL:") }}</span>
                <code class="url-code">{{ utm.fullUrl }}</code>
                <div class="url-actions">
                  <inPipeBaseButton
                    variant="ghost"
                    size="small"
                    @click="handleCopy(`${utm.id}-full`, utm.fullUrl)"
                    class="action-button"
                  >
                    <Check v-if="copied[`${utm.id}-full`]" class="button-icon icon-success" />
                    <Copy v-else class="button-icon" />
                  </inPipeBaseButton>
                  <inPipeBaseButton
                    variant="ghost"
                    size="small"
                    @click="handleEdit(utm)"
                    class="action-button edit"
                  >
                    <Pencil class="button-icon" />
                  </inPipeBaseButton>
                  <inPipeBaseButton
                    variant="ghost"
                    size="small"
                    @click="handleDelete(utm.id)"
                    class="action-button delete"
                  >
                    <Trash2 class="button-icon" />
                  </inPipeBaseButton>
                </div>
              </div>
              <div class="utm-url-row">
                <span class="url-label">{{ $__("components.utm_coder.stored_utms.coded_url", "Coded URL:") }}</span>
                <code class="url-code">{{ utm.codedUrl }}</code>
                <div class="url-actions">
                  <inPipeBaseButton
                    variant="ghost"
                    size="small"
                    @click="handleCopy(`${utm.id}-coded`, utm.codedUrl)"
                    class="action-button"
                  >
                    <Check v-if="copied[`${utm.id}-coded`]" class="button-icon icon-success" />
                    <Copy v-else class="button-icon" />
                  </inPipeBaseButton>
                </div>
              </div>
            </div>

            <div class="utm-footer">
              <span class="utm-date">
                {{ $__("components.utm_coder.stored_utms.created_date", "Created:") }} {{ formatDate(utm.createdAt) }}
              </span>
            </div>
          </div>

          <div v-if="!storedUtms || storedUtms.length === 0" class="no-utms">
            <inPipeBaseText>
              {{ $__("components.utm_coder.stored_utms.no_utms", "No stored UTM URLs found. Generate a new one to get started.") }} <Rocket class="inline-icon" />
              <br />
              {{ $__("components.utm_coder.stored_utms.switch_reminder", "And when you are ready, switch ON the UTM processing in the Settings tab.") }}
            </inPipeBaseText>
          </div>
        </div>
      </inPipeBaseCard>
    </div>
  </div>
</template>

<script setup>
/**
 * UTM Coder Component Script - Free Version
 * Clean UTM generation logic with no premium dependencies
 */

import { ref, computed, watch, onMounted, onBeforeMount, getCurrentInstance, nextTick } from "vue";
import { useToast } from "vue-toastification";
import { $__ } from "@utils/inPipeTranslate.js";
import api from "@utils/inPipeAxios.js";
import API_ENDPOINTS, { PARAM_NAMES } from '@utils/inPipeApiEndpoints.js';

// Import clean base components
import inPipeBaseCard from "@base/inPipeBaseCard.vue";
import inPipeBaseText from "@base/inPipeBaseText.vue";
import inPipeBaseInput from "@base/inPipeBaseInput.vue";
import inPipeBaseButton from "@base/inPipeBaseButton.vue";
import inPipeBaseSelect from "@base/inPipeBaseSelect.vue";
import inPipeBaseModal from "@base/inPipeBaseModal.vue";

// Lucide icons
import {
  Download, RefreshCcw, Pencil, Trash2, Save,
  Copy, Check, Loader, Plus, Zap, CircleAlert, Rocket,
} from "lucide-vue-next";

// Define emits for parent communication
const emit = defineEmits(['utm-saved']);

// Toast instance
const toast = useToast();

// Security check state
const securityCheckFailed = ref(false);

// Debounce timer and saving state
const saveDebounceTimer = ref(null);
const isSaving = ref(false);

// Constants
const BASE_URL = (window.inPipeData?.baseUrl || "").replace(/\/$/, "") + "/";
const maxCustomParams = 3;

// UTM dropdown options loaded from database (defaults + user-added)
const utmOptions = ref({
  source: [],
  medium: [],
  campaign: [],
  term: [],
  content: [],
  id: [],
});

/**
 * Fetch UTM dropdown options from server via dedicated endpoint
 * All options (seeded defaults + user-added) are stored in the database.
 *
 * @since 1.1.1
 */
const fetchUtmOptions = async () => {
  try {
    const response = await api.get(API_ENDPOINTS.UTM_OPTIONS_FETCH);

    if (response?.data?.success && response.data.data?.options) {
      utmOptions.value = response.data.data.options;

      // DEBUG_START
      console.log('[inPipe UTM] Loaded UTM options:', utmOptions.value);
      // DEBUG_END
    }
  } catch (error) {
    // DEBUG_START
    console.error('[inPipe UTM] Error fetching UTM options:', error);
    // DEBUG_END
  }
};

/**
 * Save a new option when manual value is used (auto-save on UTM save)
 *
 * @param {string} field - Field name (source, medium, etc.)
 * @param {string} value - Value to save
 * @since 1.1.1
 */
const saveUtmOption = async (field, value) => {
  if (!value || !field) return;

  // Sanitize: allowlist approach — normalize, strip zero-width, keep only safe chars
  value = sanitizeUtmValue(value.toLowerCase());

  if (!value) {
    toast.warning(
      $__("components.utm_coder.notifications.invalid_option", "Value contains invalid characters and was rejected.")
    );
    return;
  }

  // Skip if already in options
  if (utmOptions.value[field]?.includes(value)) return;

  // Optimistic update
  if (!utmOptions.value[field]) {
    utmOptions.value[field] = [];
  }
  utmOptions.value[field].push(value);

  try {
    await api.post(API_ENDPOINTS.UTM_OPTIONS_SAVE, {
      field: field,
      value: value
    });

    toast.success($__("components.utm_coder.options.add_success", `${value} added`));

    // DEBUG_START
    console.log(`[inPipe UTM] Saved option: ${field} = ${value}`);
    // DEBUG_END
  } catch (error) {
    // DEBUG_START
    console.error('[inPipe UTM] Error saving option:', error);
    // DEBUG_END

    // Rollback optimistic update
    utmOptions.value[field] = utmOptions.value[field].filter(v => v !== value);
  }
};

/**
 * Delete an option from a field
 *
 * @param {string} field - Field name
 * @param {string} value - Value to delete
 * @since 1.1.1
 */
const deleteUtmOption = async (field, value) => {
  try {
    await api.post(API_ENDPOINTS.UTM_OPTIONS_DELETE, {
      field: field,
      value: value
    });

    // Remove from local state
    utmOptions.value[field] = utmOptions.value[field].filter(v => v !== value);

    toast.success($__("components.utm_coder.options.delete_success", `${value} removed`));

    // DEBUG_START
    console.log(`[inPipe UTM] Deleted option: ${field} = ${value}`);
    // DEBUG_END

  } catch (error) {
    // DEBUG_START
    console.error('[inPipe UTM] Error deleting option:', error);
    // DEBUG_END
    toast.error($__("components.utm_coder.options.delete_error", "Failed to delete option"));
  }
};

/**
 * Handle delete from dropdown - called when x button clicked inside the dropdown
 *
 * @param {string} field - Field name (source, medium, etc.)
 * @param {string} value - Value to delete
 * @since 1.1.1
 */
const handleDeleteFromDropdown = async (field, value) => {
  // Clear field if the deleted value is currently selected
  if (utmParams.value[field] === value) {
    utmParams.value[field] = "";
  }

  // Delete the option from server
  await deleteUtmOption(field, value);
};

/**
 * Handle add from dropdown - called when user types a custom value in the inline input
 *
 * @param {string} field - Field name (source, medium, etc.)
 * @param {string} value - Value to add
 * @since 1.1.1
 */
const handleAddFromDropdown = async (field, value) => {
  const lowered = value.toLowerCase();
  const clean = sanitizeUtmValue(lowered);

  if (!clean || clean !== lowered.trim()) {
    // Reject: input contained characters stripped by sanitization
    utmParams.value[field] = "";
    toast.warning(
      $__("components.utm_coder.notifications.invalid_option", "Value contains invalid characters and was rejected.")
    );
    return;
  }

  // Update model to the sanitized value
  utmParams.value[field] = clean;
  await saveUtmOption(field, clean);
};

// Initial state for comparison (used for new UTM creation)
const initialState = {
  utmParams: {
    source: "",
    medium: "",
    campaign: "",
    term: "",
    content: "",
    id: "",
  },
  customParams: [],
};

// Snapshot of the loaded state when editing — tracks what was loaded so
// hasUnsavedChanges can compare against the actual edit baseline, not empty defaults.
const editSnapshot = ref(null);

// Component state - simple reactive variables
const utmParams = ref({ ...initialState.utmParams });
const customParams = ref([...initialState.customParams]);
const copied = ref({});
const generatedUrl = ref("");
const codedUrl = ref("");
const storedUtms = ref([]);
const savedMessage = ref(false);
const utmProcessingEnabled = ref(false);
const editingUtmId = ref(null);
const editingCodedValue = ref(null); // Preserves the coded URL's random value during edits
const isLoadingEdit = ref(false); // Guards watcher during edit population
const highlightedUtmId = ref(null); // Temporarily highlights a newly saved/updated UTM
const utmParamsCard = ref(null); // Template ref for scroll-to-card on edit
const landingPath = ref("");
const landingPathConfirmed = ref(false);
const landingPathWarning = ref("");

// Import UTM modal state - added in 1.1.1
const showImportModal = ref(false);
const importUrl = ref("");
const importError = ref("");

/**
 * Perform security check
 * 
 * @return {boolean} True if security check passes
 * @since 1.0.0
 */
const performSecurityCheck = () => {
  if (!window.inPipeData) {
    securityCheckFailed.value = true;
    return false;
  }
  return true;
};

/**
 * Check if there are unsaved changes
 * 
 * @return {boolean} True if there are unsaved changes
 * @since 1.0.0
 */
const hasUnsavedChanges = computed(() => {
  // Compare against editSnapshot when editing, otherwise against initialState
  const baseline = editSnapshot.value || initialState;

  // Check UTM parameters
  const utmParamChanged = Object.keys(utmParams.value).some(
    (key) => utmParams.value[key] !== baseline.utmParams[key],
  );

  // Check custom parameters
  const customParamsChanged =
    customParams.value.length !== baseline.customParams.length ||
    customParams.value.some(
      (param, index) =>
        param.key !== (baseline.customParams[index]?.key || "") ||
        param.value !== (baseline.customParams[index]?.value || ""),
    ) ||
    customParams.value.some((param, index) => {
      const baselineParam = baseline.customParams[index];
      if (!baselineParam) return param.key && param.value;
      return false;
    });

  return utmParamChanged || customParamsChanged;
});

/**
 * Live preview of the UTM query string being built
 *
 * @since 1.1.1
 */
const hasUtmParams = computed(() => {
  return Object.values(utmParams.value).some((v) => v) ||
    customParams.value.some((p) => p.key && p.value);
});

const pathPendingConfirm = computed(() => {
  return landingPath.value.trim().length > 0 && !landingPathConfirmed.value;
});

const utmQueryPreview = computed(() => {
  const parts = Object.entries(utmParams.value)
    .filter(([_, value]) => value)
    .map(([key, value]) => `utm_${key}=${encodeURIComponent(value)}`);

  const customParts = customParams.value
    .filter((p) => p.key && p.value)
    .map((p) => `${encodeURIComponent(p.key)}=${encodeURIComponent(p.value)}`);

  const all = [...parts, ...customParts];
  if (all.length === 0) return "";

  const cleanPath = landingPath.value.replace(/^\/+/, "").replace(/\/+$/, "");
  const pathPart = cleanPath ? `${cleanPath}` : "";
  return `${pathPart}?${all.join("&")}`;
});

/**
 * Clear all parameters
 * 
 * @param {boolean} showToast - Whether to show toast notification
 * @since 1.0.0
 */
const clearAllParams = (showToast = true) => {
  Object.keys(utmParams.value).forEach((key) => {
    utmParams.value[key] = "";
  });

  customParams.value = [];
  landingPath.value = "";
  landingPathConfirmed.value = false;
  generatedUrl.value = "";
  codedUrl.value = "";
  editingUtmId.value = null;
  editingCodedValue.value = null;
  editSnapshot.value = null;

  if (showToast) {
    toast.info($__("components.utm_coder.notifications.all_cleared", "All parameters cleared."));
  }
};

/**
 * Generate coded URL
 * 
 * @return {string} Generated coded URL
 * @since 1.0.0
 */
const generateCodedUrl = () => {
  // Coded URL is always at root domain - landing path gets applied after decoding
  if (editingUtmId.value) {
    // Reuse the original coded value when editing to avoid breaking existing shared links
    if (!editingCodedValue.value) {
      editingCodedValue.value = Math.floor(Math.random() * 90000000) + 10000000;
    }
    return `${BASE_URL}?${editingUtmId.value}=${editingCodedValue.value}`;
  }

  const chars = '0123456789abcdefghijklmnopqrstuvwxyz';
  const randomChars = Array.from({length: 4}, () => chars[Math.floor(Math.random() * chars.length)]).join('');
  const randomNum = Math.floor(Math.random() * 90000000) + 10000000;
  return `${BASE_URL}?u${randomChars}=${randomNum}`;
};

/**
 * Generate URLs based on current parameters
 * 
 * @since 1.0.0
 */
const generateUrls = () => {
  const utmString = Object.entries(utmParams.value)
    .filter(([_, value]) => value)
    .map(([key, value]) => `utm_${key}=${encodeURIComponent(value)}`)
    .join("&");

  const customParamsString = customParams.value
    .filter((param) => param.key && param.value)
    .map(
      (param) =>
        `${encodeURIComponent(param.key)}=${encodeURIComponent(param.value)}`,
    )
    .join("&");

  const allParams = [utmString, customParamsString].filter(Boolean).join("&");

  // Build base with optional landing path
  const cleanPath = landingPath.value.replace(/^\/+/, "").replace(/\/+$/, "");
  const baseWithPath = cleanPath ? `${BASE_URL}${cleanPath}` : BASE_URL;

  generatedUrl.value = allParams ? `${baseWithPath}?${allParams}` : baseWithPath;

  if (allParams) {
    codedUrl.value = generateCodedUrl();
  } else {
    codedUrl.value = "";
  }
};

/**
 * Handle copy to clipboard
 * 
 * @param {string} type - Type of copy action
 * @param {string} url - URL to copy
 * @since 1.0.0
 */
const handleCopy = async (type, url) => {
  if (!url) return;

  try {
    if (navigator.clipboard && navigator.clipboard.writeText) {
      await navigator.clipboard.writeText(url);
    } else {
      // Fallback method
      const textArea = document.createElement("textarea");
      textArea.value = url;
      textArea.style.position = "fixed";
      textArea.style.opacity = "0";
      document.body.appendChild(textArea);
      textArea.focus();
      textArea.select();

      const successful = document.execCommand("copy");
      document.body.removeChild(textArea);

      if (!successful) {
        throw new Error("execCommand copy failed");
      }
    }

    copied.value[type] = true;
    setTimeout(() => {
      copied.value[type] = false;
    }, 2000);
  } catch (err) {
    // DEBUG_START
    console.error("[inPipe UTM] Copy to clipboard error:", err);
    // DEBUG_END

    toast.error($__("components.utm_coder.notifications.copy_error", "Failed to copy URL"));
  }
};

/**
 * Add custom parameter
 * 
 * @since 1.0.0
 */
const addCustomParam = () => {
  if (customParams.value.length < maxCustomParams) {
    customParams.value.push({ key: "", value: "" });
  } else {
    toast.info($__("components.utm_coder.notifications.param_limit", "Maximum 3 custom parameters allowed."));
  }
};

/**
 * Remove custom parameter
 * 
 * @param {number} index - Index of parameter to remove
 * @since 1.0.0
 */
const removeCustomParam = (index) => {
  customParams.value = customParams.value.filter((_, i) => i !== index);
};

/**
 * Handle edit UTM
 * 
 * @param {Object} utm - UTM object to edit
 * @since 1.0.0
 */
const handleEdit = async (utm) => {
  // Suppress watcher-driven URL regeneration while populating the form
  isLoadingEdit.value = true;

  // Reset all UTM params
  Object.keys(utmParams.value).forEach((key) => {
    utmParams.value[key] = "";
  });

  // Fill in existing UTM params
  Object.keys(utm.params).forEach((key) => {
    if (Object.prototype.hasOwnProperty.call(utmParams.value, key)) {
      utmParams.value[key] = utm.params[key] || "";
    }
  });

  // Restore custom parameters
  if (utm.customParams && utm.customParams.length > 0) {
    customParams.value = utm.customParams.map((p) => ({ ...p }));
  } else {
    customParams.value = [];
  }

  // Restore landing path from stored URL
  landingPath.value = "";
  landingPathConfirmed.value = false;
  if (utm.fullUrl) {
    try {
      const editUrl = new URL(utm.fullUrl);
      const path = editUrl.pathname.replace(/^\/+|\/+$/g, "");
      if (path) {
        landingPath.value = path;
        landingPathConfirmed.value = true;
      }
    } catch {
      // Invalid URL — leave path empty
    }
  }

  // Preserve the existing coded URL's random value so shared links aren't broken
  editingCodedValue.value = null;
  if (utm.codedUrl) {
    try {
      const codedUrlObj = new URL(utm.codedUrl);
      const codedVal = codedUrlObj.searchParams.get(utm.id);
      if (codedVal) {
        editingCodedValue.value = codedVal;
      }
    } catch {
      // Fallback — will generate a new value if needed
    }
  }

  editingUtmId.value = utm.id;

  // Take a snapshot of the loaded state for accurate unsaved-changes detection
  editSnapshot.value = {
    utmParams: { ...utmParams.value },
    customParams: customParams.value.map((p) => ({ ...p })),
  };

  // Re-enable watcher, then generate URLs once
  isLoadingEdit.value = false;
  generateUrls();

  // Scroll to the top of the UTM Parameters card so the editing banner is visible
  utmParamsCard.value?.$el?.scrollIntoView({ behavior: "smooth", block: "start" });
  toast.info("UTM parameters loaded for editing.");
};

/**
 * Validate and confirm the landing page path.
 * Five-layer validation:
 *   Layer 1: Reject query characters (?#&=)
 *   Layer 2: Reject UTM parameter patterns (utm_source, utm_medium, etc.)
 *   Layer 3: Allowlist — only a-z, 0-9, hyphens, underscores, dots, slashes
 *   Layer 4: Reject path traversal and dot abuse (../, ./, leading/trailing dots)
 *   Layer 5: Reject consecutive slashes
 *
 * @since 1.1.1
 */
const validateAndConfirmPath = () => {
  const cleanPath = landingPath.value.replace(/^\/+/, "").replace(/\/+$/, "");

  // Allow empty path (root landing page)
  if (!cleanPath) {
    landingPathConfirmed.value = true;
    return;
  }

  // Layer 1: Reject query characters
  if (/[?#&=]/.test(cleanPath)) {
    landingPath.value = "";
    toast.warning(
      $__("components.utm_coder.params_section.path_query_error", "Landing page path cannot contain query parameters. Use the UTM fields below.")
    );
    return;
  }

  // Layer 2: Reject UTM parameter patterns (underscore variant only — hyphens are legitimate slugs)
  if (/utm_(source|medium|campaign|term|content|id)/i.test(cleanPath)) {
    landingPath.value = "";
    toast.warning(
      $__("components.utm_coder.params_section.path_utm_error", "UTM parameters cannot be added in the landing page path. Use the UTM fields below.")
    );
    return;
  }

  // Layer 3: Allowlist — only valid path characters
  if (/[^a-z0-9_\-./]/i.test(cleanPath)) {
    landingPath.value = "";
    toast.warning(
      $__("components.utm_coder.params_section.path_chars_error", "Landing page path can only contain letters, numbers, hyphens, underscores, dots, and slashes.")
    );
    return;
  }

  // Layer 4: Reject path traversal and dot abuse
  if (/\.\./.test(cleanPath) || /^\./.test(cleanPath) || /\.$/.test(cleanPath) || /\/\./.test(cleanPath) || /\.\//.test(cleanPath)) {
    landingPath.value = "";
    toast.warning(
      $__("components.utm_coder.params_section.path_traversal_error", "Landing page path cannot contain relative path patterns.")
    );
    return;
  }

  // Layer 5: Reject consecutive slashes
  if (/\/{2,}/.test(cleanPath)) {
    landingPath.value = "";
    toast.warning(
      $__("components.utm_coder.params_section.path_slashes_error", "Landing page path contains invalid slash patterns.")
    );
    return;
  }

  // Normalize the path value
  landingPath.value = cleanPath;
  landingPathConfirmed.value = true;
  toast.success(
    $__("components.utm_coder.params_section.path_confirmed", `Landing page set to /${cleanPath}`)
  );
};

/**
 * Close the import UTM modal and reset its state
 *
 * @since 1.1.1
 */
const closeImportModal = () => {
  showImportModal.value = false;
  importUrl.value = "";
  importError.value = "";
};

/**
 * Parse UTM parameters from a pasted URL and auto-fill the form
 * Handles full URLs, query strings, missing protocols, and case-insensitive params.
 * Non-standard utm_* params are placed into the custom parameters section.
 *
 * @since 1.1.1
 */
const parseAndImport = () => {
  // Strip newlines/tabs and trim — textarea allows multi-line paste
  const rawInput = importUrl.value.replace(/[\r\n\t]/g, "").trim();

  if (!rawInput) {
    importError.value = $__("components.utm_coder.import_modal.error_empty", "Please paste a URL");
    return;
  }

  // Length cap — no valid UTM URL should exceed 512 characters
  if (rawInput.length > 512) {
    importError.value = $__("components.utm_coder.import_modal.error_too_long", "URL is too long. Maximum 512 characters.");
    return;
  }

  // Reject multiple URLs pasted together
  const protocolMatches = rawInput.match(/https?:\/\//gi);
  if (protocolMatches && protocolMatches.length > 1) {
    importError.value = $__("components.utm_coder.import_modal.error_multiple", "Please paste only one URL at a time.");
    return;
  }

  // Block dangerous protocols (javascript:, data:, vbscript:, etc.)
  if (/^(?!https?:\/\/)[a-z][a-z0-9+.-]*:/i.test(rawInput)) {
    importError.value = $__("components.utm_coder.import_modal.error_invalid", "Invalid URL format. Please check and try again.");
    return;
  }

  let url;
  try {
    if (rawInput.startsWith("http://") || rawInput.startsWith("https://")) {
      url = new URL(rawInput);
    } else if (rawInput.startsWith("?")) {
      url = new URL("https://placeholder.test" + rawInput);
    } else if (rawInput.includes("?")) {
      url = new URL("https://" + rawInput);
    } else {
      url = new URL("https://placeholder.test?" + rawInput);
    }
  } catch {
    importError.value = $__("components.utm_coder.import_modal.error_invalid", "Invalid URL format. Please check and try again.");
    return;
  }

  // Validate domain matches the site — skip check for bare query strings
  if (url.hostname !== "placeholder.test") {
    try {
      const siteHost = new URL(BASE_URL).hostname;
      if (url.hostname.toLowerCase() !== siteHost.toLowerCase()) {
        importError.value = $__("components.utm_coder.import_modal.error_domain", `This URL doesn't match your site domain (${siteHost}).`);
        return;
      }
    } catch {
      // BASE_URL misconfigured — skip domain check
    }
  }

  const params = url.searchParams;

  // Standard UTM field mapping (lowercase key → utmParams field name)
  const utmFieldMap = {
    utm_source: "source",
    utm_medium: "medium",
    utm_campaign: "campaign",
    utm_term: "term",
    utm_content: "content",
    utm_id: "id",
  };

  const extracted = {};
  const extractedCustom = [];
  const invalidParams = [];

  for (const [key, value] of params.entries()) {
    const lowerKey = key.toLowerCase();

    // Reject empty values
    if (!value || value.trim() === "") {
      invalidParams.push(`${key}= (empty value)`);
      continue;
    }

    // Sanitize and validate the value
    const sanitizedValue = sanitizeUtmValue(value);
    if (!sanitizedValue || sanitizedValue !== value.trim()) {
      invalidParams.push(`${key}=${value} (contains invalid characters)`);
      continue;
    }

    if (utmFieldMap[lowerKey]) {
      extracted[utmFieldMap[lowerKey]] = sanitizedValue;
    } else {
      // Validate custom parameter key
      const customKey = lowerKey.startsWith("utm_") ? key.replace(/^utm_/i, "") : key;
      const sanitizedKey = sanitizeCustomKey(customKey);

      if (!sanitizedKey || sanitizedKey !== customKey.trim().toLowerCase()) {
        invalidParams.push(`${key}=${value} (invalid parameter name)`);
        continue;
      }

      // Any non-standard param (utm_ prefixed or not) → custom parameter
      extractedCustom.push({
        key: sanitizedKey,
        value: sanitizedValue,
      });
    }
  }

  // If there are invalid parameters, reject the import
  if (invalidParams.length > 0) {
    const errorList = invalidParams.slice(0, 3).join(", ");
    const remaining = invalidParams.length > 3 ? ` (+${invalidParams.length - 3} more)` : "";
    importError.value = $__("components.utm_coder.import_modal.error_invalid_params", `Invalid parameters found: ${errorList}${remaining}. Please fix and try again.`);
    return;
  }

  // Check if any UTM params were found
  const paramCount = Object.keys(extracted).length;
  if (paramCount === 0 && extractedCustom.length === 0) {
    importError.value = $__("components.utm_coder.import_modal.error_no_utm", "No UTM parameters found in this URL.");
    return;
  }

  // Clear editing state — this is a fresh import
  editingUtmId.value = null;

  // Extract landing path from URL (ignore placeholder hosts)
  if (url.hostname !== "placeholder.test") {
    const path = url.pathname.replace(/^\/+|\/+$/g, "");
    landingPath.value = path || "";
    landingPathConfirmed.value = true;
  } else {
    landingPath.value = "";
    landingPathConfirmed.value = true;
  }

  // Fill standard UTM fields (overwrite existing values)
  Object.keys(utmParams.value).forEach((field) => {
    if (extracted[field]) {
      utmParams.value[field] = extracted[field];
    }
  });

  // Fill custom parameters (up to maxCustomParams)
  if (extractedCustom.length > 0) {
    customParams.value = extractedCustom.slice(0, maxCustomParams);
  }

  // Close modal
  closeImportModal();

  // Toast feedback
  const customImported = Math.min(extractedCustom.length, maxCustomParams);
  const customDropped = extractedCustom.length - customImported;
  const totalImported = paramCount + customImported;
  toast.success(
    $__("components.utm_coder.import_modal.toast_success", `Imported ${totalImported} UTM parameter(s)`)
  );
  if (customDropped > 0) {
    toast.warning(
      $__("components.utm_coder.import_modal.toast_custom_limit", `${customDropped} custom parameter(s) were skipped — maximum ${maxCustomParams} allowed.`)
    );
  }

  // Scroll to top of UTM Parameters card
  nextTick(() => {
    utmParamsCard.value?.$el?.scrollIntoView({ behavior: "smooth", block: "start" });
  });
};

/**
 * Fetch stored UTMs from server
 * 
 * @since 1.0.0
 */
const fetchStoredUtms = async () => {
  // DEBUG_START
  console.log('[inPipe UTM] Fetching stored UTMs...');
  // DEBUG_END
  
  try {
    const response = await api.get(API_ENDPOINTS.UTM_FETCH);
    
    // DEBUG_START
    console.log('[inPipe UTM] Raw API response:', response);
    // DEBUG_END
    
    let rawData = [];
    if (response && response.data) {
      if (response.data.data && Array.isArray(response.data.data.records)) {
        rawData = response.data.data.records;
      } else if (Array.isArray(response.data)) {
        rawData = response.data;
      }
    }
    
    const utmData = Array.isArray(rawData) ? rawData : [];
    
    // Check for valid UTM structure
    const validUtms = utmData.filter(utm => 
      utm && 
      (utm.id !== null && utm.id !== undefined) && 
      (utm.params || utm.fullUrl || utm.codedUrl)
    );
    
    const newArray = validUtms.map((utm, index) => {
      let fullUrl = utm.fullUrl || utm.url;
      
      // Generate fullUrl if it doesn't exist but we have params
      if (!fullUrl && utm.params) {
        const utmParams = Object.entries(utm.params)
          .filter(([key, value]) => value)
          .map(([key, value]) => `utm_${key}=${encodeURIComponent(value)}`)
          .join('&');
          
        const customParams = Array.isArray(utm.customParams) ? utm.customParams
          .filter(param => param.key && param.value)
          .map(param => `${encodeURIComponent(param.key)}=${encodeURIComponent(param.value)}`)
          .join('&') : '';
        
        const allParams = [utmParams, customParams].filter(Boolean).join('&');
        fullUrl = allParams ? `${BASE_URL}?${allParams}` : BASE_URL;
      }
      
      // Create a unique identifier if ID is 0 or problematic
      let uniqueId = utm.id;
      if (utm.id === 0 || utm.id === null || utm.id === undefined) {
        // Create a unique ID based on content and timestamp
        uniqueId = `utm_${Date.now()}_${index}`;
      }
      
      return { 
        ...utm, 
        id: uniqueId,
        fullUrl,
        // Ensure we have a codedUrl
        codedUrl: utm.codedUrl || generateCodedUrl()
      };
    });
    
    storedUtms.value = newArray;
    
    // DEBUG_START
    console.log('[inPipe UTM] Loaded UTMs:', storedUtms.value.length);
    console.log('[inPipe UTM] UTM data:', storedUtms.value);
    // DEBUG_END
    
    return storedUtms.value;
  } catch (error) {
    // DEBUG_START
    console.error('[inPipe UTM] Error fetching stored UTMs:', error);
    // DEBUG_END
    
    toast.error($__("components.utm_coder.notifications.load_error", "Failed to load stored UTM URLs"));
    storedUtms.value = [];
    return [];
  }
};

/**
 * Handle delete UTM
 * 
 * @param {string} utmId - UTM ID to delete
 * @since 1.0.0
 */
const handleDelete = async (utmId) => {
  try {
    const result = await api.post(API_ENDPOINTS.UTM_DELETE, {
      [PARAM_NAMES.UTM_ID]: utmId,
    });

    const success = result?.data?.success || result?.success || false;

    if (success) {
      storedUtms.value = storedUtms.value.filter(utm => utm.id !== utmId);
      toast.success($__("components.utm_coder.stored_utms.delete_success", "UTM URL deleted successfully"));
      
      try {
        await fetchStoredUtms();
      } catch (err) {
        // DEBUG_START
        console.error("[inPipe UTM] Error fetching UTMs after delete:", err);
        // DEBUG_END
      }
      
      if (editingUtmId.value === utmId) {
        editingUtmId.value = null;
        clearAllParams(false);
      }
    } else {
      const errorMessage = result?.data?.message || result?.message || "Failed to delete UTM URL";
      throw new Error(errorMessage);
    }
  } catch (error) {
    // DEBUG_START
    console.error("[inPipe UTM] Error deleting UTM:", error);
    // DEBUG_END
    
    toast.error(error.message || $__("components.utm_coder.stored_utms.delete_error", "Failed to delete UTM URL"));
  }
};

/**
 * Handle save UTM button click with debouncing
 * 
 * @since 1.0.0
 */
 const handleSaveUTM = () => {
  // Clear any existing timer
  if (saveDebounceTimer.value) {
    clearTimeout(saveDebounceTimer.value);
  }

  // Prevent multiple rapid clicks
  if (isSaving.value) {
    // DEBUG_START
    console.log("[inPipe UTM] Save ignored - already processing");
    // DEBUG_END
    return;
  }

  // Set debounce timer
  saveDebounceTimer.value = setTimeout(async () => {
    await performSave();
  }, 300); // 300ms debounce delay
};

/**
 * Perform the actual save operation
 * 
 * @since 1.0.0
 */
const performSave = async () => {
  if (!generatedUrl.value || !hasUnsavedChanges.value || isSaving.value) {
    return;
  }

  try {
    isSaving.value = true;

    // DEBUG_START
    console.log('[inPipe UTM] Saving UTM...', {
      generatedUrl: generatedUrl.value,
      landingPath: landingPath.value,
      utmParams: { ...utmParams.value },
    });
    // DEBUG_END

    // Sanitize UTM params
    Object.keys(utmParams.value).forEach((key) => {
      const original = utmParams.value[key];
      utmParams.value[key] = sanitizeUtmValue(original);
    });

    // Sanitize custom params (stricter key sanitization)
    customParams.value = customParams.value.map((param) => ({
      key: sanitizeCustomKey(param.key),
      value: sanitizeUtmValue(param.value),
    }));

    // Regenerate URLs to ensure they include latest params + landing path
    generateUrls();

    const utmData = {
      id: editingUtmId.value || "",
      fullUrl: generatedUrl.value,
      codedUrl: codedUrl.value,
      params: { ...utmParams.value },
      customParams: customParams.value.filter((param) => param.key && param.value),
    };

    const result = await api.post(API_ENDPOINTS.UTM_SAVE, {
      [PARAM_NAMES.UTM_DATA]: utmData,
    });

    const success = result?.data?.success || (result?.data?.data?.success ?? false);

    if (success) {
      // Copy coded URL to clipboard automatically
      const savedCodedUrl = utmData.codedUrl;
      let clipboardOk = false;
      if (savedCodedUrl) {
        try {
          if (navigator.clipboard && navigator.clipboard.writeText) {
            await navigator.clipboard.writeText(savedCodedUrl);
          } else {
            const textArea = document.createElement("textarea");
            textArea.value = savedCodedUrl;
            textArea.style.position = "fixed";
            textArea.style.opacity = "0";
            document.body.appendChild(textArea);
            textArea.focus();
            textArea.select();
            document.execCommand("copy");
            document.body.removeChild(textArea);
          }
          clipboardOk = true;
        } catch {
          // Clipboard write failed — still show save success
        }
      }

      toast.success(
        clipboardOk
          ? $__("components.utm_coder.notifications.save_copied", "UTM saved — coded URL copied to clipboard!")
          : $__("components.utm_coder.notifications.save_success", "UTM URL saved successfully!")
      );

      // Resolve the saved UTM's ID for highlighting
      const savedId = result?.data?.data?.utm_id || result?.data?.utm_id || utmData.id;

      await fetchStoredUtms();

      // Highlight the saved UTM and scroll it into view
      highlightedUtmId.value = savedId;
      await nextTick();
      document.querySelector('.utm-item.is-highlighted')?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
      setTimeout(() => {
        highlightedUtmId.value = null;
      }, 3000);

      clearFormAfterSave();

      // Emit to parent
      emit('utm-saved', utmData);
    } else {
      throw new Error("Failed to save UTM URL");
    }
  } catch (error) {
    // DEBUG_START
    console.error('[inPipe UTM] Error saving UTM:', error);
    // DEBUG_END

    toast.error($__("components.utm_coder.notifications.save_error", "Failed to save UTM URL - try again"));
  } finally {
    isSaving.value = false;
  }
};

/**
 * Clear form after successful save
 * 
 * @since 1.0.0
 */
const clearFormAfterSave = () => {
  // Reset UTM parameters to initial state
  Object.keys(utmParams.value).forEach((key) => {
    utmParams.value[key] = initialState.utmParams[key];
  });

  // Reset custom parameters to initial state
  customParams.value = [...initialState.customParams];

  // Clear landing path
  landingPath.value = "";
  landingPathConfirmed.value = false;

  // Clear editing state
  editingUtmId.value = null;
  editingCodedValue.value = null;
  editSnapshot.value = null;

  // Clear URLs (these will be regenerated by the watcher)
  generatedUrl.value = "";
  codedUrl.value = "";

  // DEBUG_START
  console.log('[inPipe UTM] Form cleared after save');
  // DEBUG_END
};

/**
 * Sanitize UTM parameter value using allowlist approach.
 * Steps: normalize NFKC → strip zero-width chars → spaces to plus → allowlist filter → trim.
 * Allows: a-z, 0-9, underscore, hyphen, period, plus. Spaces auto-convert to plus.
 *
 * @param {string} value - Value to sanitize
 * @return {string} Sanitized value
 * @since 1.1.1
 */
const sanitizeUtmValue = (value) => {
  if (typeof value !== "string") return "";
  return value
    .normalize("NFKC")
    .replace(/[\u200B-\u200F\u2028-\u202F\uFEFF]/g, "")
    .replace(/\s+/g, "+")
    .replace(/[^a-zA-Z0-9_\-.+]/g, "")
    .trim();
};

/**
 * Sanitize custom parameter key (stricter than values).
 * Allows: a-z, 0-9, underscore, hyphen. No spaces or periods.
 *
 * @param {string} key - Key to sanitize
 * @return {string} Sanitized key
 * @since 1.1.1
 */
const sanitizeCustomKey = (key) => {
  if (typeof key !== "string") return "";
  return key
    .normalize("NFKC")
    .toLowerCase()
    .replace(/[\u200B-\u200F\u2028-\u202F\uFEFF]/g, "")
    .replace(/[^a-z0-9_-]/g, "")
    .trim();
};

/**
 * Validate a custom parameter key with real-time feedback.
 * Checks: invalid characters, standard UTM name collision, utm_ prefix, duplicates.
 *
 * @param {string} key - Key to validate
 * @param {number} index - Index of the custom param row (for duplicate detection)
 * @return {boolean} True if valid
 * @since 1.1.1
 */
const validateCustomKey = (key, index) => {
  const clean = key.normalize("NFKC").trim().toLowerCase();

  if (!clean) return true; // Empty is allowed (not yet filled)

  if (/[?#&=<>"'`\\%\s]/.test(clean)) {
    customParams.value[index].key = "";
    toast.warning(
      $__("components.utm_coder.custom_params.key_invalid", "Parameter key contains invalid characters.")
    );
    return false;
  }

  const standardUtmKeys = [
    "utm_source", "utm_medium", "utm_campaign",
    "utm_term", "utm_content", "utm_id"
  ];
  if (standardUtmKeys.includes(clean)) {
    customParams.value[index].key = "";
    toast.warning(
      $__("components.utm_coder.custom_params.key_standard", "Standard UTM parameters must be set using the UTM fields above.")
    );
    return false;
  }

  if (/^utm_/.test(clean)) {
    customParams.value[index].key = "";
    toast.warning(
      $__("components.utm_coder.custom_params.key_utm_prefix", "Custom keys cannot use the utm_ prefix.")
    );
    return false;
  }

  // Duplicate key detection
  const isDuplicate = customParams.value.some(
    (p, i) => i !== index && p.key.toLowerCase() === clean
  );
  if (isDuplicate) {
    customParams.value[index].key = "";
    toast.warning(
      $__("components.utm_coder.custom_params.key_duplicate", "This custom parameter key already exists.")
    );
    return false;
  }

  // Normalize to lowercase in the field
  customParams.value[index].key = clean;
  return true;
};

/**
 * Validate a custom parameter value with real-time feedback.
 * Checks: query-breaking and injection characters.
 *
 * @param {string} value - Value to validate
 * @param {number} index - Index of the custom param row
 * @return {boolean} True if valid
 * @since 1.1.1
 */
const validateCustomValue = (value, index) => {
  const clean = value.normalize("NFKC").trim().toLowerCase();

  if (!clean) return true; // Empty is allowed

  if (/[?#&=<>"'`\\%]/.test(clean)) {
    customParams.value[index].value = "";
    toast.warning(
      $__("components.utm_coder.custom_params.value_invalid", "Parameter value contains invalid characters.")
    );
    return false;
  }

  // Normalize to lowercase in the field
  customParams.value[index].value = clean;
  return true;
};

/**
 * Load settings from server
 * 
 * @since 1.0.0
 */
const loadSettings = async () => {
  try {
    if (securityCheckFailed.value) {
      utmProcessingEnabled.value = false;
      return;
    }

    const response = await api.get(API_ENDPOINTS.SETTINGS);
    const responseData = response?.data?.data || response?.data || response;

    let processingEnabled = false;

    if (responseData && typeof responseData === "object") {
      if ("utm_processing_enabled" in responseData) {
        processingEnabled = !!responseData.utm_processing_enabled;
      } else if (responseData.settings && "utm_processing_enabled" in responseData.settings) {
        processingEnabled = !!responseData.settings.utm_processing_enabled;
      }
    }

    utmProcessingEnabled.value = processingEnabled;

    // DEBUG_START
    console.log("[inPipe UTM] UTM processing setting loaded:", { enabled: processingEnabled });
    // DEBUG_END
  } catch (error) {
    // DEBUG_START
    console.error("[inPipe UTM] Error loading settings:", error);
    // DEBUG_END

    toast.error($__("components.utm_coder.settings.load_error", "Failed to load settings. Please refresh the page."));
    utmProcessingEnabled.value = false;
  }
};

/**
 * Format date for display
 * 
 * @param {string} dateStr - Date string to format
 * @return {string} Formatted date
 * @since 1.0.0
 */
const formatDate = (dateStr) => {
  try {
    const locale = getCurrentInstance()?.appContext.config.globalProperties.$locale || "en-US";
    const date = new Date(dateStr);
    return new Intl.DateTimeFormat(locale, {
      day: "2-digit",
      month: "short",
      year: "numeric",
      hour: "2-digit",
      minute: "2-digit",
      hour12: false,
    }).format(date);
  } catch (error) {
    return dateStr;
  }
};

// Watch for changes to update URLs
watch(
  [utmParams, customParams, landingPath],
  () => {
    // Skip regeneration while handleEdit is populating the form
    if (isLoadingEdit.value) return;

    // Silently clear dirty values on selection (toast is shown by handleAddFromDropdown)
    for (const [key, value] of Object.entries(utmParams.value)) {
      if (value && value !== sanitizeUtmValue(value)) {
        utmParams.value[key] = "";
        return;
      }
    }

    generateUrls();
  },
  { deep: true },
);

// Watch landing path for UTM parameters
watch(
  landingPath,
  (newPath) => {
    if (!newPath || landingPathConfirmed.value) {
      landingPathWarning.value = "";
      return;
    }

    // Check if the landing path contains UTM parameters
    const utmMatches = newPath.match(/utm_[a-z]+=/gi);

    if (!utmMatches) {
      landingPathWarning.value = "";
      return;
    }

    const utmCount = utmMatches.length;

    if (utmCount === 1) {
      landingPathWarning.value = $__("components.utm_coder.landing_path.warning_single_utm", "It looks like you pasted a UTM parameter. Use the UTM fields below instead.");
    } else {
      landingPathWarning.value = $__("components.utm_coder.landing_path.warning_multiple_utm", "It looks like you pasted a URL with UTM parameters. Use the Import button to parse it.");
    }
  }
);

// Lifecycle hooks
onBeforeMount(() => {
  performSecurityCheck();
});

onMounted(async () => {
  if (!storedUtms.value) {
    storedUtms.value = [];
  }
  
  await nextTick();
  
  if (!securityCheckFailed.value) {
    try {
      await loadSettings();
      await fetchUtmOptions();

      let attempts = 0;
      const maxAttempts = 3;
      
      storedUtms.value = [];
      await nextTick();
      
      while (attempts < maxAttempts) {
        attempts++;
        try {
          const utms = await fetchStoredUtms();
          if (Array.isArray(storedUtms.value)) {
            break;
          }
          
          if (attempts < maxAttempts) {
            await new Promise(resolve => setTimeout(resolve, 500));
          }
        } catch (err) {
          if (attempts < maxAttempts) {
            await new Promise(resolve => setTimeout(resolve, 500));
          }
        }
      }
      
      if (!Array.isArray(storedUtms.value)) {
        storedUtms.value = [];
      }
    } catch (error) {
      // DEBUG_START
      console.error('[inPipe UTM] Initialization error:', error);
      // DEBUG_END

      toast.error($__("components.utm_coder.notifications.init_error", "Error initializing UTM Coder. Please refresh the page."));
    }
  }
});
</script>

<style scoped>
/**
 * UTM Coder Component Styles
 * Clean styling for free version with emoji icons
 */

.utm-coder-container {
  max-width: 1200px;
  margin: 0 auto;
}

.utm-coder-content {
  space-y: 1.5rem;
}

.main-title {
  font-size: 2rem;
  margin-bottom: 1rem;
}

.text-center {
  text-align: center;
}

.text-group {
  margin-top: 1rem;
  space-y: 0.5rem;
}

.url-display-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 1.5rem;
  gap: 1rem;
}

.save-utm-button {
  flex-shrink: 0;
}

.button-content {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.button-icon {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}

.icon-spin {
  animation: spin 1s linear infinite;
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.icon-success {
  color: var(--color-primary);
}

.inline-icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
}

.warning-icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
  color: #dc2626;
}

.saved-message {
  padding: 0.75rem;
  background-color: #d1fae5;
  border: 1px solid #059669;
  border-radius: 0.5rem;
  margin-bottom: 1rem;
}

.flex-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.success-icon {
  font-size: 1rem;
}

.urls-container {
  space-y: 1rem;
}

.url-section {
  padding: 1rem;
  border: 1px solid #e5e7eb;
  border-radius: 0.5rem;
  background-color: #f9fafb;
}

.url-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.5rem;
}

.url-title {
  font-size: 1rem;
  font-weight: 600;
  margin: 0;
}

.url-content {
  font-family: monospace;
  font-size: 0.875rem;
  padding: 0.5rem;
  background-color: #ffffff;
  border: 1px solid #d1d5db;
  border-radius: 0.25rem;
  word-break: break-all;
  min-height: 2rem;
  display: flex;
  align-items: center;
}

/* Landing Page Path Input */
.landing-path-group {
  margin-bottom: 1.25rem;
}

.landing-path-input {
  display: flex;
  align-items: center;
  margin-top: 0.375rem;
  border: 1px solid #d1d5db;
  border-radius: 0.5rem;
  overflow: hidden;
  background-color: #ffffff;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.landing-path-input:focus-within {
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

.landing-path-domain {
  padding: 0.5rem 0.75rem;
  background-color: #f3f4f6;
  color: #6b7280;
  font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
  font-size: 0.875rem;
  white-space: nowrap;
  border-right: 1px solid #d1d5db;
  user-select: none;
}

.landing-path-field {
  flex: 1;
  padding: 0.5rem 0.75rem;
  border: none;
  outline: none;
  font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
  font-size: 0.875rem;
  color: #1f2937;
  background: transparent;
  min-width: 0;
}

.landing-path-field::placeholder {
  color: #9ca3af;
  font-style: italic;
}

.landing-path-check {
  padding: 0.5rem 0.75rem;
  border: none;
  border-left: 1px solid #d1d5db;
  background-color: #f0fdf4;
  color: #16a34a;
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
  flex-shrink: 0;
}

.landing-path-check:hover:not(:disabled) {
  background-color: #dcfce7;
  color: #15803d;
}

.landing-path-check:disabled {
  background-color: #f9fafb;
  color: #d1d5db;
  cursor: not-allowed;
}

.landing-path-input.has-preview,
.landing-path-input.is-confirmed {
  background-color: #f9fafb;
  cursor: not-allowed;
}

.landing-path-input.has-warning {
  border-color: #f59e0b;
}

.landing-path-input.has-warning:focus-within {
  border-color: #f59e0b;
  box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.1);
}

.landing-path-warning {
  margin-top: 0.5rem;
  padding: 0.625rem 0.875rem;
  background-color: #fffbeb;
  border: 1px solid #fef3c7;
  border-radius: 0.375rem;
  color: #92400e;
  font-size: 0.875rem;
  line-height: 1.5;
}

.landing-path-confirmed {
  flex: 1;
  padding: 0.5rem 0.75rem;
  font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
  font-size: 0.875rem;
  color: #374151;
  min-width: 0;
}

.landing-path-preview {
  flex: 1;
  padding: 0.5rem 0.75rem;
  font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
  font-size: 0.8125rem;
  color: #6b7280;
  background: transparent;
  white-space: nowrap;
  overflow-x: auto;
  line-height: 1.5;
  min-width: 0;
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 1.5rem;
  gap: 1rem;
}

/* Editing state banner */
.editing-banner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.625rem 1rem;
  margin-bottom: 1rem;
  background-color: #eff6ff;
  border: 1px solid #3b82f6;
  border-radius: 0.5rem;
  gap: 0.75rem;
}

.editing-banner-content {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: #1e40af;
}

.editing-banner-icon {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}

.editing-banner-id {
  font-family: monospace;
  font-size: 0.8125rem;
  padding: 0.125rem 0.375rem;
  background-color: #dbeafe;
  border-radius: 0.25rem;
  color: #1e3a8a;
}

.editing-banner-cancel {
  flex-shrink: 0;
  color: #1e40af;
}

.utm-params-wrapper {
  position: relative;
}

.utm-params-wrapper.is-locked {
  opacity: 0.5;
  pointer-events: none;
}

.utm-params-lock-overlay {
  position: absolute;
  inset: 0;
  z-index: 10;
  cursor: not-allowed;
  pointer-events: auto;
}

.utm-params {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}

.param-group {
  padding: 1rem;
  border: 1px solid #e5e7eb;
  border-radius: 0.5rem;
  background-color: #f9fafb;
  transition: border-color 0.2s, background-color 0.2s;
}

.param-group.has-value {
  border-color: var(--color-primary);
  background-color: color-mix(in srgb, var(--color-primary) 6%, #ffffff);
}

.param-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.5rem;
}

.param-label {
  font-weight: 600;
  font-family: monospace;
}

.param-select {
  width: 100%;
}

/* Custom Parameters Inline (inside UTM Parameters card) */
.custom-params-inline {
  margin-top: 1.5rem;
  padding-top: 1.5rem;
  border-top: 1px dashed #e5e7eb;
}

.custom-params-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.75rem;
}

.custom-params-label {
  font-weight: 600;
  color: #374151;
}

.param-limit {
  font-size: 0.8125rem;
  color: #9ca3af;
}

/* UTM Card Footer */
.utm-card-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 1.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid #e5e7eb;
}

.param-limit {
  font-size: 0.875rem;
}

.param-row {
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.5rem;
}

.param-key-input,
.param-value-input {
  width: 100%;
}

.delete-button {
  padding: 0.5rem;
}

.utm-warning {
  padding: 0.75rem;
  background-color: #fef3c7;
  border: 1px solid #f59e0b;
  border-radius: 0.5rem;
  margin: 1rem 0;
}

.stored-utms {
  margin-top: 1rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.utm-item {
  padding: 1rem;
  border: 1px solid #e5e7eb;
  border-radius: 0.5rem;
  background-color: #ffffff;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.utm-item.is-editing {
  border-color: #3b82f6;
  box-shadow: 0 0 0 1px #3b82f6;
  background-color: #eff6ff;
}

.utm-item.is-highlighted {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 1px var(--color-primary);
  background-color: color-mix(in srgb, var(--color-primary) 8%, #ffffff);
  animation: highlight-fade 3s ease-out forwards;
}

@keyframes highlight-fade {
  0% { background-color: color-mix(in srgb, var(--color-primary) 15%, #ffffff); box-shadow: 0 0 0 2px var(--color-primary); }
  70% { background-color: color-mix(in srgb, var(--color-primary) 8%, #ffffff); box-shadow: 0 0 0 1px var(--color-primary); }
  100% { background-color: #ffffff; box-shadow: none; border-color: #e5e7eb; }
}

.utm-urls {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.utm-url-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 0.5rem;
  align-items: center;
}

.url-label {
  font-weight: 600;
  font-size: 0.875rem;
  white-space: nowrap;
}

.url-code {
  font-family: monospace;
  font-size: 0.75rem;
  padding: 0.25rem 0.5rem;
  background-color: #f3f4f6;
  border: 1px solid #d1d5db;
  border-radius: 0.25rem;
  word-break: break-all;
  overflow: hidden;
}

.url-actions {
  display: flex;
  gap: 0.25rem;
}

.action-button {
  padding: 0.25rem 0.5rem;
  font-size: 0.75rem;
}

.utm-footer {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid #e5e7eb;
}

.utm-date {
  font-size: 0.75rem;
  color: #6b7280;
}

.no-utms {
  text-align: center;
  padding: 2rem;
  color: #6b7280;
}

.security-warning {
  padding: 2rem;
}

/* Import UTM - Header actions container (1.1.1) */
.header-actions {
  display: flex;
  gap: 0.5rem;
  align-items: flex-start;
  flex-shrink: 0;
}

/* Import UTM - Modal content (1.1.1) */
.import-modal-content {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.import-textarea {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid #d1d5db;
  border-radius: 0.5rem;
  font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
  font-size: 0.875rem;
  line-height: 1.5;
  resize: vertical;
  min-height: 3.5rem;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
  background-color: #ffffff;
  box-sizing: border-box;
}

.import-textarea:focus {
  outline: none;
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

.import-textarea::placeholder {
  color: #9ca3af;
  font-style: italic;
}

.import-error {
  color: #dc2626;
  font-size: 0.875rem;
  margin: 0;
  padding: 0.5rem 0.75rem;
  background-color: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: 0.375rem;
}

.import-modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  padding-top: 0.5rem;
}

/* Responsive design */
@media (max-width: 768px) {
  .url-display-header {
    flex-direction: column;
    align-items: stretch;
  }

  .card-header {
    flex-direction: column;
    align-items: stretch;
  }

  .header-actions {
    flex-direction: row;
    width: 100%;
    gap: 0.5rem;
  }

  .header-actions > * {
    flex: 1;
    min-width: 0;
  }

  /* Hide icons on mobile - text only */
  .button-content .button-icon {
    display: none;
  }

  .button-content {
    justify-content: center;
  }

  /* Landing page input - hide domain prefix on mobile */
  .landing-path-domain {
    display: none;
  }

  .landing-path-input {
    border-left: 3px solid #3b82f6;
  }

  .landing-path-field {
    padding-left: 0.5rem;
  }

  .landing-path-field::placeholder {
    font-size: 0.8125rem;
  }

  /* Make landing page input more mobile-friendly */
  .landing-path-group {
    margin-bottom: 1rem;
  }

  .landing-path-warning {
    font-size: 0.8125rem;
    padding: 0.5rem 0.75rem;
  }

  .utm-params {
    grid-template-columns: 1fr;
  }

  .param-row {
    grid-template-columns: 1fr;
    gap: 0.5rem;
  }

  .utm-card-footer {
    flex-direction: column;
    gap: 0.75rem;
  }

  .utm-card-footer > * {
    width: 100%;
  }

  .utm-url-row {
    grid-template-columns: 1fr;
    gap: 0.25rem;
  }

  .url-actions {
    justify-content: center;
  }

  .import-modal-actions {
    flex-direction: column;
  }

  /* Make modals more mobile-friendly */
  .import-textarea {
    font-size: 0.875rem;
    min-height: 120px;
  }

  /* Stored UTMs mobile fixes */
  .utm-item {
    padding: 0.75rem;
  }

  .utm-url-row {
    grid-template-columns: 1fr;
    gap: 0.375rem;
  }

  .url-label {
    font-size: 0.8125rem;
    margin-bottom: 0.125rem;
  }

  .url-code {
    font-size: 0.6875rem;
    padding: 0.375rem 0.5rem;
    line-height: 1.4;
    max-width: 100%;
    overflow-wrap: break-word;
  }

  .url-actions {
    margin-top: 0.375rem;
    justify-content: flex-start;
    gap: 0.375rem;
  }

  .url-actions .button-icon {
    display: block;
    width: 1.125rem;
    height: 1.125rem;
  }

  .action-button {
    padding: 0.375rem 0.5rem;
    min-width: 44px;
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .utm-footer {
    font-size: 0.8125rem;
  }
}

/* Utility classes */
.mt-2 { margin-top: 0.5rem; }
.mt-4 { margin-top: 1rem; }
.mt-6 { margin-top: 1.5rem; }
</style>