import { CommonModule } from '@angular/common';
import { Component, OnInit, inject } from '@angular/core';
import { NavigationEnd, Router, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { filter } from 'rxjs/operators';

import { CashlineApiService } from './core/cashline-api.service';
import { CutoverI18nService } from './core/cutover-i18n.service';
import {
  FrontendPackageId,
  allowedJourneyIds,
  canAccessJourney,
  defaultJourneyId,
  frontendPackageForJourney,
  frontendPackageForSession,
  isAdministratorSession,
  isCustomerSession,
  isFinanceSession,
  isOperationsSession,
  isSystemOwnerSession,
  isSupplierSession,
  journeyIdFromRoutePath,
  resolveJourneyRoute
} from './core/uat-access.util';
import { JOURNEY_ORDER, getDefaultJourneyStepId, getJourneyDefinition } from './journeys/journey-definitions';
import { JourneyId, JourneyStep } from './journeys/journey.models';
import { WorkspaceSideNavComponent } from './workspace-side-nav.component';

interface JourneyLink {
  id: JourneyId;
  label: string;
  route: string;
  section: string;
  summary: string;
  packageId: FrontendPackageId;
}

interface FeatureLink {
  id: string;
  label: string;
  subtitle: string;
  route: string;
  stepId: string;
  requiredFunctionCodes?: readonly string[];
}

interface FrontendPackageDefinition {
  id: FrontendPackageId;
  label: string;
  title: string;
  summary: string;
  governance: string;
  featureLinks: readonly FeatureLink[];
}

interface BreadcrumbItem {
  label: string;
  route?: string;
}

interface NotificationCenterItem {
  id: string;
  title: string;
  summary: string;
  source: string;
  status: 'action' | 'info' | 'attention' | 'success';
  route: string;
  stepId?: string;
}

const DEFERRED_JOURNEY_IDS = new Set<JourneyId>(['islamic-finance']);

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterLink, RouterLinkActive, RouterOutlet, WorkspaceSideNavComponent],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})
export class AppComponent implements OnInit {
  protected readonly i18n = inject(CutoverI18nService);
  private readonly router = inject(Router);
  private readonly api = inject(CashlineApiService);
  protected readonly journeyCards = JOURNEY_ORDER.map((id) => getJourneyDefinition(id));
  protected readonly packageDefinitions: readonly FrontendPackageDefinition[] = [
    {
      id: 'internal',
      label: 'Internal Users Frontend',
      title: 'Internal Users Frontend',
      summary: 'Finance, Operations, approvals, monitoring, and internal payment visibility stay inside one governed back-office workspace.',
      governance: 'Internal approvals, case handling, monitoring, and payment controls only.',
      featureLinks: [
        { id: 'internal-accounting-office', label: 'Accounting Office', subtitle: 'Delegated accounting-office onboarding intake, approved client servicing, ETA submissions, and governed reporting.', route: resolveJourneyRoute('accounting-office'), stepId: 'ao-onboarding' },
        { id: 'internal-payments', label: 'Payments', subtitle: 'Finance gateway controls and payment visibility prepared for future provider activation.', route: resolveJourneyRoute('finance-approvals'), stepId: 'fa-gateway' },
        { id: 'internal-reports', label: 'Reports', subtitle: 'Financial statements, trial balance, end-of-day entries, operations reporting, and governed export or print actions.', route: resolveJourneyRoute('reporting-center'), stepId: 'reports-financial-statements' },
        { id: 'internal-onboarding', label: 'Onboarding Cases', subtitle: 'Operations review, approval, return, hold, and activation for supplier and customer onboarding.', route: resolveJourneyRoute('operations-control'), stepId: 'ops-onboarding' },
        { id: 'internal-monitoring', label: 'Workflow Monitoring', subtitle: 'Real-time operational traceability, queue posture, and notification recovery.', route: resolveJourneyRoute('workflow-monitoring'), stepId: 'monitor-live' }
      ]
    },
    {
      id: 'supplier',
      label: 'Supplier Frontend',
      title: 'Supplier Frontend',
      summary: 'Supplier onboarding, sourcing, purchase-order collaboration, payment visibility, and account servicing stay isolated inside the supplier-facing package.',
      governance: 'Supplier-only journeys, requests, status tracking, and commercial interactions.',
      featureLinks: [
        { id: 'supplier-onboarding', label: 'Onboarding & KYC', subtitle: 'Self-service onboarding, KYC uploads, completeness checks, and status visibility.', route: resolveJourneyRoute('supplier'), stepId: 'supplier-profile' },
        { id: 'supplier-payments', label: 'Payments', subtitle: 'Supplier payment readiness, settlement visibility, and future provider channel preparation.', route: resolveJourneyRoute('supplier'), stepId: 'supplier-payments' },
        { id: 'supplier-reports', label: 'Reports', subtitle: 'Supplier statements, account-facing outputs, and export or print actions inside the supplier package.', route: resolveJourneyRoute('supplier'), stepId: 'supplier-reports' }
      ]
    },
    {
      id: 'customer',
      label: 'Customer Frontend',
      title: 'Customer Frontend',
      summary: 'Customer onboarding, requests, order activity, payments, and account servicing remain focused inside the customer-facing package.',
      governance: 'Customer-only workflows, requests, tracking, and account-facing services.',
      featureLinks: [
        { id: 'customer-onboarding', label: 'Onboarding & KYC', subtitle: 'Customer lead capture, onboarding steps, document uploads, and activation status.', route: resolveJourneyRoute('customer'), stepId: 'customer-profile' },
        { id: 'customer-payments', label: 'Payments', subtitle: 'Customer dues, payment requests, refund visibility, and future channel readiness.', route: resolveJourneyRoute('customer'), stepId: 'customer-payments' },
        { id: 'customer-reports', label: 'Reports', subtitle: 'Customer statements, billing-facing outputs, and export or print actions inside the customer package.', route: resolveJourneyRoute('customer'), stepId: 'customer-reports' },
        { id: 'customer-orders', label: 'Requests & Orders', subtitle: 'Commercial requests, service follow-up, and order-linked business actions.', route: resolveJourneyRoute('customer'), stepId: 'customer-orders' }
      ]
    }
  ];
  protected readonly allJourneyLinks: readonly JourneyLink[] = [
    {
      id: 'supplier',
      label: 'Supplier Portal',
      route: resolveJourneyRoute('supplier'),
      section: 'Supplier Frontend',
      summary: 'Supplier onboarding, sourcing, purchase orders, payments, documents, and account servicing.',
      packageId: 'supplier'
    },
    {
      id: 'customer',
      label: 'Customer Portal',
      route: resolveJourneyRoute('customer'),
      section: 'Customer Frontend',
      summary: 'Customer orders, service requests, payments, and account status.',
      packageId: 'customer'
    },
    {
      id: 'accounting-office',
      label: 'Accounting Office',
      route: resolveJourneyRoute('accounting-office'),
      section: 'Internal Frontend',
      summary: 'Accounting-office client onboarding intake, approved client finance follow-up, ETA submissions, and office reporting under CashLine governance.',
      packageId: 'internal'
    },
    {
      id: 'islamic-finance',
      label: 'Islamic Finance',
      route: resolveJourneyRoute('islamic-finance'),
      section: 'Internal Frontend',
      summary: 'Islamic Finance applications, offers, approvals, execution, and case oversight.',
      packageId: 'internal'
    },
    {
      id: 'finance-approvals',
      label: 'Finance Approvals',
      route: resolveJourneyRoute('finance-approvals'),
      section: 'Internal Frontend',
      summary: 'Finance approvals, review queues, posting controls, and payments visibility.',
      packageId: 'internal'
    },
    {
      id: 'operations-control',
      label: 'Operations Control',
      route: resolveJourneyRoute('operations-control'),
      section: 'Internal Frontend',
      summary: 'Operations command, onboarding cases, procurement, inventory, sales, and execution follow-up.',
      packageId: 'internal'
    },
    {
      id: 'reporting-center',
      label: 'Reports',
      route: resolveJourneyRoute('reporting-center'),
      section: 'Internal Frontend',
      summary: 'Business reports, statements, exports, and operational reporting outputs.',
      packageId: 'internal'
    },
    {
      id: 'workflow-monitoring',
      label: 'Workflow Monitoring',
      route: resolveJourneyRoute('workflow-monitoring'),
      section: 'Internal Frontend',
      summary: 'Operational traceability, workflow queues, exceptions, payment signals, and support visibility.',
      packageId: 'internal'
    }
  ];
  protected visibleJourneyLinksData: readonly JourneyLink[] = [];
  protected visibleJourneySectionsData: Array<{ title: string; links: JourneyLink[] }> = [];
  protected currentJourneyLink: JourneyLink | null = null;
  protected currentFeatureLinksData: readonly FeatureLink[] = [];
  protected journeyFeatureLinkMap: Record<string, readonly FeatureLink[]> = {};
  protected currentPackageDefinition: FrontendPackageDefinition | null = null;
  protected currentPackageFeatureLinksData: readonly FeatureLink[] = [];
  protected currentFeatureTitle = 'Features';
  protected currentFeatureStepId: string | null = null;
  protected notificationCenterOpen = false;
  protected notificationItems: NotificationCenterItem[] = [];
  protected notificationLoading = false;
  protected notificationErrorText = '';
  protected unreadNotificationCount = 0;
  private readonly notificationReadStorageKey = 'cashline-kendo-notifications-read';
  private notificationLoadedForUser = '';

