import {
  Alert,
  Box,
  Button,
  Chip,
  Paper,
  Stack,
  StructuredDialog,
  Tab,
  Tabs,
  Tooltip,
  Typography
} from "@exotel-npm-dev/signal-design-system";
import { useEffect, useRef, useState, type SyntheticEvent } from "react";
import {
  GWT_ALL_GLOBAL_EVENT_NAMES,
  initAafClient,
  registerGlobalEvent,
  type AafClient
} from "../aaf/gwtAafSdk";
import { ensureAafSdkLoaded, getAafSdkScriptUrl } from "../aaf/loadAafSdkIfNeeded";
import { extractContextUpdates, type AafEventContext } from "../utils/eventContext";
import { eventStore, makePersistable } from "../utils/eventStore";
import { EventsPanel, type EventEntry } from "./EventsPanel";
import { InvokeMethodPanel } from "./InvokeMethodPanel";

type ErrorEntry = {
  id: string;
  ts: number;
  name: string;
  message: string;
};

type TabId = "events" | "methods";

function uid(): string {
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
}

function formatTs(ts: number): string {
  const d = new Date(ts);
  const hh = String(d.getHours()).padStart(2, "0");
  const mm = String(d.getMinutes()).padStart(2, "0");
  const ss = String(d.getSeconds()).padStart(2, "0");
  return `${hh}:${mm}:${ss}`;
}

