import { useImperativeHandle, forwardRef, useCallback } from "react";
import type { TrackedAnnotation } from "@embedpdf/plugin-annotation";
import { useAnnotationCapability } from "@embedpdf/plugin-annotation/react";
import { PdfAnnotationSubtype, PdfAnnotationIcon } from "@embedpdf/models";
import type {
  AnnotationToolId,
  AnnotationToolOptions,
  AnnotationAPI,
  AnnotationEvent,
  AnnotationPatch,
  AnnotationRect,
  AnnotationSelection,
  ClearDocumentAnnotationsResult,
} from "@app/components/viewer/viewerTypes";
import { useDocumentReady } from "@app/components/viewer/hooks/useDocumentReady";

type NoteIcon = NonNullable<AnnotationToolOptions["icon"]>;

type AnnotationDefaults =
  | {
      type:
        | PdfAnnotationSubtype.HIGHLIGHT
        | PdfAnnotationSubtype.UNDERLINE
        | PdfAnnotationSubtype.STRIKEOUT
        | PdfAnnotationSubtype.SQUIGGLY;
      strokeColor: string;
      color?: string;
      opacity: number;
      customData?: Record<string, unknown>;
    }
  | {
      type: PdfAnnotationSubtype.INK;
      strokeColor: string;
      color?: string;
      opacity?: number;
      borderWidth?: number;
      strokeWidth?: number;
      lineWidth?: number;
      customData?: Record<string, unknown>;
    }
  | {
      type: PdfAnnotationSubtype.FREETEXT;
      fontColor?: string;
      fontSize?: number;
      fontFamily?: string;
      textAlign?: number;
      opacity?: number;
      color?: string;
      interiorColor?: string;
      borderWidth?: number;
      contents?: string;
      icon?: PdfAnnotationIcon;
      customData?: Record<string, unknown>;
    }
  | {
      type:
        | PdfAnnotationSubtype.SQUARE
        | PdfAnnotationSubtype.CIRCLE
        | PdfAnnotationSubtype.POLYGON;
      color: string;
      strokeColor: string;
      opacity: number;
      fillOpacity: number;
      strokeOpacity: number;
      borderWidth: number;
      strokeWidth: number;
      lineWidth: number;
      customData?: Record<string, unknown>;
    }
  | {
      type: PdfAnnotationSubtype.LINE | PdfAnnotationSubtype.POLYLINE;
      color: string;
      strokeColor?: string;
      opacity: number;
      borderWidth?: number;
      strokeWidth?: number;
      lineWidth?: number;
      startStyle?: string;
      endStyle?: string;
      lineEndingStyles?: { start: string; end: string };
      customData?: Record<string, unknown>;
    }
  | {
      type: PdfAnnotationSubtype.STAMP;
      imageSrc?: string;
      imageSize?: { width: number; height: number };
      customData?: Record<string, unknown>;
    }
  | {
      type: PdfAnnotationSubtype.TEXT;
      strokeColor?: string;
      opacity?: number;
      customData?: Record<string, unknown>;
    }
  | {
      type: PdfAnnotationSubtype.CARET;
      strokeColor?: string;
      opacity?: number;
      customData?: Record<string, unknown>;
    }
  | null;

const EMBEDPDF_CLEAR_DOCUMENT_ANNOTATIONS = "deleteAllAnnotations";

type AnnotationApiSurface = {
  setActiveTool: (toolId: AnnotationToolId | null) => void;
  getActiveTool?: () => { id: AnnotationToolId } | null;
  setToolDefaults?: (
    toolId: AnnotationToolId,
    defaults: AnnotationDefaults,
  ) => void;
  getSelectedAnnotation?: () => AnnotationSelection | null;
  deselectAnnotation?: () => void;
  updateAnnotation?: (
    pageIndex: number,
    annotationId: string,
    patch: AnnotationPatch,
  ) => void;
  deleteAnnotation?: (pageIndex: number, annotationId: string) => void;
  deleteAnnotations?: (
    annotations: Array<{ pageIndex: number; id: string }>,
  ) => void;
  getAnnotations?: () => unknown[];
  [EMBEDPDF_CLEAR_DOCUMENT_ANNOTATIONS]?: () => void | Promise<void>;
  createAnnotation?: (
    pageIndex: number,
    annotation: Record<string, unknown>,
  ) => void;
  getSelectedAnnotations?: () => AnnotationSelection[];
  onAnnotationEvent?: (
    listener: (event: AnnotationEvent) => void,
  ) => void | (() => void);
  purgeAnnotation?: (pageIndex: number, annotationId: string) => void;
  /** v2.7.0: move annotation without regenerating its appearance stream */
  moveAnnotation?: (
    pageIndex: number,
    annotationId: string,
    newRect: AnnotationRect,
  ) => void;
};