  ngOnInit(): void {
    if (typeof document !== 'undefined') {
      document.title = this.t(this.shellTitle);
    }

    this.refreshShellState();
    this.router.events
      .pipe(filter((event): event is NavigationEnd => event instanceof NavigationEnd))
      .subscribe(() => this.refreshShellState());
  }

  protected isLoginRoute(): boolean {
    return this.router.url === '/'
      || this.router.url.startsWith('/login')
      || this.router.url.startsWith('/become')
      || this.router.url.startsWith('/demo-center');
  }

  protected get session() {
    return this.api.currentSession;
  }

  protected get visibleJourneyLinks() {
    return this.visibleJourneyLinksData;
  }

  protected get visibleJourneySections() {
    return this.visibleJourneySectionsData;
  }

  protected get currentJourney() {
    return this.currentJourneyLink;
  }

  protected get currentFeatureLinks() {
    return this.currentFeatureLinksData;
  }

  protected get featureLinkMap() {
    return this.journeyFeatureLinkMap;
  }

  protected get currentPackage() {
    return this.currentPackageDefinition;
  }

  protected get currentPackageFeatureLinks() {
    return this.currentPackageFeatureLinksData;
  }

  protected get breadcrumbs(): readonly BreadcrumbItem[] {
    if (this.isLoginRoute()) {
      return [
        { label: 'CashLine ERP' },
        { label: 'Access and onboarding' }
      ];
    }

    const crumbs: BreadcrumbItem[] = [{ label: 'CashLine ERP', route: '/login' }];

    if (this.currentPackage) {
      crumbs.push({ label: this.currentPackage.title });
    }

    if (this.currentJourney) {
      crumbs.push({ label: this.currentJourney.label, route: this.currentJourney.route });
    }

    const feature = this.currentFeatureLinks.find((link) => link.stepId === this.currentFeatureStepId);
    if (feature) {
      crumbs.push({ label: feature.label });
    }

    return crumbs;
  }

