import { DeesElement, customElement, html, css, cssManager, state, type TemplateResult } from '../plugins.js';
import { deesCatalog } from '../plugins.js';
import { appState, type IAppState } from '../state/appstate.js';
import { viewHostCss } from './shared/index.js';
import type { IIncomingNumberConfig, ISipRoute } from '../../ts/config.ts';

const { DeesModal, DeesToast } = deesCatalog;

const CUSTOM_REGEX_KEY = '__custom_regex__';
const CUSTOM_COUNTRY_CODE_KEY = '__custom_country_code__';

function clone<T>(value: T): T {
  return JSON.parse(JSON.stringify(value));
}

function createRoute(): ISipRoute {
  return {
    id: `route-${Date.now()}`,
    name: '',
    priority: 0,
    enabled: true,
    match: { direction: 'outbound' },
    action: {},
  };
}

function createIncomingNumber(): IIncomingNumberConfig {
  return {
    id: `incoming-${Date.now()}`,
    label: '',
    mode: 'single',
    countryCode: '+49',
    areaCode: '',
    localNumber: '',
  };
}

function normalizeCountryCode(value?: string): string {
  const trimmed = (value || '').trim();
  if (!trimmed) return '';
  const digits = trimmed.replace(/\D/g, '');
  return digits ? `+${digits}` : '';
}

function normalizeNumberPart(value?: string): string {
  return (value || '').replace(/\D/g, '');
}

