import { CommonModule, DatePipe, NgClass } from '@angular/common';
import { Component, OnInit, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { ButtonModule } from '@progress/kendo-angular-buttons';

import {
  CashlineApiService,
  SessionSummary
} from '../core/cashline-api.service';
import { CutoverI18nService } from '../core/cutover-i18n.service';
import { canAccessJourney, defaultJourneyId, isSupplierSession, resolveJourneyRoute } from '../core/uat-access.util';
import { getDefaultJourneyStepId, getJourneyDefinition } from './journey-definitions';
import { JourneyStepActionsComponent } from './journey-step-actions.component';
import { JourneyStepFormComponent } from './journey-step-form.component';
import { JourneyDefinition, JourneyField, JourneyFieldOption, JourneyId, JourneyStep } from './journey.models';

type LiveRecord = Record<string, unknown>;

interface LivePanel {
  title: string;
  description: string;
  data: unknown;
  metrics: SummaryMetric[];
  previewRows: LiveRecord[];
  previewKeys: string[];
  narrative: string;
}

interface RawLivePanel {
  title: string;
  description: string;
  data: unknown;
}

interface SummaryMetric {
  label: string;
  value: string;
}

interface StepAction {
  id: string;
  label: string;
  emphasis?: 'primary' | 'secondary';
}

interface WorkspaceCapabilityCard {
  title: string;
  summary: string;
  tone?: 'default' | 'attention' | 'finance' | 'monitoring';
}

interface PortalHighlightCard {
  title: string;
  summary: string;
}

interface ReportWorkspaceCard {
  title: string;
  summary: string;
  tone?: 'default' | 'finance' | 'operations' | 'external';
}

interface PaymentWorkspaceCard {
  title: string;
  summary: string;
  tone?: 'default' | 'finance' | 'external' | 'held';
}

interface MonitoringWorkspaceCard {
  title: string;
  summary: string;
  tone?: 'default' | 'warning' | 'critical' | 'trace';
}

interface DetailFocusCard {
  title: string;
  summary: string;
  tone?: 'default' | 'attention' | 'finance' | 'monitoring' | 'external' | 'held';
}

interface DependencyStatusCard {
  label: string;
  value: string;
  status?: 'ready' | 'attention' | 'held';
}

interface DetailStateCard {
  title: string;
  summary: string;
  badge?: string;
  tone?: 'default' | 'attention' | 'finance' | 'monitoring' | 'external' | 'held';
}

interface StateTimelineItem {
  label: string;
  value: string;
  tone?: 'default' | 'attention' | 'finance' | 'monitoring' | 'external' | 'held';
}

interface DemoGuideCard {
  label: string;
  value: string;
}

@Component({
  selector: 'app-journey-page',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    NgClass,
    DatePipe,
    RouterLink,
    ButtonModule,
    JourneyStepFormComponent,
    JourneyStepActionsComponent
  ],
  templateUrl: './journey-page.component.html'
})
export class JourneyPageComponent implements OnInit {
  private readonly route = inject(ActivatedRoute);
  private readonly router = inject(Router);
  private readonly api = inject(CashlineApiService);
  protected readonly i18n = inject(CutoverI18nService);

  protected readonly journey = this.resolveJourneyDefinition();
  protected currentStep = 0;
  protected readonly formValues: Record<string, string> = this.buildInitialValues(this.journey);
  protected readonly selectedFiles: Record<string, File | null> = {};
  protected session: SessionSummary | null = null;
  protected loading = false;
  protected messageText = '';
  protected errorText = '';
  protected livePanels: LivePanel[] = [];
  protected stepActions: StepAction[] = [];
  protected stageSummaryMetricsValue: SummaryMetric[] = [];
  protected workbenchMetricsValue: SummaryMetric[] = [];
  protected workspaceBadgeTextValue = 'Business workspace';
  protected readonly showWorkbenchBoardValue = this.journey.id !== 'supplier' && this.journey.id !== 'customer';
  protected readonly isDemoMode = this.resolveDemoMode();
  protected dynamicFieldOptions: Record<string, JourneyFieldOption[]> = {};

  private readonly defaultBranchId = 1;
  private readonly defaultWarehouseId = 1;
  private readonly defaultItemId = 1001;
  private readonly defaultCurrencyCode = 'EGP';
  private readonly qaRoute = this.route.snapshot.queryParamMap.get('qaRoute');
  private readonly qaExpected = this.route.snapshot.queryParamMap.get('qaExpected') ?? '';

  async ngOnInit(): Promise<void> {
    this.route.queryParamMap.subscribe((params) => {
      const stepId = params.get('step');
      if (!stepId) {
        return;
      }

      const stepIndex = this.visibleJourneySteps.findIndex((step) => step.id === stepId);
      if (stepIndex >= 0 && stepIndex !== this.currentStep) {
        this.currentStep = stepIndex;
        this.updateDerivedState();
        void this.refreshCurrentStep();
      }
    });

    await this.initializeJourney();
  }

  protected get currentStepDefinition(): JourneyStep {
    return this.visibleJourneySteps[this.currentStep];
  }

  protected get currentStepFields(): JourneyField[] {
    const fields = this.currentStepDefinition.fields ?? [];
    if (!fields.length || !Object.keys(this.dynamicFieldOptions).length) {
      return fields;
    }

    return fields.map((field) => {
      const options = this.dynamicFieldOptions[field.key];
      if (!options?.length) {
        return field;
      }

      // If we have dynamic options for a field, force it to be a select so the user
      // does not need to type IDs manually (especially for accounting-office client selection).
      return {
        ...field,
        type: 'select',
        options
      };
    });
  }

  protected get visibleJourneySteps(): JourneyStep[] {
    if (this.journey.id !== 'reporting-center' || !this.session) {
      return this.journey.steps;
    }

    if (this.isFinanceReportingUser(this.session)) {
      return this.journey.steps.filter((step) =>
        ['reports-summary', 'reports-financial-statements', 'reports-snapshots'].includes(step.id));
    }

    if (this.isOperationsReportingUser(this.session)) {
      return this.journey.steps.filter((step) =>
        ['reports-operations', 'reports-snapshots'].includes(step.id));
    }

    if (this.isIslamicFinanceReportingUser(this.session)) {
      return this.journey.steps.filter((step) =>
        ['reports-summary', 'reports-snapshots'].includes(step.id));
    }

    return this.journey.steps;
  }

  protected get stageSummaryMetrics(): SummaryMetric[] {
    return this.stageSummaryMetricsValue;
  }

  protected get workspaceBadgeText(): string {
    return this.workspaceBadgeTextValue;
  }

  protected get showWorkbenchBoard(): boolean {
    return this.showWorkbenchBoardValue;
  }

  protected get workbenchMetrics(): SummaryMetric[] {
    return this.workbenchMetricsValue;
  }

  protected get demoGuideCards(): DemoGuideCard[] {
    return [
      { label: 'Role', value: this.journey.persona },
      { label: 'Module', value: this.journey.moduleGroup },
      { label: 'Current step', value: this.currentStepDefinition.subtitle },
      { label: 'Expected result', value: this.currentStepDefinition.output }
    ];
  }

  protected get demoGuideFields(): string[] {
    return this.currentStepDefinition.fields.slice(0, 4).map((field) => field.label);
  }

  protected get demoGuideCheckpoints(): string[] {
    return this.currentStepDefinition.checkpoints.slice(0, 3);
  }

  protected get demoGuideLinkedApis(): string[] {
    return this.currentStepDefinition.linkedApis.slice(0, 4);
  }

  protected get demoCenterRoute(): string {
    return '/demo-center';
  }

  protected get isInternalJourney(): boolean {
    return ['accounting-office', 'islamic-finance', 'finance-approvals', 'operations-control', 'reporting-center', 'workflow-monitoring'].includes(this.journey.id);
  }

  protected get isExternalJourney(): boolean {
    return this.journey.id === 'supplier' || this.journey.id === 'customer';
  }

  protected get isReportStep(): boolean {
    return this.currentStepDefinition.id.includes('reports');
  }

  protected get isPaymentStep(): boolean {
    return ['supplier-payments', 'customer-payments', 'fa-gateway'].includes(this.currentStepDefinition.id);
  }

  protected get isMonitoringStep(): boolean {
    return this.journey.id === 'workflow-monitoring';
  }

  protected get selectedFileNames(): Record<string, string> {
    return Object.fromEntries(
      Object.entries(this.selectedFiles)
        .filter(([, file]) => !!file)
        .map(([key, file]) => [key, file!.name])
    );
  }

  protected get showsOnboardingStatePanel(): boolean {
    return ['supplier-profile', 'customer-profile', 'ops-onboarding', 'ao-onboarding'].includes(this.currentStepDefinition.id);
  }

  protected get showsFinanceOutputPanel(): boolean {
    return this.currentStepDefinition.id === 'reports-financial-statements';
  }

  protected get showsPaymentStatePanel(): boolean {
    return ['supplier-payments', 'customer-payments', 'fa-gateway'].includes(this.currentStepDefinition.id);
  }

  protected get showsMonitoringIncidentPanel(): boolean {
    return this.isMonitoringStep;
  }

  protected get onboardingStateCards(): readonly DetailStateCard[] {
    switch (this.currentStepDefinition.id) {
      case 'ops-onboarding':
      case 'ao-onboarding':
        return [
          {
            title: 'Lead and case intake',
            summary: 'Review whether the record is still a lead, already a submitted onboarding case, or is waiting for internal follow-up before a formal decision.',
            badge: 'Intake',
            tone: 'monitoring'
          },
          {
            title: 'KYC completeness and findings',
            summary: 'Use checklist coverage, uploaded evidence, and reviewer findings to judge whether the case is ready for approval, return, hold, or rejection.',
            badge: 'Review',
            tone: 'attention'
          },
          {
            title: 'Activation gating',
            summary: 'Only activate after approval is complete, blocking findings are cleared, and the account can safely move from onboarding-only access to normal service access.',
            badge: 'Activation'
          }
        ];
      default:
        return [
          {
            title: 'Lead capture and identity setup',
            summary: 'The prospect starts with minimal lead data and moves into onboarding-only access without requiring full system access first.',
            badge: 'Lead',
            tone: 'external'
          },
          {
            title: 'KYC evidence readiness',
            summary: 'Show document type, file attachment, completeness posture, and missing items clearly so the user understands what still blocks submission.',
            badge: 'KYC',
            tone: 'attention'
          },
          {
            title: 'Returned-for-rework support',
            summary: 'If Operations returns the case, the screen should make the return reason and the exact rework expectations visible without exposing internal-only controls.',
            badge: 'Rework'
          }
        ];
    }
  }

  protected get financeOutputStateCards(): readonly DetailStateCard[] {
    return [
      {
        title: 'Statement scope and accounting period',
        summary: 'The finance user should always understand whether the output is as-of-date, date-range based, or tied to a closing or end-of-day control point.',
        badge: 'Scope',
        tone: 'finance'
      },
      {
        title: 'Output format and audit usability',
        summary: 'PDF, CSV, and Excel choices should feel like formal output controls, with the on-screen review acting as the confidence check before export.',
        badge: 'Export'
      },
      {
        title: 'Sign-off confidence',
        summary: 'The statement view should help Finance judge whether the output is ready for print, distribution, and audit-facing evidence capture.',
        badge: 'Sign-off'
      }
    ];
  }

  protected get paymentStateCards(): readonly DetailStateCard[] {
    switch (this.currentStepDefinition.id) {
      case 'fa-gateway':
        return [
          {
            title: 'Execution queue posture',
            summary: 'Finance should see which collections, refunds, or payouts are queued, which are pending provider execution, and which need intervention.',
            badge: 'Queue',
            tone: 'finance'
          },
          {
            title: 'Provider contract visibility',
            summary: 'Provider status and cancel-contract traces need to be readable from the same screen so the team can understand true payment posture.',
            badge: 'Provider',
            tone: 'held'
          },
          {
            title: 'Posting and reconciliation continuity',
            summary: 'Payment execution should still feel connected to later posting and settlement control, not like an isolated gateway action.',
            badge: 'Continuity'
          }
        ];
      default:
        return [
          {
            title: 'Transaction visibility',
            summary: 'The user should understand dues, payouts, refunds, or settlements in plain business language with the current status visible immediately.',
            badge: 'Status',
            tone: 'external'
          },
          {
            title: 'Channel and settlement clarity',
            summary: 'Show which payment channel is visible, what settlement state applies, and whether the next action belongs to the user or to CashLine.',
            badge: 'Channel'
          },
          {
            title: 'Future-ready provider UX',
            summary: 'Keep the payment experience production-shaped even while some live provider integrations remain governed as held lanes.',
            badge: 'Readiness',
            tone: 'held'
          }
        ];
    }
  }

  protected get monitoringIncidentCards(): readonly DetailStateCard[] {
    switch (this.currentStepDefinition.id) {
      case 'monitor-live':
        return [
          {
            title: 'Posture scan',
            summary: 'Start with an immediate read of queue load, active workflow items, and whether the system is trending stable, busy, or at risk.',
            badge: 'Live',
            tone: 'monitoring'
          },
          {
            title: 'Escalation route',
            summary: 'Users should know whether the next action is watchful monitoring, queue triage, or a true operational escalation.',
            badge: 'Escalate'
          }
        ];
      case 'monitor-exceptions':
        return [
          {
            title: 'Exception clustering',
            summary: 'Group issues by recovery lane so support can separate onboarding blockers, finance exceptions, reconciliation breaks, and transactional stalls.',
            badge: 'Recovery',
            tone: 'attention'
          },
          {
            title: 'Operational owner',
            summary: 'The screen should make it obvious which team owns the next intervention and whether the issue is waiting, progressing, or blocked.',
            badge: 'Owner'
          },
          {
            title: 'Trace to resolution',
            summary: 'Exception handling should retain visibility into what happened, what action was taken, and whether the backlog is reducing.',
            badge: 'Trace',
            tone: 'monitoring'
          }
        ];
      default:
        return [
          {
            title: 'Signal and notification trace',
            summary: 'Support should understand notification backlog, ETA signals, and provider traces from one coherent downstream-monitoring view.',
            badge: 'Signals',
            tone: 'monitoring'
          },
          {
            title: 'Manual recovery tools',
            summary: 'Replay, retry, and batch controls should feel like governed recovery actions with clear expected outcomes.',
            badge: 'Recovery',
            tone: 'attention'
          }
        ];
    }
  }

  protected get onboardingReadinessTimeline(): readonly StateTimelineItem[] {
    const selectedFileCount = Object.values(this.selectedFiles).filter((file) => !!file).length;
    const messageState = this.messageText
      ? 'Latest user action completed'
      : this.errorText
        ? 'Latest user action needs attention'
        : 'Awaiting the next onboarding action';

    return [
      {
        label: 'Current lane',
        value: this.currentStepDefinition.internalLane,
        tone: 'external'
      },
      {
        label: 'Evidence attached in this session',
        value: selectedFileCount > 0 ? `${selectedFileCount} file(s) selected` : 'No new files selected in this session',
        tone: selectedFileCount > 0 ? 'monitoring' : 'attention'
      },
      {
        label: 'Submission posture',
        value: this.currentStepDefinition.output,
        tone: 'default'
      },
      {
        label: 'Latest action state',
        value: messageState,
        tone: this.errorText ? 'attention' : 'monitoring'
      }
    ];
  }

  protected get financeOutputTimeline(): readonly StateTimelineItem[] {
    return [
      {
        label: 'Selected output family',
        value: this.valueOrDefault('financialStatementCode', 'Statement not selected'),
        tone: 'finance'
      },
      {
        label: 'Output format',
        value: this.valueOrDefault('financialStatementFormat', 'PDF'),
        tone: 'finance'
      },
      {
        label: 'As-of or date range posture',
        value: this.valueOrDefault('financialAsOfDate', this.valueOrDefault('financialDateFrom', 'Use current reporting range')),
        tone: 'default'
      },
      {
        label: 'Export readiness',
        value: this.livePanels.length > 0 ? 'Live report data is present for review before export' : 'Open the report view to populate the current output',
        tone: this.livePanels.length > 0 ? 'monitoring' : 'attention'
      }
    ];
  }

  protected get paymentStatusTimeline(): readonly StateTimelineItem[] {
    const channel = this.valueOrDefault('paymentChannel', this.valueOrDefault('supplierPaymentChannel', 'Visible governed channel'));
    return [
      {
        label: 'Current payment lane',
        value: this.currentStepDefinition.internalLane,
        tone: this.journey.id === 'finance-approvals' ? 'finance' : 'external'
      },
      {
        label: 'Visible payment channel',
        value: channel,
        tone: 'default'
      },
      {
        label: 'Connected data posture',
        value: this.livePanels.length > 0 ? `${this.livePanels.length} live panel(s) connected for review` : 'Waiting for current transaction data',
        tone: this.livePanels.length > 0 ? 'monitoring' : 'attention'
      },
      {
        label: 'Latest action state',
        value: this.messageText || this.errorText || 'No payment action has been triggered in this session yet',
        tone: this.errorText ? 'attention' : this.messageText ? 'monitoring' : 'default'
      }
    ];
  }

  protected get monitoringRecoveryTimeline(): readonly StateTimelineItem[] {
    return [
      {
        label: 'Current monitoring lane',
        value: this.currentStepDefinition.title,
        tone: 'monitoring'
      },
      {
        label: 'Connected monitoring feeds',
        value: this.livePanels.length > 0 ? `${this.livePanels.length} feed(s) visible in the current view` : 'No feeds visible yet for this lane',
        tone: this.livePanels.length > 0 ? 'monitoring' : 'attention'
      },
      {
        label: 'Recovery posture',
        value: this.currentStepDefinition.output,
        tone: 'default'
      },
      {
        label: 'Latest operator feedback',
        value: this.messageText || this.errorText || 'No manual recovery action has been triggered in this session yet',
        tone: this.errorText ? 'attention' : this.messageText ? 'monitoring' : 'default'
      }
    ];
  }

  protected get liveDataSurfaceMetrics(): SummaryMetric[] {
    const previewRows = this.livePanels.reduce((sum, panel) => sum + panel.previewRows.length, 0);
    const metricCount = this.livePanels.reduce((sum, panel) => sum + panel.metrics.length, 0);
    const statusValue = this.errorText
      ? 'Needs attention'
      : this.messageText
        ? 'Recently updated'
        : 'Awaiting next action';

    return [
      { label: 'Connected panels', value: `${this.livePanels.length}` },
      { label: 'Preview rows', value: `${previewRows}` },
      { label: 'Surface metrics', value: `${metricCount}` },
      { label: 'Current status', value: statusValue }
    ];
  }

  protected get liveDataSurfaceTitle(): string {
    if (this.isReportStep) {
      return 'Live report result surface';
    }

    if (this.isPaymentStep) {
      return 'Payment transaction surface';
    }

    if (this.isMonitoringStep) {
      return 'Monitoring trace surface';
    }

    if (this.showsOnboardingStatePanel) {
      return 'Onboarding and KYC live surface';
    }

    return 'Connected business data surface';
  }

  protected get liveDataSurfaceSummary(): string {
    if (this.isReportStep) {
      return 'Keep report outputs readable before export, with clear panel grouping, metrics, and preview rows tied to live backend data.';
    }

    if (this.isPaymentStep) {
      return 'Use this surface to read current payment posture, transaction evidence, and settlement-linked records from the active backend state.';
    }

    if (this.isMonitoringStep) {
      return 'Use this surface to inspect queue signals, incident traces, and downstream monitoring data without losing the current recovery context.';
    }

    if (this.showsOnboardingStatePanel) {
      return 'Use this surface to review onboarding cases, KYC evidence posture, and current operational outcomes from live connected records.';
    }

    return 'Use this surface to review the current records, metrics, and preview rows connected to the active workflow step.';
  }

  protected get liveDataToolbarPills(): readonly string[] {
    if (this.isReportStep) {
      return ['Review live rows', 'Validate before export', 'Open printable output'];
    }

    if (this.isPaymentStep) {
      return ['Review payment status', 'Inspect settlement context', 'Check provider posture'];
    }

    if (this.isMonitoringStep) {
      return ['Scan queue signals', 'Trace incident detail', 'Prepare recovery action'];
    }

    if (this.showsOnboardingStatePanel) {
      return ['Check case completeness', 'Review KYC evidence', 'Confirm next reviewer action'];
    }

    return ['Review connected records', 'Check current metrics', 'Confirm workflow readiness'];
  }

  protected get detailFocusCards(): readonly DetailFocusCard[] {
    switch (`${this.journey.id}:${this.currentStepDefinition.id}`) {
      case 'operations-control:op-onboarding-review':
        return [
          {
            title: 'Lead the case to a governed decision',
            summary: 'Operations should move submitted onboarding work from intake into review, decision, and activation control without losing follow-up traceability.'
          },
          {
            title: 'Separate review quality from commercial follow-up',
            summary: 'Keep KYC completeness, compliance findings, and customer contact actions visible as related but different operational tasks.',
            tone: 'attention'
          },
          {
            title: 'Preserve activation discipline',
            summary: 'Approval, hold, return, reject, and reopen decisions must remain visible as deliberate controls tied to case readiness.'
          }
        ];
      case 'finance-approvals:fa-gateway':
        return [
          {
            title: 'Run payments as governed finance control',
            summary: 'Collections, payouts, refunds, and provider execution need to read like one supervised finance workspace rather than separate utility actions.',
            tone: 'finance'
          },
          {
            title: 'Keep provider posture explicit',
            summary: 'Teams should understand which channels are active, which are held, and where recovery or manual intervention is expected.',
            tone: 'held'
          }
        ];
      case 'reporting-center:reports-financial-statements':
        return [
          {
            title: 'Treat the output as formal accounting evidence',
            summary: 'Financial statements, trial balance, and end-of-day outputs should present date scope, output controls, and print or export confidence clearly.',
            tone: 'finance'
          },
          {
            title: 'Make review readable before export',
            summary: 'Finance users should be able to trust the on-screen review state before deciding whether to print, export to Excel, or issue a PDF.'
          }
        ];
      case 'workflow-monitoring:monitor-exceptions':
        return [
          {
            title: 'Prioritize recovery over noise',
            summary: 'The monitoring lane should group actionable failures by urgency, affected lane, and likely intervention instead of surfacing raw technical noise.',
            tone: 'monitoring'
          },
          {
            title: 'Keep cross-module impact visible',
            summary: 'Users should quickly understand whether an exception is blocking onboarding, finance, settlement, or downstream notifications.',
            tone: 'attention'
          }
        ];
      case 'supplier:supplier-onboarding':
      case 'customer:customer-onboarding':
        return [
          {
            title: 'Guide the user toward completion',
            summary: 'The onboarding screen should make the next required fields, evidence needs, and current status obvious without exposing internal review mechanics.',
            tone: 'external'
          },
          {
            title: 'Show readiness before submission',
            summary: 'Prospective users should understand what is complete, what still needs evidence, and what will happen after submission.'
          }
        ];
      case 'supplier:supplier-payments':
      case 'customer:customer-payments':
        return [
          {
            title: 'Explain account-facing payment posture',
            summary: 'Users should understand dues, settlements, refunds, and payout readiness in plain business terms from the same workspace.',
            tone: 'external'
          },
          {
            title: 'Keep channel readiness honest',
            summary: 'Visible payment channels should feel production-ready in design while still making any held provider posture transparent.',
            tone: 'held'
          }
        ];
      default:
        return [
          {
            title: 'Keep the step outcome explicit',
            summary: 'Each module view should make the current objective, the required user action, and the expected business result clear inside the working area.'
          },
          {
            title: 'Preserve governed next steps',
            summary: 'The user should always understand what happens next, who owns the next decision, and whether the workflow is ready to move forward.'
          }
        ];
    }
  }