  protected get shellSummary(): string {
    return this.currentJourney?.summary
      ?? this.currentPackage?.summary
      ?? 'Role-based access to live business services and operational workflows.';
  }

  protected get shellFeatureSummary(): string {
    if (this.currentFeatureLinks.length) {
      return `${this.currentFeatureLinks.length} guided sections`;
    }

    if (this.currentPackageFeatureLinks.length) {
      return `${this.currentPackageFeatureLinks.length} package services`;
    }

    return 'Role-governed workspace';
  }

  protected get shellTitle(): string {
    return this.isLocalhostWorkspace()
      ? 'CashLine ERP Localhost'
      : 'CashLine ERP UAT';
  }

  protected get loginEnvironmentLabel(): string {
    return this.isLocalhostWorkspace()
      ? 'Localhost environment'
      : 'UAT environment';
  }

  protected signOut(): void {
    this.api.clearToken();
    this.notificationCenterOpen = false;
    this.notificationItems = [];
    this.notificationLoadedForUser = '';
    this.unreadNotificationCount = 0;
    void this.router.navigate(['/login']);
  }

  protected async toggleNotificationCenter(): Promise<void> {
    this.notificationCenterOpen = !this.notificationCenterOpen;
    if (this.notificationCenterOpen) {
      await this.ensureNotificationCenterLoaded();
    }
  }

  protected async refreshNotifications(): Promise<void> {
    await this.ensureNotificationCenterLoaded(true);
  }

