var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
    var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;
    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _;
            if (_ = accept(result.set)) descriptor.set = _;
            if (_ = accept(result.init)) initializers.unshift(_);
        }
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }
    if (target) Object.defineProperty(target, contextIn.name, descriptor);
    done = true;
};
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
    var useValue = arguments.length > 2;
    for (var i = 0; i < initializers.length; i++) {
        value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
    }
    return useValue ? value : void 0;
};
import * as appstate from '../../appstate.js';
import * as interfaces from '../../../dist_ts_interfaces/index.js';
import { viewHostCss } from '../shared/css.js';
import {} from '@design.estate/dees-catalog';
import { DeesElement, css, cssManager, customElement, html, state, } from '@design.estate/dees-element';
// TLS dropdown options shared by create and edit dialogs
const tlsModeOptions = [
    { key: 'none', option: '(none — plain TCP/HTTP, use for SSH)' },
    { key: 'passthrough', option: 'Passthrough (TLS only)' },
    { key: 'terminate', option: 'Terminate TLS' },
    { key: 'terminate-and-reencrypt', option: 'Terminate & Re-encrypt TLS' },
];
const tlsCertOptions = [
    { key: 'auto', option: 'Auto (ACME/Let\'s Encrypt)' },
    { key: 'custom', option: 'Custom certificate' },
];
const giteaSourcePolicyProfileNames = ['TRUSTED NETWORKS', 'AI CRAWLERS', 'PUBLIC'];
function rateLimit(maxRequests) {
    return { enabled: true, maxRequests, window: 60, keyBy: 'ip' };
}
function getDropdownKey(value) {
    return typeof value === 'string' ? value : value?.key || '';
}
function getGiteaPresetProfileRefs(profiles) {
    const refs = [];
    const missingNames = [];
    for (const profileName of giteaSourcePolicyProfileNames) {
        const profile = profiles.find((item) => item.name.trim().toUpperCase() === profileName);
        if (profile) {
            refs.push(profile.id);
        }
        else {
            missingNames.push(profileName);
        }
    }
    return { refs, missingNames };
}
function buildGiteaSourceBindingsMetadata(profileRefs) {
    const [trustedRef, aiRef, publicRef] = profileRefs;
    return [
        {
            sourceProfileRef: trustedRef,
            onExceeded: { type: '429' },
        },
        {
            sourceProfileRef: aiRef,
            onExceeded: { type: '429' },
            pathPolicies: [
                { pathClass: 'git-smart-http', rateLimit: rateLimit(1200) },
                { pathClass: 'static', rateLimit: rateLimit(240) },
                { pathClass: 'raw', rateLimit: rateLimit(20) },
                { pathClass: 'archive', rateLimit: rateLimit(6) },
                { pathClass: 'expensive-html', rateLimit: rateLimit(6) },
                { pathClass: 'normal-html', rateLimit: rateLimit(20) },
            ],
        },
        {
            sourceProfileRef: publicRef,
            onExceeded: { type: '429' },
            pathPolicies: [
                { pathClass: 'git-smart-http', rateLimit: rateLimit(1200) },
                { pathClass: 'static', rateLimit: rateLimit(600) },
                { pathClass: 'raw', rateLimit: rateLimit(120) },
                { pathClass: 'archive', rateLimit: rateLimit(30) },
                { pathClass: 'expensive-html', rateLimit: rateLimit(30) },
                { pathClass: 'normal-html', rateLimit: rateLimit(120) },
            ],
        },
    ];
}
function getGiteaSourcePolicyPresets(profiles) {
    const { refs, missingNames } = getGiteaPresetProfileRefs(profiles);
    if (missingNames.length > 0) {
        return [];
    }
    return [
        {
            key: 'gitea-bot-protection',
            label: 'Gitea bot protection',
            description: 'TRUSTED NETWORKS -> AI CRAWLERS -> PUBLIC with path-class rate limits.',
            bindings: buildGiteaSourceBindingsMetadata(refs),
        },
    ];
}
function normalizeSecurityListEntries(entries) {
    if (!Array.isArray(entries)) {
        return [];
    }
    return entries
        .map((entry) => {
        if (typeof entry === 'string')
            return entry.trim();
        if (entry && typeof entry === 'object' && 'ip' in entry) {
            const ip = entry.ip;
            return typeof ip === 'string' ? ip.trim() : '';
        }
        return '';
    })
        .filter(Boolean);
}
function sourceProfileMatchesAll(profile) {
    return normalizeSecurityListEntries(profile.security?.ipAllowList).some((source) => {
        return ['*', '0.0.0.0/0', '::/0'].includes(source.trim());
    });
}
function sourceProfileHasSourceMatches(profile) {
    return normalizeSecurityListEntries(profile.security?.ipAllowList).length > 0;
}
function normalizeCatalogRateLimit(rateLimitValue) {
    if (rateLimitValue === null)
        return null;
    if (!rateLimitValue)
        return undefined;
    const keyBy = ['ip', 'path', 'header'].includes(String(rateLimitValue.keyBy))
        ? rateLimitValue.keyBy
        : undefined;
    return {
        enabled: Boolean(rateLimitValue.enabled),
        maxRequests: Number(rateLimitValue.maxRequests) || 0,
        window: Number(rateLimitValue.window) || 0,
        ...(keyBy ? { keyBy } : {}),
        ...(rateLimitValue.onExceeded ? { onExceeded: rateLimitValue.onExceeded } : {}),
    };
}
function normalizeCatalogChallenge(challengeValue) {
    if (challengeValue === null)
        return null;
    if (!challengeValue)
        return undefined;
    return structuredClone(challengeValue);
}
function getSourceProfileOptions(profiles) {
    return profiles.map((profile) => {
        const ipAllowList = normalizeSecurityListEntries(profile.security?.ipAllowList);
        const ipBlockList = normalizeSecurityListEntries(profile.security?.ipBlockList);
        const rateLimitValue = normalizeCatalogRateLimit(profile.security?.rateLimit);
        const challengeValue = normalizeCatalogChallenge(profile.security?.challenge);
        const security = {
            ...(ipAllowList.length ? { ipAllowList } : {}),
            ...(ipBlockList.length ? { ipBlockList } : {}),
            ...(typeof profile.security?.maxConnections === 'number' ? { maxConnections: profile.security.maxConnections } : {}),
            ...(rateLimitValue !== undefined ? { rateLimit: rateLimitValue } : {}),
            ...(challengeValue !== undefined ? { challenge: challengeValue } : {}),
        };
        return {
            id: profile.id,
            name: profile.name,
            description: profile.description,
            security,
            hasSourceMatches: sourceProfileHasSourceMatches(profile),
            matchesAllSources: sourceProfileMatchesAll(profile),
        };
    });
}
function getRoutePathClassOptions() {
    return interfaces.data.routePathClasses.map((pathClass) => ({
        key: pathClass,
        label: interfaces.data.giteaRoutePathClassLabels[pathClass],
        defaultPatterns: interfaces.data.giteaRoutePathClassPatterns[pathClass],
    }));
}
function getSourcePolicyInfoText(profiles) {
    const { missingNames } = getGiteaPresetProfileRefs(profiles);
    const presetText = missingNames.length > 0
        ? `Gitea preset hidden until these source profiles exist: ${missingNames.join(', ')}.`
        : 'Use the Gitea preset as a starting point, then edit the generated bindings before saving.';
    return `First matching source profile wins. Leave empty for no route-level source access control. ${presetText}`;
}
function validateSourcePolicyInput(form) {
    const sourcePolicyInput = form.querySelector('sz-input-route-source-policy');
    if (!sourcePolicyInput || sourcePolicyInput.isValid()) {
        return true;
    }
    alert(sourcePolicyInput.getValidationMessages().join('\n'));
    return false;
}
function getSourceBindingsFromFormData(formData) {
    const sourceBindings = formData.sourceBindings;
    return Array.isArray(sourceBindings)
        ? sourceBindings
        : [];
}
function parseTargetPort(value) {
    const parsed = typeof value === 'number'
        ? value
        : typeof value === 'string'
            ? parseInt(value.trim(), 10)
            : Number.NaN;
    if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
        return undefined;
    }
    return parsed;
}
function getRouteTargetInputs(formEl) {
    const textInputs = Array.from(formEl.querySelectorAll('dees-input-text'));
    const checkboxInputs = Array.from(formEl.querySelectorAll('dees-input-checkbox'));
    return {
        hostInput: textInputs.find((input) => input.key === 'targetHost'),
        portInput: textInputs.find((input) => input.key === 'targetPort'),
        preservePortInput: checkboxInputs.find((input) => input.key === 'preserveMatchPort'),
    };
}
function setupTargetInputState(formEl) {
    const updateState = async () => {
        const data = await formEl.collectFormData();
        const contentEl = formEl.closest('.content') || formEl.parentElement;
        const usesNetworkTarget = !!getDropdownKey(data.networkTargetRef);
        const preserveMatchPort = !usesNetworkTarget && Boolean(data.preserveMatchPort);
        const { hostInput, portInput, preservePortInput } = getRouteTargetInputs(formEl);
        const hostDescription = usesNetworkTarget
            ? 'Controlled by the selected network target'
            : 'Used when no network target is selected';
        const portDescription = usesNetworkTarget
            ? 'Controlled by the selected network target'
            : preserveMatchPort
                ? 'Forwarded to the backend on the same port the client matched'
                : 'Used when no network target is selected';
        if (hostInput) {
            hostInput.disabled = usesNetworkTarget;
            hostInput.required = !usesNetworkTarget;
            hostInput.description = hostDescription;
        }
        if (portInput) {
            portInput.disabled = usesNetworkTarget || preserveMatchPort;
            portInput.required = !usesNetworkTarget && !preserveMatchPort;
            portInput.description = portDescription;
        }
        if (preservePortInput) {
            preservePortInput.disabled = usesNetworkTarget;
            preservePortInput.description = usesNetworkTarget
                ? 'Unavailable when a network target is selected'
                : 'Forward to the backend using the same port that matched this route';
            if (usesNetworkTarget) {
                preservePortInput.value = false;
            }
        }
        const remoteIngressGroup = contentEl?.querySelector('.remoteIngressGroup');
        if (remoteIngressGroup) {
            remoteIngressGroup.style.display = Boolean(data.remoteIngressEnabled) ? 'flex' : 'none';
        }
        await formEl.updateRequiredStatus?.();
    };
    formEl.changeSubject.subscribe(() => updateState());
    updateState();
}
/**
 * Toggle TLS form field visibility based on selected TLS mode and certificate type.
 */