  protected get dependencyStatusCards(): readonly DependencyStatusCard[] {
    const sharedCards: DependencyStatusCard[] = [
      {
        label: 'Decision owner',
        value: this.currentStepDefinition.internalLane,
        status: 'ready'
      },
      {
        label: 'Expected output',
        value: this.currentStepDefinition.output,
        status: 'ready'
      }
    ];

    switch (`${this.journey.id}:${this.currentStepDefinition.id}`) {
      case 'operations-control:op-onboarding-review':
        return [
          ...sharedCards,
          {
            label: 'Primary dependency',
            value: 'Submitted onboarding case and KYC review state',
            status: 'attention'
          },
          {
            label: 'Activation gate',
            value: 'Approval allowed only when blocking findings are resolved',
            status: 'ready'
          }
        ];
      case 'reporting-center:reports-financial-statements':
        return [
          ...sharedCards,
          {
            label: 'Primary dependency',
            value: 'Live finance ledger and statement filters',
            status: 'ready'
          },
          {
            label: 'Output posture',
            value: 'PDF, CSV, and Excel export paths',
            status: 'ready'
          }
        ];
      case 'finance-approvals:fa-gateway':
        return [
          ...sharedCards,
          {
            label: 'Primary dependency',
            value: 'Gateway execution data, refunds, collections, and payout state',
            status: 'attention'
          },
          {
            label: 'Held external lane',
            value: 'Paymob and Fawry target-environment closure still governed separately',
            status: 'held'
          }
        ];
      case 'workflow-monitoring:monitor-notifications':
        return [
          ...sharedCards,
          {
            label: 'Primary dependency',
            value: 'Queue state, downstream notifications, ETA/provider trace',
            status: 'attention'
          },
          {
            label: 'Recovery posture',
            value: 'Manual replay and batch recovery controls',
            status: 'ready'
          }
        ];
      case 'supplier:supplier-onboarding':
      case 'customer:customer-onboarding':
        return [
          ...sharedCards,
          {
            label: 'Primary dependency',
            value: 'Lead record, onboarding payload, and KYC evidence set',
            status: 'attention'
          },
          {
            label: 'Access posture',
            value: 'Restricted onboarding-only access until activation',
            status: 'ready'
          }
        ];
      default:
        return [
          ...sharedCards,
          {
            label: 'Primary dependency',
            value: 'Live workflow data and role-appropriate records',
            status: 'attention'
          }
        ];
    }
  }

  protected get journeyCapabilityCards(): readonly WorkspaceCapabilityCard[] {
    if (this.isInternalJourney) {
      return this.internalCapabilityCards;
    }

    if (this.journey.id === 'supplier') {
      return [
        {
          title: 'Commercial supplier journey',
          summary: 'Manage your profile, documents, requests, orders, invoices, and status updates through one secure portal designed to make supplier interaction faster, easier, and more transparent.'
        },
        {
          title: 'Supplier-facing actions only',
          summary: 'The portal should surface commercial actions, document readiness, settlement visibility, and account-facing outputs without leaking internal controls.',
          tone: 'attention'
        },
        {
          title: 'Account and financing visibility',
          summary: 'Statements, payouts, Murabaha requests, and commercial follow-up should feel like a business partner experience, not an internal ERP queue.'
        }
      ];
    }

    if (this.journey.id === 'customer') {
      return [
        {
          title: 'Customer business workspace',
          summary: 'Keep onboarding, requests, orders, billing, payments, reports, and financing inside a focused customer-facing package.'
        },
        {
          title: 'Service and transaction clarity',
          summary: 'Requests, order activity, dues, and statements should remain clear and customer-facing rather than shaped like internal operational screens.',
          tone: 'attention'
        },
        {
          title: 'Guided account progression',
          summary: 'The customer experience should make account status, next actions, and financing posture visible without exposing internal workflow mechanics.'
        }
      ];
    }

    return [];
  }

  protected get portalHighlightCards(): readonly PortalHighlightCard[] {
    if (this.journey.id === 'supplier') {
      return [
        {
          title: 'Sourcing and quotation follow-up',
          summary: 'Suppliers should quickly understand open RFQs, quotation readiness, and commercial next actions.'
        },
        {
          title: 'Settlement and account visibility',
          summary: 'Payout posture, statement visibility, and account-facing report actions should stay easy to reach.'
        },
        {
          title: 'Financing interaction',
          summary: 'Murabaha requests, offers, and execution status should remain visible without leaving the supplier package.'
        }
      ];
    }

    if (this.journey.id === 'customer') {
      return [
        {
          title: 'Requests and order progress',
          summary: 'Customers should see how to create requests, track order status, and understand the next service action immediately.'
        },
        {
          title: 'Billing and statement access',
          summary: 'Statements, dues, payment follow-up, and export or print actions should feel like core account services.'
        },
        {
          title: 'Financing and account support',
          summary: 'Customers should be able to understand financing posture and account support options from the same guided workspace.'
        }
      ];
    }

    return [];
  }

  protected get portalHighlightsEyebrow(): string {
    return this.journey.id === 'customer'
      ? 'Customer service layer'
      : 'Supplier service layer';
  }

  protected get portalHighlightsTitle(): string {
    return this.journey.id === 'customer'
      ? 'Customer portal services'
      : 'Supplier portal services';
  }

  protected get reportWorkspaceCards(): readonly ReportWorkspaceCard[] {
    switch (`${this.journey.id}:${this.currentStepDefinition.id}`) {
      case 'reporting-center:reports-summary':
        return [
          {
            title: 'Assigned finance package',
            summary: 'Finance users should see only their governed finance and shared reports, linked to live statements, settlements, and transaction data.',
            tone: 'finance'
          },
          {
            title: 'Review before export',
            summary: 'The reporting center should lead with practical navigation, filters, and readable result sets before any export action.'
          },
          {
            title: 'Decision-ready reporting',
            summary: 'Profitability, settlement, AR, AP, and account statements should feel like business review tools, not technical outputs.'
          }
        ];
      case 'reporting-center:reports-financial-statements':
        return [
          {
            title: 'Formal accounting outputs',
            summary: 'Financial statements, trial balance, and end-of-day entries should read as formal finance outputs ready for export and print.',
            tone: 'finance'
          },
          {
            title: 'Controlled formats',
            summary: 'PDF, CSV, and Excel choices should remain visible and deliberate from the same reporting workspace.'
          },
          {
            title: 'Finance confidence',
            summary: 'As-of dates and date ranges should stay easy to interpret so the output feels audit-ready and operationally safe.'
          }
        ];
      case 'reporting-center:reports-operations':
        return [
          {
            title: 'Operations report package',
            summary: 'Operations users should see the assigned onboarding, activity, user, transaction, and analytical reports only.',
            tone: 'operations'
          },
          {
            title: 'Export and print readiness',
            summary: 'CSV, PDF, and print actions should be easy to find from the same operations reporting lane.'
          }
        ];
      case 'reporting-center:reports-snapshots':
        return [
          {
            title: 'Evidence library',
            summary: 'Snapshots should help management and governance users retain formal reporting evidence from the same reporting center.'
          },
          {
            title: 'Reusable reporting posture',
            summary: 'The snapshot lane should make it easy to create, revisit, and review governed report captures.'
          }
        ];
      case 'supplier:supplier-reports':
        return [
          {
            title: 'Supplier statements and account outputs',
            summary: 'Suppliers should understand statements, delivery logs, and account summaries from a partner-facing reporting lane.',
            tone: 'external'
          },
          {
            title: 'Simple export and print',
            summary: 'The supplier package should keep export and print visible without exposing internal finance controls.'
          }
        ];
      case 'customer:customer-reports':
        return [
          {
            title: 'Customer statements and billing visibility',
            summary: 'Customers should be able to review statements, account summaries, and delivery-linked outputs from a customer-friendly reporting lane.',
            tone: 'external'
          },
          {
            title: 'Account-facing outputs',
            summary: 'Exports and print views should feel like part of normal account self-service rather than technical report extraction.'
          }
        ];
      default:
        return [];
    }
  }

  protected get reportingEyebrow(): string {
    return this.journey.id === 'reporting-center'
      ? 'Governed reporting workspace'
      : 'Reports and statements';
  }

  protected get reportingTitle(): string {
    switch (`${this.journey.id}:${this.currentStepDefinition.id}`) {
      case 'reporting-center:reports-summary':
        return 'Finance reporting package';
      case 'reporting-center:reports-financial-statements':
        return 'Financial statement execution';
      case 'reporting-center:reports-operations':
        return 'Operations reporting package';
      case 'reporting-center:reports-snapshots':
        return 'Snapshot and evidence library';
      case 'supplier:supplier-reports':
        return 'Supplier reports and statements';
      case 'customer:customer-reports':
        return 'Customer reports and statements';
      default:
        return 'Reporting workspace';
    }
  }

  protected get reportingSummary(): string {
    if (this.journey.id === 'reporting-center') {
      return 'Keep the reporting center focused on governed report packages, readable outputs, and visible export and print controls.';
    }

    return 'Keep account-facing report views easy to understand, easy to export, and clearly separated from internal reporting workbenches.';
  }

  protected get reportingToolbarPrimary(): string {
    if (this.currentStepDefinition.id === 'reports-financial-statements') {
      return 'View governed output';
    }

    return 'Review filtered report data';
  }

  protected get reportingToolbarSecondary(): string {
    if (this.journey.id === 'reporting-center') {
      return 'Export in approved format';
    }

    return 'Export account-facing output';
  }

  protected get reportingToolbarTertiary(): string {
    return 'Open print-ready view';
  }

  protected get paymentWorkspaceCards(): readonly PaymentWorkspaceCard[] {
    switch (`${this.journey.id}:${this.currentStepDefinition.id}`) {
      case 'finance-approvals:fa-gateway':
        return [
          {
            title: 'Collections, refunds, and payouts',
            summary: 'Finance users should see customer collections, customer refunds, and supplier payouts as one controlled payment decision lane.',
            tone: 'finance'
          },
          {
            title: 'Provider execution posture',
            summary: 'Gateway execution should feel like finance control work with visibility into provider-status and cancel-contract behavior.'
          },
          {
            title: 'Held integration honesty',
            summary: 'Paymob and Fawry remain visible as governed channels even where full target-environment closure is still carried as a held condition.',
            tone: 'held'
          }
        ];
      case 'supplier:supplier-payments':
        return [
          {
            title: 'Settlement and payout visibility',
            summary: 'Suppliers should understand payout readiness, settlement posture, and account-facing payment visibility from one external payment lane.',
            tone: 'external'
          },
          {
            title: 'Provider readiness without overpromising',
            summary: 'The supplier package should show the planned payment channels clearly while still signaling governed readiness where activation is held.',
            tone: 'held'
          }
        ];
      case 'customer:customer-payments':
        return [
          {
            title: 'Collections and dues',
            summary: 'Customers should see dues, payment initiation, and collection follow-up from a clear account-facing payment workspace.',
            tone: 'external'
          },
          {
            title: 'Refund and financing continuity',
            summary: 'Refund visibility and Islamic Finance requests should remain understandable from the same customer-owned payment flow.'
          },
          {
            title: 'Channel readiness',
            summary: 'Paymob and Fawry should appear as part of the experience model while the backend integration posture stays honestly governed.',
            tone: 'held'
          }
        ];
      default:
        return [];
    }
  }

  protected get paymentEyebrow(): string {
    return this.journey.id === 'finance-approvals'
      ? 'Finance payment controls'
      : 'Payments workspace';
  }

  protected get paymentTitle(): string {
    switch (`${this.journey.id}:${this.currentStepDefinition.id}`) {
      case 'finance-approvals:fa-gateway':
        return 'Gateway control and provider posture';
      case 'supplier:supplier-payments':
        return 'Supplier payments and settlement visibility';
      case 'customer:customer-payments':
        return 'Customer collections and payment services';
      default:
        return 'Payments workspace';
    }
  }

  protected get paymentSummary(): string {
    if (this.journey.id === 'finance-approvals') {
      return 'Keep internal payment controls focused on finance decisions, provider posture, and visible exception handling.';
    }

    return 'Keep payment lanes understandable, role-scoped, and honest about provider readiness while preserving a production-style experience.';
  }

  protected get paymentToolbarPrimary(): string {
    return this.journey.id === 'finance-approvals'
      ? 'Review current payment posture'
      : 'Review account payment status';
  }

  protected get paymentToolbarSecondary(): string {
    return this.journey.id === 'finance-approvals'
      ? 'Inspect provider execution'
      : 'Track settlement or collection state';
  }

  protected get paymentToolbarTertiary(): string {
    return 'Keep held channels visible';
  }

  protected get monitoringWorkspaceCards(): readonly MonitoringWorkspaceCard[] {
    switch (this.currentStepDefinition.id) {
      case 'monitor-live':
        return [
          {
            title: 'Live queue posture',
            summary: 'Monitoring should begin with an immediately readable view of current workflow pressure, queue alignment, and operational attention.',
            tone: 'trace'
          },
          {
            title: 'Current urgency',
            summary: 'Users should quickly distinguish routine activity from items that need live intervention or escalation.',
            tone: 'warning'
          },
          {
            title: 'Traceable progression',
            summary: 'The monitoring lane should make it easy to move from headline posture into item-level trace without losing context.'
          }
        ];
      case 'monitor-exceptions':
        return [
          {
            title: 'Exception-first recovery view',
            summary: 'Workflow, collections, and reconciliation exceptions should be grouped as actionable recovery lanes rather than scattered technical feeds.',
            tone: 'critical'
          },
          {
            title: 'Operational intervention guidance',
            summary: 'Support teams should be able to understand which lane needs attention now and what kind of intervention is expected.',
            tone: 'warning'
          },
          {
            title: 'Cross-lane control',
            summary: 'Monitoring should show how exception pressure moves across workflow, finance, and reconciliation spaces.'
          }
        ];
      case 'monitor-notifications':
        return [
          {
            title: 'Notification backlog and downstream signals',
            summary: 'Notification queue posture, ETA authority traffic, and provider trace should read as one connected signal-management flow.',
            tone: 'trace'
          },
          {
            title: 'Manual recovery support',
            summary: 'Support users should see reprocess and batch actions as deliberate recovery tools, not hidden technical shortcuts.',
            tone: 'warning'
          },
          {
            title: 'Provider and authority visibility',
            summary: 'ETA and provider contracts should stay visible enough for traceability while still reading as governed operational checks.'
          }
        ];
      default:
        return [];
    }
  }

  protected get monitoringEyebrow(): string {
    return 'Live workflow monitoring';
  }

  protected get monitoringTitle(): string {
    switch (this.currentStepDefinition.id) {
      case 'monitor-live':
        return 'Live posture and queue visibility';
      case 'monitor-exceptions':
        return 'Exceptions and recovery workspace';
      case 'monitor-notifications':
        return 'Notifications and downstream trace';
      default:
        return 'Monitoring workspace';
    }
  }

  protected get monitoringSummary(): string {
    return 'Keep monitoring centered on live posture, urgency, recovery actions, and end-to-end operational traceability.';
  }

  protected get monitoringToolbarPrimary(): string {
    return 'Review live status';
  }

  protected get monitoringToolbarSecondary(): string {
    return 'Investigate queue or trace detail';
  }

  protected get monitoringToolbarTertiary(): string {
    return 'Prepare recovery action';
  }

  protected get capabilityPanelEyebrow(): string {
    return this.isInternalJourney ? 'Internal control tower' : 'Role-guided workspace';
  }

  protected get capabilityPanelTitle(): string {
    if (this.isInternalJourney) {
      return 'Operational focus for this workspace';
    }

    if (this.journey.id === 'supplier') {
      return 'Supplier portal priorities';
    }

    if (this.journey.id === 'customer') {
      return 'Customer portal priorities';
    }

    return 'Workspace priorities';
  }

  protected get capabilityPanelSummary(): string {
    if (this.isInternalJourney) {
      return 'Keep internal users centered on queues, approvals, exceptions, reporting, and governed case actions.';
    }

    if (this.journey.id === 'supplier') {
      return 'Keep supplier users inside a commercial portal language with clear sourcing, payment, reporting, and financing actions.';
    }

    if (this.journey.id === 'customer') {
      return 'Keep customer users inside a guided account workspace for requests, payments, reports, and financing visibility.';
    }

    return 'Keep users oriented around the highest-value actions in their assigned workspace.';
  }

  protected get internalCapabilityCards(): readonly WorkspaceCapabilityCard[] {
    switch (this.journey.id) {
      case 'operations-control':
        return [
          {
            title: 'Cases and operational routing',
            summary: 'Drive onboarding review, procurement follow-up, inventory posture, and sales-linked exception handling from one operational lane.'
          },
          {
            title: 'Decision and follow-up controls',
            summary: 'Hold, return, reopen, and approval actions should remain visible as deliberate operations controls rather than generic buttons.',
            tone: 'attention'
          },
          {
            title: 'Live queue awareness',
            summary: 'Use connected panels and workflow data to keep Operations aware of pending items, blocked work, and next actions.',
            tone: 'monitoring'
          }
        ];
      case 'finance-approvals':
        return [
          {
            title: 'Formal finance review',
            summary: 'Approval work, posting controls, and payment visibility must feel disciplined, auditable, and ready for finance sign-off.',
            tone: 'finance'
          },
          {
            title: 'Accounting confidence',
            summary: 'Financial statements, trial balance, and end-of-day readiness should stay connected to decision workflows and exceptions.'
          },
          {
            title: 'Export and traceability',
            summary: 'Finance users need clean reporting, export, print, and trace visibility from the same governed workspace.'
          }
        ];
      case 'reporting-center':
        return [
          {
            title: 'Role-based reporting catalog',
            summary: 'Finance, Operations, and Islamic Finance users should each see only the report lanes assigned to their role.'
          },
          {
            title: 'Controlled outputs',
            summary: 'Export, print, and snapshot actions must look formal and usable as real production-style reporting controls.',
            tone: 'finance'
          },
          {
            title: 'Decision-ready summaries',
            summary: 'Reports should surface filters, tables, and business summaries before raw data so the workspace feels intentional.'
          }
        ];
      case 'workflow-monitoring':
        return [
          {
            title: 'Operational visibility',
            summary: 'Alerts, exceptions, queues, and workflow instances should remain distinct and immediately scannable.',
            tone: 'monitoring'
          },
          {
            title: 'Hypercare readiness',
            summary: 'Monitoring screens should make runbooks, traceability, and escalation paths feel like live support tools.'
          },
          {
            title: 'Real-time action context',
            summary: 'The team should be able to move from summary to incident detail without losing queue posture or current status.'
          }
        ];
      case 'islamic-finance':
        return [
          {
            title: 'Murabaha-first workflow',
            summary: 'Requests, Shariah review, approvals, servicing, and settlements should read as one staged financing journey.'
          },
          {
            title: 'Decision governance',
            summary: 'Shariah review and financing decisions need clear separation from routine operational updates.',
            tone: 'finance'
          },
          {
            title: 'Case lifecycle visibility',
            summary: 'Users should see queue posture, case progression, and servicing outcomes in a controlled workspace rhythm.'
          }
        ];
      default:
        return [
          {
            title: 'Governed internal workspace',
            summary: 'Keep internal users oriented around approval, monitoring, reporting, and controlled operational decision-making.'
          }
        ];
    }
  }

  protected resolveInputType(field: JourneyField): string {
    return field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text';
  }

  protected onFileSelected(fieldKey: string, event: Event): void {
    const input = event.target as HTMLInputElement | null;
    this.selectedFiles[fieldKey] = input?.files?.[0] ?? null;
  }

  protected t(value: string): string {
    return this.i18n.text(value);
  }

  protected prettifyKey(key: string): string {
    return this.t(this.prettifyLabel(key));
  }

  private prettifyLabel(key: string): string {
    return key
      .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
      .replace(/_/g, ' ')
      .replace(/\b\w/g, (char) => char.toUpperCase());
  }

  protected formatPreviewValue(value: unknown): string {
    if (value == null) {
      return '-';
    }

    if (Array.isArray(value)) {
      return this.t(`${value.length} items`);
    }

    if (typeof value === 'object') {
      return this.t('Object');
    }

    if (typeof value === 'boolean') {
      return this.t(value ? 'Yes' : 'No');
    }

    if (typeof value === 'string') {
      const exactTranslation = this.t(value);
      if (exactTranslation !== value) {
        return exactTranslation;
      }

      if (/[A-Z_]/.test(value) || /[a-z][A-Z]/.test(value)) {
        const pretty = this.prettifyLabel(value);
        const prettyTranslation = this.t(pretty);
        if (prettyTranslation !== pretty) {
          return prettyTranslation;
        }
      }
    }

    return String(value);
  }

  private buildPanelMetrics(data: unknown): SummaryMetric[] {
    const metrics: SummaryMetric[] = [];

    if (Array.isArray(data)) {
      metrics.push({ label: 'Rows', value: `${data.length}` });
      const objectRows = data.filter(this.isRecord);
      if (objectRows.length > 0) {
        metrics.push({ label: 'Columns', value: `${this.pickPreviewKeys(objectRows).length}` });
      }
      return metrics;
    }

    if (this.isRecord(data)) {
      metrics.push({ label: 'Fields', value: `${Object.keys(data).length}` });

      const numericHighlights = Object.entries(data)
        .filter(([, value]) => typeof value === 'number')
        .slice(0, 2)
        .map(([key, value]) => ({ label: this.prettifyLabel(key), value: this.formatPreviewValue(value) }));

      return [...metrics, ...numericHighlights];
    }

    if (data != null) {
      metrics.push({ label: 'Value type', value: typeof data });
    }

    return metrics;
  }

  private buildPanelPreviewRows(data: unknown): LiveRecord[] {
    if (Array.isArray(data)) {
      return data.filter(this.isRecord).slice(0, 4);
    }

    if (this.isRecord(data)) {
      const nestedArray = Object.values(data).find(
        (value): value is LiveRecord[] => Array.isArray(value) && value.some(this.isRecord)
      );
      if (nestedArray) {
        return nestedArray.filter(this.isRecord).slice(0, 4);
      }
    }

    return [];
  }

  private buildPanelNarrative(data: unknown): string {
    if (Array.isArray(data)) {
      return data.length === 0
        ? this.t('No records are currently available for this view.')
        : this.t(`${data.length} records are currently available for review.`);
    }

    if (this.isRecord(data)) {
      return this.t(`${Object.keys(data).length} business fields are currently available for review.`);
    }

    if (data == null) {
      return this.t('No records are currently available for this view.');
    }

    return this.t('Business data is available for review.');
  }

  protected async setCurrentStep(index: number): Promise<void> {
    this.currentStep = index;
    this.updateDerivedState();
    await this.router.navigate([], {
      relativeTo: this.route,
      queryParams: { step: this.visibleJourneySteps[index]?.id ?? null },
      queryParamsHandling: 'merge',
      replaceUrl: true
    });
    await this.refreshCurrentStep();
  }

  protected async goPrevious(): Promise<void> {
    if (this.currentStep === 0) {
      return;
    }

    await this.setCurrentStep(this.currentStep - 1);
  }

  protected async goNext(): Promise<void> {
    if (this.currentStep >= this.visibleJourneySteps.length - 1) {
      return;
    }

    await this.setCurrentStep(this.currentStep + 1);
  }

  protected signOut(): void {
    this.api.clearToken();
    this.session = null;
    this.updateDerivedState();
    this.clearQaStatus();
    void this.router.navigate(['/login']);
  }

  protected async refreshCurrentStep(): Promise<void> {
    await this.execute(async () => {
      this.stepActions = this.getActionsForCurrentStep();
      this.livePanels = this.prepareLivePanels(await this.loadLivePanelsForCurrentStep());
      await this.refreshDynamicFieldOptionsForCurrentStep();
      this.updateDerivedState();
    }, false);
  }