  protected async openNotification(item: NotificationCenterItem): Promise<void> {
    this.markNotificationRead(item.id);
    this.notificationCenterOpen = false;
    await this.router.navigateByUrl(`${item.route}?step=${encodeURIComponent(item.stepId ?? '')}`);
  }

  protected openHomeJourney(): void {
    if (this.isAdministrator() || this.isSystemOwner()) {
      this.openUserAdministration();
      return;
    }

    const journeyId = defaultJourneyId(this.api.currentSession);
    void this.router.navigateByUrl(`${resolveJourneyRoute(journeyId)}?step=${encodeURIComponent(getDefaultJourneyStepId(journeyId))}`);
  }

  protected isAdministrator(): boolean {
    return this.api.currentSession ? isAdministratorSession(this.api.currentSession) : false;
  }

  protected isSystemOwner(): boolean {
    return this.api.currentSession ? isSystemOwnerSession(this.api.currentSession) : false;
  }

  protected openUserAdministration(): void {
    void this.router.navigateByUrl('/internal/user-administration');
  }

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

  private isLocalhostWorkspace(): boolean {
    if (typeof window === 'undefined') {
      return false;
    }

    const desktopConfig = (window as Window & {
      cashlineDesktopConfig?: { environmentLabel?: string; apiBaseUrl?: string; shellUrl?: string };
    }).cashlineDesktopConfig;

    const environmentLabel = desktopConfig?.environmentLabel?.toLowerCase() ?? '';
    const apiBaseUrl = desktopConfig?.apiBaseUrl?.toLowerCase() ?? '';
    const shellUrl = desktopConfig?.shellUrl?.toLowerCase() ?? '';
    const currentOrigin = window.location.origin.toLowerCase();

    return environmentLabel.includes('localhost')
      || apiBaseUrl.includes('localhost:5080')
      || shellUrl.includes('localhost:5080')
      || currentOrigin.includes('localhost:5080');
  }

  private refreshShellState(): void {
    const urlTree = this.router.parseUrl(this.router.url);
    const path = (urlTree.root.children['primary']?.segments.map((segment) => segment.path).join('/') ?? '') || 'login';
    const normalizedPath = path.startsWith('/') ? path : `/${path}`;
    this.currentJourneyLink = this.allJourneyLinks.find((link) => normalizedPath.startsWith(link.route)) ?? null;

    const allowedIds = new Set(allowedJourneyIds(this.api.currentSession));
    const routePackageId = this.currentJourneyLink?.packageId ?? this.resolvePackageFromRoute();
    const sessionPackageId = frontendPackageForSession(this.api.currentSession);
    const activePackageId = this.api.currentSession ? sessionPackageId : routePackageId;
    const visibleLinks = this.allJourneyLinks.filter((link) => {
      return !DEFERRED_JOURNEY_IDS.has(link.id)
        && allowedIds.has(link.id)
        && link.packageId === activePackageId
        && this.hasJourneyFunctionAccess(link.id);
    });
    this.visibleJourneyLinksData = visibleLinks;
    const sections = new Map<string, JourneyLink[]>();

    for (const link of visibleLinks) {
      const items = sections.get(link.section) ?? [];
      items.push(link);
      sections.set(link.section, items);
    }

    this.visibleJourneySectionsData = Array.from(sections.entries()).map(([title, links]) => ({ title, links }));
    const currentPackageId = this.currentJourneyLink?.packageId ?? activePackageId;
    this.currentPackageDefinition = this.packageDefinitions.find((definition) => definition.id === currentPackageId) ?? null;
    this.currentPackageFeatureLinksData = this.filterFeatureLinksForSession(this.currentPackageDefinition?.featureLinks ?? []);
    const currentJourneyDefinition = this.currentJourneyLink ? getJourneyDefinition(this.currentJourneyLink.id) : null;
    const requestedStepId = urlTree.queryParams['step'] as string | undefined;
    this.currentFeatureStepId = requestedStepId
      ?? (this.currentJourneyLink ? getDefaultJourneyStepId(this.currentJourneyLink.id) : null)
      ?? currentJourneyDefinition?.steps[0]?.id
      ?? null;

    this.currentFeatureTitle = currentJourneyDefinition?.title ?? 'Features';
    this.currentFeatureLinksData = currentJourneyDefinition
      ? this.filterFeatureLinksForSession(currentJourneyDefinition.steps.map((step: JourneyStep) => ({
          id: step.id,
          label: step.title,
          subtitle: step.subtitle,
          route: this.currentJourneyLink!.route,
          stepId: step.id,
          requiredFunctionCodes: this.functionCodesForStep(step.id)
        })))
      : [];

    this.journeyFeatureLinkMap = Object.fromEntries(
      visibleLinks.map((link) => {
        const definition = getJourneyDefinition(link.id);
        return [
          link.id,
          this.filterFeatureLinksForSession(definition.steps.map((step: JourneyStep) => ({
            id: step.id,
            label: step.title,
            subtitle: step.subtitle,
            route: link.route,
            stepId: step.id,
            requiredFunctionCodes: this.functionCodesForStep(step.id)
          })))
        ];
      })
    );

    const sessionKey = this.api.currentSession?.username ?? '';
    if (sessionKey !== this.notificationLoadedForUser) {
      this.notificationItems = [];
      this.notificationLoadedForUser = '';
      this.unreadNotificationCount = 0;
    }
  }

