import { useEffect, useState } from "react";
import type {
  DashboardDocument,
  DashboardModule,
  ServiceEntry,
  StatusItem,
  TelemetryCard,
} from "@dimensionlab/dashboard-model";
import type { DashboardRuntimeState } from "$lib/server/dashboard";
import {
  attachDashboardRefreshLifecycle,
  createDashboardTileSnapshotStore,
  createDashboardTileBackoff,
  createDashboardRequestAborter,
  runDashboardHydrationQueue,
  shouldPauseDashboardRefresh,
  type DashboardTileSnapshotStoreContext,
  type RestoredDashboardTileSnapshot,
} from "$lib/client/dashboard-refresh";
import { dashboardDocumentToUiDashboard } from "$lib/ui-adapter/model-renderer";
import {
  DashboardFrame,
  SystemState,
  ThemeToggle,
  persistUiTheme,
  resolveInitialUiTheme,
  type UiTheme,
  type UiSeverity,
  type UiDashboardPreview,
} from "@dimensionlab/ui";

type DashboardTileReference =
  | { kind: "telemetry"; id: string }
  | { kind: "service"; groupId: string; id: string }
  | { kind: "module"; id: string }
  | { kind: "status"; stripId: string; id: string };

type DashboardTileResponse =
  | {
      state: "ready";
      tile: DashboardTileReference;
      item: DashboardModule | ServiceEntry | StatusItem | TelemetryCard;
    }
  | {
      state: "not_found";
      tile: DashboardTileReference;
      message: string;
    }
  | {
      state: "disabled";
      tile: DashboardTileReference;
      message: string;
    };

type DashboardTileHydrationResult = "aborted" | "failed" | "ready";

const dashboardTileHydrationConcurrency = 6;
type DashboardTileSnapshotStore = ReturnType<typeof createDashboardTileSnapshotStore>;

export function AppStateView({
  dashboard,
  onThemeChange,
  theme,
  hydratingItemIds,
}: {
  dashboard: DashboardRuntimeState;
  hydratingItemIds?: ReadonlySet<string>;
  onThemeChange?: (theme: UiTheme) => void;
  theme?: UiTheme;
}) {
  const themeToggle =
    theme && onThemeChange ? (
      <ThemeToggle theme={theme} onThemeChange={onThemeChange} />
    ) : null;

  if (dashboard.state === "ready") {
    const uiDashboard = markHydratingItems(
      dashboardDocumentToUiDashboard(dashboard.document),
      hydratingItemIds,
    );

    return (
      <DashboardFrame
        dashboard={uiDashboard}
        actions={themeToggle}
      />
    );
  }

  const detail = `${dashboard.subtitle}: ${dashboard.message}`;
  const errors = dashboard.state === "invalid" ? dashboard.errors : [];

  return (
    <main className="state-shell" data-dashboard-state={dashboard.state}>
      {themeToggle ? (
        <div className="state-shell__actions">{themeToggle}</div>
      ) : null}
      <SystemState
        title={dashboard.title}
        detail={detail}
        severity={stateSeverity(dashboard.state)}
        icon={stateIcon(dashboard.state)}
      />
      {errors.length ? (
        <ul aria-label="Validation errors">
          {errors.map((error) => (
            <li key={error}>{error}</li>
          ))}
        </ul>
      ) : null}
    </main>
  );
}

export function resolveDocumentMetadata(dashboard: DashboardRuntimeState): {
  description: string;
  title: string;
} {
  if (dashboard.state === "ready") {
    const uiDashboard = dashboardDocumentToUiDashboard(dashboard.document);
    return {
      title: uiDashboard.title,
      description:
        uiDashboard.subtitle ||
        dashboard.document.metadata.description ||
        uiDashboard.title,
    };
  }

  return {
    title: dashboard.title,
    description: dashboard.subtitle || dashboard.message,
  };
}