  private async refreshDynamicFieldOptionsForCurrentStep(): Promise<void> {
    this.dynamicFieldOptions = {};

    if (this.journey.id === 'operations-control' && this.currentStepDefinition.id === 'ops-command') {
      const missing = this.asRecordArray(await this.api.get('/api/v1/internal/parties/missing-firm-national-id'));
      this.dynamicFieldOptions['opsMissingNationalIdPartyId'] = missing.length
        ? [
            { label: 'Select a client', value: '' },
            ...missing.map((party) => ({
              label: `${String(party['partyCode'] ?? party['partyId'])} · ${String(party['partyName'] ?? 'Client')} (${String(party['partyType'] ?? '')})`,
              value: String(party['partyId'] ?? '')
            })).filter((option) => option.value)
          ]
        : [{ label: 'No clients are currently missing firm national ID', value: '' }];
      return;
    }

    if (this.journey.id !== 'accounting-office') {
      return;
    }

    const stepId = this.currentStepDefinition.id;
    const supportsClientSelection = stepId === 'ao-clients' || stepId === 'ao-submissions' || stepId === 'ao-reports';
    if (!supportsClientSelection) {
      return;
    }

    const clients = await this.loadAccountingOfficeClientAssignments();
    this.dynamicFieldOptions['aoSelectedPartyId'] = clients.length
      ? [
          { label: 'Auto select first assigned client', value: '' },
          ...clients.map((client) => ({
            label: `${String(client['partyName'] ?? 'Client')} (${String(client['partyCode'] ?? client['partyId'])})`,
            value: String(client['partyId'] ?? '')
          })).filter((option) => option.value)
        ]
      : [{ label: 'No assigned clients found', value: '' }];

    if (stepId === 'ao-submissions') {
      const partyId = await this.resolveAccountingOfficeClientPartyId();
      const workspace = await this.loadAccountingOfficeClientWorkspace(clients);
      const invoices = workspace && Array.isArray((workspace as LiveRecord)['invoices'])
        ? this.asRecordArray((workspace as LiveRecord)['invoices'])
        : [];
      this.dynamicFieldOptions['aoSelectedInvoiceId'] = invoices.length
        ? [
            { label: 'Auto select first invoice', value: '' },
            ...invoices.map((invoice) => ({
              label: `${String(invoice['invoiceNumber'] ?? invoice['accountsReceivableInvoiceId'] ?? invoice['invoiceId'] ?? 'Invoice')} (${String(invoice['statusCode'] ?? '')})`,
              value: String(invoice['accountsReceivableInvoiceId'] ?? invoice['invoiceId'] ?? '')
            })).filter((option) => option.value)
          ]
        : [{ label: 'No assigned client invoices found', value: '' }];

      const statements = this.asRecordArray(await this.api.get(`/api/v1/accounting-office/clients/${partyId}/statements`));
      this.dynamicFieldOptions['aoSelectedStatementId'] = statements.length
        ? [
            { label: 'Auto select first statement', value: '' },
            ...statements.map((statement) => ({
              label: `${String(statement['statementNumber'] ?? statement['statementId'] ?? 'Statement')} · ${String(statement['statementType'] ?? '')}`,
              value: String(statement['statementId'] ?? '')
            })).filter((option) => option.value)
          ]
        : [{ label: 'No assigned client statements found', value: '' }];

      const statementId = this.resolveAccountingOfficeStatementId(statements);
      if (!statementId) {
        this.dynamicFieldOptions['aoSelectedDeliveryLogId'] = [{ label: 'Select a statement first', value: '' }];
        return;
      }

      const deliveryLogs = this.asRecordArray(await this.api.get(`/api/v1/accounting-office/clients/${partyId}/statements/${statementId}/delivery-logs`));
      this.dynamicFieldOptions['aoSelectedDeliveryLogId'] = deliveryLogs.length
        ? [
            { label: 'Auto select latest delivery log', value: '' },
            ...deliveryLogs.map((log) => ({
              label: `${String(log['channelCode'] ?? 'Channel')} · ${String(log['deliveryStatusCode'] ?? '')} (${String(log['deliveryLogId'] ?? '')})`,
              value: String(log['deliveryLogId'] ?? '')
            })).filter((option) => option.value)
          ]
        : [{ label: 'No delivery logs found for the selected statement', value: '' }];
      return;
    }

    if (stepId !== 'ao-clients') {
      return;
    }

    const partyId = await this.resolveAccountingOfficeClientPartyId();
    if (!partyId) {
      this.dynamicFieldOptions['aoSelectedClientRequestId'] = [{ label: 'Select an assigned client first', value: '' }];
      return;
    }

    const requests = this.asRecordArray(await this.api.get(`/api/v1/accounting-office/clients/${partyId}/requests`));
    this.dynamicFieldOptions['aoSelectedClientRequestId'] = requests.length
      ? [
          { label: 'Auto select first request', value: '' },
          ...requests.map((request) => ({
            label: `${String(request['requestNumber'] ?? 'REQ')}: ${String(request['subjectText'] ?? request['requestTypeCode'] ?? '')} (${String(request['statusCode'] ?? '')})`,
            value: String(request['requestId'] ?? '')
          })).filter((option) => option.value)
        ]
      : [{ label: 'No client requests found', value: '' }];
  }

  protected async runAction(actionId: string): Promise<void> {
    await this.execute(async () => {
      switch (actionId) {
        case 'create-supplier-profile':
          await this.createSupplierProfile();
          break;
        case 'submit-supplier-profile':
          await this.requirePartyAction(async (partyId) => {
            await this.api.post(`/api/v1/external/parties/${partyId}/supplier-profile/submit`, {});
          });
          break;
        case 'create-supplier-document':
          await this.createExternalDocumentWorkflow('TAX_CERTIFICATE');
          break;
        case 'upload-supplier-kyc':
          await this.uploadSupplierKycDocument();
          break;
        case 'evaluate-supplier-onboarding':
          await this.evaluateSupplierOnboarding();
          break;
        case 'create-quotation':
          await this.createSupplierQuotation();
          break;
        case 'submit-quotation':
          await this.submitFirstDraftQuotation();
          break;
        case 'acknowledge-purchase-order':
          await this.acknowledgeFirstPurchaseOrder();
          break;
        case 'raise-purchase-order-issue':
          await this.raiseIssueOnFirstPurchaseOrder();
          break;
        case 'create-customer-profile':
          await this.createCustomerProfile();
          break;
        case 'submit-customer-profile':
          await this.requirePartyAction(async (partyId) => {
            await this.api.post(`/api/v1/external/parties/${partyId}/customer-profile/submit`, {});
          });
          break;
        case 'upload-customer-document':
          await this.recordCustomerDocument();
          break;
        case 'upload-customer-kyc':
          await this.uploadCustomerKycDocument();
          break;
        case 'evaluate-customer-onboarding':
          await this.evaluateCustomerOnboarding();
          break;
        case 'create-order':
          await this.createCustomerOrder();
          break;
        case 'submit-order':
          await this.submitFirstDraftOrder();
          break;
        case 'create-service-request':
          await this.createCustomerServiceRequest();
          break;
        case 'submit-service-request':
          await this.submitFirstDraftServiceRequest();
          break;
        case 'escalate-service-request':
          await this.escalateFirstServiceRequest();
          break;
        case 'create-accounting-office-request':
          await this.createExternalAccountingOfficeRequest();
          break;
        case 'create-payment':
          await this.createCustomerPayment();
          break;
        case 'create-financing-request':
          await this.createFinancingRequest();
          break;
        case 'submit-financing-request':
          await this.submitFirstDraftFinancingRequest();
          break;
        case 'accept-financing-offer':
          await this.acceptFirstFinancingOffer();
          break;
        case 'export-current-report-csv':
          this.exportCurrentPanelsAsCsv();
          break;
        case 'export-current-report-pdf':
          this.exportCurrentPanelsAsPdf();
          break;
        case 'export-current-report-excel':
          this.exportCurrentPanelsAsExcel();
          break;
        case 'ao-upload-client-compliance-document':
          await this.uploadAccountingOfficeClientComplianceDocument();
          break;
        case 'ao-submit-client-eta-invoice':
          await this.submitAccountingOfficeClientEtaInvoice();
          break;
        case 'ao-record-client-statement-delivery':
          await this.recordAccountingOfficeClientStatementDelivery();
          break;
        case 'ao-generate-client-statement':
          await this.generateAccountingOfficeClientStatement();
          break;
        case 'ao-acknowledge-client-statement-delivery':
          await this.acknowledgeAccountingOfficeSupplierStatementDelivery();
          break;
        case 'ao-retry-client-statement-delivery':
          await this.retryAccountingOfficeClientStatementDelivery();
          break;
        case 'ao-start-client-request-review':
          await this.startAccountingOfficeClientRequestReview();
          break;
        case 'ao-complete-client-request':
          await this.completeAccountingOfficeClientRequest();
          break;
        case 'ao-reject-client-request':
          await this.rejectAccountingOfficeClientRequest();
          break;
        case 'export-accounting-office-client-report-csv':
          await this.exportAccountingOfficeClientReportCsv();
          break;
        case 'print-current-report':
          this.printCurrentReport();
          break;
        case 'export-core-finance-csv':
          await this.exportDownload(
            this.buildQueryPath('/api/v1/reports/core-finance/export', {
              dateFrom: this.optionalValue('operationsDateFrom') ?? this.optionalValue('financialDateFrom'),
              dateTo: this.optionalValue('operationsDateTo') ?? this.optionalValue('financialDateTo')
            }),
            'core-finance-report.csv'
          );
          break;
        case 'export-finance-stakeholders-csv':
          await this.exportDownload(
            this.buildQueryPath('/api/v1/reports/finance-stakeholders/export', {
              dateFrom: this.optionalValue('operationsDateFrom'),
              dateTo: this.optionalValue('operationsDateTo')
            }),
            'finance-stakeholders.csv'
          );
          break;
        case 'export-financial-statement':
          await this.exportFinancialStatement();
          break;
        case 'export-core-operations-csv':
          await this.exportDownload(
            this.buildQueryPath('/api/v1/reports/core-operations/export', {
              dateFrom: this.optionalValue('operationsDateFrom'),
              dateTo: this.optionalValue('operationsDateTo')
            }),
            'core-operations-report.csv'
          );
          break;
        case 'export-aging-ar-csv':
          await this.exportDownload(
            this.buildQueryPath('/api/v1/reports/aging/ar/export', {
              asOfDate: this.optionalValue('operationsAsOfDate')
            }),
            'accounts-receivable-aging.csv'
          );
          break;
        case 'export-aging-ap-csv':
          await this.exportDownload(
            this.buildQueryPath('/api/v1/reports/aging/ap/export', {
              asOfDate: this.optionalValue('operationsAsOfDate')
            }),
            'accounts-payable-aging.csv'
          );
          break;
        case 'export-if-pipeline-csv':
          await this.exportDownload(
            this.buildQueryPath('/api/v1/reports/islamic-finance/pipeline/export', {
              dateFrom: this.optionalValue('operationsDateFrom'),
              dateTo: this.optionalValue('operationsDateTo')
            }),
            'islamic-finance-pipeline.csv'
          );
          break;
        case 'create-report-snapshot':
          await this.createReportSnapshot();
          break;
        case 'process-eta-notification-batch':
          await this.processEtaNotificationBatch();
          break;
        case 'reprocess-eta-notification':
          await this.reprocessFirstEtaNotification();
          break;
        case 'inspect-fawry-status-contract':
          await this.inspectFawryContract('status');
          break;
        case 'inspect-fawry-cancel-contract':
          await this.inspectFawryContract('cancel');
          break;
        case 'ops-create-supplier-onboarding':
          await this.createOperationsSupplierOnboarding();
          break;
        case 'ops-create-customer-onboarding':
          await this.createOperationsCustomerOnboarding();
          break;
        case 'ao-create-supplier-onboarding':
          await this.createAccountingOfficeSupplierOnboarding();
          break;
        case 'ao-create-customer-onboarding':
          await this.createAccountingOfficeCustomerOnboarding();
          break;
        case 'ops-approve-onboarding':
          await this.reviewOperationsOnboardingCase('approve');
          break;
        case 'ops-return-onboarding':
          await this.reviewOperationsOnboardingCase('return');
          break;
        case 'ops-reject-onboarding':
          await this.reviewOperationsOnboardingCase('reject');
          break;
        case 'ops-hold-onboarding':
          await this.reviewOperationsOnboardingCase('hold');
          break;
        case 'ops-reopen-onboarding':
          await this.reviewOperationsOnboardingCase('reopen');
          break;
        case 'ops-record-firm-national-id':
          await this.recordOperationsFirmNationalId();
          break;
        default:
          this.messageText = `No business action is configured yet for ${actionId}.`;
          break;
      }

      await this.refreshCurrentStep();
    });
  }

  private async initializeJourney(): Promise<void> {
    const session = this.api.currentSession;

    if (!session) {
      const returnPath = this.router.url.split('?')[0] || resolveJourneyRoute(this.journey.id);
      await this.router.navigate(
        ['/login'],
        { queryParams: { returnUrl: returnPath } }
      );
      return;
    }

    if (!canAccessJourney(session, this.journey.id)) {
      const fallbackJourneyId = defaultJourneyId(session);
      await this.router.navigateByUrl(
        `${resolveJourneyRoute(fallbackJourneyId)}?step=${encodeURIComponent(getDefaultJourneyStepId(fallbackJourneyId))}`
      );
      return;
    }

    this.session = session;
    const requestedStep = this.route.snapshot.queryParamMap.get('step');
    const effectiveStepId = requestedStep || this.resolveDefaultStepId();
    const requestedStepIndex = effectiveStepId
      ? this.visibleJourneySteps.findIndex((step) => step.id === effectiveStepId)
      : -1;
    this.currentStep = requestedStepIndex >= 0 ? requestedStepIndex : 0;
    const normalizedStepId = this.visibleJourneySteps[this.currentStep]?.id ?? null;
    if (!requestedStep || requestedStepIndex < 0) {
      await this.router.navigate([], {
        relativeTo: this.route,
        queryParams: { step: normalizedStepId },
        queryParamsHandling: 'merge',
        replaceUrl: true
      });
    }
    this.stepActions = this.getActionsForCurrentStep();
    this.updateDerivedState();
    this.markQaReady();
    void this.refreshCurrentStep();
  }

  private markQaReady(): void {
    if (typeof document === 'undefined' || this.qaRoute !== this.journey.id) {
      return;
    }

    const text = (document.body.textContent || '').replace(/\s+/g, ' ').trim();
    const expectedMatched = !this.qaExpected || text.includes(this.qaExpected) || document.title.includes(this.qaExpected);
    document.body.dataset['qaStatus'] = expectedMatched ? 'passed' : 'failed';
    document.body.dataset['qaEvidence'] = JSON.stringify({
      route: this.journey.id,
      expectedText: this.qaExpected,
      title: document.title,
      workspace: this.session?.workspace ?? null,
      renderedJourneyId: this.journey.id,
      textSample: text.slice(0, 260)
    });
  }

  private clearQaStatus(): void {
    if (typeof document === 'undefined') {
      return;
    }

    delete document.body.dataset['qaStatus'];
    delete document.body.dataset['qaEvidence'];
  }

  private pickPreviewKeys(rows: LiveRecord[]): string[] {
    const keySet = new Set<string>();
    rows.forEach((row) => {
      Object.keys(row).slice(0, 6).forEach((key) => keySet.add(key));
    });
    return Array.from(keySet).slice(0, 6);
  }

  private countRecords(data: unknown): number {
    if (Array.isArray(data)) {
      return data.length;
    }

    if (this.isRecord(data)) {
      const nestedArrays = Object.values(data).filter((value): value is unknown[] => Array.isArray(value));
      if (nestedArrays.length > 0) {
        return nestedArrays.reduce<number>((sum, value) => sum + this.countRecords(value), 0);
      }
      return Object.keys(data).length > 0 ? 1 : 0;
    }

    return data == null ? 0 : 1;
  }

  private isRecord(value: unknown): value is LiveRecord {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
  }