  private resolvePackageFromRoute(): FrontendPackageId {
    const urlTree = this.router.parseUrl(this.router.url);
    const path = (urlTree.root.children['primary']?.segments.map((segment) => segment.path).join('/') ?? '') || 'login';
    const normalizedPath = path.startsWith('/') ? path : `/${path}`;

    if (normalizedPath.startsWith('/supplier')) {
      return 'supplier';
    }

    if (normalizedPath.startsWith('/customer')) {
      return 'customer';
    }

    if (this.currentJourneyLink) {
      return frontendPackageForJourney(this.currentJourneyLink.id);
    }

    return 'internal';
  }

  private filterFeatureLinksForSession(featureLinks: readonly FeatureLink[]): readonly FeatureLink[] {
    const session = this.api.currentSession;
    if (!session) {
      return featureLinks;
    }

    return featureLinks.filter((link) => {
      const journeyId = journeyIdFromRoutePath(link.route);
      return (!journeyId || canAccessJourney(session, journeyId)) && this.hasFunctionAccess(link.requiredFunctionCodes);
    });
  }

  private hasJourneyFunctionAccess(journeyId: JourneyId): boolean {
    const definition = getJourneyDefinition(journeyId);
    return definition.steps.some((step) => this.hasFunctionAccess(this.functionCodesForStep(step.id)));
  }

  private hasFunctionAccess(requiredFunctionCodes?: readonly string[]): boolean {
    const session = this.api.currentSession;
    if (!session || !requiredFunctionCodes?.length) {
      return true;
    }

    if (isAdministratorSession(session) || isSystemOwnerSession(session)) {
      return true;
    }

    const granted = new Set((session.functionCodes ?? []).map((code) => code.toUpperCase()));
    return requiredFunctionCodes.some((code) => granted.has(code.toUpperCase()));
  }