export default function App() {
  const [dashboard, setDashboard] =
    useState<DashboardRuntimeState | null>(null);
  const [hydratingItemIds, setHydratingItemIds] = useState<Set<string>>(
    () => new Set(),
  );
  const [theme, setTheme] = useState<UiTheme>(() => {
    if (typeof window === "undefined") return "dark";

    return resolveInitialUiTheme(getThemeStorage());
  });

  useEffect(() => {
    if (!dashboard) return;

    const metadata = resolveDocumentMetadata(dashboard);
    document.title = metadata.title;

    let description = document.querySelector<HTMLMetaElement>(
      'meta[name="description"]',
    );
    if (!description) {
      description = document.createElement("meta");
      description.name = "description";
      document.head.append(description);
    }
    description.content = metadata.description;
  }, [dashboard]);

  useEffect(() => {
    document.documentElement.dataset.uiTheme = theme;
    persistUiTheme(theme, getThemeStorage());
  }, [theme]);

  useEffect(() => {
    let cancelled = false;
    let refreshTimer: number | undefined;
    let hydrationRun = 0;
    const requestAborter = createDashboardRequestAborter();
    const tileBackoff = createDashboardTileBackoff();
    const tileSnapshotStore = createDashboardTileSnapshotStore(
      getTileSnapshotStorage(),
    );

    function refreshPaused() {
      return shouldPauseDashboardRefresh({
        visibilityState: document.visibilityState,
        online: navigator.onLine,
      });
    }

    function clearRefreshTimer() {
      if (refreshTimer) {
        window.clearInterval(refreshTimer);
        refreshTimer = undefined;
      }
    }

    function pauseRefreshes() {
      clearRefreshTimer();
      requestAborter.abortActiveRequests();
      setHydratingItemIds(new Set());
    }

    async function loadDashboard() {
      if (cancelled || refreshPaused()) return;

      const shellSignal = requestAborter.beginShellRun();

      try {
        const response = await fetch("/api/dashboard", { signal: shellSignal });
        const nextDashboard = (await response.json()) as DashboardRuntimeState;

        if (cancelled || shellSignal.aborted || refreshPaused()) return;
        const restored = restoreDashboardTileSnapshots(
          nextDashboard,
          nextDashboard.state === "ready"
            ? tileSnapshotStore.restore({
                currentRevisionId: nextDashboard.currentRevisionId,
                schemaVersion: nextDashboard.schemaVersion,
              })
            : [],
        );
        setDashboard(restored.dashboard);

        const currentRun = ++hydrationRun;
        if (
          restored.dashboard.state === "ready" &&
          restored.dashboard.liveDatasourceHydration?.enabled !== false
        ) {
          const tiles = dashboardHydrationTiles(restored.dashboard.document).filter(
            (tile) => tileBackoff.canAttempt(dashboardTileKey(tile)),
          );
          const tileSignal = requestAborter.beginTileRun();
          setHydratingItemIds(
            new Set(
              tiles
                .map(dashboardTileKey)
                .filter((key) => !restored.restoredItemIds.has(key)),
            ),
          );
          hydrateDashboardTiles(
            tiles,
            currentRun,
            tileSignal,
            {
              currentRevisionId: restored.dashboard.currentRevisionId,
              schemaVersion: restored.dashboard.schemaVersion,
            },
          );
        } else {
          setHydratingItemIds(new Set());
        }

        clearRefreshTimer();

        const refreshIntervalSeconds =
          nextDashboard.state === "ready"
            ? nextDashboard.document.metadata.refreshIntervalSeconds
            : undefined;

        if (refreshIntervalSeconds && !refreshPaused()) {
          refreshTimer = window.setInterval(
            () => void loadDashboard(),
            refreshIntervalSeconds * 1000,
          );
        }
      } catch (error) {
        if (!isAbortError(error) && !cancelled) {
          console.error("Dashboard refresh failed", error);
        }
      }
    }

    function hydrateDashboardTiles(
      tiles: DashboardTileReference[],
      run: number,
      signal: AbortSignal,
      snapshotContext: DashboardTileSnapshotStoreContext,
    ) {
      void runDashboardHydrationQueue({
        concurrency: dashboardTileHydrationConcurrency,
        hydrate: async (tile) => {
          const key = dashboardTileKey(tile);
          const result = await hydrateDashboardTile(
            tile,
            run,
            signal,
            snapshotContext,
          );

          if (result === "ready") {
            tileBackoff.recordSuccess(key);
          } else if (result === "failed") {
            tileBackoff.recordFailure(key);
          }
        },
        items: tiles,
        signal,
      });
    }

    async function hydrateDashboardTile(
      tile: DashboardTileReference,
      run: number,
      signal: AbortSignal,
      snapshotContext: DashboardTileSnapshotStoreContext,
    ): Promise<DashboardTileHydrationResult> {
      const key = dashboardTileKey(tile);

      try {
        const response = await fetch(dashboardTileUrl(tile), { signal });
        const tileResponse = (await response.json()) as DashboardTileResponse;

        if (
          cancelled ||
          signal.aborted ||
          run !== hydrationRun ||
          !response.ok ||
          tileResponse.state !== "ready"
        ) {
          return signal.aborted || cancelled || run !== hydrationRun
            ? "aborted"
            : "failed";
        }

        const readyTileResponse = tileResponse;
        setDashboard((current) =>
          current?.state === "ready"
            ? {
                ...current,
                document: applyDashboardTile(current.document, readyTileResponse),
              }
            : current,
        );
        tileSnapshotStore.saveReadyTile({
          ...snapshotContext,
          response: readyTileResponse,
        });
        return "ready";
      } catch (error) {
        if (!isAbortError(error) && !cancelled) {
          console.error("Dashboard tile hydration failed", error);
          return "failed";
        }
        return "aborted";
      } finally {
        if (!cancelled && !signal.aborted && run === hydrationRun) {
          setHydratingItemIds((current) => {
            const next = new Set(current);
            next.delete(key);
            return next;
          });
        }
      }
    }

    const detachRefreshLifecycle = attachDashboardRefreshLifecycle({
      documentTarget: document,
      windowTarget: window,
      loadDashboard,
      pauseRefreshes,
      refreshPaused: () => cancelled || refreshPaused(),
    });

    void loadDashboard();

    return () => {
      cancelled = true;
      clearRefreshTimer();
      requestAborter.abortActiveRequests();
      detachRefreshLifecycle();
    };
  }, []);

  return dashboard ? (
    <AppStateView
      dashboard={dashboard}
      hydratingItemIds={hydratingItemIds}
      theme={theme}
      onThemeChange={setTheme}
    />
  ) : null;
}