export function EventLoggerView() {
  const [tab, setTab] = useState<TabId>("events");
  const [events, setEvents] = useState<EventEntry[]>(() => eventStore.loadEvents<EventEntry>());
  const [errors, setErrors] = useState<ErrorEntry[]>(() => eventStore.loadErrors<ErrorEntry>());
  const [registered, setRegistered] = useState<string[]>([]);
  const [bootMsg, setBootMsg] = useState<string>("Bootstrapping SDK…");
  const [bootError, setBootError] = useState<string | null>(null);
  const [lifetimeCount, setLifetimeCount] = useState<number>(() => eventStore.loadCounter());
  const [errorsOpen, setErrorsOpen] = useState(false);
  const [ctx, setCtx] = useState<AafEventContext>({});
  const clientRef = useRef<AafClient | null>(null);

  useEffect(() => {
    eventStore.saveEvents(events);
  }, [events]);
  useEffect(() => {
    eventStore.saveErrors(errors);
  }, [errors]);
  useEffect(() => {
    eventStore.saveCounter(lifetimeCount);
  }, [lifetimeCount]);

  useEffect(() => {
    let cancelled = false;

    void (async () => {
      try {
        await ensureAafSdkLoaded();
        if (cancelled) return;
        const client = initAafClient();
        if (cancelled) return;
        clientRef.current = client;
        setBootMsg(
          `AAF client ready — registering ${GWT_ALL_GLOBAL_EVENT_NAMES.length} globalEvent listener(s)…`
        );

        const okList: string[] = [];
        await Promise.all(
          GWT_ALL_GLOBAL_EVENT_NAMES.map((name) => {
            const handler = (data: unknown) => {
              const printable = makePersistable(data);
              try {
                console.info(`[AAF globalEvent] ${String(name)}`, data);
              } catch {
                // ignore console failures
              }
              setLifetimeCount((n) => n + 1);
              const updated = extractContextUpdates(ctxRef.current, data);
              if (updated) {
                ctxRef.current = updated;
                setCtx(updated);
              }
              setEvents((prev) =>
                [{ id: uid(), ts: Date.now(), name: String(name), data: printable }, ...prev].slice(
                  0,
                  500
                )
              );
            };
            return registerGlobalEvent(client, name, handler).then(
              () => {
                okList.push(String(name));
              },
              (err: unknown) => {
                setErrors((prev) =>
                  [
                    {
                      id: uid(),
                      ts: Date.now(),
                      name: String(name),
                      message: err instanceof Error ? err.message : String(err)
                    },
                    ...prev
                  ].slice(0, 500)
                );
              }
            );
          })
        );

        if (cancelled) return;
        setRegistered(okList.sort());
        setBootMsg(`Registered ${okList.length}/${GWT_ALL_GLOBAL_EVENT_NAMES.length} listener(s).`);
      } catch (err: unknown) {
        if (cancelled) return;
        setBootError(err instanceof Error ? err.message : String(err));
        setBootMsg("AAF bootstrap failed — see Registration errors.");
      }
    })();

    return () => {
      cancelled = true;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Mirror ctx into a ref so the long-lived event handlers see the latest value
  // without re-registering when ctx changes.
  const ctxRef = useRef<AafEventContext>(ctx);
  useEffect(() => {
    ctxRef.current = ctx;
  }, [ctx]);

  return (
    <Stack spacing={2}>
      <Box>
        <Typography variant="h5" component="h1" gutterBottom>
          GWT AAF — SDK Debugger
        </Typography>
        <Typography variant="body2" color="text.secondary">
          Mirrors the Omni SDK debugger workflow: chronological event timeline (click to expand
          payload), method playground with placeholders that auto-fill from recent events, and a
          manual-invoke dialog for any <code>aaf_sdk.js</code> namespace. SDK URL:{" "}
          <code>{getAafSdkScriptUrl()}</code>
        </Typography>
      </Box>

      <Paper
        variant="outlined"
        sx={{ p: 1.25, display: "flex", flexWrap: "wrap", gap: 1, alignItems: "center" }}
      >
        <Chip
          size="small"
          label={`Registered: ${registered.length}/${GWT_ALL_GLOBAL_EVENT_NAMES.length}`}
          color={registered.length > 0 ? "success" : "default"}
        />
        <Chip
          size="small"
          label={`Errors: ${errors.length}`}
          color={errors.length > 0 ? "warning" : "default"}
          variant={errors.length > 0 ? "filled" : "outlined"}
          onClick={errors.length > 0 ? () => setErrorsOpen(true) : undefined}
          clickable={errors.length > 0}
        />
        <Chip size="small" label={`Events: ${events.length}`} variant="outlined" />
        <Tooltip
          title="Total events received since this browser tab opened — counts even after the iframe was destroyed/recreated by the host"
          arrow
        >
          <Chip size="small" label={`Lifetime: ${lifetimeCount}`} variant="outlined" />
        </Tooltip>
        <Tooltip title={contextSummary(ctx) || "No context captured yet"} arrow>
          <Chip
            size="small"
            label={`Context: ${countContextKeys(ctx)}`}
            variant="outlined"
          />
        </Tooltip>
        <Box sx={{ flexGrow: 1 }} />
        <Typography variant="caption" color="text.secondary">
          {bootError ? bootError : bootMsg}
        </Typography>
        <Button
          size="small"
          variant="outlined"
          onClick={() => {
            setEvents([]);
            setErrors([]);
            setLifetimeCount(0);
            setCtx({});
            ctxRef.current = {};
            eventStore.clear();
          }}
        >
          Reset all
        </Button>
      </Paper>

      <Box sx={{ borderBottom: 1, borderColor: "divider", maxWidth: "100%" }}>
        <Tabs
          value={tab}
          onChange={(_e: SyntheticEvent, v: string | number) => setTab(v as TabId)}
          textColor="primary"
          indicatorColor="primary"
          variant="scrollable"
          scrollButtons="auto"
          allowScrollButtonsMobile
          aria-label="SDK Debugger sections"
        >
          <Tab value="events" label={`Events (${events.length})`} />
          <Tab value="methods" label="Invoke method" />
        </Tabs>
      </Box>

      {tab === "events" && (
        <EventsPanel events={events} onClear={() => setEvents([])} registered={registered} />
      )}
      {tab === "methods" && <InvokeMethodPanel ctx={ctx} clientRef={clientRef} />}

      <RegistrationErrorsDialog
        open={errorsOpen}
        errors={errors}
        bootError={bootError}
        onClose={() => setErrorsOpen(false)}
        onClear={() => {
          setErrors([]);
          setErrorsOpen(false);
        }}
      />
    </Stack>
  );
}

function countContextKeys(ctx: AafEventContext): number {
  return Object.values(ctx).filter((v) => v != null && v !== "").length;
}

function contextSummary(ctx: AafEventContext): string {
  return Object.entries(ctx)
    .filter(([, v]) => v != null && v !== "")
    .map(([k, v]) => `${k}=${String(v)}`)
    .join(" · ");
}

function RegistrationErrorsDialog({
  open,
  errors,
  bootError,
  onClose,
  onClear
}: {
  open: boolean;
  errors: ErrorEntry[];
  bootError: string | null;
  onClose: () => void;
  onClear: () => void;
}) {
  return (
    <StructuredDialog
      open={open}
      onClose={onClose}
      title={`Registration errors (${errors.length})`}
      showFooter
      footerContent={
        <>
          <Button size="small" variant="outlined" onClick={onClear} disabled={errors.length === 0}>
            Clear
          </Button>
          <Button size="small" variant="contained" onClick={onClose}>
            Close
          </Button>
        </>
      }
    >
      <Stack spacing={1.5}>
        {bootError && (
          <Alert severity="error" variant="outlined">
            {bootError}
          </Alert>
        )}
        <Typography variant="body2" color="text.secondary">
          Per-event registration failures. The host typically returns 121 / 122 codes when an event
          name is unknown to this SDK build.
        </Typography>
        {errors.length === 0 ? (
          <Alert severity="success" variant="outlined">
            No registration errors.
          </Alert>
        ) : (
          <Stack spacing={1}>
            {errors.map((er) => (
              <Paper key={er.id} variant="outlined" sx={{ p: 1.25 }}>
                <Stack direction="row" spacing={1} alignItems="center">
                  <Chip size="small" label={er.name} color="warning" />
                  <Typography variant="caption" color="text.secondary">
                    {formatTs(er.ts)}
                  </Typography>
                </Stack>
                <Typography variant="body2" sx={{ mt: 0.5, fontFamily: "monospace", fontSize: 12 }}>
                  {er.message}
                </Typography>
              </Paper>
            ))}
          </Stack>
        )}
      </Stack>
    </StructuredDialog>
  );
}