  private functionCodesForStep(stepId: string): readonly string[] {
    if (stepId.startsWith('supplier-access')) return ['SUPPLIER_ONBOARDING'];
    if (stepId.startsWith('supplier-profile')) return ['SUPPLIER_ONBOARDING', 'SUPPLIER_DOCUMENTS'];
    if (stepId.startsWith('supplier-procurement')) return ['SUPPLIER_PURCHASE_ORDERS'];
    if (stepId.startsWith('supplier-orders')) return ['SUPPLIER_PURCHASE_ORDERS'];
    if (stepId.startsWith('supplier-payments')) return ['SUPPLIER_INVOICES', 'SUPPLIER_STATEMENTS'];
    if (stepId.startsWith('supplier-reports')) return ['SUPPLIER_STATEMENTS'];
    if (stepId.startsWith('supplier-finance')) return ['SUPPLIER_INVOICES'];
    if (stepId.startsWith('customer-context')) return ['CUSTOMER_ONBOARDING'];
    if (stepId.startsWith('customer-profile')) return ['CUSTOMER_ONBOARDING', 'CUSTOMER_DOCUMENTS'];
    if (stepId.startsWith('customer-orders') || stepId.startsWith('customer-service')) return ['CUSTOMER_ORDERS'];
    if (stepId.startsWith('customer-payments')) return ['CUSTOMER_PAYMENTS'];
    if (stepId.startsWith('customer-reports')) return ['CUSTOMER_STATEMENTS'];
    if (stepId.startsWith('fa-intake')) return ['FINANCE_PREPARATION'];
    if (stepId.startsWith('fa-review')) return ['FINANCE_REVIEW'];
    if (stepId.startsWith('fa-gateway')) return ['PAYMENT_INITIATION', 'PAYMENT_APPROVAL'];
    if (stepId.startsWith('fa-eta') || stepId.startsWith('fa-posting')) return ['FINANCE_REPORTING'];
    if (stepId.startsWith('ao-command')) return ['ACCOUNTING_OFFICE_DASHBOARD'];
    if (stepId.startsWith('ao-onboarding')) return ['ACCOUNTING_OFFICE_CLIENT_ONBOARDING'];
    if (stepId.startsWith('ao-clients')) return ['ACCOUNTING_OFFICE_CLIENT_FINANCE'];
    if (stepId.startsWith('ao-submissions')) return ['ACCOUNTING_OFFICE_ETA_SUBMISSIONS'];
    if (stepId.startsWith('ao-reports')) return ['ACCOUNTING_OFFICE_REPORTING'];
    if (stepId.startsWith('reports-')) return ['FINANCE_REPORTING', 'OPERATIONAL_REPORTING', 'STATEMENT_PREPARATION', 'STATEMENT_APPROVAL'];
    if (stepId.startsWith('monitor-')) return ['WORKFLOW_MONITORING'];
    if (stepId.startsWith('ops-onboarding')) return ['ONBOARDING_REVIEW'];
    if (stepId.startsWith('ops-')) return ['OPERATIONS_WORKBENCH', 'OPERATIONAL_REPORTING'];
    if (stepId.startsWith('if-')) return ['OPERATIONS_WORKBENCH'];
    return [];
  }

  private async ensureNotificationCenterLoaded(force = false): Promise<void> {
    const session = this.api.currentSession;
    if (!session) {
      this.notificationItems = [];
      this.unreadNotificationCount = 0;
      this.notificationLoadedForUser = '';
      return;
    }

    if (!force && this.notificationLoadedForUser === session.username && this.notificationItems.length) {
      this.syncUnreadNotificationCount();
      return;
    }

    this.notificationLoading = true;
    this.notificationErrorText = '';

    try {
      this.notificationItems = await this.loadNotificationCenterItems();
      this.notificationLoadedForUser = session.username;
      this.syncUnreadNotificationCount();
    } catch (error) {
      this.notificationErrorText = error instanceof Error ? error.message : 'Unable to load notifications right now.';
    } finally {
      this.notificationLoading = false;
    }
  }

  private async loadNotificationCenterItems(): Promise<NotificationCenterItem[]> {
    const session = this.api.currentSession;
    if (!session) {
      return [];
    }

    if (isSupplierSession(session)) {
      return this.loadSupplierNotificationItems(session.partyId);
    }

    if (isCustomerSession(session)) {
      return this.loadCustomerNotificationItems(session.partyId);
    }

    return this.loadInternalNotificationItems();
  }