function stateSeverity(state: DashboardRuntimeState["state"]): UiSeverity {
  if (state === "invalid") return "danger";
  if (state === "loading") return "loading";
  return "stale";
}

function stateIcon(state: DashboardRuntimeState["state"]): string {
  if (state === "invalid") return "mdi:file-alert-outline";
  if (state === "loading") return "mdi:progress-clock";
  return "mdi:tray";
}

function getThemeStorage(): Storage | undefined {
  try {
    return window.localStorage;
  } catch {
    return undefined;
  }
}

function getTileSnapshotStorage(): Storage | undefined {
  try {
    return window.localStorage;
  } catch {
    return undefined;
  }
}

function isAbortError(error: unknown): boolean {
  return (
    typeof error === "object" &&
    error !== null &&
    "name" in error &&
    error.name === "AbortError"
  );
}

export function restoreDashboardTileSnapshots(
  dashboard: DashboardRuntimeState,
  snapshots: RestoredDashboardTileSnapshot[],
): {
  dashboard: DashboardRuntimeState;
  restoredItemIds: Set<string>;
} {
  if (dashboard.state !== "ready" || snapshots.length === 0) {
    return {
      dashboard,
      restoredItemIds: new Set(),
    };
  }

  return snapshots.reduce(
    (current, snapshot) => ({
      dashboard: {
        ...current.dashboard,
        document: applyDashboardTile(
          current.dashboard.document,
          snapshot.response as Extract<DashboardTileResponse, { state: "ready" }>,
        ),
      },
      restoredItemIds: new Set([
        ...current.restoredItemIds,
        dashboardTileKey(snapshot.response.tile),
      ]),
    }),
    {
      dashboard,
      restoredItemIds: new Set<string>(),
    },
  );
}

