import type {
  EffectiveViewProjection,
  PlatformRouteContribution,
  PlatformViewSurface,
  PlatformWebModule
} from "../types";

export const PLATFORM_VIEW_CHANGED_EVENT = "govoplan:view-changed";
export const PLATFORM_WORKFLOW_VIEW_CHANGED_EVENT =
  "govoplan:workflow-view-changed";

export type WorkflowViewChangedEventDetail = {
  projection: EffectiveViewProjection | null;
  contextId?: string | null;
};

export function moduleViewSurfaceId(moduleId: string): string {
  return `${moduleId}.module`;
}

export function navigationViewSurfaceId(moduleId: string, path: string): string {
  return `${moduleId}.nav.${surfaceSlug(path)}`;
}

export function routeViewSurfaceId(moduleId: string, path: string): string {
  return `${moduleId}.route.${surfaceSlug(path)}`;
}

export function viewSurfaceCatalogueForModules(
  modules: PlatformWebModule[]
): PlatformViewSurface[] {
  const surfaces = new Map<string, PlatformViewSurface>();
  for (const module of modules) {
    const rootId = moduleViewSurfaceId(module.id);
    addSurface(surfaces, {
      id: rootId,
      moduleId: module.id,
      kind: "module",
      label: module.label,
      order: Math.min(
        ...(module.navItems?.map((item) => item.order ?? 100) ?? [100])
      )
    });
    for (const item of module.navItems ?? []) {
      addSurface(surfaces, {
        id: item.surfaceId ?? navigationViewSurfaceId(module.id, item.to),
        moduleId: module.id,
        kind: "navigation",
        label: item.label,
        parentId: rootId,
        order: item.order
      });
    }
    for (const route of module.routes ?? []) {
      addSurface(surfaces, {
        id: route.surfaceId ?? routeViewSurfaceId(module.id, route.path),
        moduleId: module.id,
        kind: "route",
        label: route.path,
        description: route.path,
        parentId: rootId,
        order: route.order
      });
    }
    for (const surface of module.viewSurfaces ?? []) {
      addSurface(surfaces, {
        ...surface,
        parentId:
          surface.id === rootId
            ? null
            : surface.parentId ?? rootId
      });
    }
  }
  return [...surfaces.values()].sort(
    (left, right) =>
      (left.order ?? 100) - (right.order ?? 100) ||
      left.label.localeCompare(right.label)
  );
}

export function isViewSurfaceVisible(
  projection: EffectiveViewProjection | null | undefined,
  surfaceId: string | null | undefined,
  catalogue: PlatformViewSurface[]
): boolean {
  const projectionActive =
    projection?.projectionActive ?? Boolean(projection?.activeViewId);
  if (!surfaceId || !projectionActive) return true;
  const byId = new Map(catalogue.map((surface) => [surface.id, surface]));
  const surface = byId.get(surfaceId);
  if (!surface) return true;
  const visible = new Set(projection?.visibleSurfaceIds ?? []);
  let current: PlatformViewSurface | undefined = surface;
  const visited = new Set<string>();
  while (current) {
    if (visited.has(current.id)) return false;
    visited.add(current.id);
    if (!current.required && !visible.has(current.id)) return false;
    current = current.parentId ? byId.get(current.parentId) : undefined;
  }
  return true;
}

export function visibleRoutesForProjection(
  modules: PlatformWebModule[],
  projection: EffectiveViewProjection | null | undefined
): PlatformRouteContribution[] {
  const catalogue = viewSurfaceCatalogueForModules(modules);
  return modules.flatMap((module) =>
    (module.routes ?? []).filter((route) =>
      isViewSurfaceVisible(
        projection,
        route.surfaceId ?? routeViewSurfaceId(module.id, route.path),
        catalogue
      )
    )
  );
}

export function dispatchPlatformViewChanged(): void {
  if (typeof window !== "undefined") {
    window.dispatchEvent(new CustomEvent(PLATFORM_VIEW_CHANGED_EVENT));
  }
}

export function dispatchWorkflowViewChanged(
  projection: EffectiveViewProjection | null,
  contextId?: string | null
): void {
  if (typeof window !== "undefined") {
    window.dispatchEvent(
      new CustomEvent<WorkflowViewChangedEventDetail>(
        PLATFORM_WORKFLOW_VIEW_CHANGED_EVENT,
        { detail: { projection, contextId } }
      )
    );
  }
}

function addSurface(
  target: Map<string, PlatformViewSurface>,
  surface: PlatformViewSurface
): void {
  const existing = target.get(surface.id);
  if (!existing) {
    target.set(surface.id, surface);
    return;
  }
  target.set(surface.id, {
    ...existing,
    ...surface,
    required: Boolean(existing.required || surface.required)
  });
}

function surfaceSlug(value: string): string {
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, ".").replace(/^\.+|\.+$/g, "") || "root";
}