type ToolDefaultsBuilder = (
  options?: AnnotationToolOptions,
) => AnnotationDefaults;

const NOTE_ICON_MAP: Record<NoteIcon, PdfAnnotationIcon> = {
  Comment: PdfAnnotationIcon.Comment,
  Key: PdfAnnotationIcon.Key,
  Note: PdfAnnotationIcon.Note,
  Help: PdfAnnotationIcon.Help,
  NewParagraph: PdfAnnotationIcon.NewParagraph,
  Paragraph: PdfAnnotationIcon.Paragraph,
  Insert: PdfAnnotationIcon.Insert,
};

const DEFAULTS = {
  highlight: "#ffd54f",
  underline: "#ffb300",
  strikeout: "#e53935",
  squiggly: "#00acc1",
  ink: "#1f2933",
  inkHighlighter: "#ffd54f",
  text: "#111111",
  note: "#ffd54f",
  shapeFill: "#0000ff",
  shapeStroke: "#cf5b5b",
  shapeOpacity: 0.5,
};

const withCustomData = (options?: AnnotationToolOptions) =>
  options?.customData ? { customData: options.customData } : {};

const getIconEnum = (icon?: NoteIcon) =>
  NOTE_ICON_MAP[icon ?? "Comment"] ?? PdfAnnotationIcon.Comment;

const buildStampDefaults: ToolDefaultsBuilder = (options) => ({
  type: PdfAnnotationSubtype.STAMP,
  ...(options?.imageSrc ? { imageSrc: options.imageSrc } : {}),
  ...(options?.imageSize ? { imageSize: options.imageSize } : {}),
  ...withCustomData(options),
});

const buildInkDefaults = (
  options?: AnnotationToolOptions,
  opacityOverride?: number,
): AnnotationDefaults => {
  const colorValue =
    options?.color ??
    (opacityOverride ? DEFAULTS.inkHighlighter : DEFAULTS.ink);
  return {
    type: PdfAnnotationSubtype.INK,
    strokeColor: colorValue,
    color: colorValue,
    opacity: options?.opacity ?? opacityOverride ?? 1,
    borderWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
    strokeWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
    lineWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
    ...withCustomData(options),
  };
};