  private async loadLivePanelsForCurrentStep(): Promise<RawLivePanel[]> {
    const panels: RawLivePanel[] = [];
    const partyId = this.session?.partyId ?? null;

    if (!this.session) {
      return [
        {
          title: 'Sign-in required',
          description: 'A signed-in session is required to load live business data.',
          data: null
        }
      ];
    }

    switch (`${this.journey.id}:${this.currentStepDefinition.id}`) {
      case 'supplier:supplier-access':
        panels.push({
          title: 'Journey session',
          description: 'Current external supplier account and session context.',
          data: this.session
        });
        break;
      case 'supplier:supplier-profile':
        panels.push(...await this.requirePartyPanels(partyId, async (resolvedPartyId) => [
          {
            title: 'Supplier profile',
            description: 'Current supplier profile returned by the external profile API.',
            data: await this.getAllowNotFound(`/api/v1/external/parties/${resolvedPartyId}/supplier-profile`)
          },
          {
            title: 'Supplier compliance documents',
            description: 'Current KYC and compliance evidence attached to the supplier onboarding case.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/compliance-documents`)
          },
          {
            title: 'Supplier onboarding policy',
            description: 'Current supplier KYC fields and document checklist requirements.',
            data: await this.api.get('/api/v1/onboarding/policies/suppliers')
          },
          {
            title: 'Supplier onboarding completeness',
            description: 'Live completeness evaluation for the current supplier onboarding payload.',
            data: await this.buildSupplierOnboardingEvaluation(resolvedPartyId)
          },
          {
            title: 'Document workflows',
            description: 'Governed document workflow items attached to the supplier journey.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/document-workflows`)
          }
        ]));
        break;
      case 'supplier:supplier-procurement':
        panels.push(...await this.requirePartyPanels(partyId, async (resolvedPartyId) => [
          {
            title: 'RFQ invitations',
            description: 'Live sourcing invitations for the supplier portal.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/rfq-invitations`)
          },
          {
            title: 'Quotations',
            description: 'Existing external supplier quotations in the current journey.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/quotations`)
          }
        ]));
        break;
      case 'supplier:supplier-orders':
        panels.push(...await this.requirePartyPanels(partyId, async (resolvedPartyId) => [
          {
            title: 'Purchase orders',
            description: 'Supplier-facing purchase order collaboration records.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/purchase-orders`)
          }
        ]));
        break;
      case 'supplier:supplier-finance':
        panels.push(...await this.loadExternalFinancingPanels(partyId));
        break;
      case 'supplier:supplier-reports':
        panels.push(...await this.loadExternalStatementPanels(partyId, 'supplier'));
        break;
      case 'customer:customer-context':
        panels.push({
          title: 'Journey session',
          description: 'Current external customer account and session context.',
          data: this.session
        });
        panels.push(...await this.requirePartyPanels(partyId, async (resolvedPartyId) => [
          {
            title: 'Customer status tracking',
            description: 'Business-facing status feed for the customer journey.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/customer-status-tracking`)
          }
        ]));
        break;
      case 'customer:customer-profile':
        panels.push(...await this.requirePartyPanels(partyId, async (resolvedPartyId) => [
          {
            title: 'Customer profile',
            description: 'Current customer profile returned by the external onboarding API.',
            data: await this.getAllowNotFound(`/api/v1/external/parties/${resolvedPartyId}/customer-profile`)
          },
          {
            title: 'Customer compliance documents',
            description: 'Current KYC and compliance evidence attached to the customer onboarding case.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/compliance-documents`)
          },
          {
            title: 'Customer onboarding policy',
            description: 'Current customer KYC fields and document checklist requirements.',
            data: await this.api.get('/api/v1/onboarding/policies/customers')
          },
          {
            title: 'Customer onboarding completeness',
            description: 'Live completeness evaluation for the current customer onboarding payload.',
            data: await this.buildCustomerOnboardingEvaluation(resolvedPartyId)
          },
          {
            title: 'Customer documents',
            description: 'Current customer document and statement exposure.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/customer-documents`)
          }
        ]));
        break;
      case 'operations-control:ops-onboarding':
      case 'accounting-office:ao-onboarding':
        const onboardingSubjectType = this.resolveOnboardingSubjectType();
        const isAccountingOfficeLane = this.journey.id === 'accounting-office';
        const leads = isAccountingOfficeLane
          ? []
          : this.asRecordArray(await this.api.get(`/api/v1/internal/onboarding/leads?prospectTypeCode=${encodeURIComponent(onboardingSubjectType)}`));
        const cases = this.asRecordArray(await this.api.get(`/api/v1/internal/onboarding/cases?subjectTypeCode=${encodeURIComponent(onboardingSubjectType)}`));
        const selectedCase = this.resolveSelectedOnboardingCaseRecord(cases);

        if (!isAccountingOfficeLane) {
          panels.push({
            title: 'Onboarding leads',
            description: 'Incoming supplier and customer leads captured before full onboarding starts.',
            data: leads
          });
        }
        panels.push({
          title: 'Onboarding cases',
          description: isAccountingOfficeLane
            ? 'Current supplier and customer onboarding cases submitted by this accounting office and routed into CashLine Operations review.'
            : 'Live supplier and customer onboarding cases for operations review.',
          data: cases
        });
        if (selectedCase) {
          const profileId = this.getNumericId(selectedCase, ['subjectProfileId']);
          if (profileId) {
            this.formValues['onboardingCaseProfileId'] = String(profileId);
            panels.push({
              title: 'Selected onboarding case',
              description: isAccountingOfficeLane
                ? 'Case details visible after the accounting office submits the onboarding package for Operations review.'
                : 'Case details used by Operations for review, approval, and follow-up decisions.',
              data: await this.api.get(`/api/v1/internal/onboarding/cases/${encodeURIComponent(onboardingSubjectType)}/${profileId}`)
            });
            panels.push({
              title: 'Selected onboarding review',
              description: isAccountingOfficeLane
                ? 'Current KYC checklist and review findings visible after submission into the Operations-controlled cycle.'
                : 'Current KYC checklist and review findings for the selected onboarding case.',
              data: await this.api.get(`/api/v1/${onboardingSubjectType === 'SUPPLIER' ? 'suppliers' : 'customers'}/${profileId}/onboarding-review`)
            });
          }
        }
        panels.push({
          title: 'Workflow monitoring',
          description: isAccountingOfficeLane
            ? 'Submission monitoring remains visible beside onboarding intake so the office can follow what has moved into Operations.'
            : 'Operational monitoring remains visible beside onboarding review.',
          data: await this.api.get('/api/v1/internal/workflow-monitoring?itemLimit=5')
        });
        break;
      case 'customer:customer-orders':
        panels.push(...await this.requirePartyPanels(partyId, async (resolvedPartyId) => [
          {
            title: 'Orders',
            description: 'Customer-originated commercial requests and orders.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/orders`)
          },
          {
            title: 'Deliveries',
            description: 'Customer delivery visibility from the live portal API.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/customer-deliveries`)
          }
        ]));
        break;
      case 'customer:customer-service':
        panels.push(...await this.requirePartyPanels(partyId, async (resolvedPartyId) => [
          {
            title: 'Service requests',
            description: 'Live customer disputes and service follow-up records.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/service-requests`)
          }
        ]));
        break;
      case 'supplier:supplier-accounting-office':
      case 'customer:customer-accounting-office':
        panels.push(...await this.requirePartyPanels(partyId, async (resolvedPartyId) => [
          {
            title: 'Accounting office notices',
            description: 'Notices and evidence sent to the assigned accounting office for governed follow-up.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/accounting-office/requests`)
          }
        ]));
        break;
      case 'customer:customer-payments':
        panels.push(...await this.requirePartyPanels(partyId, async (resolvedPartyId) => [
          {
            title: 'Invoices',
            description: 'Customer-visible invoice records used for payment initiation.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/invoices`)
          },
          {
            title: 'Payments',
            description: 'Current external customer payment requests and statuses.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/payments`)
          }
        ]));
        panels.push(...await this.loadExternalFinancingPanels(partyId));
        break;
      case 'customer:customer-reports':
        panels.push(...await this.loadExternalStatementPanels(partyId, 'customer'));
        break;
      case 'islamic-finance:if-product':
        panels.push(...await this.requirePartyPanels(partyId, async (resolvedPartyId) => [
          {
            title: 'Financing products',
            description: 'Product families exposed to the selected external applicant.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/financing/products`)
          },
          {
            title: 'Product families',
            description: 'Product-family readiness and future-route posture.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/financing/product-families`)
          }
        ]));
        break;
      case 'islamic-finance:if-application':
        panels.push({
          title: 'Officer workbench',
          description: 'Internal Islamic Finance officer queue aligned behind externally initiated applications.',
          data: await this.api.get('/api/v1/islamic-finance/officer-workbench')
        });
        panels.push(...await this.requirePartyPanels(partyId, async (resolvedPartyId) => [
          {
            title: 'External financing requests',
            description: 'Applicant-facing financing requests for the selected external party.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/financing/requests`)
          }
        ]));
        break;
      case 'islamic-finance:if-offers':
        panels.push(...await this.loadExternalFinancingPanels(partyId));
        panels.push({
          title: 'Partner orchestration',
          description: 'Partner-lender orchestration is shown when a request exists and the user switches to the internal IF lane.',
          data: await this.loadPartnerOrchestrationPreview()
        });
        break;
      case 'islamic-finance:if-execution':
        panels.push({
          title: 'Execution workbench',
          description: 'Internal execution and settlement queue for Islamic Finance operations.',
          data: await this.api.get('/api/v1/islamic-finance/execution-workbench')
        });
        panels.push(...await this.requirePartyPanels(partyId, async (resolvedPartyId) => [
          {
            title: 'Execution cases',
            description: 'External execution views covering Murabaha, receivables, disbursements, and settlements.',
            data: {
              murabahaCases: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/financing/murabaha-cases`),
              receivablesCases: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/financing/receivables-cases`),
              disbursements: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/financing/disbursements`),
              settlements: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/financing/settlements`)
            }
          }
        ]));
        break;
      case 'finance-approvals:fa-intake':
        panels.push({
          title: 'Approval queue',
          description: 'Finance approval queue for the current signed-in finance user.',
          data: await this.api.get('/api/v1/internal/work-queues/approvals?moduleCode=FINANCE')
        });
        break;
      case 'finance-approvals:fa-review':
        panels.push({
          title: 'Exception queue',
          description: 'Finance exceptions and workflow attention items.',
          data: await this.api.get('/api/v1/internal/work-queues/exceptions')
        });
        panels.push({
          title: 'Workflow monitoring',
          description: 'Real-time workflow monitoring for controlled finance review.',
          data: await this.api.get('/api/v1/internal/workflow-monitoring?itemLimit=8')
        });
        break;
      case 'finance-approvals:fa-gateway':
        panels.push({
          title: 'Workflow monitoring',
          description: 'Live operational picture for gateway-related finance work.',
          data: await this.api.get('/api/v1/internal/workflow-monitoring?itemLimit=8')
        });
        panels.push(...await this.loadFawryOperationPanels(true));
        panels.push(...await this.requirePartyPanels(partyId, async (resolvedPartyId) => [
          {
            title: 'Customer payment status',
            description: 'Gateway-related customer payment visibility from the external side.',
            data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/payments`)
          }
        ]));
        break;
      case 'finance-approvals:fa-posting':
        panels.push({
          title: 'Approval queue',
          description: 'Final finance approval visibility before posting and sign-off.',
          data: await this.api.get('/api/v1/internal/work-queues/approvals?moduleCode=FINANCE')
        });
        panels.push({
          title: 'Execution workbench',
          description: 'Islamic Finance execution context visible to the approval lane.',
          data: await this.api.get('/api/v1/islamic-finance/execution-workbench')
        });
        break;
      case 'finance-approvals:fa-eta':
        panels.push(...await this.loadEtaNotificationPanels(true));
        break;
      case 'accounting-office:ao-command':
        const accountingOfficeClients = this.asRecordArray(await this.api.get('/api/v1/accounting-office/clients'));
        const accountingOfficeCases = this.asRecordArray(await this.api.get('/api/v1/internal/onboarding/cases'));
        const accountingOfficeActivity = await this.loadAccountingOfficeActivity();
        panels.push({
          title: 'Assigned clients',
          description: 'Client companies currently assigned to the signed-in accounting office user.',
          data: accountingOfficeClients
        });
        panels.push({
          title: 'Onboarding cases submitted by this office',
          description: 'Only onboarding cases submitted by the signed-in accounting office user are visible here for follow-up.',
          data: accountingOfficeCases
        });
        panels.push({
          title: 'Delegated activity trail',
          description: 'Recent delegated actions performed by this accounting office user across assigned clients.',
          data: accountingOfficeActivity
        });
        break;
      case 'operations-control:ops-command':
        panels.push({
          title: 'Workflow queue',
          description: 'Current operational queue posture for the signed-in internal user.',
          data: await this.api.get('/api/v1/workflow/queue')
        });
        panels.push({
          title: 'Workflow monitoring',
          description: 'Live monitoring view of workflow attention items.',
          data: await this.api.get('/api/v1/internal/workflow-monitoring?itemLimit=8')
        });
        panels.push({
          title: 'Clients missing firm national ID (Egypt)',
          description: 'Onboarded supplier and customer clients that were activated without الرقم القومي للمنشأة. Operations should follow up and record it when available.',
          data: await this.api.get('/api/v1/internal/parties/missing-firm-national-id')
        });
        break;
      case 'operations-control:ops-execution':
        panels.push({
          title: 'Core operations report',
          description: 'Live operational reporting summary inside the business workspace.',
          data: await this.api.get('/api/v1/reports/core-operations')
        });
        panels.push({
          title: 'Exception queue',
          description: 'Current internal exceptions affecting operational throughput.',
          data: await this.api.get('/api/v1/internal/work-queues/exceptions')
        });
        break;
      case 'accounting-office:ao-clients':
        const assignedClients = await this.loadAccountingOfficeClientAssignments();
        const assignedWorkspace = await this.loadAccountingOfficeClientWorkspace(assignedClients);
        const assignedPartyId = Number(this.formValues['aoSelectedPartyId'] ?? '');
        const assignedClientRequests = Number.isFinite(assignedPartyId) && assignedPartyId > 0
          ? this.asRecordArray(await this.api.get(`/api/v1/accounting-office/clients/${assignedPartyId}/requests`))
          : [];
        const assignedActivity = await this.loadAccountingOfficeActivity();
        panels.push({
          title: 'Assigned clients',
          description: 'Only client parties explicitly assigned to this accounting office user are visible here.',
          data: assignedClients
        });
        panels.push({
          title: 'Client requests',
          description: 'Governed client notices submitted by assigned suppliers or customers for accounting-office follow-up.',
          data: assignedClientRequests
        });
        if (assignedWorkspace) {
          panels.push({
            title: 'Selected client workspace',
            description: 'Scoped client detail for finance, statements, payments, and compliance follow-up.',
            data: assignedWorkspace
          });
        }
        panels.push({
          title: 'Delegated activity trail',
          description: 'Recent delegated client-service actions performed by this accounting office user.',
          data: assignedActivity
        });
        break;
      case 'accounting-office:ao-submissions':
        const submissionClients = await this.loadAccountingOfficeClientAssignments();
        const submissionPosture = await this.loadAccountingOfficeSubmissionPosture(submissionClients);
        const submissionWorkspace = await this.loadAccountingOfficeClientWorkspace(submissionClients);
        const submissionEtaPanels = await this.loadAccountingOfficeInvoiceEtaPanels(submissionWorkspace);
        const submissionStatementPanels = await this.loadAccountingOfficeStatementPanels(submissionClients);
        const submissionActivity = await this.loadAccountingOfficeActivity();
        panels.push({
          title: 'Assigned clients',
          description: 'Client parties available to the accounting office for governed submission follow-up.',
          data: submissionClients
        });
        if (submissionPosture) {
          panels.push({
            title: 'Selected client submission posture',
            description: 'Client-specific governed readiness summary used by the accounting office before ETA or government follow-up.',
            data: submissionPosture
          });
        }
        if (submissionWorkspace) {
          panels.push({
            title: 'Selected client submission workspace',
            description: 'Current client invoices and supporting workspace detail used before delegated ETA submission.',
            data: submissionWorkspace
          });
        }
        panels.push(...submissionEtaPanels);
        panels.push(...submissionStatementPanels);
        panels.push({
          title: 'Delegated submission activity',
          description: 'Submission-facing activity recorded for this accounting office user and selected client set.',
          data: submissionActivity
        });
        panels.push({
          title: 'Workflow monitoring',
          description: 'Submission-related workflow monitoring remains visible beside ETA readiness.',
          data: await this.api.get('/api/v1/internal/workflow-monitoring?itemLimit=5')
        });
        break;
      case 'accounting-office:ao-reports':
        const reportingClients = await this.loadAccountingOfficeClientAssignments();
        const reportingWorkspace = await this.loadAccountingOfficeClientWorkspace(reportingClients);
        const reportingActivity = await this.loadAccountingOfficeActivity();
        panels.push({
          title: 'Assigned clients',
          description: 'Client parties available to the accounting office for governed reporting.',
          data: reportingClients
        });
        if (reportingWorkspace) {
          panels.push({
            title: 'Selected client reporting workspace',
            description: 'Scoped client-facing statement, invoice, payment, and compliance outputs for reporting follow-up.',
            data: reportingWorkspace
          });
        }
        panels.push({
          title: 'Delegated reporting activity',
          description: 'Recent report-view and export actions recorded for this accounting office user.',
          data: reportingActivity
        });
        break;
      case 'reporting-center:reports-summary':
        panels.push(...await this.loadFinanceAssignedReportPanels());
        break;
      case 'reporting-center:reports-financial-statements':
        panels.push({
          title: 'Financial statement',
          description: 'Selected finance or accounting report for review, print, and export.',
          data: await this.api.get(this.buildQueryPath(
            `/api/v1/reports/finance-statements/${encodeURIComponent(this.resolveFinancialStatementCode())}`,
            {
              dateFrom: this.optionalValue('financialDateFrom'),
              dateTo: this.optionalValue('financialDateTo'),
              asOfDate: this.optionalValue('financialAsOfDate') ?? this.today()
            }))
        });
        break;
      case 'reporting-center:reports-operations':
        panels.push(...await this.loadOperationsAssignedReportPanels());
        break;
      case 'reporting-center:reports-snapshots':
        panels.push({
          title: 'Snapshot library',
          description: 'Reporting snapshots already captured by the system.',
          data: await this.api.get('/api/v1/reports/snapshots')
        });
        break;
      case 'workflow-monitoring:monitor-live':
        panels.push({
          title: 'Workflow monitoring',
          description: 'Live end-to-end monitoring posture with current item limit.',
          data: await this.api.get(`/api/v1/internal/workflow-monitoring?itemLimit=${this.valueOrDefault('monitorItemLimit', '10')}`)
        });
        panels.push({
          title: 'Workflow queue',
          description: 'Queue view aligned to the monitoring posture.',
          data: await this.api.get('/api/v1/workflow/queue')
        });
        break;
      case 'workflow-monitoring:monitor-exceptions':
        panels.push({
          title: 'Exception lanes',
          description: 'Current workflow, collections, and reconciliation exceptions.',
          data: {
            workflowExceptions: await this.api.get('/api/v1/internal/work-queues/exceptions'),
            collectionsQueue: await this.api.get('/api/v1/finance/collections/queue'),
            reconciliationExceptions: await this.api.get('/api/v1/finance/reconciliations/exception-queue')
          }
        });
        break;
      case 'workflow-monitoring:monitor-notifications':
        panels.push({
          title: 'Notification queue',
          description: 'Current notification backlog and downstream signal posture.',
          data: await this.api.get('/api/v1/notifications/queue')
        });
        panels.push(...await this.loadEtaNotificationPanels(false));
        panels.push(...await this.loadFawryOperationPanels(false));
        panels.push({
          title: 'Workflow monitoring',
          description: 'Live workflow posture kept alongside the notification lane.',
          data: await this.api.get('/api/v1/internal/workflow-monitoring?itemLimit=5')
        });
        break;
      default:
        panels.push({
          title: 'Journey session',
          description: 'This step is available, but no business data has been loaded for it yet.',
          data: this.session
        });
        break;
    }

    return panels;
  }

  private prepareLivePanels(panels: RawLivePanel[]): LivePanel[] {
    return panels.map((panel) => {
      const previewRows = this.buildPanelPreviewRows(panel.data);
      return {
        ...panel,
        metrics: this.buildPanelMetrics(panel.data),
        previewRows,
        previewKeys: this.pickPreviewKeys(previewRows),
        narrative: this.buildPanelNarrative(panel.data)
      };
    });
  }

  private updateDerivedState(): void {
    this.workspaceBadgeTextValue = this.session?.workspace
      ? `${this.session.workspace} workspace`
      : 'Business workspace';

    this.stageSummaryMetricsValue = [
      { label: 'Owner', value: this.currentStepDefinition.internalLane },
      { label: 'Expected result', value: this.currentStepDefinition.output },
      { label: 'Control points', value: `${this.currentStepDefinition.checkpoints.length}` },
      { label: 'Available actions', value: `${this.stepActions.length}` }
    ];

    const panelRecords = this.livePanels.reduce((sum, panel) => sum + this.countRecords(panel.data), 0);
    this.workbenchMetricsValue = [
      { label: 'Current function', value: this.currentStepDefinition.internalLane },
      { label: 'Connected panels', value: `${this.livePanels.length}` },
      { label: 'Records in view', value: `${panelRecords}` },
      { label: 'Session status', value: this.session ? 'Active' : 'Not connected' }
    ];
  }

  private getActionsForCurrentStep(): StepAction[] {
    switch (`${this.journey.id}:${this.currentStepDefinition.id}`) {
      case 'supplier:supplier-profile':
        return [
          { id: 'create-supplier-profile', label: 'Create supplier profile' },
          { id: 'submit-supplier-profile', label: 'Submit supplier profile', emphasis: 'secondary' },
          { id: 'upload-supplier-kyc', label: 'Upload supplier KYC document', emphasis: 'secondary' },
          { id: 'evaluate-supplier-onboarding', label: 'Evaluate supplier onboarding', emphasis: 'secondary' },
          { id: 'create-supplier-document', label: 'Create sample document workflow', emphasis: 'secondary' }
        ];
      case 'supplier:supplier-procurement':
        return [
          { id: 'create-quotation', label: 'Create quotation' },
          { id: 'submit-quotation', label: 'Submit first draft quotation', emphasis: 'secondary' }
        ];
      case 'supplier:supplier-orders':
        return [
          { id: 'acknowledge-purchase-order', label: 'Acknowledge first purchase order' },
          { id: 'raise-purchase-order-issue', label: 'Raise issue on first purchase order', emphasis: 'secondary' }
        ];
      case 'supplier:supplier-reports':
      case 'customer:customer-reports':
        return [
          { id: 'export-current-report-csv', label: 'Export current report as CSV' },
          { id: 'export-current-report-excel', label: 'Export current report as Excel', emphasis: 'secondary' },
          { id: 'print-current-report', label: 'Print current report', emphasis: 'secondary' }
        ];
      case 'customer:customer-profile':
        return [
          { id: 'create-customer-profile', label: 'Create customer profile' },
          { id: 'submit-customer-profile', label: 'Submit customer profile', emphasis: 'secondary' },
          { id: 'upload-customer-kyc', label: 'Upload customer KYC document', emphasis: 'secondary' },
          { id: 'evaluate-customer-onboarding', label: 'Evaluate customer onboarding', emphasis: 'secondary' },
          { id: 'upload-customer-document', label: 'Upload customer document', emphasis: 'secondary' }
        ];
      case 'customer:customer-orders':
        return [
          { id: 'create-order', label: 'Create customer order' },
          { id: 'submit-order', label: 'Submit first draft order', emphasis: 'secondary' }
        ];
      case 'customer:customer-service':
        return [
          { id: 'create-service-request', label: 'Create service request' },
          { id: 'submit-service-request', label: 'Submit first draft request', emphasis: 'secondary' },
          { id: 'escalate-service-request', label: 'Escalate first active request', emphasis: 'secondary' }
        ];
      case 'supplier:supplier-accounting-office':
      case 'customer:customer-accounting-office':
        return [
          { id: 'create-accounting-office-request', label: 'Create accounting office notice' }
        ];
      case 'customer:customer-payments':
        return [
          { id: 'create-payment', label: 'Create payment request' },
          { id: 'create-financing-request', label: 'Create financing request', emphasis: 'secondary' },
          { id: 'submit-financing-request', label: 'Submit first draft financing request', emphasis: 'secondary' }
        ];
      case 'supplier:supplier-finance':
      case 'islamic-finance:if-application':
      case 'islamic-finance:if-offers':
      case 'islamic-finance:if-execution':
        return [
          { id: 'create-financing-request', label: 'Create financing request' },
          { id: 'submit-financing-request', label: 'Submit first draft financing request', emphasis: 'secondary' },
          { id: 'accept-financing-offer', label: 'Accept first available offer', emphasis: 'secondary' }
        ];
      case 'operations-control:ops-command':
        return [
          { id: 'ops-record-firm-national-id', label: 'Record firm national ID for selected client', emphasis: 'secondary' }
        ];
      case 'operations-control:ops-onboarding':
        return [
          { id: 'ops-create-supplier-onboarding', label: 'Create supplier onboarding case' },
          { id: 'ops-create-customer-onboarding', label: 'Create customer onboarding case', emphasis: 'secondary' },
          { id: 'ops-approve-onboarding', label: 'Approve selected onboarding case', emphasis: 'secondary' },
          { id: 'ops-return-onboarding', label: 'Return selected case for rework', emphasis: 'secondary' },
          { id: 'ops-reject-onboarding', label: 'Reject selected onboarding case', emphasis: 'secondary' },
          { id: 'ops-hold-onboarding', label: 'Hold selected onboarding case', emphasis: 'secondary' },
          { id: 'ops-reopen-onboarding', label: 'Reopen selected onboarding case', emphasis: 'secondary' }
        ];
      case 'accounting-office:ao-onboarding':
        return [
          { id: 'ao-create-supplier-onboarding', label: 'Create supplier onboarding case' },
          { id: 'ao-create-customer-onboarding', label: 'Create customer onboarding case', emphasis: 'secondary' }
        ];
      case 'accounting-office:ao-clients':
        return [
          { id: 'ao-start-client-request-review', label: 'Start review on selected client request' },
          { id: 'ao-complete-client-request', label: 'Complete selected client request', emphasis: 'secondary' },
          { id: 'ao-reject-client-request', label: 'Reject selected client request', emphasis: 'secondary' }
        ];
      case 'accounting-office:ao-reports':
        return [
          { id: 'export-accounting-office-client-report-csv', label: 'Export selected client report as CSV' },
          { id: 'export-current-report-csv', label: 'Export current report as CSV', emphasis: 'secondary' },
          { id: 'export-current-report-excel', label: 'Export current report as Excel', emphasis: 'secondary' },
          { id: 'print-current-report', label: 'Print current report', emphasis: 'secondary' }
        ];
      case 'accounting-office:ao-submissions':
        return [
          { id: 'ao-upload-client-compliance-document', label: 'Upload client compliance document' },
          { id: 'ao-submit-client-eta-invoice', label: 'Submit selected client invoice to ETA', emphasis: 'secondary' },
          { id: 'ao-generate-client-statement', label: 'Generate client statement', emphasis: 'secondary' },
          { id: 'ao-record-client-statement-delivery', label: 'Record selected client statement delivery', emphasis: 'secondary' },
          { id: 'ao-acknowledge-client-statement-delivery', label: 'Acknowledge selected supplier delivery log', emphasis: 'secondary' },
          { id: 'ao-retry-client-statement-delivery', label: 'Retry selected statement delivery log', emphasis: 'secondary' }
        ];
      case 'reporting-center:reports-snapshots':
        return [
          { id: 'create-report-snapshot', label: 'Create reporting snapshot' },
          { id: 'export-current-report-csv', label: 'Export current report as CSV', emphasis: 'secondary' },
          { id: 'print-current-report', label: 'Print current report', emphasis: 'secondary' }
        ];
      case 'reporting-center:reports-summary':
        return [
          { id: 'export-current-report-csv', label: 'Export current finance report set as CSV' },
          { id: 'export-current-report-pdf', label: 'Export current finance report set as PDF', emphasis: 'secondary' },
          { id: 'export-core-finance-csv', label: 'Export core finance as CSV' },
          { id: 'export-finance-stakeholders-csv', label: 'Export finance stakeholders as CSV', emphasis: 'secondary' },
          { id: 'print-current-report', label: 'Print current report', emphasis: 'secondary' }
        ];
      case 'reporting-center:reports-financial-statements':
        return [
          { id: 'export-financial-statement', label: 'Export selected financial report' },
          { id: 'print-current-report', label: 'Print current report', emphasis: 'secondary' }
        ];
      case 'reporting-center:reports-operations':
        return [
          { id: 'export-current-report-csv', label: 'Export current operations report set as CSV' },
          { id: 'export-current-report-pdf', label: 'Export current operations report set as PDF', emphasis: 'secondary' },
          { id: 'export-core-operations-csv', label: 'Export core operations as CSV' },
          { id: 'print-current-report', label: 'Print current report', emphasis: 'secondary' }
        ];
      case 'finance-approvals:fa-gateway':
        return [
          { id: 'inspect-fawry-status-contract', label: 'Inspect latest Fawry status contract' },
          { id: 'inspect-fawry-cancel-contract', label: 'Inspect latest Fawry cancel contract', emphasis: 'secondary' }
        ];
      case 'finance-approvals:fa-eta':
      case 'workflow-monitoring:monitor-notifications':
        return [
          { id: 'process-eta-notification-batch', label: 'Process ETA notification batch' },
          { id: 'reprocess-eta-notification', label: 'Reprocess first unresolved ETA notification', emphasis: 'secondary' },
          { id: 'inspect-fawry-status-contract', label: 'Inspect latest Fawry status contract', emphasis: 'secondary' },
          { id: 'inspect-fawry-cancel-contract', label: 'Inspect latest Fawry cancel contract', emphasis: 'secondary' }
        ];
      default:
        return [];
    }
  }

  private async createSupplierProfile(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      await this.api.post(`/api/v1/external/parties/${partyId}/supplier-profile`, {
        businessPartyId: partyId,
        firmNationalId: this.optionalValue('firmNationalId'),
        supplierCategoryCode: this.valueOrDefault('supplierCategory', 'CORE_SUPPLIER').toUpperCase(),
        defaultCurrencyCode: this.defaultCurrencyCode,
        preferredPaymentMethodCode: 'BANK_TRANSFER',
        paymentTermsDays: 30,
        leadTimeDays: 14,
        minimumOrderAmount: null,
        bankName: 'National Bank of Egypt',
        bankAccountNumber: `SUP-${partyId}`,
        bankSwiftCode: 'NBEGEGCX',
        notes: this.valueOrDefault('bankReady', 'Supplier onboarding initiated from the business portal.')
      });
      this.messageText = 'Supplier profile created successfully.';
    });
  }

  private async uploadSupplierKycDocument(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const file = this.requireSelectedFile('supplierKycFile', 'Select a supplier KYC PDF or image before uploading.');
      const form = new FormData();
      form.append('documentType', this.valueOrDefault('supplierDocumentType', 'COMMERCIAL_REGISTER').toUpperCase());
      form.append('file', file, file.name);
      await this.api.postForm(`/api/v1/external/parties/${partyId}/compliance-documents/upload`, form);
      this.messageText = `Supplier KYC file ${file.name} uploaded successfully.`;
    });
  }

  private async evaluateSupplierOnboarding(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const evaluation = await this.buildSupplierOnboardingEvaluation(partyId);
      this.messageText = evaluation['readyForSubmission']
        ? 'Supplier onboarding is ready for submission.'
        : `Supplier onboarding is not ready yet. ${Array.isArray(evaluation['blockingIssues']) ? evaluation['blockingIssues'].length : 0} blocking issues remain.`;
    });
  }

  private async createCustomerProfile(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      await this.api.post(`/api/v1/external/parties/${partyId}/customer-profile`, {
        businessPartyId: partyId,
        firmNationalId: this.optionalValue('firmNationalId'),
        customerClassificationCode: this.resolveCustomerClassificationCode(),
        customerSegmentCode: this.valueOrDefault('customerSegment', 'B2B').toUpperCase(),
        defaultCurrencyCode: this.defaultCurrencyCode,
        salesChannelCode: 'PORTAL',
        priceTierCode: 'STANDARD',
        invoiceDeliveryMethodCode: this.valueOrDefault('billingMode', 'STATEMENT').toUpperCase(),
        notes: 'Customer onboarding initiated from the business portal.'
      });
      this.messageText = 'Customer profile created successfully.';
    });
  }

  private async uploadCustomerKycDocument(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const file = this.requireSelectedFile('customerKycFile', 'Select a customer KYC PDF or image before uploading.');
      const form = new FormData();
      form.append('documentType', this.valueOrDefault('customerDocumentType', 'VAT_CERTIFICATE').toUpperCase());
      form.append('file', file, file.name);
      await this.api.postForm(`/api/v1/external/parties/${partyId}/compliance-documents/upload`, form);
      this.messageText = `Customer KYC file ${file.name} uploaded successfully.`;
    });
  }

  private async evaluateCustomerOnboarding(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const evaluation = await this.buildCustomerOnboardingEvaluation(partyId);
      this.messageText = evaluation['readyForSubmission']
        ? 'Customer onboarding is ready for submission.'
        : `Customer onboarding is not ready yet. ${Array.isArray(evaluation['blockingIssues']) ? evaluation['blockingIssues'].length : 0} blocking issues remain.`;
    });
  }

  private async createOperationsSupplierOnboarding(): Promise<void> {
    await this.api.post('/api/v1/internal/onboarding/suppliers', {
      partyName: this.valueOrDefault('opsLeadCompanyName', 'Operations Intake Supplier'),
      countryCode: 'EG',
      taxRegistrationNumber: null,
      taxBranchCode: null,
      email: this.valueOrDefault('opsLeadEmail', 'onboarding.intake@cashline.test'),
      mobileNumber: this.valueOrDefault('opsLeadMobile', '+201000000123'),
      addressLine1: 'Operations intake',
      supplierCategoryCode: 'CORE',
      defaultCurrencyCode: this.defaultCurrencyCode,
      preferredPaymentMethodCode: 'BANK_TRANSFER',
      paymentTermsDays: 30,
      leadTimeDays: 14,
      minimumOrderAmount: null,
      bankName: 'National Bank of Egypt',
      bankAccountNumber: `OPS-SUP-${Date.now()}`,
      bankSwiftCode: 'NBEGEGCX',
      notes: 'Supplier onboarding case opened manually by Operations.'
    });
    this.messageText = 'Supplier onboarding case created by Operations and submitted for review.';
  }

  private async createOperationsCustomerOnboarding(): Promise<void> {
    await this.api.post('/api/v1/internal/onboarding/customers', {
      partyName: this.valueOrDefault('opsLeadCompanyName', 'Operations Intake Customer'),
      countryCode: 'EG',
      taxRegistrationNumber: null,
      taxBranchCode: null,
      email: this.valueOrDefault('opsLeadEmail', 'onboarding.intake@cashline.test'),
      mobileNumber: this.valueOrDefault('opsLeadMobile', '+201000000123'),
      addressLine1: 'Operations intake',
      creditLimitAmount: null,
      creditTermsDays: 30,
      customerClassificationCode: 'COMPANY',
      customerSegmentCode: 'STANDARD',
      defaultCurrencyCode: this.defaultCurrencyCode,
      salesChannelCode: 'DIRECT',
      priceTierCode: 'STANDARD',
      invoiceDeliveryMethodCode: 'EMAIL',
      notes: 'Customer onboarding case opened manually by Operations.'
    });
    this.messageText = 'Customer onboarding case created by Operations and submitted for review.';
  }

  private async recordOperationsFirmNationalId(): Promise<void> {
    const partyId = Number(this.formValues['opsMissingNationalIdPartyId'] ?? '');
    if (!Number.isFinite(partyId) || partyId <= 0) {
      throw new Error('Select a client party first.');
    }

    const firmNationalId = this.optionalValue('opsFirmNationalId');
    if (!firmNationalId) {
      throw new Error('Enter the firm national ID (Egypt) before submitting.');
    }

    await this.api.put(`/api/v1/internal/parties/${partyId}/firm-national-id`, { firmNationalId });
    this.messageText = 'Firm national ID recorded successfully.';

    // Clear form inputs after a successful record to avoid accidental reuse.
    this.formValues['opsFirmNationalId'] = '';
  }

  private async createAccountingOfficeSupplierOnboarding(): Promise<void> {
    await this.api.post('/api/v1/internal/onboarding/suppliers', {
      partyName: this.valueOrDefault('opsLeadCompanyName', 'Accounting Office Supplier Client'),
      countryCode: 'EG',
      taxRegistrationNumber: null,
      taxBranchCode: null,
      email: this.valueOrDefault('opsLeadEmail', 'accounting.office.client@cashline.test'),
      mobileNumber: this.valueOrDefault('opsLeadMobile', '+201000000234'),
      addressLine1: 'Accounting office submission',
      supplierCategoryCode: 'CORE',
      defaultCurrencyCode: this.defaultCurrencyCode,
      preferredPaymentMethodCode: 'BANK_TRANSFER',
      paymentTermsDays: 30,
      leadTimeDays: 14,
      minimumOrderAmount: null,
      bankName: 'National Bank of Egypt',
      bankAccountNumber: `AO-SUP-${Date.now()}`,
      bankSwiftCode: 'NBEGEGCX',
      notes: 'Supplier onboarding case submitted by Accounting Office and routed to CashLine Operations.'
    });
    this.messageText = 'Supplier onboarding case submitted by Accounting Office and routed to CashLine Operations for approval.';
  }

  private async createAccountingOfficeCustomerOnboarding(): Promise<void> {
    await this.api.post('/api/v1/internal/onboarding/customers', {
      partyName: this.valueOrDefault('opsLeadCompanyName', 'Accounting Office Customer Client'),
      countryCode: 'EG',
      taxRegistrationNumber: null,
      taxBranchCode: null,
      email: this.valueOrDefault('opsLeadEmail', 'accounting.office.client@cashline.test'),
      mobileNumber: this.valueOrDefault('opsLeadMobile', '+201000000234'),
      addressLine1: 'Accounting office submission',
      creditLimitAmount: null,
      creditTermsDays: 30,
      customerClassificationCode: 'COMPANY',
      customerSegmentCode: 'STANDARD',
      defaultCurrencyCode: this.defaultCurrencyCode,
      salesChannelCode: 'DIRECT',
      priceTierCode: 'STANDARD',
      invoiceDeliveryMethodCode: 'EMAIL',
      notes: 'Customer onboarding case submitted by Accounting Office and routed to CashLine Operations.'
    });
    this.messageText = 'Customer onboarding case submitted by Accounting Office and routed to CashLine Operations for approval.';
  }

  private async reviewOperationsOnboardingCase(action: 'approve' | 'return' | 'reject' | 'hold' | 'reopen'): Promise<void> {
    const subjectType = this.resolveOnboardingSubjectType();
    const profileId = await this.resolveOnboardingCaseProfileId(subjectType);
    const resourceName = subjectType === 'SUPPLIER' ? 'suppliers' : 'customers';

    switch (action) {
      case 'approve':
        await this.api.post(`/api/v1/${resourceName}/${profileId}/approve`, {});
        this.messageText = `${this.toTitleCase(subjectType)} onboarding case ${profileId} approved by Operations.`;
        break;
      case 'return':
        await this.api.post(`/api/v1/${resourceName}/${profileId}/return`, {
          reviewNotes: this.valueOrDefault('onboardingReviewNotes', 'Operations returned the case for applicant correction and follow-up.')
        });
        this.messageText = `${this.toTitleCase(subjectType)} onboarding case ${profileId} returned for follow-up.`;
        break;
      case 'reject':
        await this.api.post(`/api/v1/${resourceName}/${profileId}/reject`, {
          reviewNotes: this.valueOrDefault('onboardingReviewNotes', 'Operations rejected the onboarding case after review.')
        });
        this.messageText = `${this.toTitleCase(subjectType)} onboarding case ${profileId} rejected by Operations.`;
        break;
      case 'hold':
        await this.api.post(`/api/v1/${resourceName}/${profileId}/hold`, {
          holdReason: this.valueOrDefault('onboardingHoldReason', 'Operations placed the case on hold pending applicant clarification.')
        });
        this.messageText = `${this.toTitleCase(subjectType)} onboarding case ${profileId} placed on hold.`;
        break;
      case 'reopen':
        await this.api.post(`/api/v1/${resourceName}/${profileId}/reopen`, {
          reopenReason: this.valueOrDefault('onboardingReopenReason', 'Operations reopened the onboarding case for continued review.')
        });
        this.messageText = `${this.toTitleCase(subjectType)} onboarding case ${profileId} reopened for review.`;
        break;
    }
  }

  private async createExternalDocumentWorkflow(documentType: string): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      await this.api.post(`/api/v1/external/parties/${partyId}/document-workflows`, {
        documentType,
        fileName: `${documentType.toLowerCase()}-uat-portal.pdf`,
        storageReference: `uat://portal/${documentType.toLowerCase()}.pdf`,
        contentType: 'application/pdf',
        fileSizeBytes: 2048,
        workflowNotes: 'Created from the business portal.'
      });
      this.messageText = 'Document workflow created successfully.';
    });
  }

  private async createSupplierQuotation(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const rfqs = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/rfq-invitations`));
      const rfq = rfqs[0];
      const rfqId = this.getNumericId(rfq, ['rfqId', 'requestForQuotationId']);

      if (!rfqId) {
        throw new Error('No RFQ invitation is currently available for quotation creation.');
      }

      const rfqDetail = await this.api.get<LiveRecord>(`/api/v1/external/parties/${partyId}/rfq-invitations/${rfqId}`);
      const lines = this.asRecordArray(rfqDetail['lines']).map((line) => ({
        requestForQuotationLineId: this.getNumericId(line, ['rfqLineId', 'requestForQuotationLineId']) ?? 0,
        itemId: this.getNumericId(line, ['itemId']),
        quotedQuantity: Number(line['requestedQuantity'] ?? 1),
        unitOfMeasureCode: String(line['unitOfMeasureCode'] ?? 'EA'),
        unitPrice: Number(this.valueOrDefault('quotationValue', '95000')),
        discountPercent: 0,
        discountAmount: 0,
        taxCode: 'VAT',
        leadTimeDays: 10
      }));

      await this.api.post(`/api/v1/external/parties/${partyId}/quotations`, {
        requestForQuotationId: rfqId,
        quoteDate: this.today(),
        validUntilDate: this.plusDays(7),
        currencyCode: this.defaultCurrencyCode,
        fxRate: 1,
        notes: 'Quotation prepared from the supplier portal.',
        lines
      });

      this.messageText = 'Supplier quotation created successfully.';
    });
  }

  private async submitFirstDraftQuotation(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const quotations = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/quotations`));
      const draft = quotations.find((row) => this.asText(row['statusCode']) === 'DRAFT') ?? quotations[0];
      const quotationId = this.getNumericId(draft, ['quotationId']);

      if (!quotationId) {
        throw new Error('No supplier quotation is available to submit.');
      }

      await this.api.post(`/api/v1/external/parties/${partyId}/quotations/${quotationId}/submit`, {});
      this.messageText = `Quotation ${quotationId} submitted.`;
    });
  }

  private async acknowledgeFirstPurchaseOrder(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const orders = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/purchase-orders`));
      const order = orders[0];
      const purchaseOrderId = this.getNumericId(order, ['purchaseOrderId']);

      if (!purchaseOrderId) {
        throw new Error('No purchase order is available to acknowledge.');
      }

      await this.api.post(`/api/v1/external/parties/${partyId}/purchase-orders/${purchaseOrderId}/acknowledge`, {
        acknowledgmentStatusCode: 'ACKNOWLEDGED',
        notes: 'Acknowledged from the supplier portal.'
      });

      this.messageText = `Purchase order ${purchaseOrderId} acknowledged.`;
    });
  }

  private async raiseIssueOnFirstPurchaseOrder(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const orders = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/purchase-orders`));
      const order = orders[0];
      const purchaseOrderId = this.getNumericId(order, ['purchaseOrderId']);

      if (!purchaseOrderId) {
        throw new Error('No purchase order is available for issue raising.');
      }

      await this.api.post(`/api/v1/external/parties/${partyId}/purchase-orders/${purchaseOrderId}/raise-issue`, {
        issueCategoryCode: this.valueOrDefault('issueIntent', 'DELIVERY_WINDOW').toUpperCase().replace(/-/g, '_'),
        issueNotes: 'Issue raised from the supplier portal.'
      });

      this.messageText = `An issue was raised for purchase order ${purchaseOrderId}.`;
    });
  }

  private async recordCustomerDocument(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      await this.api.post(`/api/v1/external/parties/${partyId}/customer-documents`, {
        documentType: 'BILLING_INSTRUCTION',
        fileName: 'customer-billing-instructions.pdf',
        storageReference: 'uat://portal/customer-billing-instructions.pdf',
        contentType: 'application/pdf',
        fileSizeBytes: 2048
      });

      this.messageText = 'Customer document recorded successfully.';
    });
  }

  private async buildSupplierOnboardingEvaluation(partyId: number): Promise<LiveRecord> {
    const documents = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/compliance-documents`));
    return await this.api.post('/api/v1/public/onboarding/suppliers/evaluate', {
      partyName: this.valueOrDefault('supplierName', 'Shatta Supplies Co.'),
      countryCode: 'EG',
      taxRegistrationNumber: `SUP-TAX-${partyId}`,
      taxBranchCode: '001',
      email: this.session?.username ?? null,
      mobileNumber: null,
      addressLine1: 'Cairo',
      supplierCategoryCode: this.valueOrDefault('supplierCategory', 'CORE').toUpperCase(),
      defaultCurrencyCode: this.defaultCurrencyCode,
      preferredPaymentMethodCode: 'BANK_TRANSFER',
      paymentTermsDays: 30,
      leadTimeDays: 14,
      minimumOrderAmount: null,
      bankName: 'National Bank of Egypt',
      bankAccountNumber: `SUP-${partyId}`,
      bankSwiftCode: 'NBEGEGCX',
      notes: this.valueOrDefault('bankReady', 'Supplier onboarding initiated from the business portal.'),
      complianceDocuments: this.mapComplianceDocuments(documents)
    });
  }

  private async buildCustomerOnboardingEvaluation(partyId: number): Promise<LiveRecord> {
    const documents = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/compliance-documents`));
    return await this.api.post('/api/v1/public/onboarding/customers/evaluate', {
      partyName: this.valueOrDefault('customerName', 'CashLine Trading Customer'),
      countryCode: 'EG',
      taxRegistrationNumber: `CUS-TAX-${partyId}`,
      taxBranchCode: '001',
      email: this.session?.username ?? null,
      mobileNumber: null,
      addressLine1: 'Cairo',
      creditLimitAmount: 50000,
      creditTermsDays: 45,
      isCreditHoldEnabled: false,
      creditHoldReason: null,
      customerClassificationCode: this.resolveCustomerClassificationCode(),
      customerSegmentCode: this.valueOrDefault('customerSegment', 'B2B').toUpperCase(),
      defaultCurrencyCode: this.defaultCurrencyCode,
      salesChannelCode: 'PORTAL',
      priceTierCode: 'STANDARD',
      invoiceDeliveryMethodCode: this.valueOrDefault('billingMode', 'EMAIL').toUpperCase(),
      notes: 'Customer onboarding initiated from the business portal.',
      complianceDocuments: this.mapComplianceDocuments(documents)
    });
  }

  private async createCustomerOrder(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const branchId = this.session?.branchId ?? this.defaultBranchId;
      await this.api.post(`/api/v1/external/parties/${partyId}/orders`, {
        branchId,
        orderDate: this.today(),
        currencyCode: this.defaultCurrencyCode,
        exchangeRate: 1,
        lines: [
          {
            itemId: this.defaultItemId,
            warehouseId: this.defaultWarehouseId,
            orderedQuantity: 10,
            unitOfMeasureCode: 'EA',
            unitPriceAmount: Number(this.valueOrDefault('requestValue', '145000')),
            taxCode: 'VAT'
          }
        ]
      });

      this.messageText = 'Customer order created successfully.';
    });
  }

  private async submitFirstDraftOrder(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const orders = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/orders`));
      const draft = orders.find((row) => this.asText(row['statusCode']) === 'DRAFT') ?? orders[0];
      const orderId = this.getNumericId(draft, ['salesOrderId', 'orderId']);

      if (!orderId) {
        throw new Error('No customer order is available to submit.');
      }

      await this.api.post(`/api/v1/external/parties/${partyId}/orders/${orderId}/submit`, {});
      this.messageText = `Customer order ${orderId} submitted.`;
    });
  }

  private async createCustomerServiceRequest(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      await this.api.post(`/api/v1/external/parties/${partyId}/service-requests`, {
        requestTypeCode: 'SERVICE_SUPPORT',
        subjectText: this.valueOrDefault('serviceSubject', 'Need customer support follow-up'),
        detailsText: 'Created from the customer portal for the live dispute and service flow.',
        priorityCode: this.valueOrDefault('serviceImpact', 'MEDIUM').toUpperCase(),
        commercialImpactCode: this.valueOrDefault('serviceImpact', 'MEDIUM').toUpperCase(),
        requestedResolutionCode: 'COMMERCIAL_REVIEW',
        assignedBackofficeLaneCode: 'COMMERCIAL',
        relatedDocumentType: 'SALES_ORDER',
        relatedDocumentId: null
      });

      this.messageText = 'Customer service request created successfully.';
    });
  }

  private async submitFirstDraftServiceRequest(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const requests = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/service-requests`));
      const draft = requests.find((row) => this.asText(row['statusCode']) === 'DRAFT') ?? requests[0];
      const requestId = this.getNumericId(draft, ['serviceRequestId']);

      if (!requestId) {
        throw new Error('No service request is available to submit.');
      }

      await this.api.post(`/api/v1/external/parties/${partyId}/service-requests/${requestId}/submit`, {});
      this.messageText = `Service request ${requestId} submitted.`;
    });
  }

  private async escalateFirstServiceRequest(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const requests = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/service-requests`));
      const active = requests.find((row) => this.asText(row['statusCode']) !== 'CLOSED' && this.asText(row['statusCode']) !== 'REJECTED') ?? requests[0];
      const requestId = this.getNumericId(active, ['serviceRequestId']);

      if (!requestId) {
        throw new Error('No active service request is available to escalate.');
      }

      await this.api.post(`/api/v1/external/parties/${partyId}/service-requests/${requestId}/escalate`, {
        escalationReason: 'Escalated from the customer portal.',
        assignedBackofficeLaneCode: 'COMMERCIAL',
        requestedResolutionCode: 'COMMERCIAL_REVIEW'
      });

      this.messageText = `Service request ${requestId} escalated.`;
    });
  }

  private async createExternalAccountingOfficeRequest(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const created = await this.api.post(`/api/v1/external/parties/${partyId}/accounting-office/requests`, {
        requestTypeCode: this.valueOrDefault('aoRequestType', 'ETA_NOTICE').toUpperCase(),
        subjectText: this.valueOrDefault('aoRequestSubject', 'Accounting notice submitted'),
        detailsText: this.valueOrDefault('aoRequestDetails', 'Client submitted an accounting office notice with attached evidence.')
      }) as LiveRecord;

      const requestId = this.getNumericId(created, ['requestId']);
      if (!requestId) {
        this.messageText = 'Accounting office notice created.';
        return;
      }

      const file = this.selectedFiles['aoRequestFile'] ?? null;
      if (file) {
        const form = new FormData();
        form.append('documentType', this.valueOrDefault('aoRequestDocumentType', 'ACCOUNTING_NOTICE').toUpperCase());
        form.append('subjectTypeCode', this.optionalValue('aoRequestSubjectType') ?? 'ACCOUNTING_NOTICE');
        form.append('file', file, file.name);
        await this.api.postForm(`/api/v1/external/parties/${partyId}/accounting-office/requests/${requestId}/documents/upload`, form);
      }

      this.messageText = file
        ? `Accounting office notice ${requestId} created and evidence uploaded.`
        : `Accounting office notice ${requestId} created.`;
    });
  }

  private async createCustomerPayment(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const invoices = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/invoices`));
      const invoice = invoices[0];
      const invoiceId = this.getNumericId(invoice, ['accountsReceivableInvoiceId', 'invoiceId']);

      if (!invoiceId) {
        throw new Error('No customer invoice is available for payment initiation.');
      }

      await this.api.post(`/api/v1/external/parties/${partyId}/payments`, {
        accountsReceivableInvoiceId: invoiceId,
        integrationProviderCode: this.valueOrDefault('paymentChannel', 'PAYMOB').toUpperCase(),
        currencyCode: this.defaultCurrencyCode,
        amountAmount: Number(invoice['totalAmount'] ?? invoice['balanceAmount'] ?? 0),
        paymentDate: this.today(),
        referenceNumber: `ANG-KENDO-${invoiceId}`,
        notes: 'Payment request created from the customer portal.'
      });

      this.messageText = `Payment request created for invoice ${invoiceId}.`;
    });
  }

  private async createFinancingRequest(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const products = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/financing/products`));
      const selectedProduct = products[0];
      const productProfileId = this.getNumericId(selectedProduct, ['id', 'productProfileId']);

      if (!productProfileId) {
        throw new Error('No financing product is currently available for request creation.');
      }

      const sourceDocumentId = await this.resolveFinancingSourceDocumentId(partyId);
      const applicantPartyTypeCode = this.session && isSupplierSession(this.session) ? 'SUPPLIER' : 'CUSTOMER';

      await this.api.post(`/api/v1/external/parties/${partyId}/financing/requests`, {
        requestNumber: null,
        applicantPartyTypeCode,
        productProfileId,
        facilityId: null,
        fundingSourceMode: 'PARTNER_LENDER',
        sourceDocumentType: this.journey.id === 'supplier' ? 'PURCHASE_ORDER' : 'SALES_ORDER',
        sourceDocumentId,
        transactionCurrencyCode: this.defaultCurrencyCode,
        requestedAmount: Number(this.valueOrDefault(this.journey.id === 'supplier' ? 'supplierAmount' : 'requestedAmount', '120000'))
      });

      this.messageText = 'Financing request created successfully.';
    });
  }

  private async submitFirstDraftFinancingRequest(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const requests = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/financing/requests`));
      const draft = requests.find((row) => this.asText(row['statusCode']) === 'DRAFT') ?? requests[0];
      const financingRequestId = this.getNumericId(draft, ['financingRequestId', 'requestId', 'id']);

      if (!financingRequestId) {
        throw new Error('No financing request is available to submit.');
      }

      await this.api.post(`/api/v1/external/parties/${partyId}/financing/requests/${financingRequestId}/submit`, {});
      this.messageText = `Financing request ${financingRequestId} submitted.`;
    });
  }

  private async acceptFirstFinancingOffer(): Promise<void> {
    await this.requirePartyAction(async (partyId) => {
      const requests = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/financing/requests`));
      const request = requests[0];
      const financingRequestId = this.getNumericId(request, ['financingRequestId', 'requestId', 'id']);

      if (!financingRequestId) {
        throw new Error('No financing request is available for offer acceptance.');
      }

      const offers = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/financing/requests/${financingRequestId}/offers`));
      const firstOffer = offers[0];
      const decisionId = this.getNumericId(firstOffer, ['financingDecisionId', 'decisionId', 'id']);

      if (!decisionId) {
        throw new Error('No financing offer is currently available to accept.');
      }

      await this.api.post(`/api/v1/external/parties/${partyId}/financing/requests/${financingRequestId}/accept-offer`, {
        financingDecisionId: decisionId
      });

      this.messageText = `Financing offer ${decisionId} accepted.`;
    });
  }

  private async loadExternalFinancingPanels(partyId: number | null): Promise<RawLivePanel[]> {
    return this.requirePartyPanels(partyId, async (resolvedPartyId) => {
      const requests = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/financing/requests`));
      const firstRequest = requests[0];
      const financingRequestId = this.getNumericId(firstRequest, ['financingRequestId', 'requestId', 'id']);

      const panels: RawLivePanel[] = [
        {
          title: 'Financing products',
          description: 'Live Islamic Finance products visible to the selected external party.',
          data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/financing/products`)
        },
        {
          title: 'Financing requests',
          description: 'Current external financing requests for the selected party.',
          data: requests
        },
        {
          title: 'Financing tasks',
          description: 'Cross-request financing tasks and notifications for the selected party.',
          data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/financing/tasks`)
        }
      ];

      if (financingRequestId) {
        panels.push({
          title: 'Financing offers',
          description: 'Offer comparison records for the first live financing request.',
          data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/financing/requests/${financingRequestId}/offers`)
        });
        panels.push({
          title: 'Journey summary',
          description: 'Execution and settlement summary for the first financing request.',
          data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/financing/requests/${financingRequestId}/journey-summary`)
        });
      }

      return panels;
    });
  }

  private async createReportSnapshot(): Promise<void> {
    const reportCode = this.resolveSnapshotReportCode();
    await this.api.post('/api/v1/reports/snapshots', {
      reportCode,
      dateFrom: null,
      dateTo: null,
      asOfDate: this.today(),
      notes: this.journey.id === 'operations-control'
        ? this.valueOrDefault('opsSnapshotNote', 'Generated from the operations control workspace.')
        : 'Generated from the reporting center workspace.'
    });

    this.messageText = `${reportCode} snapshot generated.`;
  }

  private async loadExternalStatementPanels(
    partyId: number | null,
    audience: 'supplier' | 'customer'
  ): Promise<RawLivePanel[]> {
    return this.requirePartyPanels(partyId, async (resolvedPartyId) => {
      const summary = await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/reports-statements/summary`);
      const statements = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/reports-statements`));
      const requestedStatementId = Number(this.formValues[`${audience}StatementId`] ?? '');
      const selectedStatement = Number.isFinite(requestedStatementId) && requestedStatementId > 0
        ? statements.find((entry) => this.getNumericId(entry, ['statementId', 'id']) === requestedStatementId) ?? statements[0]
        : statements[0];
      const selectedStatementId = this.getNumericId(selectedStatement, ['statementId', 'id']);
      if (selectedStatementId) {
        this.formValues[`${audience}StatementId`] = String(selectedStatementId);
      }

      const panels: RawLivePanel[] = [
        {
          title: `${this.toTitleCase(audience)} statement summary`,
          description: `${this.toTitleCase(audience)}-facing statement and account summary visibility for reporting review.`,
          data: summary
        },
        {
          title: `${this.toTitleCase(audience)} statements`,
          description: `${this.toTitleCase(audience)} statement list available to the signed-in external user.`,
          data: statements
        }
      ];

      if (selectedStatementId) {
        panels.push({
          title: 'Selected statement detail',
          description: 'Detailed statement lines for the currently selected statement.',
          data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/reports-statements/${selectedStatementId}`)
        });
        panels.push({
          title: 'Statement delivery logs',
          description: 'Delivery and acknowledgment history for the selected statement.',
          data: await this.api.get(`/api/v1/external/parties/${resolvedPartyId}/reports-statements/${selectedStatementId}/delivery-logs`)
        });
      }

      return panels;
    });
  }

  private async loadFinanceAssignedReportPanels(): Promise<RawLivePanel[]> {
    const financeStakeholders = this.asRecordArray(await this.api.get(this.buildQueryPath('/api/v1/reports/finance-stakeholders', {
      dateFrom: this.optionalValue('financialDateFrom') ?? this.optionalValue('operationsDateFrom'),
      dateTo: this.optionalValue('financialDateTo') ?? this.optionalValue('operationsDateTo')
    })));
    const coreFinance = await this.api.get(this.buildQueryPath('/api/v1/reports/core-finance', {
      dateFrom: this.optionalValue('financialDateFrom') ?? this.optionalValue('operationsDateFrom'),
      dateTo: this.optionalValue('financialDateTo') ?? this.optionalValue('operationsDateTo')
    }));
    const salesOrders = this.asRecordArray(await this.getAllowUnavailable('/api/v1/sales/orders'));
    const settlements = await this.getAllowUnavailable('/api/v1/finance/payment-settlement-batches');
    const customerStatements = await this.getAllowUnavailable('/api/v1/finance/customer-statements');
    const supplierStatements = await this.getAllowUnavailable('/api/v1/finance/supplier-statements');
    const arQueue = await this.getAllowUnavailable(this.buildQueryPath('/api/v1/finance/collections/queue', {
      asOfDate: this.optionalValue('financialAsOfDate') ?? this.today()
    }));
    const supplierReconciliations = await this.getAllowUnavailable(this.buildQueryPath('/api/v1/finance/supplier-account-reconciliations', {
      asOfDate: this.optionalValue('financialAsOfDate') ?? this.today()
    }));
    const workflowInstances = this.asRecordArray(await this.getAllowUnavailable('/api/v1/workflow/instances'));
    const bootstrap = await this.api.get('/api/v1/demo/bootstrap', false);
    const bootstrapCredentials = this.asRecordArray((bootstrap as LiveRecord | null)?.['credentials']);
    const focus = this.valueOrDefault('financeReportFocus', 'gained-profit-per-customer');

    const panels: RawLivePanel[] = [
      {
        title: 'Finance assigned report catalog',
        description: 'Exact finance and shared report package loaded from the attached workbook for live go-live use.',
        data: this.buildFinanceReportCatalog()
      }
    ];

    switch (focus) {
      case 'gained-profit-per-customer':
        panels.push({
          title: 'Gained-profit per customer reprot',
          description: 'Customer-focused gain and exposure view derived from live finance stakeholder data.',
          data: financeStakeholders.filter((entry) => this.asText(entry['stakeholderType']) === 'CUSTOMER')
        });
        break;
      case 'currencies-margin-profit-per-customer':
        panels.push({
          title: 'Currencies margin profit per customer report',
          description: 'Customer stakeholder positions grouped by currency from live finance stakeholder reporting.',
          data: this.groupRecordsByField(
            financeStakeholders.filter((entry) => this.asText(entry['stakeholderType']) === 'CUSTOMER'),
            'currencyCode',
            ['outstandingAmount', 'settledAmount', 'exposureAmount', 'grossAmount', 'netAmount'])
        });
        break;
      case 'currencies-margin-profile-per-user':
        panels.push({
          title: 'Currencies margin profile per user reprot',
          description: 'Live user package linked to current finance workflow posture and assigned user visibility.',
          data: {
            users: bootstrapCredentials.map((credential) => ({
              displayName: this.asText(credential['displayName']),
              username: this.asText(credential['username']),
              userType: this.asText(credential['userType']),
              workspace: this.asText(credential['workspace'])
            })),
            workflowInstances
          }
        });
        break;
      case 'sales-by-currencies':
        panels.push({
          title: 'Sales by currencies report (EGP / USD)',
          description: 'Live sales orders grouped by transaction currency.',
          data: this.groupRecordsByField(salesOrders, 'currencyCode', ['totalAmount', 'netAmount', 'grossAmount'])
        });
        break;
      case 'currency-performance-sales-volume':
        panels.push({
          title: 'Currency performance report (Sales volume)',
          description: 'Sales volume by currency derived from live sales orders.',
          data: this.groupRecordsByField(salesOrders, 'currencyCode', ['orderedQuantity', 'totalAmount', 'netAmount'])
        });
        break;
      case 'settlement-per-customer-supplier':
        panels.push({
          title: 'Settlement report per customer / supplier',
          description: 'Live settlement and reconciliation posture across customer and supplier-facing finance lanes.',
          data: {
            settlementBatches: settlements,
            customerStatements,
            supplierReconciliations
          }
        });
        break;
      case 'ar-per-customer':
        panels.push({
          title: 'AR report per customer',
          description: 'Customer accounts receivable posture from live collections and statement data.',
          data: {
            collectionsQueue: arQueue,
            customerStatements
          }
        });
        break;
      case 'ap-per-supplier':
        panels.push({
          title: 'AP report per supplier',
          description: 'Supplier payable posture from live supplier reconciliation and statement data.',
          data: {
            supplierReconciliations,
            supplierStatements
          }
        });
        break;
      case 'statement-of-account':
        panels.push({
          title: 'Statement of account For (CashLine / Supplier / Customer)',
          description: 'Current statement-of-account posture across CashLine finance, supplier, and customer statement lanes.',
          data: {
            coreFinance,
            customerStatements,
            supplierStatements
          }
        });
        break;
      case 'rejected-transactions-detailed':
        panels.push({
          title: 'Rejected transactions report - detailed',
          description: 'Live rejected or failed workflow transactions from the current workflow instance history.',
          data: workflowInstances.filter((entry) => ['REJECTED', 'FAILED', 'CANCELLED'].includes(this.asText(entry['currentStatusCode'])))
        });
        break;
      case 'successful-transactions-detailed':
        panels.push({
          title: 'Successful transactions report - detailed',
          description: 'Live successful workflow transactions from the current workflow instance history.',
          data: workflowInstances.filter((entry) => ['APPROVED', 'COMPLETED', 'POSTED', 'ACTIVATED', 'CLOSED'].includes(this.asText(entry['currentStatusCode'])))
        });
        break;
    }

    return panels;
  }

  private async loadOperationsAssignedReportPanels(): Promise<RawLivePanel[]> {
    const supplierCases = this.asRecordArray(await this.api.get('/api/v1/internal/onboarding/cases?subjectTypeCode=SUPPLIER'));
    const customerCases = this.asRecordArray(await this.api.get('/api/v1/internal/onboarding/cases?subjectTypeCode=CUSTOMER'));
    const bootstrap = await this.api.get('/api/v1/demo/bootstrap', false);
    const bootstrapCredentials = this.asRecordArray((bootstrap as LiveRecord | null)?.['credentials']);
    const procurementReport = await this.loadOperationsProcurementReport();
    const inventoryReport = await this.loadOperationsInventoryReport();
    const salesReport = await this.loadOperationsSalesReport();
    const workflowMonitoring = await this.api.get('/api/v1/internal/workflow-monitoring?itemLimit=8');
    const workflowQueue = await this.api.get('/api/v1/workflow/queue');
    const workflowInstances = this.asRecordArray(await this.api.get('/api/v1/workflow/instances'));
    const activeUserAccounts = bootstrapCredentials.map((credential) => ({
      displayName: this.asText(credential['displayName']),
      username: this.asText(credential['username']),
      email: this.asText(credential['email']),
      userType: this.asText(credential['userType']),
      workspace: this.asText(credential['workspace']),
      clientType: this.asText(credential['clientType']),
      status: 'ACTIVE'
    }));
    const financeStakeholders = this.asRecordArray(await this.api.get(this.buildQueryPath('/api/v1/reports/finance-stakeholders', {
      dateFrom: this.optionalValue('operationsDateFrom'),
      dateTo: this.optionalValue('operationsDateTo')
    })));
    const settlements = await this.getAllowUnavailable('/api/v1/finance/payment-settlement-batches');
    const customerStatements = await this.getAllowUnavailable('/api/v1/finance/customer-statements');
    const supplierStatements = await this.getAllowUnavailable('/api/v1/finance/supplier-statements');
    const focus = this.valueOrDefault('operationsReportFocus', 'currencies-margin-profit-per-customer');

    const panels: RawLivePanel[] = [
      {
        title: 'Operations assigned report catalog',
        description: 'Exact Operations report list loaded from the attached workbook for governed go-live use.',
        data: this.buildOperationsReportCatalog()
      }
    ];

    switch (focus) {
      case 'currencies-margin-profit-per-customer':
        panels.push({
          title: 'Currencies margin profit per customer report',
          description: 'Customer stakeholder positions grouped by currency from live finance stakeholder reporting.',
          data: this.groupRecordsByField(
            financeStakeholders.filter((entry) => this.asText(entry['stakeholderType']) === 'CUSTOMER'),
            'currencyCode',
            ['outstandingAmount', 'settledAmount', 'exposureAmount', 'grossAmount', 'netAmount'])
        });
        break;
      case 'currencies-margin-profile-per-user':
        panels.push({
          title: 'Currencies margin profile per user reprot',
          description: 'Live user package linked to current workflow posture and assigned user visibility.',
          data: {
            groupedSummary: this.summarizeBootstrapUsersByType(bootstrapCredentials, true),
            accounts: activeUserAccounts,
            workflowInstances
          }
        });
        break;
      case 'settlement-per-customer-supplier':
        panels.push({
          title: 'Settlement report per customer / supplier',
          description: 'Live settlement and statement posture across customer and supplier lanes.',
          data: {
            settlementBatches: settlements,
            customerStatements,
            supplierStatements
          }
        });
        break;
      case 'statement-of-account':
        panels.push({
          title: 'Statement of account For (CashLine / Supplier / Customer)',
          description: 'Current statement-of-account posture across internal finance, supplier, and customer statement lanes.',
          data: {
            customerStatements,
            supplierStatements
          }
        });
        break;
      case 'complaints-detailed':
        panels.push({
          title: 'Complaints reprot - detailed',
          description: 'Detailed operational complaints and follow-up posture from live workflow and exception attention lanes.',
          data: {
            pendingSupplierCases: supplierCases.filter((entry) => this.isPendingOnboardingCase(entry)),
            pendingCustomerCases: customerCases.filter((entry) => this.isPendingOnboardingCase(entry)),
            workflowMonitoring
          }
        });
        break;
      case 'user-log-report':
        panels.push({
          title: 'User log report',
          description: 'Live user and workflow log posture from current workflow instances and monitoring.',
          data: {
            workflowInstances,
            workflowMonitoring
          }
        });
        break;
      case 'users-number-per-customer':
        panels.push({
          title: 'Users number per customer',
          description: 'Current number of customer user accounts from the live bootstrap user set.',
          data: activeUserAccounts.filter((entry) => entry.userType === 'EXTERNAL_CUSTOMER')
        });
        break;
      case 'users-number-per-supplier':
        panels.push({
          title: 'Users number per supplier',
          description: 'Current number of supplier user accounts from the live bootstrap user set.',
          data: activeUserAccounts.filter((entry) => entry.userType === 'EXTERNAL_SUPPLIER')
        });
        break;
      case 'users-number-for-cashline-per-department':
        panels.push({
          title: 'Users number for CashLine per department',
          description: 'Current CashLine internal users grouped by workspace and department-like operating area.',
          data: this.groupRecordsByField(
            activeUserAccounts.filter((entry) => !['EXTERNAL_CUSTOMER', 'EXTERNAL_SUPPLIER'].includes(entry.userType)),
            'workspace',
            [])
        });
        break;
      case 'rejected-transactions-detailed':
        panels.push({
          title: 'Rejected transactions report - detailed',
          description: 'Live rejected or failed workflow transactions from the current workflow instance history.',
          data: workflowInstances.filter((entry) => ['REJECTED', 'FAILED', 'CANCELLED'].includes(this.asText(entry['currentStatusCode'])))
        });
        break;
      case 'successful-transactions-detailed':
        panels.push({
          title: 'Successful transactions report - detailed',
          description: 'Live successful workflow transactions from the current workflow instance history.',
          data: workflowInstances.filter((entry) => ['APPROVED', 'COMPLETED', 'POSTED', 'ACTIVATED', 'CLOSED'].includes(this.asText(entry['currentStatusCode'])))
        });
        break;
      case 'analytical-report':
        panels.push({
          title: 'Analytical report',
          description: 'Cross-functional analytical view over onboarding, workflow, procurement, inventory, and sales activity.',
          data: {
            onboarding: {
              pendingSuppliers: supplierCases.filter((entry) => this.isPendingOnboardingCase(entry)),
              pendingCustomers: customerCases.filter((entry) => this.isPendingOnboardingCase(entry))
            },
            workflowMonitoring,
            workflowQueue,
            procurementReport,
            inventoryReport,
            salesReport
          }
        });
        break;
    }

    return panels;
  }

  private buildFinanceReportCatalog(): Array<Record<string, string>> {
    return [
      { reportName: 'Gained-profit per customer reprot', userType: 'Financial', deliveryMode: 'Live stakeholder view' },
      { reportName: 'Currencies margin profit per customer report', userType: 'Operations and Financial', deliveryMode: 'Live grouped view' },
      { reportName: 'Currencies margin profile per user reprot', userType: 'Operations and Financial', deliveryMode: 'Live grouped view' },
      { reportName: 'Sales by currencies report (EGP / USD)', userType: 'Financial', deliveryMode: 'Live grouped sales view' },
      { reportName: 'Currency performance report (Sales volume)', userType: 'Financial', deliveryMode: 'Live grouped sales view' },
      { reportName: 'Settlement report per customer / supplier', userType: 'Operations and Financial', deliveryMode: 'Live settlement view' },
      { reportName: 'AR report per customer', userType: 'Financial', deliveryMode: 'Live receivables view' },
      { reportName: 'AP report per supplier', userType: 'Financial', deliveryMode: 'Live payables view' },
      { reportName: 'Statement of account For (CashLine / Supplier / Customer)', userType: 'Operations and Financial', deliveryMode: 'Live statement view' },
      { reportName: 'Rejected transactions report - detailed', userType: 'Operations and Financial', deliveryMode: 'Live workflow detail' },
      { reportName: 'Successful transactions report - detailed', userType: 'Operations and Financial', deliveryMode: 'Live workflow detail' }
    ];
  }

  private buildOperationsReportCatalog(): Array<Record<string, string>> {
    return [
      { reportName: 'Currencies margin profit per customer report', userType: 'Operations and Financial', deliveryMode: 'Live grouped view' },
      { reportName: 'Currencies margin profile per user reprot', userType: 'Operations and Financial', deliveryMode: 'Live grouped view' },
      { reportName: 'Settlement report per customer / supplier', userType: 'Operations and Financial', deliveryMode: 'Live settlement view' },
      { reportName: 'Statement of account For (CashLine / Supplier / Customer)', userType: 'Operations and Financial', deliveryMode: 'Live statement view' },
      { reportName: 'Complaints reprot - detailed', userType: 'Operations', deliveryMode: 'Live exception view' },
      { reportName: 'User log report', userType: 'Operations', deliveryMode: 'Live workflow log view' },
      { reportName: 'Users number per customer', userType: 'Operations', deliveryMode: 'Live grouped account view' },
      { reportName: 'Users number per supplier', userType: 'Operations', deliveryMode: 'Live grouped account view' },
      { reportName: 'Users number for CashLine per department', userType: 'Operations', deliveryMode: 'Live grouped account view' },
      { reportName: 'Rejected transactions report - detailed', userType: 'Operations and Financial', deliveryMode: 'Live workflow detail' },
      { reportName: 'Successful transactions report - detailed', userType: 'Operations and Financial', deliveryMode: 'Live workflow detail' },
      { reportName: 'Analytical report', userType: 'Operations', deliveryMode: 'Live analytical composite' }
    ];
  }

  private isPendingOnboardingCase(record: LiveRecord): boolean {
    const statusCode = this.asText(record['caseStatusCode']);
    return ['SUBMITTED', 'UNDER_REVIEW', 'PENDING_APPROVAL', 'ON_HOLD', 'RETURNED_FOR_REWORK', 'APPROVED']
      .includes(statusCode);
  }

  private summarizeBootstrapUsersByType(credentials: LiveRecord[], active: boolean): unknown {
    if (!active) {
      return [
        {
          userType: 'NONE_PUBLISHED',
          accountCount: '0',
          note: 'No inactive assigned users are seeded in the current controlled bootstrap set.'
        }
      ];
    }

    const grouped = new Map<string, { accountCount: number; workspaces: Set<string> }>();
    for (const credential of credentials) {
      const userType = this.asText(credential['userType']) || 'UNSPECIFIED';
      const workspace = this.asText(credential['workspace']) || 'general';
      const entry = grouped.get(userType) ?? { accountCount: 0, workspaces: new Set<string>() };
      entry.accountCount += 1;
      entry.workspaces.add(workspace);
      grouped.set(userType, entry);
    }

    return Array.from(grouped.entries())
      .map(([userType, entry]) => ({
        userType,
        accountCount: String(entry.accountCount),
        workspaces: Array.from(entry.workspaces).join(', '),
        status: 'ACTIVE'
      }))
      .sort((left, right) => left.userType.localeCompare(right.userType));
  }

  private groupRecordsByField(records: LiveRecord[], field: string, numericCandidates: string[]): Array<Record<string, string>> {
    const grouped = new Map<string, { count: number; totals: Record<string, number> }>();
    for (const record of records) {
      const key = this.asText(record[field]) || 'UNSPECIFIED';
      const entry = grouped.get(key) ?? { count: 0, totals: {} };
      entry.count += 1;
      for (const numericField of numericCandidates) {
        const value = record[numericField];
        if (typeof value === 'number' && Number.isFinite(value)) {
          entry.totals[numericField] = (entry.totals[numericField] ?? 0) + value;
        }
      }
      grouped.set(key, entry);
    }

    return Array.from(grouped.entries()).map(([groupValue, entry]) => ({
      [this.prettifyLabel(field)]: groupValue,
      Count: String(entry.count),
      ...Object.fromEntries(Object.entries(entry.totals).map(([totalKey, totalValue]) => [this.prettifyLabel(totalKey), String(totalValue)]))
    }));
  }

  private async loadOperationsProcurementReport(): Promise<Record<string, unknown>> {
    return {
      purchaseRequisitions: await this.getAllowUnavailable('/api/v1/procurement/purchase-requisitions'),
      rfqs: await this.getAllowUnavailable('/api/v1/procurement/rfqs'),
      quotations: await this.getAllowUnavailable('/api/v1/procurement/quotations'),
      purchaseOrders: await this.getAllowUnavailable('/api/v1/procurement/purchase-orders'),
      goodsReceipts: await this.getAllowUnavailable('/api/v1/procurement/goods-receipts'),
      purchaseReturns: await this.getAllowUnavailable('/api/v1/procurement/purchase-returns'),
      accountsPayableBills: await this.getAllowUnavailable('/api/v1/procurement/ap-bills')
    };
  }

  private async loadOperationsInventoryReport(): Promise<Record<string, unknown>> {
    return {
      inventoryMovements: await this.getAllowUnavailable('/api/v1/inventory/movements'),
      inventoryAdjustments: await this.getAllowUnavailable('/api/v1/inventory/adjustments'),
      inventoryStatusControls: await this.getAllowUnavailable('/api/v1/inventory/status-controls'),
      stockCounts: await this.getAllowUnavailable('/api/v1/inventory/stock-counts'),
      warehouseTransfers: await this.getAllowUnavailable('/api/v1/inventory/transfers')
    };
  }

  private async loadOperationsSalesReport(): Promise<Record<string, unknown>> {
    return {
      salesOrders: await this.getAllowUnavailable('/api/v1/sales/orders'),
      salesReturns: await this.getAllowUnavailable('/api/v1/sales/returns'),
      dispatchOrders: await this.getAllowUnavailable('/api/v1/sales/dispatches'),
      pricingRules: await this.getAllowUnavailable('/api/v1/sales/pricing-rules')
    };
  }

  private async processEtaNotificationBatch(): Promise<void> {
    const result = await this.api.post<{
      reconciledCount: number;
      retryCount: number;
      failedCount: number;
    }>('/api/v1/finance/eta-notifications/process-batch', {
      processorCode: this.journey.id === 'finance-approvals' ? 'ETA_FINANCE_UI' : 'ETA_SUPPORT_UI',
      batchSize: Number(this.valueOrDefault('etaBatchSize', '10')),
      retryDelaySeconds: Number(this.valueOrDefault('etaRetryDelaySeconds', '0'))
    });

    this.messageText = `ETA batch processed. Reconciled ${result.reconciledCount}, retried ${result.retryCount}, failed ${result.failedCount}.`;
  }

  private async reprocessFirstEtaNotification(): Promise<void> {
    const queue = this.asRecordArray(await this.api.get(
      `/api/v1/finance/eta-notifications?statusCode=${encodeURIComponent(this.valueOrDefault('etaNotificationStatus', 'RECEIVED'))}`));
    const firstNotification = queue[0];
    const inboxMessageId = this.getNumericId(firstNotification, ['inboxMessageId']);

    if (!inboxMessageId) {
      throw new Error('No ETA notification is currently available in the selected queue for manual reprocess.');
    }

    const result = await this.api.post<{
      statusCode: string;
      reconciled: boolean;
      reconciledTransmissionId: number | null;
      reconciliationOutcome: string | null;
    }>(
      `/api/v1/finance/eta-notifications/${inboxMessageId}/reprocess`,
      {
        processorCode: this.journey.id === 'finance-approvals' ? 'ETA_FINANCE_MANUAL' : 'ETA_SUPPORT_MANUAL',
        retryDelaySeconds: Number(this.valueOrDefault('etaRetryDelaySeconds', '0')),
        reprocessReasonText: 'Manual reprocess triggered from the ETA notification workspace.'
      });

    this.messageText = result.reconciled
      ? `ETA notification ${inboxMessageId} reconciled with outcome ${result.reconciliationOutcome ?? 'updated'}.`
      : `ETA notification ${inboxMessageId} processed with status ${result.statusCode}.`;
  }

  private async loadPartnerOrchestrationPreview(): Promise<unknown> {
    try {
      const requests = this.asRecordArray(await this.api.get('/api/v1/islamic-finance/requests'));
      const firstRequest = requests[0];
      const requestId = this.getNumericId(firstRequest, ['requestId', 'id', 'financingRequestId']);

      if (!requestId) {
        return [];
      }

      return await this.api.get(`/api/v1/islamic-finance/requests/${requestId}/partner-orchestration`);
    } catch {
      return [];
    }
  }

  private async loadEtaNotificationPanels(includeWorkflowContext: boolean): Promise<RawLivePanel[]> {
    const statusCode = this.valueOrDefault('etaNotificationStatus', 'RECEIVED');
    const panels: RawLivePanel[] = [
      {
        title: 'ETA notification monitoring',
        description: 'Finance-facing visibility for unresolved authority notifications and ETA attention pressure.',
        data: await this.api.get('/api/v1/finance/eta-notifications/monitoring?itemLimit=8')
      },
      {
        title: 'ETA notification queue',
        description: 'ETA authority notifications filtered by the currently selected queue status.',
        data: await this.api.get(`/api/v1/finance/eta-notifications?statusCode=${encodeURIComponent(statusCode)}`)
      }
    ];

    if (includeWorkflowContext) {
      panels.push({
        title: 'Finance exception context',
        description: 'Workflow exception posture kept alongside ETA notification operations for finance users.',
        data: await this.api.get('/api/v1/internal/work-queues/exceptions')
      });
    }

    return panels;
  }

  private async loadFawryOperationPanels(includeWorkflowContext: boolean): Promise<RawLivePanel[]> {
    const resolution = await this.resolveLatestFawryExecution();
    const panels: RawLivePanel[] = [
      {
        title: 'Fawry execution trace',
        description: 'Latest visible Fawry execution posture for finance and support tracing.',
        data: resolution.summary
      }
    ];

    if (!resolution.executionId) {
      if (includeWorkflowContext) {
        panels.push({
          title: 'Gateway operation note',
          description: 'No visible Fawry execution is currently available for direct contract inspection.',
          data: {
            provider: 'FAWRY',
            status: resolution.status,
            message: resolution.message
          }
        });
      }

      return panels;
    }

    panels.push({
      title: 'Fawry status contract',
      description: 'Server-side provider-status contract for the selected Fawry execution.',
      data: await this.getAllowUnavailable(`/api/v1/finance/payment-executions/${resolution.executionId}/provider-status-contract`)
    });

    panels.push({
      title: 'Fawry cancel contract',
      description: 'Server-side unpaid-order cancellation contract for the selected Fawry execution.',
      data: await this.getAllowUnavailable(`/api/v1/finance/payment-executions/${resolution.executionId}/provider-cancel-contract`)
    });

    return panels;
  }

  private async inspectFawryContract(contractType: 'status' | 'cancel'): Promise<void> {
    const resolution = await this.resolveLatestFawryExecution();
    if (!resolution.executionId) {
      this.messageText = resolution.message;
      return;
    }

    const suffix = contractType === 'status' ? 'provider-status-contract' : 'provider-cancel-contract';
    const result = await this.getAllowUnavailable(`/api/v1/finance/payment-executions/${resolution.executionId}/${suffix}`);
    const record = typeof result === 'object' && result !== null ? result as LiveRecord : null;
    const contractTypeCode = this.asText(record?.['contractTypeCode']);
    const requestUrl = this.asText(record?.['requestUrl']);

    if (contractTypeCode) {
      this.messageText = `${contractTypeCode} loaded for execution ${resolution.executionNumber ?? resolution.executionId}. ${requestUrl}`;
      return;
    }

    this.messageText = `Fawry ${contractType} contract is not currently available for execution ${resolution.executionNumber ?? resolution.executionId}.`;
  }

  private async resolveLatestFawryExecution(): Promise<{
    executionId: number | null;
    executionNumber: string | null;
    status: string;
    message: string;
    summary: unknown;
  }> {
    const response = await this.getAllowUnavailable('/api/v1/finance/payment-executions');
    const executions = this.asRecordArray(response)
      .filter((entry) => this.asText(entry['integrationProviderCode']).toUpperCase() === 'FAWRY')
      .sort((left, right) => this.compareFawryExecutions(left, right));

    const selected = executions[0];
    const executionId = this.getNumericId(selected, ['paymentGatewayExecutionId']);
    const executionNumber = this.asText(selected?.['executionNumber']) || null;

    if (!executionId) {
      const canReadQueue = Array.isArray(response);
      const message = canReadQueue
        ? 'No Fawry payment execution is currently visible in the finance queue.'
        : 'Fawry execution inspection is not available for the current role or environment.';
      return {
        executionId: null,
        executionNumber: null,
        status: canReadQueue ? 'NO_VISIBLE_EXECUTION' : 'ACCESS_LIMITED',
        message,
        summary: canReadQueue
          ? {
              provider: 'FAWRY',
              visibleExecutions: executions.length,
              note: message
            }
          : response
      };
    }

    return {
      executionId,
      executionNumber,
      status: this.asText(selected['statusCode']) || 'VISIBLE',
      message: `Using Fawry execution ${executionNumber ?? executionId} for provider contract inspection.`,
      summary: {
        paymentGatewayExecutionId: executionId,
        executionNumber,
        statusCode: this.asText(selected['statusCode']),
        sourceDocumentNumber: this.asText(selected['sourceDocumentNumber']),
        amountAmount: selected['amountAmount'],
        currencyCode: this.asText(selected['currencyCode']),
        providerReference: this.asText(selected['providerReference']),
        createdAtUtc: selected['createdAtUtc']
      }
    };
  }

  private compareFawryExecutions(left: LiveRecord, right: LiveRecord): number {
    const statusDifference = this.fawryExecutionRank(this.asText(right['statusCode'])) - this.fawryExecutionRank(this.asText(left['statusCode']));
    if (statusDifference !== 0) {
      return statusDifference;
    }

    const rightTimestamp = this.parseTimestamp(right['createdAtUtc']);
    const leftTimestamp = this.parseTimestamp(left['createdAtUtc']);
    if (rightTimestamp !== leftTimestamp) {
      return rightTimestamp - leftTimestamp;
    }

    const rightId = this.getNumericId(right, ['paymentGatewayExecutionId']) ?? 0;
    const leftId = this.getNumericId(left, ['paymentGatewayExecutionId']) ?? 0;
    return rightId - leftId;
  }

  private fawryExecutionRank(statusCode: string): number {
    switch (statusCode.toUpperCase()) {
      case 'DISPATCHED':
        return 4;
      case 'QUEUED':
      case 'PENDING':
        return 3;
      case 'ACKNOWLEDGED':
        return 2;
      case 'SETTLED':
      case 'COMPLETED':
        return 1;
      default:
        return 0;
    }
  }

  private parseTimestamp(value: unknown): number {
    if (typeof value !== 'string' || value.trim().length === 0) {
      return 0;
    }

    const timestamp = Date.parse(value);
    return Number.isFinite(timestamp) ? timestamp : 0;
  }

  private async resolveFinancingSourceDocumentId(partyId: number): Promise<number> {
    if (this.journey.id === 'supplier') {
      const purchaseOrders = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/purchase-orders`));
      return this.getNumericId(purchaseOrders[0], ['purchaseOrderId']) ?? 1;
    }

    const orders = this.asRecordArray(await this.api.get(`/api/v1/external/parties/${partyId}/orders`));
    return this.getNumericId(orders[0], ['salesOrderId', 'orderId']) ?? 1;
  }

  private buildInitialValues(journey: JourneyDefinition): Record<string, string> {
    return journey.steps.reduce<Record<string, string>>((allValues, step) => {
      step.fields.forEach((field) => {
        allValues[field.key] = field.defaultValue ?? '';
      });
      return allValues;
    }, {});
  }

  private resolveDefaultStepId(): string {
    if (this.journey.id !== 'reporting-center' || !this.session) {
      return getDefaultJourneyStepId(this.journey.id);
    }

    return this.visibleJourneySteps[0]?.id ?? getDefaultJourneyStepId(this.journey.id);
  }

  private isFinanceReportingUser(session: SessionSummary): boolean {
    return session.roleCodes.some((roleCode) => roleCode.startsWith('FINANCE_'));
  }

  private isOperationsReportingUser(session: SessionSummary): boolean {
    return ['operations', 'procurement', 'inventory', 'sales'].includes(session.workspace);
  }

  private isIslamicFinanceReportingUser(session: SessionSummary): boolean {
    return session.roleCodes.includes('IF_OPERATIONS_MAKER') || session.workspace === 'islamic';
  }

  private valueOrDefault(key: string, fallback: string): string {
    const value = this.formValues[key];
    return value && value.trim().length > 0 ? value.trim() : fallback;
  }

  private mapComplianceDocuments(documents: LiveRecord[]): Array<Record<string, unknown>> {
    return documents.map((document) => ({
      documentType: this.asText(document['documentType']) || 'KYC_DOCUMENT',
      fileName: this.asText(document['fileName']) || 'document.pdf',
      storageReference: this.asText(document['storageReference']) || 'managed://onboarding/document.pdf',
      contentType: this.asText(document['contentType']) || 'application/pdf',
      fileSizeBytes: Number(document['fileSizeBytes'] ?? 0)
    }));
  }

  private requireSelectedFile(fieldKey: string, errorMessage: string): File {
    const file = this.selectedFiles[fieldKey];
    if (!file) {
      throw new Error(errorMessage);
    }

    return file;
  }

  private today(): string {
    return new Date().toISOString().slice(0, 10);
  }

  private resolveDemoMode(): boolean {
    if (typeof window === 'undefined') {
      return this.route.snapshot.queryParamMap.get('demo') === '1';
    }

    if (this.route.snapshot.queryParamMap.get('demo') === '1') {
      window.localStorage.setItem('cashline-kendo-demo-mode', '1');
      return true;
    }

    return window.localStorage.getItem('cashline-kendo-demo-mode') === '1';
  }

  private optionalValue(key: string): string | null {
    const value = this.formValues[key];
    if (!value) {
      return null;
    }

    const trimmed = value.trim();
    return trimmed.length > 0 ? trimmed : null;
  }

  private buildQueryPath(path: string, query: Record<string, string | null | undefined>): string {
    const params = new URLSearchParams();
    Object.entries(query).forEach(([key, value]) => {
      if (value && value.trim().length > 0) {
        params.set(key, value);
      }
    });

    const queryString = params.toString();
    return queryString.length > 0 ? `${path}?${queryString}` : path;
  }

  private resolveFinancialStatementCode(): string {
    return this.valueOrDefault('financialStatementCode', 'BALANCE_SHEET').toUpperCase();
  }

  private async exportFinancialStatement(): Promise<void> {
    const statementCode = this.resolveFinancialStatementCode();
    const format = this.valueOrDefault('financialStatementFormat', 'PDF').toUpperCase();
    await this.exportDownload(
      this.buildQueryPath(`/api/v1/reports/finance-statements/${encodeURIComponent(statementCode)}/export`, {
        format,
        dateFrom: this.optionalValue('financialDateFrom'),
        dateTo: this.optionalValue('financialDateTo'),
        asOfDate: this.optionalValue('financialAsOfDate') ?? this.today()
      }),
      `${statementCode.toLowerCase()}-${format.toLowerCase()}`
    );
  }

  private async exportDownload(path: string, fallbackFileName: string): Promise<void> {
    const file = await this.api.download(path);
    const fileName = file.fileName ?? fallbackFileName;
    this.triggerBrowserDownload(file.blob, fileName);
    this.messageText = `${fileName} downloaded.`;
  }

  private exportCurrentPanelsAsCsv(): void {
    const rows = this.collectCurrentPanelExportRows();
    const headers = this.collectExportHeaders(rows);
    const csv = [
      headers.join(','),
      ...rows.map((row) => headers.map((header) => this.escapeCsvValue(row[header])).join(','))
    ].join('\n');
    this.triggerBrowserDownload(new Blob([csv], { type: 'text/csv;charset=utf-8' }), `${this.journey.id}-${this.currentStepDefinition.id}.csv`);
    this.messageText = 'Current report exported as CSV.';
  }

  private exportCurrentPanelsAsExcel(): void {
    const rows = this.collectCurrentPanelExportRows();
    const headers = this.collectExportHeaders(rows);
    const tableRows = rows.map((row) => `<Row>${headers.map((header) => this.buildSpreadsheetCell(row[header] ?? '')).join('')}</Row>`).join('');
    const xml = `<?xml version="1.0"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:o="urn:schemas-microsoft-com:office:office"
 xmlns:x="urn:schemas-microsoft-com:office:excel"
 xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
  <Worksheet ss:Name="Report Export">
    <Table>
      <Row>${headers.map((header) => this.buildSpreadsheetCell(header)).join('')}</Row>
      ${tableRows}
    </Table>
  </Worksheet>
</Workbook>`;
    this.triggerBrowserDownload(new Blob([xml], { type: 'application/vnd.ms-excel' }), `${this.journey.id}-${this.currentStepDefinition.id}.xls`);
    this.messageText = 'Current report exported as Excel.';
  }

  private exportCurrentPanelsAsPdf(): void {
    const popup = this.openPrintableReportWindow(true);
    if (!popup) {
      this.messageText = 'Allow pop-ups to export this report as PDF.';
      return;
    }

    this.messageText = 'Printable report opened. Use Save as PDF in the print dialog to export the report as PDF.';
  }

  private printCurrentReport(): void {
    const popup = this.openPrintableReportWindow(false);
    if (!popup) {
      this.messageText = 'Allow pop-ups to print this report.';
      return;
    }

    this.messageText = 'Current report sent to print.';
  }

  private collectCurrentPanelExportRows(): Array<Record<string, string>> {
    const rows: Array<Record<string, string>> = [];
    for (const panel of this.livePanels) {
      if (panel.previewRows.length > 0) {
        for (const previewRow of panel.previewRows) {
          const exportRow: Record<string, string> = { Panel: panel.title };
          Object.entries(previewRow).forEach(([key, value]) => {
            exportRow[this.prettifyLabel(key)] = this.formatPreviewValue(value);
          });
          rows.push(exportRow);
        }
        continue;
      }

      if (panel.metrics.length > 0) {
        for (const metric of panel.metrics) {
          rows.push({
            Panel: panel.title,
            Metric: metric.label,
            Value: metric.value
          });
        }
        continue;
      }

      rows.push({
        Panel: panel.title,
        Narrative: panel.narrative
      });
    }

    return rows.length > 0 ? rows : [{ Panel: this.currentStepDefinition.title, Narrative: 'No report rows are currently available for export.' }];
  }

  private collectExportHeaders(rows: Array<Record<string, string>>): string[] {
    const ordered = new Set<string>();
    rows.forEach((row) => Object.keys(row).forEach((key) => ordered.add(key)));
    return Array.from(ordered);
  }

  private escapeCsvValue(value: string): string {
    const normalized = value.replace(/"/g, '""');
    return /[",\n]/.test(normalized) ? `"${normalized}"` : normalized;
  }

  private buildSpreadsheetCell(value: string): string {
    const escaped = value
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;');
    return `<Cell><Data ss:Type="String">${escaped}</Data></Cell>`;
  }

  private triggerBrowserDownload(blob: Blob, fileName: string): void {
    if (typeof document === 'undefined') {
      return;
    }

    const url = URL.createObjectURL(blob);
    const anchor = document.createElement('a');
    anchor.href = url;
    anchor.download = fileName;
    anchor.style.display = 'none';
    document.body.appendChild(anchor);
    anchor.click();
    document.body.removeChild(anchor);
    URL.revokeObjectURL(url);
  }

  private openPrintableReportWindow(autoPrint: boolean): Window | null {
    if (typeof window === 'undefined') {
      return null;
    }

    const popup = window.open('', '_blank', 'noopener,noreferrer,width=1080,height=820');
    if (!popup) {
      return null;
    }

    popup.document.open();
    popup.document.write(this.buildPrintableReportHtml(autoPrint));
    popup.document.close();
    return popup;
  }

  private buildPrintableReportHtml(autoPrint: boolean): string {
    const renderedPanels = this.livePanels.map((panel) => {
      const metrics = panel.metrics.length > 0
        ? `<div class="metrics">${panel.metrics.map((metric) => `<span class="metric"><strong>${this.escapeHtml(this.t(metric.label))}:</strong> ${this.escapeHtml(this.t(metric.value))}</span>`).join('')}</div>`
        : '';

      const table = panel.previewRows.length > 0
        ? `<table><thead><tr>${panel.previewKeys.map((key) => `<th>${this.escapeHtml(this.prettifyKey(key))}</th>`).join('')}</tr></thead><tbody>${panel.previewRows.map((row) => `<tr>${panel.previewKeys.map((key) => `<td>${this.escapeHtml(this.formatPreviewValue(row[key]))}</td>`).join('')}</tr>`).join('')}</tbody></table>`
        : `<p class="narrative">${this.escapeHtml(this.t(panel.narrative))}</p>`;

      return `<section class="panel"><h2>${this.escapeHtml(this.t(panel.title))}</h2><p class="description">${this.escapeHtml(this.t(panel.description))}</p>${metrics}${table}</section>`;
    }).join('');

    const autoPrintScript = autoPrint
      ? `<script>window.addEventListener('load', () => { window.print(); });</script>`
      : `<script>window.addEventListener('load', () => { window.print(); });</script>`;

    return `<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>${this.escapeHtml(this.t(this.currentStepDefinition.title))}</title>
    <style>
      body { font-family: Arial, sans-serif; margin: 24px; color: #181716; }
      h1 { margin: 0 0 8px; font-size: 24px; }
      .meta { margin-bottom: 24px; color: #555; font-size: 12px; }
      .panel { margin-bottom: 24px; page-break-inside: avoid; border: 1px solid #ddd; border-radius: 10px; padding: 16px; }
      .panel h2 { margin: 0 0 8px; font-size: 18px; }
      .description { margin: 0 0 10px; color: #666; }
      .metrics { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; }
      .metric { border: 1px solid #ddd; border-radius: 999px; padding: 6px 10px; font-size: 12px; }
      table { width: 100%; border-collapse: collapse; font-size: 12px; }
      th, td { border: 1px solid #ddd; padding: 8px; text-align: left; vertical-align: top; }
      th { background: #f5f5f5; }
      .narrative { margin: 0; }
      @media print { body { margin: 12mm; } }
    </style>
  </head>
  <body>
    <h1>${this.escapeHtml(this.t(this.currentStepDefinition.title))}</h1>
    <div class="meta">${this.escapeHtml(this.t(this.journey.title))} | ${this.escapeHtml(this.t(this.currentStepDefinition.subtitle))} | ${this.escapeHtml(new Date().toLocaleString())}</div>
    ${renderedPanels}
    ${autoPrintScript}
  </body>
</html>`;
  }

  private escapeHtml(value: string): string {
    return value
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;');
  }

  private plusDays(days: number): string {
    const date = new Date();
    date.setDate(date.getDate() + days);
    return date.toISOString().slice(0, 10);
  }

  private async requirePartyAction(action: (partyId: number) => Promise<void>): Promise<void> {
    const partyId = this.session?.partyId ?? null;
    if (!partyId) {
      throw new Error('The signed-in account is not linked to an external party record.');
    }

    await action(partyId);
  }

  private async requirePartyPanels(
    partyId: number | null,
    loader: (partyId: number) => Promise<RawLivePanel[]>
  ): Promise<RawLivePanel[]> {
    if (!partyId) {
      return [
        {
          title: 'External party context required',
          description: 'This signed-in account does not expose an external party context for this journey.',
          data: this.session
        }
      ];
    }

    return loader(partyId);
  }

  private async getAllowNotFound(path: string): Promise<unknown> {
    try {
      return await this.api.get(path);
    } catch {
      return null;
    }
  }

  private async getAllowUnavailable(path: string): Promise<unknown> {
    try {
      return await this.api.get(path);
    } catch (error) {
      return {
        status: 'UNAVAILABLE',
        path,
        message: error instanceof Error && error.message
          ? error.message
          : 'The current role or environment cannot load this finance integration view.'
      };
    }
  }

  private asRecordArray(value: unknown): LiveRecord[] {
    return Array.isArray(value)
      ? value.filter((entry): entry is LiveRecord => typeof entry === 'object' && entry !== null)
      : [];
  }

  private getNumericId(record: LiveRecord | undefined, keys: string[]): number | null {
    if (!record) {
      return null;
    }

    for (const key of keys) {
      const value = record[key];
      if (typeof value === 'number' && Number.isFinite(value)) {
        return value;
      }
    }

    return null;
  }

  private asText(value: unknown): string {
    return typeof value === 'string' ? value : '';
  }

  private resolveCustomerClassificationCode(): string {
    const candidate = this.valueOrDefault('customerSegment', 'COMPANY').toUpperCase();
    return ['GOVERNMENT', 'COMPANY', 'INSTITUTIONAL'].includes(candidate)
      ? candidate
      : 'COMPANY';
  }

  private resolveOnboardingSubjectType(): 'SUPPLIER' | 'CUSTOMER' {
    return this.valueOrDefault('onboardingSubjectType', 'SUPPLIER').toUpperCase() === 'CUSTOMER'
      ? 'CUSTOMER'
      : 'SUPPLIER';
  }

  private async loadAccountingOfficeClientAssignments(): Promise<LiveRecord[]> {
    return this.asRecordArray(await this.api.get('/api/v1/accounting-office/clients'));
  }

  private async loadAccountingOfficeClientWorkspace(clients: LiveRecord[]): Promise<LiveRecord | null> {
    const requestedPartyId = Number(this.formValues['aoSelectedPartyId'] ?? '');
    const resolvedPartyId = Number.isFinite(requestedPartyId) && requestedPartyId > 0
      ? requestedPartyId
      : this.getNumericId(clients[0], ['partyId']);

    if (!resolvedPartyId) {
      return null;
    }

    this.formValues['aoSelectedPartyId'] = String(resolvedPartyId);
    const workspace = await this.api.get(`/api/v1/accounting-office/clients/${resolvedPartyId}/workspace`);
    return typeof workspace === 'object' && workspace !== null ? workspace as LiveRecord : null;
  }

  private async loadAccountingOfficeSubmissionPosture(clients: LiveRecord[]): Promise<LiveRecord | null> {
    const requestedPartyId = Number(this.formValues['aoSelectedPartyId'] ?? '');
    const resolvedPartyId = Number.isFinite(requestedPartyId) && requestedPartyId > 0
      ? requestedPartyId
      : this.getNumericId(clients[0], ['partyId']);

    if (!resolvedPartyId) {
      return null;
    }

    this.formValues['aoSelectedPartyId'] = String(resolvedPartyId);
    const posture = await this.api.get(`/api/v1/accounting-office/clients/${resolvedPartyId}/submission-posture`);
    return typeof posture === 'object' && posture !== null ? posture as LiveRecord : null;
  }

  private async loadAccountingOfficeActivity(): Promise<LiveRecord[]> {
    const requestedPartyId = Number(this.formValues['aoSelectedPartyId'] ?? '');
    const path = Number.isFinite(requestedPartyId) && requestedPartyId > 0
      ? `/api/v1/accounting-office/activity?partyId=${requestedPartyId}`
      : '/api/v1/accounting-office/activity';
    return this.asRecordArray(await this.api.get(path));
  }

  private async loadAccountingOfficeInvoiceEtaPanels(workspace: LiveRecord | null): Promise<RawLivePanel[]> {
    const invoiceId = this.resolveAccountingOfficeInvoiceId(workspace);
    const partyId = Number(this.formValues['aoSelectedPartyId'] ?? '');

    if (!invoiceId || !Number.isFinite(partyId) || partyId <= 0) {
      return [];
    }

    return [
      {
        title: 'Selected client ETA validation',
        description: 'ETA validation posture for the selected assigned-client invoice.',
        data: await this.api.get(`/api/v1/accounting-office/clients/${partyId}/ar-invoices/${invoiceId}/eta-validation-pack`)
      },
      {
        title: 'Selected client ETA submissions',
        description: 'Submission history already recorded for the selected assigned-client invoice.',
        data: await this.api.get(`/api/v1/accounting-office/clients/${partyId}/ar-invoices/${invoiceId}/eta-submissions`)
      }
    ];
  }

  private async loadAccountingOfficeStatementPanels(clients: LiveRecord[]): Promise<RawLivePanel[]> {
    const requestedPartyId = Number(this.formValues['aoSelectedPartyId'] ?? '');
    const resolvedPartyId = Number.isFinite(requestedPartyId) && requestedPartyId > 0
      ? requestedPartyId
      : this.getNumericId(clients[0], ['partyId']);

    if (!resolvedPartyId) {
      return [];
    }

    this.formValues['aoSelectedPartyId'] = String(resolvedPartyId);
    const statements = this.asRecordArray(await this.api.get(`/api/v1/accounting-office/clients/${resolvedPartyId}/statements`));
    const statementId = this.resolveAccountingOfficeStatementId(statements);

    if (!statementId) {
      return [
        {
          title: 'Selected client statements',
          description: 'No statement documents are currently available for the selected assigned client.',
          data: statements
        }
      ];
    }

    const deliveryLogs = this.asRecordArray(await this.api.get(`/api/v1/accounting-office/clients/${resolvedPartyId}/statements/${statementId}/delivery-logs`));
    this.resolveAccountingOfficeDeliveryLogId(deliveryLogs);

    return [
      {
        title: 'Selected client statements',
        description: 'Statement documents available for delegated delivery follow-up on the selected assigned client.',
        data: statements
      },
      {
        title: 'Selected client statement delivery logs',
        description: 'Delivery-log trail captured for the selected assigned-client statement.',
        data: deliveryLogs
      }
    ];
  }

  private async exportAccountingOfficeClientReportCsv(): Promise<void> {
    const clients = await this.loadAccountingOfficeClientAssignments();
    const requestedPartyId = Number(this.formValues['aoSelectedPartyId'] ?? '');
    const resolvedPartyId = Number.isFinite(requestedPartyId) && requestedPartyId > 0
      ? requestedPartyId
      : this.getNumericId(clients[0], ['partyId']);

    if (!resolvedPartyId) {
      throw new Error('No assigned client is currently available for Accounting Office report export.');
    }

    this.formValues['aoSelectedPartyId'] = String(resolvedPartyId);
    await this.exportDownload(
      `/api/v1/accounting-office/clients/${resolvedPartyId}/report-export`,
      `accounting-office-client-${resolvedPartyId}.csv`
    );
  }

  private async uploadAccountingOfficeClientComplianceDocument(): Promise<void> {
    const clients = await this.loadAccountingOfficeClientAssignments();
    const requestedPartyId = Number(this.formValues['aoSelectedPartyId'] ?? '');
    const resolvedPartyId = Number.isFinite(requestedPartyId) && requestedPartyId > 0
      ? requestedPartyId
      : this.getNumericId(clients[0], ['partyId']);

    if (!resolvedPartyId) {
      throw new Error('No assigned client is currently available for compliance upload.');
    }

    const file = this.requireSelectedFile('aoClientComplianceFile', 'Select a client compliance document before uploading.');
    const form = new FormData();
    form.append('documentType', this.valueOrDefault('aoClientComplianceDocumentType', 'COMMERCIAL_REGISTER').toUpperCase());
    form.append('file', file, file.name);
    await this.api.postForm(`/api/v1/accounting-office/clients/${resolvedPartyId}/compliance-documents/upload`, form);
    this.formValues['aoSelectedPartyId'] = String(resolvedPartyId);
    this.messageText = `Client compliance file ${file.name} uploaded from the Accounting Office lane.`;
  }

  private async submitAccountingOfficeClientEtaInvoice(): Promise<void> {
    const clients = await this.loadAccountingOfficeClientAssignments();
    const workspace = await this.loadAccountingOfficeClientWorkspace(clients);
    const partyId = Number(this.formValues['aoSelectedPartyId'] ?? '');
    const invoiceId = this.resolveAccountingOfficeInvoiceId(workspace);

    if (!Number.isFinite(partyId) || partyId <= 0 || !invoiceId) {
      throw new Error('No assigned client invoice is currently available for ETA submission.');
    }

    await this.api.post(`/api/v1/accounting-office/clients/${partyId}/ar-invoices/${invoiceId}/eta-submit`, {
      actorUserId: 0,
      payloadText: this.optionalValue('aoEtaPayloadText')
    });
    this.messageText = `ETA submission triggered for assigned client invoice ${invoiceId}.`;
  }

  private async startAccountingOfficeClientRequestReview(): Promise<void> {
    const partyId = await this.resolveAccountingOfficeClientPartyId();
    const requestId = await this.resolveAccountingOfficeClientRequestId(partyId);
    if (!requestId) {
      throw new Error('No client request is currently available for review.');
    }

    this.formValues['aoSelectedClientRequestId'] = String(requestId);
    await this.api.post(`/api/v1/accounting-office/clients/${partyId}/requests/${requestId}/start-review`, {
      reviewNotesText: this.optionalValue('aoClientRequestReviewNotes')
    });
    this.messageText = `Client request ${requestId} moved into review.`;
  }

  private async completeAccountingOfficeClientRequest(): Promise<void> {
    const partyId = await this.resolveAccountingOfficeClientPartyId();
    const requestId = await this.resolveAccountingOfficeClientRequestId(partyId);
    if (!requestId) {
      throw new Error('No client request is currently available for completion.');
    }

    const completionNotes = this.valueOrDefault('aoClientRequestCompletionNotes', 'Completed by Accounting Office under CashLine governance.');
    this.formValues['aoSelectedClientRequestId'] = String(requestId);
    await this.api.post(`/api/v1/accounting-office/clients/${partyId}/requests/${requestId}/complete`, {
      completionNotesText: completionNotes
    });
    this.messageText = `Client request ${requestId} completed.`;
  }

  private async rejectAccountingOfficeClientRequest(): Promise<void> {
    const partyId = await this.resolveAccountingOfficeClientPartyId();
    const requestId = await this.resolveAccountingOfficeClientRequestId(partyId);
    if (!requestId) {
      throw new Error('No client request is currently available for rejection.');
    }

    const reason = this.valueOrDefault('aoClientRequestRejectionReason', 'Rejected by Accounting Office with missing evidence.');
    this.formValues['aoSelectedClientRequestId'] = String(requestId);
    await this.api.post(`/api/v1/accounting-office/clients/${partyId}/requests/${requestId}/reject`, {
      rejectionReasonText: reason
    });
    this.messageText = `Client request ${requestId} rejected.`;
  }

  private async recordAccountingOfficeClientStatementDelivery(): Promise<void> {
    const clients = await this.loadAccountingOfficeClientAssignments();
    const requestedPartyId = Number(this.formValues['aoSelectedPartyId'] ?? '');
    const resolvedPartyId = Number.isFinite(requestedPartyId) && requestedPartyId > 0
      ? requestedPartyId
      : this.getNumericId(clients[0], ['partyId']);

    if (!resolvedPartyId) {
      throw new Error('No assigned client is currently available for statement delivery logging.');
    }

    this.formValues['aoSelectedPartyId'] = String(resolvedPartyId);
    const statements = this.asRecordArray(await this.api.get(`/api/v1/accounting-office/clients/${resolvedPartyId}/statements`));
    const statementId = this.resolveAccountingOfficeStatementId(statements);
    if (!statementId) {
      throw new Error('No assigned client statement is currently available for delivery logging.');
    }

    await this.api.post(`/api/v1/accounting-office/clients/${resolvedPartyId}/statements/${statementId}/delivery-logs`, {
      deliveredAtUtc: null,
      channelCode: this.valueOrDefault('aoStatementDeliveryChannel', 'EMAIL').toUpperCase(),
      recipientAddress: this.optionalValue('aoStatementDeliveryRecipient'),
      deliveryStatusCode: this.valueOrDefault('aoStatementDeliveryStatus', 'DELIVERED').toUpperCase(),
      notes: this.optionalValue('aoStatementDeliveryNotes')
    });
    this.messageText = `Statement delivery log recorded for assigned client statement ${statementId}.`;
  }

  private async resolveAccountingOfficeClientPartyId(): Promise<number> {
    const clients = await this.loadAccountingOfficeClientAssignments();
    const requestedPartyId = Number(this.formValues['aoSelectedPartyId'] ?? '');
    const resolvedPartyId = Number.isFinite(requestedPartyId) && requestedPartyId > 0
      ? requestedPartyId
      : this.getNumericId(clients[0], ['partyId']);

    if (!resolvedPartyId) {
      throw new Error('No assigned client is currently available.');
    }

    this.formValues['aoSelectedPartyId'] = String(resolvedPartyId);
    return resolvedPartyId;
  }

  private async resolveAccountingOfficeClientRequestId(partyId: number): Promise<number | null> {
    const requestedRequestId = Number(this.formValues['aoSelectedClientRequestId'] ?? '');
    const requests = this.asRecordArray(await this.api.get(`/api/v1/accounting-office/clients/${partyId}/requests`));
    if (Number.isFinite(requestedRequestId) && requestedRequestId > 0) {
      const match = requests.find((row) => this.getNumericId(row, ['requestId']) === requestedRequestId);
      if (match) {
        return requestedRequestId;
      }
    }

    const first = this.getNumericId(requests[0], ['requestId']);
    return first ?? null;
  }

  private async generateAccountingOfficeClientStatement(): Promise<void> {
    const clients = await this.loadAccountingOfficeClientAssignments();
    const requestedPartyId = Number(this.formValues['aoSelectedPartyId'] ?? '');
    const resolvedPartyId = Number.isFinite(requestedPartyId) && requestedPartyId > 0
      ? requestedPartyId
      : this.getNumericId(clients[0], ['partyId']);

    if (!resolvedPartyId) {
      throw new Error('No assigned client is currently available for statement generation.');
    }

    this.formValues['aoSelectedPartyId'] = String(resolvedPartyId);
    const dateFrom = this.optionalValue('aoStatementDateFrom');
    const dateTo = this.optionalValue('aoStatementDateTo');
    const currencyCode = this.optionalValue('aoStatementCurrencyCode');

    const payload = {
      dateFrom: dateFrom ? dateFrom : null,
      dateTo: dateTo ? dateTo : null,
      currencyCode: currencyCode ? currencyCode.toUpperCase() : null
    };

    const generated = await this.api.post(`/api/v1/accounting-office/clients/${resolvedPartyId}/statements/generate`, payload);
    const statementId = this.getNumericId(generated as LiveRecord, ['statementId']);
    if (statementId) {
      this.formValues['aoSelectedStatementId'] = String(statementId);
    }

    this.messageText = statementId
      ? `Statement generated for assigned client (statement ${statementId}).`
      : 'Statement generated for assigned client.';
  }

  private async acknowledgeAccountingOfficeSupplierStatementDelivery(): Promise<void> {
    const clients = await this.loadAccountingOfficeClientAssignments();
    const requestedPartyId = Number(this.formValues['aoSelectedPartyId'] ?? '');
    const resolvedPartyId = Number.isFinite(requestedPartyId) && requestedPartyId > 0
      ? requestedPartyId
      : this.getNumericId(clients[0], ['partyId']);

    if (!resolvedPartyId) {
      throw new Error('No assigned client is currently available for statement delivery acknowledgment.');
    }

    this.formValues['aoSelectedPartyId'] = String(resolvedPartyId);
    const statements = this.asRecordArray(await this.api.get(`/api/v1/accounting-office/clients/${resolvedPartyId}/statements`));
    const statementId = this.resolveAccountingOfficeSupplierStatementId(statements);
    if (!statementId) {
      throw new Error('No assigned supplier statement is currently available for delivery acknowledgment.');
    }

    const deliveryLogs = this.asRecordArray(await this.api.get(`/api/v1/accounting-office/clients/${resolvedPartyId}/statements/${statementId}/delivery-logs`));
    const deliveryLogId = this.resolveAccountingOfficeDeliveryLogId(deliveryLogs);
    if (!deliveryLogId) {
      throw new Error('No supplier statement delivery log is currently available for acknowledgment.');
    }

    await this.api.post(`/api/v1/accounting-office/clients/${resolvedPartyId}/statements/${statementId}/delivery-logs/${deliveryLogId}/acknowledge`, {
      acknowledgmentStatusCode: this.valueOrDefault('aoStatementAckStatus', 'ACKNOWLEDGED').toUpperCase(),
      notes: this.optionalValue('aoStatementAckNotes')
    });
    this.messageText = `Supplier statement delivery log ${deliveryLogId} acknowledged for statement ${statementId}.`;
  }

  private async retryAccountingOfficeClientStatementDelivery(): Promise<void> {
    const clients = await this.loadAccountingOfficeClientAssignments();
    const requestedPartyId = Number(this.formValues['aoSelectedPartyId'] ?? '');
    const resolvedPartyId = Number.isFinite(requestedPartyId) && requestedPartyId > 0
      ? requestedPartyId
      : this.getNumericId(clients[0], ['partyId']);

    if (!resolvedPartyId) {
      throw new Error('No assigned client is currently available for statement delivery retry.');
    }

    this.formValues['aoSelectedPartyId'] = String(resolvedPartyId);
    const statements = this.asRecordArray(await this.api.get(`/api/v1/accounting-office/clients/${resolvedPartyId}/statements`));
    const statementId = this.resolveAccountingOfficeStatementId(statements);
    if (!statementId) {
      throw new Error('No assigned client statement is currently available for delivery retry.');
    }

    const deliveryLogs = this.asRecordArray(await this.api.get(`/api/v1/accounting-office/clients/${resolvedPartyId}/statements/${statementId}/delivery-logs`));
    const deliveryLogId = this.resolveAccountingOfficeDeliveryLogId(deliveryLogs);
    if (!deliveryLogId) {
      throw new Error('No statement delivery log is currently available for retry.');
    }

    await this.api.post(`/api/v1/accounting-office/clients/${resolvedPartyId}/statements/${statementId}/delivery-logs/${deliveryLogId}/retry`, {
      channelCode: this.valueOrDefault('aoStatementDeliveryChannel', 'EMAIL').toUpperCase(),
      recipientAddress: this.optionalValue('aoStatementDeliveryRecipient'),
      deliveryStatusCode: 'SENT',
      notes: this.optionalValue('aoStatementRetryNotes')
    });
    this.messageText = `Statement delivery retry recorded for log ${deliveryLogId} (statement ${statementId}).`;
  }

  private resolveAccountingOfficeInvoiceId(workspace: LiveRecord | null): number | null {
    const requestedInvoiceId = Number(this.formValues['aoSelectedInvoiceId'] ?? '');
    const invoices = workspace && Array.isArray(workspace['invoices'])
      ? this.asRecordArray(workspace['invoices'])
      : [];

    if (Number.isFinite(requestedInvoiceId) && requestedInvoiceId > 0) {
      const exactMatch = invoices.find((entry) => this.getNumericId(entry, ['accountsReceivableInvoiceId', 'invoiceId']) === requestedInvoiceId);
      if (exactMatch) {
        this.formValues['aoSelectedInvoiceId'] = String(requestedInvoiceId);
        return requestedInvoiceId;
      }
    }

    const firstInvoiceId = this.getNumericId(invoices[0], ['accountsReceivableInvoiceId', 'invoiceId']);
    if (!firstInvoiceId) {
      return null;
    }

    this.formValues['aoSelectedInvoiceId'] = String(firstInvoiceId);
    return firstInvoiceId;
  }

  private resolveAccountingOfficeStatementId(statements: LiveRecord[]): number | null {
    const requestedStatementId = Number(this.formValues['aoSelectedStatementId'] ?? '');
    if (Number.isFinite(requestedStatementId) && requestedStatementId > 0) {
      const exactMatch = statements.find((entry) => this.getNumericId(entry, ['statementId']) === requestedStatementId);
      if (exactMatch) {
        this.formValues['aoSelectedStatementId'] = String(requestedStatementId);
        return requestedStatementId;
      }
    }

    const firstStatementId = this.getNumericId(statements[0], ['statementId']);
    if (!firstStatementId) {
      return null;
    }

    this.formValues['aoSelectedStatementId'] = String(firstStatementId);
    return firstStatementId;
  }

  private resolveAccountingOfficeDeliveryLogId(deliveryLogs: LiveRecord[]): number | null {
    const requestedDeliveryLogId = Number(this.formValues['aoSelectedDeliveryLogId'] ?? '');
    if (Number.isFinite(requestedDeliveryLogId) && requestedDeliveryLogId > 0) {
      const exactMatch = deliveryLogs.find((entry) => this.getNumericId(entry, ['deliveryLogId']) === requestedDeliveryLogId);
      if (exactMatch) {
        this.formValues['aoSelectedDeliveryLogId'] = String(requestedDeliveryLogId);
        return requestedDeliveryLogId;
      }
    }

    const firstDeliveryLogId = this.getNumericId(deliveryLogs[0], ['deliveryLogId']);
    if (!firstDeliveryLogId) {
      return null;
    }

    this.formValues['aoSelectedDeliveryLogId'] = String(firstDeliveryLogId);
    return firstDeliveryLogId;
  }

  private resolveAccountingOfficeSupplierStatementId(statements: LiveRecord[]): number | null {
    const supplierStatements = statements.filter((entry) => this.asText(entry['statementType']).toUpperCase() === 'SUPPLIER_STATEMENT');
    const requestedStatementId = Number(this.formValues['aoSelectedStatementId'] ?? '');
    if (Number.isFinite(requestedStatementId) && requestedStatementId > 0) {
      const exactMatch = supplierStatements.find((entry) => this.getNumericId(entry, ['statementId']) === requestedStatementId);
      if (exactMatch) {
        this.formValues['aoSelectedStatementId'] = String(requestedStatementId);
        return requestedStatementId;
      }
    }

    const firstSupplierStatementId = this.getNumericId(supplierStatements[0], ['statementId']);
    if (!firstSupplierStatementId) {
      return null;
    }

    this.formValues['aoSelectedStatementId'] = String(firstSupplierStatementId);
    return firstSupplierStatementId;
  }

  private resolveSelectedOnboardingCaseRecord(cases: LiveRecord[]): LiveRecord | null {
    const requestedProfileId = Number(this.formValues['onboardingCaseProfileId'] ?? '');
    if (Number.isFinite(requestedProfileId) && requestedProfileId > 0) {
      const exactMatch = cases.find((entry) => this.getNumericId(entry, ['subjectProfileId']) === requestedProfileId);
      if (exactMatch) {
        return exactMatch;
      }
    }

    return cases[0] ?? null;
  }

  private async resolveOnboardingCaseProfileId(subjectType: 'SUPPLIER' | 'CUSTOMER'): Promise<number> {
    const requestedProfileId = Number(this.formValues['onboardingCaseProfileId'] ?? '');
    if (Number.isFinite(requestedProfileId) && requestedProfileId > 0) {
      return requestedProfileId;
    }

    const cases = this.asRecordArray(await this.api.get(`/api/v1/internal/onboarding/cases?subjectTypeCode=${encodeURIComponent(subjectType)}`));
    const firstProfileId = this.getNumericId(cases[0], ['subjectProfileId']);
    if (!firstProfileId) {
      throw new Error(`No ${subjectType.toLowerCase()} onboarding case is currently available for review.`);
    }

    this.formValues['onboardingCaseProfileId'] = String(firstProfileId);
    return firstProfileId;
  }

  private toTitleCase(value: string): string {
    const normalized = value.trim().toLowerCase();
    return normalized.length > 0
      ? `${normalized[0].toUpperCase()}${normalized.slice(1)}`
      : value;
  }

  private resolveJourneyDefinition(): JourneyDefinition {
    const routeJourneyId = this.route.snapshot.data['journeyId'] as JourneyId | undefined;
    const pathJourneyId = this.route.routeConfig?.path as JourneyId | undefined;
    const journeyId = routeJourneyId ?? pathJourneyId ?? 'supplier';
    return getJourneyDefinition(journeyId);
  }

  private resolveSnapshotReportCode(): string {
    if (this.journey.id === 'operations-control') {
      return 'CORE_OPERATIONS';
    }

    return this.valueOrDefault('snapshotReportCode', 'CORE_FINANCE').toUpperCase();
  }

  private async execute(operation: () => Promise<void>, resetMessages = true): Promise<void> {
    this.loading = true;
    if (resetMessages) {
      this.messageText = '';
      this.errorText = '';
    }

    try {
      await operation();
    } catch (error) {
      const message = error instanceof Error ? error.message : 'An unexpected error occurred.';
      this.errorText = message;
    } finally {
      this.loading = false;
    }
  }
}