function setupTlsVisibility(formEl) {
    const updateVisibility = async () => {
        const data = await formEl.collectFormData();
        const contentEl = formEl.closest('.content') || formEl.parentElement;
        if (!contentEl)
            return;
        const tlsModeValue = data.tlsMode;
        const modeKey = typeof tlsModeValue === 'string' ? tlsModeValue : tlsModeValue?.key;
        const needsCert = modeKey === 'terminate' || modeKey === 'terminate-and-reencrypt';
        const certGroup = contentEl.querySelector('.tlsCertificateGroup');
        if (certGroup)
            certGroup.style.display = needsCert ? 'flex' : 'none';
        const tlsCertValue = data.tlsCertificate;
        const certKey = typeof tlsCertValue === 'string' ? tlsCertValue : tlsCertValue?.key;
        const customGroup = contentEl.querySelector('.tlsCustomCertGroup');
        if (customGroup)
            customGroup.style.display = (needsCert && certKey === 'custom') ? 'flex' : 'none';
    };
    formEl.changeSubject.subscribe(() => updateVisibility());
    updateVisibility();
}
let OpsViewRoutes = (() => {
    let _classDecorators = [customElement('ops-view-routes')];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = DeesElement;
    let _routeFilter_decorators;
    let _routeFilter_initializers = [];
    let _routeFilter_extraInitializers = [];
    let _routeState_decorators;
    let _routeState_initializers = [];
    let _routeState_extraInitializers = [];
    let _profilesTargetsState_decorators;
    let _profilesTargetsState_initializers = [];
    let _profilesTargetsState_extraInitializers = [];
    var OpsViewRoutes = class extends _classSuper {
        static { _classThis = this; }
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            _routeFilter_decorators = [state()];
            _routeState_decorators = [state()];
            _profilesTargetsState_decorators = [state()];
            __esDecorate(this, null, _routeFilter_decorators, { kind: "accessor", name: "routeFilter", static: false, private: false, access: { has: obj => "routeFilter" in obj, get: obj => obj.routeFilter, set: (obj, value) => { obj.routeFilter = value; } }, metadata: _metadata }, _routeFilter_initializers, _routeFilter_extraInitializers);
            __esDecorate(this, null, _routeState_decorators, { kind: "accessor", name: "routeState", static: false, private: false, access: { has: obj => "routeState" in obj, get: obj => obj.routeState, set: (obj, value) => { obj.routeState = value; } }, metadata: _metadata }, _routeState_initializers, _routeState_extraInitializers);
            __esDecorate(this, null, _profilesTargetsState_decorators, { kind: "accessor", name: "profilesTargetsState", static: false, private: false, access: { has: obj => "profilesTargetsState" in obj, get: obj => obj.profilesTargetsState, set: (obj, value) => { obj.profilesTargetsState = value; } }, metadata: _metadata }, _profilesTargetsState_initializers, _profilesTargetsState_extraInitializers);
            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
            OpsViewRoutes = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        #routeFilter_accessor_storage = __runInitializers(this, _routeFilter_initializers, 'User Routes');
        get routeFilter() { return this.#routeFilter_accessor_storage; }
        set routeFilter(value) { this.#routeFilter_accessor_storage = value; }
        #routeState_accessor_storage = (__runInitializers(this, _routeFilter_extraInitializers), __runInitializers(this, _routeState_initializers, {
            mergedRoutes: [],
            warnings: [],
            httpRedirects: [],
            apiTokens: [],
            gatewayClients: [],
            isLoading: false,
            error: null,
            lastUpdated: 0,
        }));
        get routeState() { return this.#routeState_accessor_storage; }
        set routeState(value) { this.#routeState_accessor_storage = value; }
        #profilesTargetsState_accessor_storage = (__runInitializers(this, _routeState_extraInitializers), __runInitializers(this, _profilesTargetsState_initializers, {
            profiles: [],
            targets: [],
            isLoading: false,
            error: null,
            lastUpdated: 0,
        }));
        get profilesTargetsState() { return this.#profilesTargetsState_accessor_storage; }
        set profilesTargetsState(value) { this.#profilesTargetsState_accessor_storage = value; }
        constructor() {
            super();
            __runInitializers(this, _profilesTargetsState_extraInitializers);
            const sub = appstate.routeManagementStatePart
                .select((s) => s)
                .subscribe((routeState) => {
                this.routeState = routeState;
            });
            this.rxSubscriptions.push(sub);
            const ptSub = appstate.profilesTargetsStatePart
                .select((s) => s)
                .subscribe((ptState) => {
                this.profilesTargetsState = ptState;
            });
            this.rxSubscriptions.push(ptSub);
            // Re-fetch routes when user logs in (fixes race condition where
            // the view is created before authentication completes)
            const loginSub = appstate.loginStatePart
                .select((s) => s.isLoggedIn)
                .subscribe((isLoggedIn) => {
                if (isLoggedIn) {
                    appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
                    appstate.profilesTargetsStatePart.dispatchAction(appstate.fetchProfilesAndTargetsAction, null);
                }
            });
            this.rxSubscriptions.push(loginSub);
        }
        static styles = [
            cssManager.defaultStyles,
            viewHostCss,
            css `
      .routesContainer {
        display: flex;
        flex-direction: column;
        gap: 24px;
      }

      .warnings-bar {
        background: ${cssManager.bdTheme('rgba(255, 170, 0, 0.08)', 'rgba(255, 170, 0, 0.1)')};
        border: 1px solid ${cssManager.bdTheme('rgba(255, 170, 0, 0.25)', 'rgba(255, 170, 0, 0.3)')};
        border-radius: 8px;
        padding: 12px 16px;
      }

      .warning-item {
        display: flex;
        align-items: center;
        gap: 8px;
        padding: 4px 0;
        font-size: 13px;
        color: ${cssManager.bdTheme('#b45309', '#fa0')};
      }

      .warning-icon {
        flex-shrink: 0;
      }

      .empty-state {
        text-align: center;
        padding: 48px 24px;
        color: ${cssManager.bdTheme('#6b7280', '#666')};
      }

      .empty-state p {
        margin: 8px 0;
      }
    `,
        ];
        render() {
            const { mergedRoutes, warnings } = this.routeState;
            const disabledCount = mergedRoutes.filter((mr) => !mr.enabled).length;
            const configCount = mergedRoutes.filter((mr) => mr.origin !== 'api').length;
            const apiCount = mergedRoutes.filter((mr) => mr.origin === 'api').length;
            const statsTiles = [
                {
                    id: 'totalRoutes',
                    title: 'Total Routes',
                    type: 'number',
                    value: mergedRoutes.length,
                    icon: 'lucide:route',
                    description: 'All configured routes',
                    color: '#3b82f6',
                },
                {
                    id: 'configRoutes',
                    title: 'System Routes',
                    type: 'number',
                    value: configCount,
                    icon: 'lucide:settings',
                    description: 'From config, email, and DNS',
                    color: '#8b5cf6',
                },
                {
                    id: 'apiRoutes',
                    title: 'User Routes',
                    type: 'number',
                    value: apiCount,
                    icon: 'lucide:code',
                    description: 'Created via API',
                    color: '#0ea5e9',
                },
                {
                    id: 'disabled',
                    title: 'Disabled',
                    type: 'number',
                    value: disabledCount,
                    icon: 'lucide:pauseCircle',
                    description: 'Currently disabled routes',
                    color: disabledCount > 0 ? '#ef4444' : '#6b7280',
                },
            ];
            // Filter routes based on selected tab
            const isUserRoutes = this.routeFilter === 'User Routes';
            const filteredRoutes = mergedRoutes.filter((mr) => isUserRoutes ? mr.origin === 'api' : mr.origin !== 'api');
            // Map filtered routes to sz-route-list-view format
            const szRoutes = filteredRoutes.map((mr) => {
                const tags = [...(mr.route.tags || [])];
                tags.push(mr.origin);
                if (!mr.enabled)
                    tags.push('disabled');
                if (mr.route.vpnOnly)
                    tags.push('vpn-only');
                return {
                    ...mr.route,
                    enabled: mr.enabled,
                    tags,
                    id: mr.id || mr.route.name || undefined,
                    metadata: mr.metadata,
                };
            });
            return html `
      <dees-heading level="3">Route Management</dees-heading>

      <div class="routesContainer">
        <dees-statsgrid
          .tiles=${statsTiles}
          .gridActions=${[
                {
                    name: 'Add Route',
                    iconName: 'lucide:plus',
                    action: () => this.showCreateRouteDialog(),
                },
                {
                    name: 'Refresh',
                    iconName: 'lucide:refreshCw',
                    action: () => this.refreshData(),
                },
            ]}
        ></dees-statsgrid>

        <dees-input-multitoggle
          class="routeFilterToggle"
          .type=${'single'}
          .options=${['User Routes', 'System Routes']}
          .selectedOption=${this.routeFilter}
        ></dees-input-multitoggle>

        ${warnings.length > 0
                ? html `
              <div class="warnings-bar">
                ${warnings.map((w) => html `
                    <div class="warning-item">
                      <span class="warning-icon">&#9888;</span>
                      <span>${w.message}</span>
                    </div>
                  `)}
              </div>
            `
                : ''}

        ${szRoutes.length > 0
                ? html `
              <sz-route-list-view
                .routes=${szRoutes}
                .showActionsFilter=${isUserRoutes ? () => true : () => false}
                @route-click=${(e) => this.handleRouteClick(e)}
                @route-edit=${(e) => this.handleRouteEdit(e)}
                @route-delete=${(e) => this.handleRouteDelete(e)}
              ></sz-route-list-view>
            `
                : html `
              <div class="empty-state">
                <p>No ${isUserRoutes ? 'user' : 'system'} routes</p>
                <p>${isUserRoutes ? 'Add a route to get started.' : 'System routes are generated from config, email, and DNS settings.'}</p>
              </div>
            `}
      </div>
    `;
        }
        async handleRouteClick(e) {
            const clickedRoute = e.detail;
            if (!clickedRoute)
                return;
            const merged = this.findMergedRoute(clickedRoute);
            if (!merged)
                return;
            const { DeesModal } = await import('@design.estate/dees-catalog');
            const meta = merged.metadata;
            const isSystemManaged = this.isSystemManagedRoute(merged);
            const sourceBindingSummary = this.describeSourcePolicy(meta);
            await DeesModal.createAndShow({
                heading: `Route: ${merged.route.name}`,
                content: html `
        <div style="color: #ccc; padding: 8px 0;">
          <p>Origin: <strong style="color: #0af;">${merged.origin}</strong></p>
          <p>Status: <strong>${merged.enabled ? 'Enabled' : 'Disabled'}</strong></p>
          ${merged.route.vpnOnly ? html `<p>Access: <strong style="color: #22c55e;">VPN only</strong></p>` : ''}
          <p>ID: <code style="color: #888;">${merged.id}</code></p>
          ${isSystemManaged ? html `<p>This route is system-managed. Change its source config to modify it directly.</p>` : ''}
          ${sourceBindingSummary ? html `<p>Source Bindings: <strong style="color: #a78bfa;">${sourceBindingSummary}</strong></p>` : ''}
          ${meta?.networkTargetName ? html `<p>Network Target: <strong style="color: #a78bfa;">${meta.networkTargetName}</strong></p>` : ''}
        </div>
      `,
                menuOptions: [
                    {
                        name: merged.enabled ? 'Disable' : 'Enable',
                        iconName: merged.enabled ? 'lucide:pause' : 'lucide:play',
                        action: async (modalArg) => {
                            await appstate.routeManagementStatePart.dispatchAction(appstate.toggleRouteAction, { id: merged.id, enabled: !merged.enabled });
                            await modalArg.destroy();
                        },
                    },
                    ...(!isSystemManaged
                        ? [
                            {
                                name: 'Edit',
                                iconName: 'lucide:pencil',
                                action: async (modalArg) => {
                                    await modalArg.destroy();
                                    this.showEditRouteDialog(merged);
                                },
                            },
                            {
                                name: 'Delete',
                                iconName: 'lucide:trash-2',
                                action: async (modalArg) => {
                                    await appstate.routeManagementStatePart.dispatchAction(appstate.deleteRouteAction, merged.id);
                                    await modalArg.destroy();
                                },
                            },
                        ]
                        : []),
                    {
                        name: 'Close',
                        iconName: 'lucide:x',
                        action: async (modalArg) => await modalArg.destroy(),
                    },
                ],
            });
        }
        async handleRouteEdit(e) {
            const clickedRoute = e.detail;
            if (!clickedRoute)
                return;
            const merged = this.findMergedRoute(clickedRoute);
            if (!merged)
                return;
            if (this.isSystemManagedRoute(merged))
                return;
            this.showEditRouteDialog(merged);
        }
        async handleRouteDelete(e) {
            const clickedRoute = e.detail;
            if (!clickedRoute)
                return;
            const merged = this.findMergedRoute(clickedRoute);
            if (!merged)
                return;
            if (this.isSystemManagedRoute(merged))
                return;
            const { DeesModal } = await import('@design.estate/dees-catalog');
            await DeesModal.createAndShow({
                heading: `Delete Route: ${merged.route.name}`,
                content: html `
        <div style="color: #ccc; padding: 8px 0;">
          <p>Are you sure you want to delete this route? This action cannot be undone.</p>
        </div>
      `,
                menuOptions: [
                    {
                        name: 'Cancel',
                        iconName: 'lucide:x',
                        action: async (modalArg) => await modalArg.destroy(),
                    },
                    {
                        name: 'Delete',
                        iconName: 'lucide:trash-2',
                        action: async (modalArg) => {
                            await appstate.routeManagementStatePart.dispatchAction(appstate.deleteRouteAction, merged.id);
                            await modalArg.destroy();
                        },
                    },
                ],
            });
        }
        async showEditRouteDialog(merged) {
            const { DeesModal } = await import('@design.estate/dees-catalog');
            const profiles = this.profilesTargetsState.profiles;
            const targets = this.profilesTargetsState.targets;
            const targetOptions = [
                { key: '', option: '(none — inline target)' },
                ...targets.map((t) => ({
                    key: t.id,
                    option: `${t.name} (${Array.isArray(t.host) ? t.host.join(',') : t.host}:${t.port})`,
                })),
            ];
            const route = merged.route;
            const currentPorts = Array.isArray(route.match.ports)
                ? route.match.ports.map((p) => typeof p === 'number' ? String(p) : `${p.from}-${p.to}`).join(', ')
                : String(route.match.ports);
            const currentDomains = route.match.domains
                ? (Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains])
                : [];
            const firstTarget = route.action.targets?.[0];
            const currentPreserveMatchPort = firstTarget?.port === 'preserve';
            const currentTargetHost = firstTarget
                ? (Array.isArray(firstTarget.host) ? firstTarget.host[0] : firstTarget.host)
                : '';
            const currentTargetPort = typeof firstTarget?.port === 'number' ? String(firstTarget.port) : '';
            const currentVpnOnly = route.vpnOnly === true;
            const currentRemoteIngressEnabled = route.remoteIngress?.enabled === true;
            const currentEdgeFilter = route.remoteIngress?.edgeFilter || [];
            const sourceProfileOptions = getSourceProfileOptions(profiles);
            const pathClassOptions = getRoutePathClassOptions();
            const sourcePolicyPresets = getGiteaSourcePolicyPresets(profiles);
            const sourcePolicyInfoText = getSourcePolicyInfoText(profiles);
            // Compute current TLS state for pre-population
            const currentTls = route.action.tls;
            const currentTlsMode = currentTls?.mode || 'none';
            const currentTlsCert = currentTls
                ? (currentTls.certificate === 'auto' || !currentTls.certificate ? 'auto' : 'custom')
                : 'auto';
            const currentCustomKey = (typeof currentTls?.certificate === 'object') ? currentTls.certificate.key : '';
            const currentCustomCert = (typeof currentTls?.certificate === 'object') ? currentTls.certificate.cert : '';
            const needsCert = currentTlsMode === 'terminate' || currentTlsMode === 'terminate-and-reencrypt';
            const isCustom = currentTlsCert === 'custom';
            const editModal = await DeesModal.createAndShow({
                heading: `Edit Route: ${route.name}`,
                content: html `
        <dees-form>
          <dees-input-text .key=${'name'} .label=${'Route Name'} .value=${route.name || ''} .required=${true}></dees-input-text>
          <dees-input-text .key=${'ports'} .label=${'Ports'} .description=${'Comma-separated, e.g. 80, 443'} .value=${currentPorts} .required=${true}></dees-input-text>
          <dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'} .value=${currentDomains}></dees-input-list>
          <dees-input-text .key=${'priority'} .label=${'Priority'} .description=${'Higher values are matched first'} .value=${route.priority != null ? String(route.priority) : ''}></dees-input-text>
          <sz-input-route-source-policy
            .key=${'sourceBindings'}
            .label=${'Source Policy'}
            .infoText=${sourcePolicyInfoText}
            .sourceProfiles=${sourceProfileOptions}
            .pathClassOptions=${pathClassOptions}
            .presets=${sourcePolicyPresets}
            .value=${merged.metadata?.sourceBindings || []}
          ></sz-input-route-source-policy>
          <dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions} .selectedOption=${targetOptions.find((o) => o.key === (merged.metadata?.networkTargetRef || '')) || null}></dees-input-dropdown>
          <dees-input-text .key=${'targetHost'} .label=${'Target Host'} .description=${'Used when no network target is selected'} .value=${currentTargetHost}></dees-input-text>
          <dees-input-text .key=${'targetPort'} .label=${'Target Port'} .description=${'Used when no network target is selected'} .value=${currentTargetPort}></dees-input-text>
          <dees-input-checkbox .key=${'preserveMatchPort'} .label=${'Preserve incoming port'} .value=${currentPreserveMatchPort}></dees-input-checkbox>
          <dees-input-checkbox .key=${'vpnOnly'} .label=${'VPN only'} .description=${'Only VPN clients with matching target profiles can access this route'} .value=${currentVpnOnly}></dees-input-checkbox>
          <dees-input-checkbox .key=${'remoteIngressEnabled'} .label=${'Enable Remote Ingress'} .value=${currentRemoteIngressEnabled}></dees-input-checkbox>
          <div class="remoteIngressGroup" style="display: ${currentRemoteIngressEnabled ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
            <dees-input-list .key=${'remoteIngressEdgeFilter'} .label=${'Edge Filter'} .description=${'Optional edge IDs or tags. Leave empty to allow all edges.'} .placeholder=${'Add edge ID or tag...'} .value=${currentEdgeFilter}></dees-input-list>
          </div>
          <dees-input-dropdown .key=${'tlsMode'} .label=${'TLS Mode'} .options=${tlsModeOptions} .selectedOption=${tlsModeOptions.find((o) => o.key === currentTlsMode) || tlsModeOptions[0]}></dees-input-dropdown>
          <div class="tlsCertificateGroup" style="display: ${needsCert ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
            <dees-input-dropdown .key=${'tlsCertificate'} .label=${'Certificate'} .options=${tlsCertOptions} .selectedOption=${tlsCertOptions.find((o) => o.key === currentTlsCert) || tlsCertOptions[0]}></dees-input-dropdown>
            <div class="tlsCustomCertGroup" style="display: ${needsCert && isCustom ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
              <dees-input-text .key=${'tlsCertKey'} .label=${'Private Key'} .description=${'PEM-encoded private key'} .value=${currentCustomKey}></dees-input-text>
              <dees-input-text .key=${'tlsCertCert'} .label=${'Certificate'} .description=${'PEM-encoded certificate'} .value=${currentCustomCert}></dees-input-text>
            </div>
          </div>
        </dees-form>
      `,
                menuOptions: [
                    {
                        name: 'Cancel',
                        iconName: 'lucide:x',
                        action: async (modalArg) => await modalArg.destroy(),
                    },
                    {
                        name: 'Save',
                        iconName: 'lucide:check',
                        action: async (modalArg) => {
                            const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
                            if (!form)
                                return;
                            const formData = await form.collectFormData();
                            if (!formData.name || !formData.ports)
                                return;
                            if (!validateSourcePolicyInput(form))
                                return;
                            const ports = formData.ports.split(',').map((p) => parseInt(p.trim(), 10)).filter((p) => !isNaN(p));
                            const domains = Array.isArray(formData.domains)
                                ? formData.domains.filter(Boolean)
                                : [];
                            const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
                            const sourceBindings = getSourceBindingsFromFormData(formData);
                            const targetKey = getDropdownKey(formData.networkTargetRef);
                            const preserveMatchPort = !targetKey && Boolean(formData.preserveMatchPort);
                            const targetPort = preserveMatchPort
                                ? 'preserve'
                                : parseTargetPort(formData.targetPort)
                                    ?? (targetKey ? parseTargetPort(currentTargetPort) ?? ports[0] : undefined);
                            if (targetPort === undefined) {
                                alert('Target Port must be a valid port number when no network target is selected.');
                                return;
                            }
                            const remoteIngressEnabled = Boolean(formData.remoteIngressEnabled);
                            const remoteIngressEdgeFilter = Array.isArray(formData.remoteIngressEdgeFilter)
                                ? formData.remoteIngressEdgeFilter.filter(Boolean)
                                : [];
                            const vpnOnly = Boolean(formData.vpnOnly);
                            const updatedRoute = {
                                name: formData.name,
                                match: {
                                    ports,
                                    ...(domains.length > 0 ? { domains } : {}),
                                },
                                action: {
                                    type: 'forward',
                                    targets: [
                                        {
                                            host: formData.targetHost || currentTargetHost || 'localhost',
                                            port: targetPort,
                                        },
                                    ],
                                },
                                vpnOnly: vpnOnly ? true : null,
                                remoteIngress: remoteIngressEnabled
                                    ? {
                                        enabled: true,
                                        ...(remoteIngressEdgeFilter.length > 0 ? { edgeFilter: remoteIngressEdgeFilter } : {}),
                                    }
                                    : null,
                                ...(priority != null && !isNaN(priority) ? { priority } : {}),
                            };
                            // Build TLS config from form
                            const tlsModeValue = formData.tlsMode;
                            const tlsModeKey = typeof tlsModeValue === 'string' ? tlsModeValue : tlsModeValue?.key;
                            if (tlsModeKey && tlsModeKey !== 'none') {
                                const tls = { mode: tlsModeKey };
                                if (tlsModeKey !== 'passthrough') {
                                    const tlsCertValue = formData.tlsCertificate;
                                    const tlsCertKey = typeof tlsCertValue === 'string' ? tlsCertValue : tlsCertValue?.key;
                                    if (tlsCertKey === 'custom' && formData.tlsCertKey && formData.tlsCertCert) {
                                        tls.certificate = { key: formData.tlsCertKey, cert: formData.tlsCertCert };
                                    }
                                    else {
                                        tls.certificate = 'auto';
                                    }
                                }
                                updatedRoute.action.tls = tls;
                            }
                            else {
                                updatedRoute.action.tls = null; // explicit removal
                            }
                            const metadata = {};
                            if (sourceBindings.length > 0) {
                                metadata.sourceBindings = sourceBindings;
                            }
                            else if (merged.metadata?.sourceBindings) {
                                metadata.sourceBindings = [];
                            }
                            if (targetKey) {
                                metadata.networkTargetRef = targetKey;
                            }
                            else if (merged.metadata?.networkTargetRef) {
                                metadata.networkTargetRef = '';
                                metadata.networkTargetName = '';
                            }
                            await appstate.routeManagementStatePart.dispatchAction(appstate.updateRouteAction, {
                                id: merged.id,
                                route: updatedRoute,
                                metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
                            });
                            await modalArg.destroy();
                        },
                    },
                ],
            });
            // Setup conditional TLS field visibility after modal renders
            const editForm = editModal?.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
            if (editForm) {
                await editForm.updateComplete;
                setupTlsVisibility(editForm);
                setupTargetInputState(editForm);
            }
        }
        async showCreateRouteDialog() {
            const { DeesModal } = await import('@design.estate/dees-catalog');
            const profiles = this.profilesTargetsState.profiles;
            const targets = this.profilesTargetsState.targets;
            // Build dropdown options for targets and source policy metadata
            const sourceProfileOptions = getSourceProfileOptions(profiles);
            const pathClassOptions = getRoutePathClassOptions();
            const sourcePolicyPresets = getGiteaSourcePolicyPresets(profiles);
            const sourcePolicyInfoText = getSourcePolicyInfoText(profiles);
            const targetOptions = [
                { key: '', option: '(none — inline target)' },
                ...targets.map((t) => ({
                    key: t.id,
                    option: `${t.name} (${Array.isArray(t.host) ? t.host.join(',') : t.host}:${t.port})`,
                })),
            ];
            const createModal = await DeesModal.createAndShow({
                heading: 'Add Route',
                content: html `
        <dees-form>
          <dees-input-text .key=${'name'} .label=${'Route Name'} .required=${true}></dees-input-text>
          <dees-input-text .key=${'ports'} .label=${'Ports'} .description=${'Comma-separated, e.g. 80, 443'} .required=${true}></dees-input-text>
          <dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'}></dees-input-list>
          <dees-input-text .key=${'priority'} .label=${'Priority'} .description=${'Higher values are matched first'}></dees-input-text>
          <sz-input-route-source-policy
            .key=${'sourceBindings'}
            .label=${'Source Policy'}
            .infoText=${sourcePolicyInfoText}
            .sourceProfiles=${sourceProfileOptions}
            .pathClassOptions=${pathClassOptions}
            .presets=${sourcePolicyPresets}
            .value=${[]}
          ></sz-input-route-source-policy>
          <dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions}></dees-input-dropdown>
          <dees-input-text .key=${'targetHost'} .label=${'Target Host'} .description=${'Used when no network target is selected'} .value=${'localhost'}></dees-input-text>
          <dees-input-text .key=${'targetPort'} .label=${'Target Port'} .description=${'Used when no network target is selected'}></dees-input-text>
          <dees-input-checkbox .key=${'preserveMatchPort'} .label=${'Preserve incoming port'} .value=${false}></dees-input-checkbox>
          <dees-input-checkbox .key=${'vpnOnly'} .label=${'VPN only'} .description=${'Only VPN clients with matching target profiles can access this route'} .value=${false}></dees-input-checkbox>
          <dees-input-checkbox .key=${'remoteIngressEnabled'} .label=${'Enable Remote Ingress'} .value=${false}></dees-input-checkbox>
          <div class="remoteIngressGroup" style="display: none; flex-direction: column; gap: 16px;">
            <dees-input-list .key=${'remoteIngressEdgeFilter'} .label=${'Edge Filter'} .description=${'Optional edge IDs or tags. Leave empty to allow all edges.'} .placeholder=${'Add edge ID or tag...'}></dees-input-list>
          </div>
          <dees-input-dropdown .key=${'tlsMode'} .label=${'TLS Mode'} .options=${tlsModeOptions} .selectedOption=${tlsModeOptions[0]}></dees-input-dropdown>
          <div class="tlsCertificateGroup" style="display: none; flex-direction: column; gap: 16px;">
            <dees-input-dropdown .key=${'tlsCertificate'} .label=${'Certificate'} .options=${tlsCertOptions} .selectedOption=${tlsCertOptions[0]}></dees-input-dropdown>
            <div class="tlsCustomCertGroup" style="display: none; flex-direction: column; gap: 16px;">
              <dees-input-text .key=${'tlsCertKey'} .label=${'Private Key'} .description=${'PEM-encoded private key'}></dees-input-text>
              <dees-input-text .key=${'tlsCertCert'} .label=${'Certificate'} .description=${'PEM-encoded certificate'}></dees-input-text>
            </div>
          </div>
        </dees-form>
      `,
                menuOptions: [
                    {
                        name: 'Cancel',
                        iconName: 'lucide:x',
                        action: async (modalArg) => await modalArg.destroy(),
                    },
                    {
                        name: 'Create',
                        iconName: 'lucide:plus',
                        action: async (modalArg) => {
                            const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
                            if (!form)
                                return;
                            const formData = await form.collectFormData();
                            if (!formData.name || !formData.ports)
                                return;
                            if (!validateSourcePolicyInput(form))
                                return;
                            const ports = formData.ports.split(',').map((p) => parseInt(p.trim(), 10)).filter((p) => !isNaN(p));
                            const domains = Array.isArray(formData.domains)
                                ? formData.domains.filter(Boolean)
                                : [];
                            const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
                            const sourceBindings = getSourceBindingsFromFormData(formData);
                            const targetKey = getDropdownKey(formData.networkTargetRef);
                            const preserveMatchPort = !targetKey && Boolean(formData.preserveMatchPort);
                            const targetPort = preserveMatchPort
                                ? 'preserve'
                                : parseTargetPort(formData.targetPort)
                                    ?? (targetKey ? ports[0] : undefined);
                            if (targetPort === undefined) {
                                alert('Target Port must be a valid port number when no network target is selected.');
                                return;
                            }
                            const remoteIngressEnabled = Boolean(formData.remoteIngressEnabled);
                            const remoteIngressEdgeFilter = Array.isArray(formData.remoteIngressEdgeFilter)
                                ? formData.remoteIngressEdgeFilter.filter(Boolean)
                                : [];
                            const vpnOnly = Boolean(formData.vpnOnly);
                            const route = {
                                name: formData.name,
                                match: {
                                    ports,
                                    ...(domains.length > 0 ? { domains } : {}),
                                },
                                action: {
                                    type: 'forward',
                                    targets: [
                                        {
                                            host: formData.targetHost || 'localhost',
                                            port: targetPort,
                                        },
                                    ],
                                },
                                ...(vpnOnly ? { vpnOnly: true } : {}),
                                ...(remoteIngressEnabled
                                    ? {
                                        remoteIngress: {
                                            enabled: true,
                                            ...(remoteIngressEdgeFilter.length > 0 ? { edgeFilter: remoteIngressEdgeFilter } : {}),
                                        },
                                    }
                                    : {}),
                                ...(priority != null && !isNaN(priority) ? { priority } : {}),
                            };
                            // Build TLS config from form
                            const tlsModeValue = formData.tlsMode;
                            const tlsModeKey = typeof tlsModeValue === 'string' ? tlsModeValue : tlsModeValue?.key;
                            if (tlsModeKey && tlsModeKey !== 'none') {
                                const tls = { mode: tlsModeKey };
                                if (tlsModeKey !== 'passthrough') {
                                    const tlsCertValue = formData.tlsCertificate;
                                    const tlsCertKey = typeof tlsCertValue === 'string' ? tlsCertValue : tlsCertValue?.key;
                                    if (tlsCertKey === 'custom' && formData.tlsCertKey && formData.tlsCertCert) {
                                        tls.certificate = { key: formData.tlsCertKey, cert: formData.tlsCertCert };
                                    }
                                    else {
                                        tls.certificate = 'auto';
                                    }
                                }
                                route.action.tls = tls;
                            }
                            // Build metadata if profile/target selected
                            const metadata = {};
                            if (sourceBindings.length > 0) {
                                metadata.sourceBindings = sourceBindings;
                            }
                            if (targetKey) {
                                metadata.networkTargetRef = targetKey;
                            }
                            await appstate.routeManagementStatePart.dispatchAction(appstate.createRouteAction, {
                                route,
                                metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
                            });
                            await modalArg.destroy();
                        },
                    },
                ],
            });
            // Setup conditional TLS field visibility after modal renders
            const createForm = createModal?.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
            if (createForm) {
                await createForm.updateComplete;
                setupTlsVisibility(createForm);
                setupTargetInputState(createForm);
            }
        }
        refreshData() {
            appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
        }
        getSourceBindingRefs(metadata) {
            const bindingRefs = metadata?.sourceBindings
                ?.map((binding) => binding.sourceProfileRef)
                .filter(Boolean) || [];
            return bindingRefs;
        }
        describeSourcePolicy(metadata) {
            const refs = this.getSourceBindingRefs(metadata);
            if (refs.length === 0) {
                return '';
            }
            return refs.map((ref) => {
                const binding = metadata?.sourceBindings?.find((item) => item.sourceProfileRef === ref);
                const profile = this.profilesTargetsState.profiles.find((item) => item.id === ref);
                return binding?.sourceProfileName || profile?.name || ref.slice(0, 8);
            }).join(' → ');
        }
        findMergedRoute(clickedRoute) {
            if (clickedRoute.id) {
                const routeById = this.routeState.mergedRoutes.find((mr) => mr.id === clickedRoute.id);
                if (routeById)
                    return routeById;
            }
            if (clickedRoute.name) {
                return this.routeState.mergedRoutes.find((mr) => mr.route.name === clickedRoute.name);
            }
            return undefined;
        }
        isSystemManagedRoute(merged) {
            return merged.origin !== 'api';
        }
        async firstUpdated() {
            await appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
            const toggle = this.shadowRoot.querySelector('.routeFilterToggle');
            if (toggle) {
                const sub = toggle.changeSubject.subscribe(() => {
                    this.routeFilter = toggle.selectedOption;
                });
                this.rxSubscriptions.push(sub);
            }
        }
        static {
            __runInitializers(_classThis, _classExtraInitializers);
        }
    };
    return OpsViewRoutes = _classThis;
})();
export { OpsViewRoutes };
//# sourceMappingURL=data:application/json;base64, 