function markHydratingItems(
  dashboard: UiDashboardPreview,
  hydratingItemIds?: ReadonlySet<string>,
): UiDashboardPreview {
  if (!hydratingItemIds?.size) return dashboard;

  return {
    ...dashboard,
    telemetry: dashboard.telemetry.map((card) =>
      hydratingItemIds.has(`telemetry:${card.id}`)
        ? {
            ...card,
            severity: "loading",
            detail: "loading live telemetry",
          }
        : card,
    ),
    serviceGroups: dashboard.serviceGroups.map((group) => ({
      ...group,
      services: group.services.map((service) =>
        hydratingItemIds.has(`service:${group.id}:${service.id}`)
          ? {
              ...service,
              severity: "loading",
              detail: "loading",
            }
          : service,
      ),
    })),
    modules: dashboard.modules.map((module) =>
      hydratingItemIds.has(`module:${module.id}`)
        ? {
            ...module,
            severity: "loading",
            detail: "loading live data",
          }
        : module,
    ),
    statusItems: dashboard.statusItems.map((item) =>
      hydratingItemIds.has(`status:${item.id}`)
        ? {
            ...item,
            severity: "loading",
            value: "loading",
          }
        : item,
    ),
  };
}

export function dashboardHydrationTiles(
  document: DashboardDocument,
): DashboardTileReference[] {
  const telemetry = document.telemetry
    .filter((card) => card.datasource?.type === "external")
    .map((card): DashboardTileReference => ({ kind: "telemetry", id: card.id }));
  const services = document.serviceGroups.flatMap((group) =>
    group.services
      .filter((service) => service.datasource?.type === "external")
      .map((service): DashboardTileReference => ({
        kind: "service",
        groupId: group.id,
        id: service.id,
      })),
  );
  const modules = (document.modules || [])
    .filter((module) =>
      module.datasource?.type === "external" ||
      module.id === "runtime-health-summary"
    )
    .map((module): DashboardTileReference => ({ kind: "module", id: module.id }));
  const status = document.statusStrips.flatMap((strip) =>
    strip.items
      .map((item): DashboardTileReference => ({
        kind: "status",
        stripId: strip.id,
        id: item.id,
      })),
  );

  return [...status, ...telemetry, ...modules, ...services];
}

function dashboardTileKey(tile: DashboardTileReference): string {
  if (tile.kind === "status") return `${tile.kind}:${tile.stripId}:${tile.id}`;
  if (tile.kind === "service") return `${tile.kind}:${tile.groupId}:${tile.id}`;
  return `${tile.kind}:${tile.id}`;
}

function dashboardTileUrl(tile: DashboardTileReference): string {
  const parts = tile.kind === "status"
    ? ["api", "dashboard", "tile", tile.kind, tile.stripId, tile.id]
    : tile.kind === "service"
      ? ["api", "dashboard", "tile", tile.kind, tile.groupId, tile.id]
      : ["api", "dashboard", "tile", tile.kind, tile.id];
  return `/${parts.map(encodeURIComponent).join("/")}`;
}

function applyDashboardTile(
  document: DashboardDocument,
  response: Extract<DashboardTileResponse, { state: "ready" }>,
): DashboardDocument {
  if (response.tile.kind === "telemetry") {
    return {
      ...document,
      telemetry: document.telemetry.map((card) =>
        card.id === response.tile.id ? response.item as TelemetryCard : card,
      ),
    };
  }

  if (response.tile.kind === "service") {
    const tile = response.tile;
    return {
      ...document,
      serviceGroups: document.serviceGroups.map((group) => ({
        ...group,
        services: group.id === tile.groupId
          ? group.services.map((service) =>
              service.id === tile.id ? response.item as ServiceEntry : service,
            )
          : group.services,
      })),
    };
  }

  if (response.tile.kind === "module") {
    return {
      ...document,
      modules: (document.modules || []).map((module) =>
        module.id === response.tile.id ? response.item as DashboardModule : module,
      ),
    };
  }

  const tile = response.tile;
  return {
    ...document,
    statusStrips: document.statusStrips.map((strip) =>
      strip.id === tile.stripId
        ? {
            ...strip,
            items: strip.items.map((item) =>
              item.id === tile.id ? response.item as StatusItem : item,
            ),
          }
        : strip,
    ),
  };
}