const TOOL_DEFAULT_BUILDERS: Record<AnnotationToolId, ToolDefaultsBuilder> = {
  select: () => null,
  highlight: (options) => {
    const colorValue = options?.color ?? DEFAULTS.highlight;
    return {
      type: PdfAnnotationSubtype.HIGHLIGHT,
      strokeColor: colorValue,
      color: colorValue,
      opacity: options?.opacity ?? 0.6,
      ...withCustomData(options),
    };
  },
  underline: (options) => {
    const colorValue = options?.color ?? DEFAULTS.underline;
    return {
      type: PdfAnnotationSubtype.UNDERLINE,
      strokeColor: colorValue,
      color: colorValue,
      opacity: options?.opacity ?? 1,
      ...withCustomData(options),
    };
  },
  strikeout: (options) => {
    const colorValue = options?.color ?? DEFAULTS.strikeout;
    return {
      type: PdfAnnotationSubtype.STRIKEOUT,
      strokeColor: colorValue,
      color: colorValue,
      opacity: options?.opacity ?? 1,
      ...withCustomData(options),
    };
  },
  squiggly: (options) => {
    const colorValue = options?.color ?? DEFAULTS.squiggly;
    return {
      type: PdfAnnotationSubtype.SQUIGGLY,
      strokeColor: colorValue,
      color: colorValue,
      opacity: options?.opacity ?? 1,
      ...withCustomData(options),
    };
  },
  ink: (options) => buildInkDefaults(options),
  inkHighlighter: (options) =>
    buildInkDefaults(options, options?.opacity ?? 0.6),
  text: (options) => ({
    type: PdfAnnotationSubtype.FREETEXT,
    fontColor: options?.color ?? DEFAULTS.text,
    fontSize: options?.fontSize ?? 14,
    fontFamily: options?.fontFamily ?? "Helvetica",
    textAlign: options?.textAlign ?? 0,
    opacity: options?.opacity ?? 1,
    borderWidth: options?.thickness ?? 1,
    ...(options?.fillColor
      ? { color: options.fillColor, interiorColor: options.fillColor }
      : {}),
    ...withCustomData(options),
  }),
  note: (options) => {
    const bgColor = options?.fillColor ?? DEFAULTS.note;
    const fontColor = options?.color ?? DEFAULTS.text;
    return {
      type: PdfAnnotationSubtype.FREETEXT,
      fontColor,
      fontFamily: options?.fontFamily ?? "Helvetica",
      textAlign: options?.textAlign ?? 0,
      fontSize: options?.fontSize ?? 12,
      opacity: options?.opacity ?? 1,
      color: bgColor,
      interiorColor: bgColor,
      borderWidth: options?.thickness ?? 0,
      contents: options?.contents ?? "Note",
      icon: getIconEnum(options?.icon),
      ...withCustomData(options),
    };
  },
  square: (options) => ({
    type: PdfAnnotationSubtype.SQUARE,
    color: options?.color ?? DEFAULTS.shapeFill,
    strokeColor: options?.strokeColor ?? DEFAULTS.shapeStroke,
    opacity: options?.opacity ?? DEFAULTS.shapeOpacity,
    fillOpacity: options?.fillOpacity ?? DEFAULTS.shapeOpacity,
    strokeOpacity: options?.strokeOpacity ?? DEFAULTS.shapeOpacity,
    borderWidth: options?.borderWidth ?? 1,
    strokeWidth: options?.borderWidth ?? 1,
    lineWidth: options?.borderWidth ?? 1,
    ...withCustomData(options),
  }),
  circle: (options) => ({
    type: PdfAnnotationSubtype.CIRCLE,
    color: options?.color ?? DEFAULTS.shapeFill,
    strokeColor: options?.strokeColor ?? DEFAULTS.shapeStroke,
    opacity: options?.opacity ?? DEFAULTS.shapeOpacity,
    fillOpacity: options?.fillOpacity ?? DEFAULTS.shapeOpacity,
    strokeOpacity: options?.strokeOpacity ?? DEFAULTS.shapeOpacity,
    borderWidth: options?.borderWidth ?? 1,
    strokeWidth: options?.borderWidth ?? 1,
    lineWidth: options?.borderWidth ?? 1,
    ...withCustomData(options),
  }),
  line: (options) => ({
    type: PdfAnnotationSubtype.LINE,
    color: options?.color ?? "#1565c0",
    strokeColor: options?.color ?? "#1565c0",
    opacity: options?.opacity ?? 1,
    borderWidth: options?.borderWidth ?? 2,
    strokeWidth: options?.borderWidth ?? 2,
    lineWidth: options?.borderWidth ?? 2,
    ...withCustomData(options),
  }),
  lineArrow: (options) => ({
    type: PdfAnnotationSubtype.LINE,
    color: options?.color ?? "#1565c0",
    strokeColor: options?.color ?? "#1565c0",
    opacity: options?.opacity ?? 1,
    borderWidth: options?.borderWidth ?? 2,
    strokeWidth: options?.borderWidth ?? 2,
    lineWidth: options?.borderWidth ?? 2,
    startStyle: "None",
    endStyle: "ClosedArrow",
    lineEndingStyles: { start: "None", end: "ClosedArrow" },
    ...withCustomData(options),
  }),
  polyline: (options) => ({
    type: PdfAnnotationSubtype.POLYLINE,
    color: options?.color ?? "#1565c0",
    opacity: options?.opacity ?? 1,
    borderWidth: options?.borderWidth ?? 2,
    ...withCustomData(options),
  }),
  polygon: (options) => ({
    type: PdfAnnotationSubtype.POLYGON,
    color: options?.color ?? DEFAULTS.shapeFill,
    strokeColor: options?.strokeColor ?? DEFAULTS.shapeStroke,
    opacity: options?.opacity ?? DEFAULTS.shapeOpacity,
    fillOpacity: options?.fillOpacity ?? DEFAULTS.shapeOpacity,
    strokeOpacity: options?.strokeOpacity ?? DEFAULTS.shapeOpacity,
    borderWidth: options?.borderWidth ?? 1,
    strokeWidth: options?.borderWidth ?? 1,
    lineWidth: options?.borderWidth ?? 1,
    ...withCustomData(options),
  }),
  stamp: buildStampDefaults,
  signatureStamp: buildStampDefaults,
  signatureInk: (options) => buildInkDefaults(options),
  textComment: (options) => ({
    type: PdfAnnotationSubtype.TEXT,
    strokeColor: options?.color ?? DEFAULTS.note,
    opacity: options?.opacity ?? 1,
    ...withCustomData(options),
  }),
  insertText: (options) => ({
    type: PdfAnnotationSubtype.CARET,
    strokeColor: options?.color ?? "#E44234",
    opacity: options?.opacity ?? 1,
    ...withCustomData(options),
  }),
  replaceText: (options) => ({
    type: PdfAnnotationSubtype.CARET,
    strokeColor: options?.color ?? "#E44234",
    opacity: options?.opacity ?? 1,
    ...withCustomData(options),
  }),
};