  private async loadSupplierNotificationItems(partyId: number | null): Promise<NotificationCenterItem[]> {
    if (!partyId) {
      return [];
    }

    const [profile, purchaseOrders, quotations, invoices, statements] = await Promise.all([
      this.getOrNull<Record<string, unknown>>(`/api/v1/external/parties/${partyId}/supplier-profile`),
      this.getOrFallback<unknown[]>(`/api/v1/external/parties/${partyId}/purchase-orders`, []),
      this.getOrFallback<unknown[]>(`/api/v1/external/parties/${partyId}/quotations`, []),
      this.getOrFallback<unknown[]>(`/api/v1/external/parties/${partyId}/invoices`, []),
      this.getOrFallback<unknown[]>(`/api/v1/external/parties/${partyId}/reports-statements`, [])
    ]);

    const items: NotificationCenterItem[] = [];

    if (profile) {
      const status = this.pickFirstString(profile, ['statusCode', 'reviewStatusCode', 'onboardingStatusCode']) ?? 'IN_PROGRESS';
      items.push({
        id: `supplier-profile-${partyId}`,
        title: 'Supplier onboarding status',
        summary: `Current profile status: ${this.prettifyStatus(status)}.`,
        source: 'Onboarding',
        status: /APPROVED|ACTIVE/.test(status) ? 'success' : /HOLD|RETURN|REJECT/.test(status) ? 'attention' : 'action',
        route: resolveJourneyRoute('supplier'),
        stepId: 'supplier-profile'
      });
    }

    items.push(...this.buildCountNotificationItem(
      'supplier-po',
      purchaseOrders.length,
      'Purchase orders',
      'purchase orders are available for review or acknowledgement.',
      'Orders',
      'action',
      resolveJourneyRoute('supplier'),
      'supplier-orders'
    ));
    items.push(...this.buildCountNotificationItem(
      'supplier-quotations',
      quotations.length,
      'Quotations',
      'quotations are available for follow-up.',
      'Requests',
      'info',
      resolveJourneyRoute('supplier'),
      'supplier-procurement'
    ));
    items.push(...this.buildCountNotificationItem(
      'supplier-invoices',
      invoices.length,
      'Invoices',
      'invoices are available for submission or status review.',
      'Invoices',
      'action',
      resolveJourneyRoute('supplier'),
      'supplier-payments'
    ));
    items.push(...this.buildCountNotificationItem(
      'supplier-statements',
      statements.length,
      'Statement documents',
      'statement documents are available for review.',
      'Reports',
      'info',
      resolveJourneyRoute('supplier'),
      'supplier-reports'
    ));

    return items;
  }

  private async loadCustomerNotificationItems(partyId: number | null): Promise<NotificationCenterItem[]> {
    if (!partyId) {
      return [];
    }

    const [profile, orders, serviceRequests, invoices, payments] = await Promise.all([
      this.getOrNull<Record<string, unknown>>(`/api/v1/external/parties/${partyId}/customer-profile`),
      this.getOrFallback<unknown[]>(`/api/v1/external/parties/${partyId}/orders`, []),
      this.getOrFallback<unknown[]>(`/api/v1/external/parties/${partyId}/service-requests`, []),
      this.getOrFallback<unknown[]>(`/api/v1/external/parties/${partyId}/invoices`, []),
      this.getOrFallback<unknown[]>(`/api/v1/external/parties/${partyId}/payments`, [])
    ]);

    const items: NotificationCenterItem[] = [];

    if (profile) {
      const status = this.pickFirstString(profile, ['statusCode', 'reviewStatusCode', 'onboardingStatusCode']) ?? 'IN_PROGRESS';
      items.push({
        id: `customer-profile-${partyId}`,
        title: 'Customer onboarding status',
        summary: `Current profile status: ${this.prettifyStatus(status)}.`,
        source: 'Onboarding',
        status: /APPROVED|ACTIVE/.test(status) ? 'success' : /HOLD|RETURN|REJECT/.test(status) ? 'attention' : 'action',
        route: resolveJourneyRoute('customer'),
        stepId: 'customer-profile'
      });
    }

    items.push(...this.buildCountNotificationItem(
      'customer-orders',
      orders.length,
      'Customer orders',
      'customer orders are available for review.',
      'Orders',
      'action',
      resolveJourneyRoute('customer'),
      'customer-orders'
    ));
    items.push(...this.buildCountNotificationItem(
      'customer-service-requests',
      serviceRequests.length,
      'Service requests',
      'service requests require follow-up.',
      'Requests',
      'action',
      resolveJourneyRoute('customer'),
      'customer-orders'
    ));
    items.push(...this.buildCountNotificationItem(
      'customer-invoices',
      invoices.length,
      'Invoices',
      'invoices are available for submission or status review.',
      'Invoices',
      'action',
      resolveJourneyRoute('customer'),
      'customer-payments'
    ));
    items.push(...this.buildCountNotificationItem(
      'customer-payments',
      payments.length,
      'Payment updates',
      'payment updates are available for review.',
      'Payments',
      'info',
      resolveJourneyRoute('customer'),
      'customer-payments'
    ));

    return items;
  }

