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,
  createDashboardRequestAborter,
  shouldPauseDashboardRefresh,
} 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;
    };

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();

    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;
        setDashboard(nextDashboard);

        const currentRun = ++hydrationRun;
        if (
          nextDashboard.state === "ready" &&
          nextDashboard.liveDatasourceHydration?.enabled !== false
        ) {
          const tiles = dashboardHydrationTiles(nextDashboard.document);
          const tileSignal = requestAborter.beginTileRun();
          setHydratingItemIds(new Set(tiles.map(dashboardTileKey)));
          hydrateDashboardTiles(tiles, currentRun, tileSignal);
        } 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,
    ) {
      tiles.forEach((tile) => {
        void hydrateDashboardTile(tile, run, signal);
      });
    }

    async function hydrateDashboardTile(
      tile: DashboardTileReference,
      run: number,
      signal: AbortSignal,
    ) {
      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;
        }

        const readyTileResponse = tileResponse;
        setDashboard((current) =>
          current?.state === "ready"
            ? {
                ...current,
                document: applyDashboardTile(current.document, readyTileResponse),
              }
            : current,
        );
      } catch (error) {
        if (!isAbortError(error) && !cancelled) {
          console.error("Dashboard tile hydration failed", error);
        }
      } 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 isAbortError(error: unknown): boolean {
  return (
    typeof error === "object" &&
    error !== null &&
    "name" in error &&
    error.name === "AbortError"
  );
}

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 [...telemetry, ...services, ...modules, ...status];
}

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,
    ),
  };
}