export const AnnotationAPIBridge = forwardRef<AnnotationAPI>(
  function AnnotationAPIBridge(_props, ref) {
    // Use the provided annotation API just like SignatureAPIBridge/HistoryAPIBridge
    const { provides: annotationApi } = useAnnotationCapability();
    const documentReady = useDocumentReady();

    const buildAnnotationDefaults = useCallback(
      (toolId: AnnotationToolId, options?: AnnotationToolOptions) =>
        TOOL_DEFAULT_BUILDERS[toolId]?.(options) ?? null,
      [],
    );

    const configureAnnotationTool = useCallback(
      (toolId: AnnotationToolId, options?: AnnotationToolOptions) => {
        const api = annotationApi as unknown as
          | AnnotationApiSurface
          | undefined;
        if (!api?.setActiveTool) return;

        const defaults = buildAnnotationDefaults(toolId, options);

        // Reset tool first, then activate (like SignatureAPIBridge does)
        api.setActiveTool(null);
        api.setActiveTool(toolId === "select" ? null : toolId);

        // Verify tool was activated before setting defaults (like SignatureAPIBridge does)
        const activeTool = api.getActiveTool?.();
        if (activeTool && activeTool.id === toolId && defaults) {
          api.setToolDefaults?.(toolId, defaults);
        }
      },
      [annotationApi, buildAnnotationDefaults],
    );

    useImperativeHandle(
      ref,
      () => ({
        activateAnnotationTool: (
          toolId: AnnotationToolId,
          options?: AnnotationToolOptions,
        ) => {
          configureAnnotationTool(toolId, options);
        },
        isReady: () => !!annotationApi && documentReady,
        setAnnotationStyle: (
          toolId: AnnotationToolId,
          options?: AnnotationToolOptions,
        ) => {
          const defaults = buildAnnotationDefaults(toolId, options);
          const api = annotationApi as unknown as
            | AnnotationApiSurface
            | undefined;
          if (defaults && api?.setToolDefaults) {
            api.setToolDefaults(toolId, defaults);
          }
        },
        getSelectedAnnotation: () => {
          const api = annotationApi as unknown as
            | AnnotationApiSurface
            | undefined;
          if (!api?.getSelectedAnnotation) {
            return null;
          }
          try {
            return api.getSelectedAnnotation();
          } catch (error) {
            // Some EmbedPDF builds expose getSelectedAnnotation with an internal
            // `this`/state dependency (e.g. reading `selectedUid` from undefined).
            // If that happens, fail gracefully and treat it as "no selection"
            // instead of crashing the entire annotations tool.
            // Only log unexpected errors - "No active document" is a common expected state during init
            const errorMessage =
              error instanceof Error ? error.message : String(error);
            if (!errorMessage.includes("No active document")) {
              console.error(
                "[AnnotationAPIBridge] getSelectedAnnotation failed:",
                error,
              );
            }
            return null;
          }
        },
        deselectAnnotation: () => {
          const api = annotationApi as unknown as
            | AnnotationApiSurface
            | undefined;
          api?.deselectAnnotation?.();
        },
        updateAnnotation: (
          pageIndex: number,
          annotationId: string,
          patch: AnnotationPatch,
        ) => {
          const api = annotationApi as unknown as
            | AnnotationApiSurface
            | undefined;
          api?.updateAnnotation?.(pageIndex, annotationId, patch);
        },
        deactivateTools: () => {
          const api = annotationApi as unknown as
            | AnnotationApiSurface
            | undefined;
          api?.setActiveTool?.(null);
        },
        onAnnotationEvent: (listener: (event: AnnotationEvent) => void) => {
          const api = annotationApi as unknown as
            | AnnotationApiSurface
            | undefined;
          if (api?.onAnnotationEvent) {
            return api.onAnnotationEvent(listener);
          }
          return undefined;
        },
        getActiveTool: () => {
          const api = annotationApi as unknown as
            | AnnotationApiSurface
            | undefined;
          return api?.getActiveTool?.() ?? null;
        },

        deleteAnnotation: (pageIndex: number, annotationId: string) => {
          const api = annotationApi as unknown as
            | AnnotationApiSurface
            | undefined;
          api?.deleteAnnotation?.(pageIndex, annotationId);
        },

        deleteAnnotations: (
          annotations: Array<{ pageIndex: number; id: string }>,
        ) => {
          const api = annotationApi as unknown as
            | AnnotationApiSurface
            | undefined;
          api?.deleteAnnotations?.(annotations);
        },

        clearDocumentAnnotations:
          async (): Promise<ClearDocumentAnnotationsResult> => {
            const api = annotationApi;
            const clearAnnotations = api?.[EMBEDPDF_CLEAR_DOCUMENT_ANNOTATIONS];

            if (!api || !clearAnnotations || !api.getAnnotations) {
              return { available: false, cleared: false };
            }

            let annotations: TrackedAnnotation[];
            try {
              annotations = api.getAnnotations();
            } catch {
              return { available: false, cleared: false };
            }

            if (annotations.length === 0) {
              return { available: true, cleared: false };
            }

            await clearAnnotations.call(api);
            return { available: true, cleared: true };
          },

        createAnnotation: (
          pageIndex: number,
          annotation: Record<string, unknown>,
        ) => {
          const api = annotationApi as unknown as
            | AnnotationApiSurface
            | undefined;
          api?.createAnnotation?.(pageIndex, annotation);
        },

        getSelectedAnnotations: () => {
          const api = annotationApi as unknown as
            | AnnotationApiSurface
            | undefined;
          return api?.getSelectedAnnotations?.() ?? [];
        },

        purgeAnnotation: (pageIndex: number, annotationId: string) => {
          const api = annotationApi as unknown as
            | AnnotationApiSurface
            | undefined;
          api?.purgeAnnotation?.(pageIndex, annotationId);
        },

        moveAnnotation: (
          pageIndex: number,
          annotationId: string,
          newRect: AnnotationRect,
        ) => {
          const api = annotationApi as unknown as
            | AnnotationApiSurface
            | undefined;
          api?.moveAnnotation?.(pageIndex, annotationId, newRect);
        },
      }),
      [annotationApi, configureAnnotationTool, buildAnnotationDefaults],
    );

    return null;
  },
);