  private async loadInternalNotificationItems(): Promise<NotificationCenterItem[]> {
    const session = this.api.currentSession;
    const includeEta = !!session && (isFinanceSession(session) || isOperationsSession(session));
    const [notificationQueue, workflowQueue, onboardingCases, etaMonitoring] = await Promise.all([
      this.getOrFallback<unknown[]>('/api/v1/notifications/queue?statusCode=PENDING', []),
      this.getOrFallback<unknown[]>('/api/v1/workflow/queue', []),
      this.getOrFallback<unknown[]>('/api/v1/internal/onboarding/cases', []),
      includeEta
        ? this.getOrFallback<Record<string, unknown> | unknown[]>('/api/v1/finance/eta-notifications/monitoring?itemLimit=5', [])
        : Promise.resolve([])
    ]);

    const items: NotificationCenterItem[] = [];
    items.push(...this.buildCountNotificationItem(
      'internal-notification-queue',
      this.asArray(notificationQueue).length,
      'Notification queue',
      'notification items await action.',
      'Notifications',
      'action',
      resolveJourneyRoute('workflow-monitoring'),
      'monitor-notifications'
    ));
    items.push(...this.buildCountNotificationItem(
      'internal-workflow-queue',
      this.asArray(workflowQueue).length,
      'Workflow queue',
      'workflow queue items require review.',
      'Workflow',
      'attention',
      resolveJourneyRoute('workflow-monitoring'),
      'monitor-live'
    ));
    items.push(...this.buildCountNotificationItem(
      'internal-onboarding-cases',
      this.asArray(onboardingCases).length,
      'Onboarding review queue',
      'onboarding cases require review or follow-up.',
      'Operations',
      'action',
      resolveJourneyRoute('operations-control'),
      'ops-onboarding'
    ));
    items.push(...this.buildCountNotificationItem(
      'internal-eta',
      this.extractTotalCount(etaMonitoring),
      'ETA authority updates',
      'ETA authority updates require attention.',
      'ETA',
      'attention',
      resolveJourneyRoute('workflow-monitoring'),
      'monitor-notifications'
    ));

    return items;
  }

  private buildCountNotificationItem(
    idPrefix: string,
    count: number,
    title: string,
    summaryTail: string,
    source: string,
    status: NotificationCenterItem['status'],
    route: string,
    stepId: string
  ): NotificationCenterItem[] {
    if (!count) {
      return [];
    }

    return [{
      id: `${idPrefix}-${route}-${stepId}`,
      title,
      summary: `${count} ${summaryTail}`,
      source,
      status,
      route,
      stepId
    }];
  }

  private syncUnreadNotificationCount(): void {
    const readIds = this.readNotificationState();
    this.unreadNotificationCount = this.notificationItems.filter((item) => !readIds.includes(item.id)).length;
  }

  private markNotificationRead(notificationId: string): void {
    const readIds = new Set(this.readNotificationState());
    readIds.add(notificationId);
    if (typeof window !== 'undefined') {
      window.localStorage.setItem(this.notificationReadStorageKey, JSON.stringify(Array.from(readIds)));
    }
    this.syncUnreadNotificationCount();
  }

  private readNotificationState(): string[] {
    if (typeof window === 'undefined') {
      return [];
    }

    try {
      const raw = window.localStorage.getItem(this.notificationReadStorageKey);
      if (!raw) {
        return [];
      }

      const parsed = JSON.parse(raw);
      return Array.isArray(parsed) ? parsed.filter((value): value is string => typeof value === 'string') : [];
    } catch {
      return [];
    }
  }

  private async getOrFallback<T>(path: string, fallback: T): Promise<T> {
    try {
      return await this.api.get<T>(path);
    } catch {
      return fallback;
    }
  }

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

  private asArray(data: unknown): unknown[] {
    return Array.isArray(data) ? data : [];
  }

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

    if (data && typeof data === 'object') {
      const record = data as Record<string, unknown>;
      const numericCandidate = ['totalCount', 'pendingCount', 'receivedCount', 'itemCount']
        .map((key) => record[key])
        .find((value) => typeof value === 'number');
      if (typeof numericCandidate === 'number') {
        return numericCandidate;
      }

      const collectionCandidate = Object.values(record).find((value) => Array.isArray(value));
      if (Array.isArray(collectionCandidate)) {
        return collectionCandidate.length;
      }
    }

    return 0;
  }

  private pickFirstString(record: Record<string, unknown>, keys: readonly string[]): string | null {
    for (const key of keys) {
      const value = record[key];
      if (typeof value === 'string' && value.trim()) {
        return value;
      }
    }

    return null;
  }

  private prettifyStatus(value: string): string {
    return value
      .replace(/_/g, ' ')
      .toLowerCase()
      .replace(/\b\w/g, (char) => char.toUpperCase());
  }
}