function escapeRegex(value: string): string {
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function anyDigits(length: number): string {
  if (length <= 0) return '';
  if (length === 1) return '\\d';
  return `\\d{${length}}`;
}

function digitClass(start: number, end: number): string {
  if (start === end) return String(start);
  if (end === start + 1) return `[${start}${end}]`;
  return `[${start}-${end}]`;
}

function unique(values: string[]): string[] {
  return Array.from(new Set(values));
}

function buildRangeAlternatives(min: string, max: string): string[] {
  if (min.length !== max.length || !/^\d*$/.test(min) || !/^\d*$/.test(max)) {
    return [];
  }
  if (min.length === 0) {
    return [''];
  }
  if (min === max) {
    return [min];
  }
  if (/^0+$/.test(min) && /^9+$/.test(max)) {
    return [anyDigits(min.length)];
  }

  let index = 0;
  while (index < min.length && min[index] === max[index]) {
    index += 1;
  }

  const prefix = min.slice(0, index);
  const lowDigit = Number(min[index]);
  const highDigit = Number(max[index]);
  const restLength = min.length - index - 1;
  const values: string[] = [];

  values.push(
    ...buildRangeAlternatives(min.slice(index + 1), '9'.repeat(restLength)).map(
      (suffix) => `${prefix}${lowDigit}${suffix}`,
    ),
  );

  if (highDigit - lowDigit > 1) {
    values.push(`${prefix}${digitClass(lowDigit + 1, highDigit - 1)}${anyDigits(restLength)}`);
  }

  values.push(
    ...buildRangeAlternatives('0'.repeat(restLength), max.slice(index + 1)).map(
      (suffix) => `${prefix}${highDigit}${suffix}`,
    ),
  );

  return unique(values);
}

function buildRangePattern(min: string, max: string): string {
  const alternatives = buildRangeAlternatives(min, max).filter(Boolean);
  if (!alternatives.length) return '';
  if (alternatives.length === 1) return alternatives[0];
  return `(?:${alternatives.join('|')})`;
}

function validateLocalRange(start?: string, end?: string): string | null {
  const normalizedStart = normalizeNumberPart(start);
  const normalizedEnd = normalizeNumberPart(end);
  if (!normalizedStart || !normalizedEnd) {
    return 'Range start and end are required';
  }
  if (normalizedStart.length !== normalizedEnd.length) {
    return 'Range start and end must have the same length';
  }
  if (normalizedStart > normalizedEnd) {
    return 'Range start must be less than or equal to range end';
  }
  return null;
}

function getIncomingNumberPattern(entry: IIncomingNumberConfig): string {
  if (entry.mode === 'regex') {
    return (entry.pattern || '').trim();
  }

  const countryCode = normalizeCountryCode(entry.countryCode);
  const areaCode = normalizeNumberPart(entry.areaCode);
  const localNumber = normalizeNumberPart(entry.localNumber);
  if (!countryCode || !areaCode || !localNumber) {
    return '';
  }

  let localPattern = escapeRegex(localNumber);
  if (entry.mode === 'range') {
    const rangeEnd = normalizeNumberPart(entry.rangeEnd);
    if (!rangeEnd || validateLocalRange(localNumber, rangeEnd)) {
      return '';
    }
    localPattern = buildRangePattern(localNumber, rangeEnd);
  }

  const countryDigits = countryCode.slice(1);
  const nationalArea = areaCode.startsWith('0') ? areaCode : `0${areaCode}`;
  const internationalArea = areaCode.replace(/^0+/, '') || areaCode;
  return `/^(?:\\+${countryDigits}${internationalArea}|${nationalArea})${localPattern}$/`;
}

function describeIncomingNumber(entry: IIncomingNumberConfig): string {
  if (entry.mode === 'regex') {
    return (entry.pattern || '').trim() || '(regex missing)';
  }

  const countryCode = normalizeCountryCode(entry.countryCode) || '+?';
  const areaCode = normalizeNumberPart(entry.areaCode) || '?';
  const localNumber = normalizeNumberPart(entry.localNumber) || '?';
  if (entry.mode === 'range') {
    const rangeEnd = normalizeNumberPart(entry.rangeEnd) || '?';
    return `${countryCode} / ${areaCode} / ${localNumber}-${rangeEnd}`;
  }
  return `${countryCode} / ${areaCode} / ${localNumber}`;
}

function describeRouteAction(route: ISipRoute): string {
  const action = route.action;
  if (route.match.direction === 'outbound') {
    const parts: string[] = [];
    if (action.provider) parts.push(`-> ${action.provider}`);
    if (action.failoverProviders?.length) parts.push(`(failover: ${action.failoverProviders.join(', ')})`);
    if (action.stripPrefix) parts.push(`strip: ${action.stripPrefix}`);
    if (action.prependPrefix) parts.push(`prepend: ${action.prependPrefix}`);
    return parts.join(' ');
  }

  const parts: string[] = [];
  if (action.ivrMenuId) {
    parts.push(`ivr: ${action.ivrMenuId}`);
  } else if (action.targets?.length) {
    parts.push(`ring: ${action.targets.join(', ')}`);
  } else {
    parts.push('ring: all devices');
  }
  if (action.ringBrowsers) parts.push('+ browsers');
  if (action.voicemailBox) parts.push(`vm: ${action.voicemailBox}`);
  if (action.noAnswerTimeout) parts.push(`timeout: ${action.noAnswerTimeout}s`);
  return parts.join(' ');
}

@customElement('sipproxy-view-routes')
export class SipproxyViewRoutes extends DeesElement {
  @state() accessor appData: IAppState = appState.getState();
  @state() accessor config: any = null;

  public static styles = [
    cssManager.defaultStyles,
    viewHostCss,
    css`
      .view-section { margin-bottom: 24px; }
    `,
  ];

  async connectedCallback(): Promise<void> {
    await super.connectedCallback();
    appState.subscribe((state) => { this.appData = state; });
    await this.loadConfig();
  }

  private async loadConfig(): Promise<void> {
    try {
      this.config = await appState.apiGetConfig();
    } catch {
      // Show empty state.
    }
  }

  private getRoutes(): ISipRoute[] {
    return this.config?.routing?.routes || [];
  }

  private getIncomingNumbers(): IIncomingNumberConfig[] {
    return this.config?.incomingNumbers || [];
  }

  private getCountryCodeOptions(extraCode?: string): Array<{ option: string; key: string }> {
    const codes = new Set<string>(['+49']);
    for (const entry of this.getIncomingNumbers()) {
      const countryCode = normalizeCountryCode(entry.countryCode);
      if (countryCode) codes.add(countryCode);
    }
    const normalizedExtra = normalizeCountryCode(extraCode);
    if (normalizedExtra) codes.add(normalizedExtra);

    return [
      ...Array.from(codes).sort((a, b) => a.localeCompare(b)).map((code) => ({ option: code, key: code })),
      { option: 'Custom', key: CUSTOM_COUNTRY_CODE_KEY },
    ];
  }

  private getProviderLabel(providerId?: string): string {
    if (!providerId) return '(any)';
    const provider = (this.config?.providers || []).find((item: any) => item.id === providerId);
    return provider?.displayName || providerId;
  }

  private findIncomingNumberForRoute(route: ISipRoute): IIncomingNumberConfig | undefined {
    if (route.match.direction !== 'inbound' || !route.match.numberPattern) {
      return undefined;
    }

    return this.getIncomingNumbers().find((entry) => {
      if (getIncomingNumberPattern(entry) !== route.match.numberPattern) {
        return false;
      }
      if (entry.providerId && route.match.sourceProvider && entry.providerId !== route.match.sourceProvider) {
        return false;
      }
      return true;
    });
  }

  private countRoutesUsingIncomingNumber(entry: IIncomingNumberConfig): number {
    const pattern = getIncomingNumberPattern(entry);
    return this.getRoutes().filter((route) => {
      if (route.match.direction !== 'inbound' || route.match.numberPattern !== pattern) {
        return false;
      }
      if (entry.providerId && route.match.sourceProvider && entry.providerId !== route.match.sourceProvider) {
        return false;
      }
      return true;
    }).length;
  }

  private async saveRoutes(routes: ISipRoute[]): Promise<boolean> {
    const result = await appState.apiSaveConfig({ routing: { routes } });
    if (!result.ok) return false;
    await this.loadConfig();
    return true;
  }

  private async saveIncomingNumbers(incomingNumbers: IIncomingNumberConfig[]): Promise<boolean> {
    const result = await appState.apiSaveConfig({ incomingNumbers });
    if (!result.ok) return false;
    await this.loadConfig();
    return true;
  }

  public render(): TemplateResult {
    const routes = this.getRoutes();
    const incomingNumbers = [...this.getIncomingNumbers()].sort((a, b) => a.label.localeCompare(b.label));
    const sortedRoutes = [...routes].sort((a, b) => b.priority - a.priority);

    const tiles: any[] = [
      {
        id: 'incoming-numbers',
        title: 'Incoming Numbers',
        value: incomingNumbers.length,
        type: 'number',
        icon: 'lucide:phoneIncoming',
        description: 'Managed DIDs and regexes',
      },
      {
        id: 'total-routes',
        title: 'Total Routes',
        value: routes.length,
        type: 'number',
        icon: 'lucide:route',
        description: `${routes.filter((route) => route.enabled).length} active`,
      },
      {
        id: 'inbound-routes',
        title: 'Inbound Routes',
        value: routes.filter((route) => route.match.direction === 'inbound').length,
        type: 'number',
        icon: 'lucide:phoneCall',
        description: 'Incoming call routing rules',
      },
      {
        id: 'outbound-routes',
        title: 'Outbound Routes',
        value: routes.filter((route) => route.match.direction === 'outbound').length,
        type: 'number',
        icon: 'lucide:phoneOutgoing',
        description: 'Outgoing call routing rules',
      },
    ];

    return html`
      <dees-heading level="3">Route Management</dees-heading>

      <div class="view-section">
        <dees-statsgrid .tiles=${tiles} .minTileWidth=${220} .gap=${16}></dees-statsgrid>
      </div>

      <div class="view-section">
        <dees-table
          heading1="Incoming Numbers"
          heading2="${incomingNumbers.length} managed"
          dataName="incomingNumbers"
          .data=${incomingNumbers}
          .rowKey=${'id'}
          .columns=${this.getIncomingNumberColumns()}
          .dataActions=${this.getIncomingNumberActions()}
        ></dees-table>
      </div>

      <div class="view-section">
        <dees-table
          heading1="Call Routes"
          heading2="${routes.length} configured"
          dataName="routes"
          .data=${sortedRoutes}
          .rowKey=${'id'}
          .columns=${this.getRouteColumns()}
          .dataActions=${this.getRouteActions()}
        ></dees-table>
      </div>
    `;
  }

  private getIncomingNumberColumns() {
    return [
      {
        key: 'label',
        header: 'Label',
        sortable: true,
      },
      {
        key: 'providerId',
        header: 'Provider',
        renderer: (_value: string | undefined, row: IIncomingNumberConfig) =>
          html`<span>${this.getProviderLabel(row.providerId)}</span>`,
      },
      {
        key: 'mode',
        header: 'Type',
        renderer: (value: string) => {
          const label = value === 'regex' ? 'regex' : value === 'range' ? 'range' : 'number';
          const color = value === 'regex' ? '#f59e0b' : value === 'range' ? '#60a5fa' : '#4ade80';
          const bg = value === 'regex' ? '#422006' : value === 'range' ? '#1e3a5f' : '#1a3c2a';
          return html`<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:.7rem;font-weight:600;text-transform:uppercase;background:${bg};color:${color}">${label}</span>`;
        },
      },
      {
        key: 'match',
        header: 'Definition',
        renderer: (_value: unknown, row: IIncomingNumberConfig) =>
          html`<span style="font-family:'JetBrains Mono',monospace;font-size:.82rem">${describeIncomingNumber(row)}</span>`,
      },
      {
        key: 'pattern',
        header: 'Generated Pattern',
        renderer: (_value: unknown, row: IIncomingNumberConfig) =>
          html`<span style="font-family:'JetBrains Mono',monospace;font-size:.78rem;color:#94a3b8">${getIncomingNumberPattern(row) || '(incomplete)'}</span>`,
      },
      {
        key: 'usage',
        header: 'Used By',
        renderer: (_value: unknown, row: IIncomingNumberConfig) => {
          const count = this.countRoutesUsingIncomingNumber(row);
          return html`<span style="font-weight:600;color:${count > 0 ? '#e2e8f0' : '#64748b'}">${count} route${count === 1 ? '' : 's'}</span>`;
        },
      },
    ];
  }

  private getIncomingNumberActions() {
    return [
      {
        name: 'Add Number',
        iconName: 'lucide:plus' as any,
        type: ['header'] as any,
        actionFunc: async () => {
          await this.openIncomingNumberEditor(null);
        },
      },
      {
        name: 'Edit',
        iconName: 'lucide:pencil' as any,
        type: ['inRow'] as any,
        actionFunc: async ({ item }: { item: IIncomingNumberConfig }) => {
          await this.openIncomingNumberEditor(item);
        },
      },
      {
        name: 'Delete',
        iconName: 'lucide:trash2' as any,
        type: ['inRow'] as any,
        actionFunc: async ({ item }: { item: IIncomingNumberConfig }) => {
          const incomingNumbers = this.getIncomingNumbers().filter((entry) => entry.id !== item.id);
          if (await this.saveIncomingNumbers(incomingNumbers)) {
            DeesToast.success('Incoming number deleted');
          } else {
            DeesToast.error('Failed to delete incoming number');
          }
        },
      },
    ];
  }

  private getRouteColumns() {
    return [
      {
        key: 'priority',
        header: 'Priority',
        sortable: true,
        renderer: (value: number) => html`<span style="font-weight:600;color:#94a3b8">${value}</span>`,
      },
      {
        key: 'name',
        header: 'Name',
        sortable: true,
      },
      {
        key: 'match',
        header: 'Direction',
        renderer: (_value: unknown, row: ISipRoute) => {
          const direction = row.match.direction;
          const color = direction === 'inbound' ? '#60a5fa' : '#4ade80';
          const bg = direction === 'inbound' ? '#1e3a5f' : '#1a3c2a';
          return html`<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:.7rem;font-weight:600;text-transform:uppercase;background:${bg};color:${color}">${direction}</span>`;
        },
      },
      {
        key: 'match',
        header: 'Match',
        renderer: (_value: unknown, row: ISipRoute) => {
          const parts: string[] = [];
          if (row.match.sourceProvider) parts.push(`provider: ${row.match.sourceProvider}`);
          const incomingNumber = this.findIncomingNumberForRoute(row);
          if (incomingNumber) {
            parts.push(`did: ${incomingNumber.label}`);
          } else if (row.match.numberPattern) {
            parts.push(`number: ${row.match.numberPattern}`);
          }
          if (row.match.callerPattern) parts.push(`caller: ${row.match.callerPattern}`);
          if (!parts.length) return html`<span style="color:#64748b;font-style:italic">catch-all</span>`;
          return html`<span style="font-family:'JetBrains Mono',monospace;font-size:.82rem">${parts.join(', ')}</span>`;
        },
      },
      {
        key: 'action',
        header: 'Action',
        renderer: (_value: unknown, row: ISipRoute) =>
          html`<span style="font-family:'JetBrains Mono',monospace;font-size:.82rem">${describeRouteAction(row)}</span>`,
      },
      {
        key: 'enabled',
        header: 'Status',
        renderer: (value: boolean) => {
          const color = value ? '#4ade80' : '#71717a';
          const bg = value ? '#1a3c2a' : '#3f3f46';
          return html`<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:.7rem;font-weight:600;text-transform:uppercase;background:${bg};color:${color}">${value ? 'Active' : 'Disabled'}</span>`;
        },
      },
    ];
  }

  private getRouteActions() {
    return [
      {
        name: 'Add Route',
        iconName: 'lucide:plus' as any,
        type: ['header'] as any,
        actionFunc: async () => {
          await this.openRouteEditor(null);
        },
      },
      {
        name: 'Edit',
        iconName: 'lucide:pencil' as any,
        type: ['inRow'] as any,
        actionFunc: async ({ item }: { item: ISipRoute }) => {
          await this.openRouteEditor(item);
        },
      },
      {
        name: 'Toggle',
        iconName: 'lucide:toggleLeft' as any,
        type: ['inRow'] as any,
        actionFunc: async ({ item }: { item: ISipRoute }) => {
          const routes = this.getRoutes().map((route) =>
            route.id === item.id ? { ...route, enabled: !route.enabled } : route,
          );
          if (await this.saveRoutes(routes)) {
            DeesToast.success(item.enabled ? 'Route disabled' : 'Route enabled');
          } else {
            DeesToast.error('Failed to update route');
          }
        },
      },
      {
        name: 'Delete',
        iconName: 'lucide:trash2' as any,
        type: ['inRow'] as any,
        actionFunc: async ({ item }: { item: ISipRoute }) => {
          const routes = this.getRoutes().filter((route) => route.id !== item.id);
          if (await this.saveRoutes(routes)) {
            DeesToast.success('Route deleted');
          } else {
            DeesToast.error('Failed to delete route');
          }
        },
      },
    ];
  }

  private async openIncomingNumberEditor(existing: IIncomingNumberConfig | null): Promise<void> {
    const providers = this.config?.providers || [];
    const formData = existing ? clone(existing) : createIncomingNumber();
    const countryCodeOptions = this.getCountryCodeOptions(formData.countryCode);
    let definitionType: 'number' | 'regex' = formData.mode === 'regex' ? 'regex' : 'number';
    let selectedCountryCode = countryCodeOptions.some((option) => option.key === normalizeCountryCode(formData.countryCode))
      ? normalizeCountryCode(formData.countryCode)
      : CUSTOM_COUNTRY_CODE_KEY;
    let customCountryCode = selectedCountryCode === CUSTOM_COUNTRY_CODE_KEY
      ? normalizeCountryCode(formData.countryCode)
      : '';
    let modalRef: any;

    const applySelectedCountryCode = () => {
      formData.countryCode = selectedCountryCode === CUSTOM_COUNTRY_CODE_KEY
        ? normalizeCountryCode(customCountryCode)
        : selectedCountryCode;
    };

    const rerender = () => {
      if (!modalRef) return;
      modalRef.content = renderContent();
      modalRef.requestUpdate();
    };

    const renderContent = () => {
      applySelectedCountryCode();
      const generatedPattern = getIncomingNumberPattern(formData);

      return html`
        <div style="display:flex;flex-direction:column;gap:12px;padding:4px 0;">
          <dees-input-text
            .key=${'label'} .label=${'Label'} .value=${formData.label}
            @input=${(e: Event) => { formData.label = (e.target as any).value; }}
          ></dees-input-text>

          <dees-input-dropdown
            .key=${'providerId'} .label=${'Provider'}
            .selectedOption=${formData.providerId
              ? { option: this.getProviderLabel(formData.providerId), key: formData.providerId }
              : { option: '(any)', key: '' }}
            .options=${[
              { option: '(any)', key: '' },
              ...providers.map((provider: any) => ({ option: provider.displayName || provider.id, key: provider.id })),
            ]}
            @selectedOption=${(e: CustomEvent) => { formData.providerId = e.detail.key || undefined; }}
          ></dees-input-dropdown>

          <dees-input-dropdown
            .key=${'definitionType'} .label=${'Definition Type'}
            .selectedOption=${definitionType === 'regex'
              ? { option: 'Custom regex', key: 'regex' }
              : { option: 'Phone number', key: 'number' }}
            .options=${[
              { option: 'Custom regex', key: 'regex' },
              { option: 'Phone number', key: 'number' },
            ]}
            @selectedOption=${(e: CustomEvent) => {
              definitionType = e.detail.key;
              formData.mode = definitionType === 'regex' ? 'regex' : formData.mode === 'range' ? 'range' : 'single';
              if (definitionType === 'number' && !formData.countryCode) {
                formData.countryCode = '+49';
                selectedCountryCode = '+49';
              }
              rerender();
            }}
          ></dees-input-dropdown>

          ${definitionType === 'regex' ? html`
            <dees-input-text
              .key=${'pattern'}
              .label=${'Number Pattern'}
              .description=${'Free-form pattern. Use this only when the structured number fields are not enough.'}
              .value=${formData.pattern || ''}
              @input=${(e: Event) => { formData.pattern = (e.target as any).value || undefined; }}
            ></dees-input-text>
          ` : html`
            <dees-input-dropdown
              .key=${'countryCode'} .label=${'Country Code'}
              .selectedOption=${countryCodeOptions.find((option) => option.key === selectedCountryCode) || countryCodeOptions[0]}
              .options=${countryCodeOptions}
              @selectedOption=${(e: CustomEvent) => {
                selectedCountryCode = e.detail.key || '+49';
                rerender();
              }}
            ></dees-input-dropdown>

            ${selectedCountryCode === CUSTOM_COUNTRY_CODE_KEY ? html`
              <dees-input-text
                .key=${'customCountryCode'} .label=${'Custom Country Code'}
                .description=${'Example: +49'}
                .value=${customCountryCode}
                @input=${(e: Event) => {
                  customCountryCode = (e.target as any).value || '';
                  formData.countryCode = normalizeCountryCode(customCountryCode);
                }}
              ></dees-input-text>
            ` : ''}

            <dees-input-text
              .key=${'areaCode'} .label=${'Area Code'}
              .description=${'Example: 421 or 0421'}
              .value=${formData.areaCode || ''}
              @input=${(e: Event) => { formData.areaCode = (e.target as any).value || undefined; }}
            ></dees-input-text>

            <dees-input-text
              .key=${'localNumber'} .label=${formData.mode === 'range' ? 'Number Start' : 'Number'}
              .description=${'Example: 219694'}
              .value=${formData.localNumber || ''}
              @input=${(e: Event) => { formData.localNumber = (e.target as any).value || undefined; }}
            ></dees-input-text>

            <dees-input-checkbox
              .key=${'rangeEnabled'} .label=${'Use range'} .value=${formData.mode === 'range'}
              @newValue=${(e: CustomEvent) => {
                formData.mode = e.detail ? 'range' : 'single';
                rerender();
              }}
            ></dees-input-checkbox>

            ${formData.mode === 'range' ? html`
              <dees-input-text
                .key=${'rangeEnd'} .label=${'Number End'}
                .description=${'Range applies to the local number part only'}
                .value=${formData.rangeEnd || ''}
                @input=${(e: Event) => { formData.rangeEnd = (e.target as any).value || undefined; }}
              ></dees-input-text>
            ` : ''}

            <div style="padding:10px 12px;border:1px solid #334155;border-radius:8px;background:#0f172a;">
              <div style="font-size:.72rem;color:#94a3b8;text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px;font-weight:600;">Generated Regex</div>
              <div style="font-family:'JetBrains Mono',monospace;font-size:.8rem;color:#e2e8f0;word-break:break-all;">${generatedPattern || '(complete the fields to generate a pattern)'}</div>
            </div>
          `}
        </div>
      `;
    };

    modalRef = await DeesModal.createAndShow({
      heading: existing ? `Edit Incoming Number: ${existing.label}` : 'New Incoming Number',
      width: 'small',
      showCloseButton: true,
      content: renderContent(),
      menuOptions: [
        {
          name: 'Cancel',
          iconName: 'lucide:x',
          action: async (modal: any) => { modal.destroy(); },
        },
        {
          name: 'Save',
          iconName: 'lucide:check',
          action: async (modal: any) => {
            const next = clone(formData);
            next.label = next.label.trim();
            if (!next.label) {
              DeesToast.error('Label is required');
              return;
            }

            if (definitionType === 'regex') {
              next.mode = 'regex';
              next.pattern = (next.pattern || '').trim();
              if (!next.pattern) {
                DeesToast.error('Number pattern is required for custom regex');
                return;
              }
              delete next.countryCode;
              delete next.areaCode;
              delete next.localNumber;
              delete next.rangeEnd;
              delete next.number;
              delete next.rangeStart;
            } else {
              next.countryCode = selectedCountryCode === CUSTOM_COUNTRY_CODE_KEY
                ? normalizeCountryCode(customCountryCode)
                : selectedCountryCode;
              next.areaCode = normalizeNumberPart(next.areaCode);
              next.localNumber = normalizeNumberPart(next.localNumber);
              next.rangeEnd = normalizeNumberPart(next.rangeEnd);
              next.mode = next.mode === 'range' ? 'range' : 'single';

              if (!next.countryCode || !next.areaCode || !next.localNumber) {
                DeesToast.error('Country code, area code, and number are required');
                return;
              }
              if (next.mode === 'range') {
                const validationError = validateLocalRange(next.localNumber, next.rangeEnd);
                if (validationError) {
                  DeesToast.error(validationError);
                  return;
                }
              } else {
                delete next.rangeEnd;
              }
              delete next.pattern;
              delete next.number;
              delete next.rangeStart;
            }

            if (!next.providerId) delete next.providerId;
            const incomingNumbers = [...this.getIncomingNumbers()];
            const index = incomingNumbers.findIndex((entry) => entry.id === next.id);
            if (index >= 0) incomingNumbers[index] = next;
            else incomingNumbers.push(next);

            if (await this.saveIncomingNumbers(incomingNumbers)) {
              modal.destroy();
              DeesToast.success(existing ? 'Incoming number updated' : 'Incoming number created');
            } else {
              DeesToast.error('Failed to save incoming number');
            }
          },
        },
      ],
    });
  }

  private async openRouteEditor(existing: ISipRoute | null): Promise<void> {
    const providers = this.config?.providers || [];
    const devices = this.config?.devices || [];
    const voiceboxes = this.config?.voiceboxes || [];
    const ivrMenus = this.config?.ivr?.menus || [];
    const incomingNumbers = this.getIncomingNumbers();
    const formData = existing ? clone(existing) : createRoute();
    let selectedIncomingNumberId = this.findIncomingNumberForRoute(formData)?.id || CUSTOM_REGEX_KEY;
    let modalRef: any;

    const rerender = () => {
      if (!modalRef) return;
      modalRef.content = renderContent();
      modalRef.requestUpdate();
    };

    const renderContent = () => {
      const incomingNumberOptions = [
        { option: 'Custom regex', key: CUSTOM_REGEX_KEY },
        ...incomingNumbers.map((entry) => ({
          option: `${entry.label} | ${describeIncomingNumber(entry)}`,
          key: entry.id,
        })),
      ];

      return html`
        <div style="display:flex;flex-direction:column;gap:12px;padding:4px 0;">
          <dees-input-text
            .key=${'name'} .label=${'Route Name'} .value=${formData.name}
            @input=${(e: Event) => { formData.name = (e.target as any).value; }}
          ></dees-input-text>

          <dees-input-dropdown
            .key=${'direction'} .label=${'Direction'}
            .selectedOption=${formData.match.direction === 'inbound'
              ? { option: 'inbound', key: 'inbound' }
              : { option: 'outbound', key: 'outbound' }}
            .options=${[
              { option: 'inbound', key: 'inbound' },
              { option: 'outbound', key: 'outbound' },
            ]}
            @selectedOption=${(e: CustomEvent) => {
              formData.match.direction = e.detail.key;
              rerender();
            }}
          ></dees-input-dropdown>

          <dees-input-text
            .key=${'priority'} .label=${'Priority (higher = matched first)'}
            .value=${String(formData.priority)}
            @input=${(e: Event) => { formData.priority = parseInt((e.target as any).value, 10) || 0; }}
          ></dees-input-text>

          <dees-input-checkbox
            .key=${'enabled'} .label=${'Enabled'} .value=${formData.enabled}
            @newValue=${(e: CustomEvent) => { formData.enabled = e.detail; }}
          ></dees-input-checkbox>

          <div style="margin-top:8px;padding-top:12px;border-top:1px solid #334155;">
            <div style="font-size:.75rem;color:#94a3b8;text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px;font-weight:600;">Match Criteria</div>
          </div>

          ${formData.match.direction === 'inbound' ? html`
            <dees-input-dropdown
              .key=${'sourceProvider'} .label=${'Source Provider'}
              .selectedOption=${formData.match.sourceProvider
                ? { option: this.getProviderLabel(formData.match.sourceProvider), key: formData.match.sourceProvider }
                : { option: '(any)', key: '' }}
              .options=${[
                { option: '(any)', key: '' },
                ...providers.map((provider: any) => ({ option: provider.displayName || provider.id, key: provider.id })),
              ]}
              @selectedOption=${(e: CustomEvent) => { formData.match.sourceProvider = e.detail.key || undefined; }}
            ></dees-input-dropdown>

            <dees-input-dropdown
              .key=${'incomingNumberId'} .label=${'DID'}
              .selectedOption=${incomingNumberOptions.find((option) => option.key === selectedIncomingNumberId) || incomingNumberOptions[0]}
              .options=${incomingNumberOptions}
              @selectedOption=${(e: CustomEvent) => {
                selectedIncomingNumberId = e.detail.key || CUSTOM_REGEX_KEY;
                const selectedIncomingNumber = incomingNumbers.find((entry) => entry.id === selectedIncomingNumberId);
                if (selectedIncomingNumber) {
                  formData.match.numberPattern = getIncomingNumberPattern(selectedIncomingNumber) || undefined;
                  if (selectedIncomingNumber.providerId) {
                    formData.match.sourceProvider = selectedIncomingNumber.providerId;
                  }
                }
                rerender();
              }}
            ></dees-input-dropdown>

            ${selectedIncomingNumberId === CUSTOM_REGEX_KEY ? html`
              <dees-input-text
                .key=${'numberPattern'}
                .label=${'Number Pattern'}
                .description=${'Free-form inbound number pattern used only for Custom regex routes'}
                .value=${formData.match.numberPattern || ''}
                @input=${(e: Event) => { formData.match.numberPattern = (e.target as any).value || undefined; }}
              ></dees-input-text>
            ` : html`
              <div style="padding:10px 12px;border:1px solid #334155;border-radius:8px;background:#0f172a;">
                <div style="font-size:.72rem;color:#94a3b8;text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px;font-weight:600;">Selected Pattern</div>
                <div style="font-family:'JetBrains Mono',monospace;font-size:.8rem;color:#e2e8f0;word-break:break-all;">${formData.match.numberPattern || '(not set)'}</div>
              </div>
            `}

            <dees-input-text
              .key=${'callerPattern'}
              .label=${'Caller Pattern'}
              .description=${'Optional caller-ID filter'}
              .value=${formData.match.callerPattern || ''}
              @input=${(e: Event) => { formData.match.callerPattern = (e.target as any).value || undefined; }}
            ></dees-input-text>
          ` : html`
            <dees-input-text
              .key=${'numberPattern'}
              .label=${'Number Pattern'}
              .description=${'Outbound dialed-number match. Exact, prefix*, range, or /regex/'}
              .value=${formData.match.numberPattern || ''}
              @input=${(e: Event) => { formData.match.numberPattern = (e.target as any).value || undefined; }}
            ></dees-input-text>
          `}

          <div style="margin-top:8px;padding-top:12px;border-top:1px solid #334155;">
            <div style="font-size:.75rem;color:#94a3b8;text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px;font-weight:600;">Action</div>
          </div>

          ${formData.match.direction === 'inbound' ? html`
            <dees-input-text
              .key=${'targets'}
              .label=${'Ring Devices (comma-separated IDs)'}
              .description=${'Leave empty to ring all devices'}
              .value=${(formData.action.targets || []).join(', ')}
              @input=${(e: Event) => {
                const value = (e.target as any).value.trim();
                formData.action.targets = value ? value.split(',').map((item: string) => item.trim()).filter(Boolean) : undefined;
              }}
            ></dees-input-text>

            <dees-input-checkbox
              .key=${'ringBrowsers'} .label=${'Ring browser clients'}
              .value=${formData.action.ringBrowsers ?? false}
              @newValue=${(e: CustomEvent) => { formData.action.ringBrowsers = e.detail; }}
            ></dees-input-checkbox>

            <dees-input-dropdown
              .key=${'voicemailBox'} .label=${'Voicemail Box (fallback)'}
              .selectedOption=${formData.action.voicemailBox
                ? { option: formData.action.voicemailBox, key: formData.action.voicemailBox }
                : { option: '(none)', key: '' }}
              .options=${[
                { option: '(none)', key: '' },
                ...voiceboxes.map((voicebox: any) => ({ option: voicebox.id, key: voicebox.id })),
              ]}
              @selectedOption=${(e: CustomEvent) => { formData.action.voicemailBox = e.detail.key || undefined; }}
            ></dees-input-dropdown>

            <dees-input-dropdown
              .key=${'ivrMenuId'} .label=${'IVR Menu'}
              .selectedOption=${formData.action.ivrMenuId
                ? { option: formData.action.ivrMenuId, key: formData.action.ivrMenuId }
                : { option: '(none)', key: '' }}
              .options=${[
                { option: '(none)', key: '' },
                ...ivrMenus.map((menu: any) => ({ option: menu.name || menu.id, key: menu.id })),
              ]}
              @selectedOption=${(e: CustomEvent) => { formData.action.ivrMenuId = e.detail.key || undefined; }}
            ></dees-input-dropdown>
          ` : html`
            <dees-input-dropdown
              .key=${'provider'} .label=${'Outbound Provider'}
              .selectedOption=${formData.action.provider
                ? { option: this.getProviderLabel(formData.action.provider), key: formData.action.provider }
                : { option: '(none)', key: '' }}
              .options=${[
                { option: '(none)', key: '' },
                ...providers.map((provider: any) => ({ option: provider.displayName || provider.id, key: provider.id })),
              ]}
              @selectedOption=${(e: CustomEvent) => { formData.action.provider = e.detail.key || undefined; }}
            ></dees-input-dropdown>

            <dees-input-text
              .key=${'stripPrefix'} .label=${'Strip Prefix'}
              .value=${formData.action.stripPrefix || ''}
              @input=${(e: Event) => { formData.action.stripPrefix = (e.target as any).value || undefined; }}
            ></dees-input-text>

            <dees-input-text
              .key=${'prependPrefix'} .label=${'Prepend Prefix'}
              .value=${formData.action.prependPrefix || ''}
              @input=${(e: Event) => { formData.action.prependPrefix = (e.target as any).value || undefined; }}
            ></dees-input-text>
          `}

          ${formData.match.direction === 'inbound' && devices.length ? html`
            <div style="font-size:.78rem;color:#94a3b8;">Known devices: ${devices.map((device: any) => device.id).join(', ')}</div>
          ` : ''}
        </div>
      `;
    };

    modalRef = await DeesModal.createAndShow({
      heading: existing ? `Edit Route: ${existing.name}` : 'New Route',
      width: 'small',
      showCloseButton: true,
      content: renderContent(),
      menuOptions: [
        {
          name: 'Cancel',
          iconName: 'lucide:x',
          action: async (modal: any) => { modal.destroy(); },
        },
        {
          name: 'Save',
          iconName: 'lucide:check',
          action: async (modal: any) => {
            const next = clone(formData);
            next.name = next.name.trim();
            if (!next.name) {
              DeesToast.error('Route name is required');
              return;
            }

            const selectedIncomingNumber = incomingNumbers.find((entry) => entry.id === selectedIncomingNumberId);
            if (next.match.direction === 'inbound' && selectedIncomingNumber) {
              next.match.numberPattern = getIncomingNumberPattern(selectedIncomingNumber) || undefined;
              if (selectedIncomingNumber.providerId) {
                next.match.sourceProvider = selectedIncomingNumber.providerId;
              }
            }

            if (!next.match.numberPattern) delete next.match.numberPattern;
            if (!next.match.callerPattern) delete next.match.callerPattern;
            if (!next.match.sourceProvider) delete next.match.sourceProvider;
            if (!next.match.sourceDevice) delete next.match.sourceDevice;
            if (!next.action.provider) delete next.action.provider;
            if (!next.action.stripPrefix) delete next.action.stripPrefix;
            if (!next.action.prependPrefix) delete next.action.prependPrefix;
            if (!next.action.targets?.length) delete next.action.targets;
            if (!next.action.ringBrowsers) delete next.action.ringBrowsers;
            if (!next.action.voicemailBox) delete next.action.voicemailBox;
            if (!next.action.ivrMenuId) delete next.action.ivrMenuId;
            if (!next.action.noAnswerTimeout) delete next.action.noAnswerTimeout;

            const routes = [...this.getRoutes()];
            const index = routes.findIndex((route) => route.id === next.id);
            if (index >= 0) routes[index] = next;
            else routes.push(next);

            if (await this.saveRoutes(routes)) {
              modal.destroy();
              DeesToast.success(existing ? 'Route updated' : 'Route created');
            } else {
              DeesToast.error('Failed to save route');
            }
          },
        },
      ],
    });
  }
}
