'use client';

import React, { useEffect, useRef, useState } from 'react';

import type { GradientController } from '../hooks/useGradientController';
import {
  type GradientPreset,
  type GradientUniformKey,
  defaultGradientColors,
  gradientUniformKeys,
} from './gradientConfig';

// Tweakpane type definitions
type BindingChangeEvent<TValue> = {
  value: TValue;
};

interface ButtonApi {
  on(eventName: 'click', handler: () => void): void;
}

export interface InputBindingApi<TValue = unknown> {
  on(
    eventName: 'change',
    handler: (event: BindingChangeEvent<TValue>) => void
  ): void;
  refresh(): void;
  dispose(): void;
}

export interface FolderApi {
  addBinding<T extends object, K extends keyof T>(
    target: T,
    key: K,
    params?: Record<string, unknown>
  ): InputBindingApi<T[K]>;
  addButton(params: { title: string }): ButtonApi;
  addFolder(params: { title: string; expanded?: boolean }): FolderApi;
  dispose(): void;
}

export interface Pane {
  element: HTMLElement;
  addBinding<T extends object, K extends keyof T>(
    target: T,
    key: K,
    params?: Record<string, unknown>
  ): InputBindingApi<T[K]>;
  addButton(params: { title: string }): ButtonApi;
  addFolder(params: { title: string; expanded?: boolean }): FolderApi;
  dispose(): void;
}

type PaneConstructor = new (options?: {
  title?: string;
  expanded?: boolean;
  container?: HTMLElement;
}) => Pane;

interface TweakpaneNamespace {
  Pane: PaneConstructor;
}

declare global {
  interface Window {
    Tweakpane?: TweakpaneNamespace;
    tweakpane?: TweakpaneNamespace;
  }
}

const TWEAKPANE_STYLE_ID = 'tweakpane-cdn-style';
const TWEAKPANE_CDN_STYLE_SRC =
  'https://cdn.jsdelivr.net/npm/tweakpane@4.0.5/dist/tweakpane.css';
const TWEAKPANE_MODULE_SRC =
  'https://cdn.jsdelivr.net/npm/tweakpane@4.0.5/dist/tweakpane.min.js';
const TWEAKPANE_MODULE_LOADER_ID = 'tweakpane-cdn-module-loader';
const TWEAKPANE_MODULE_LOADED_EVENT = 'tweakpane-cdn-loaded';

let loadPromise: Promise<TweakpaneNamespace> | null = null;

const loadTweakpane = (): Promise<TweakpaneNamespace> => {
  if (typeof window === 'undefined') {
    return Promise.reject(
      new Error('Tweakpane can only be loaded in a browser environment')
    );
  }

  if (window.Tweakpane) {
    return Promise.resolve(window.Tweakpane);
  }

  if (loadPromise) {
    return loadPromise;
  }

  loadPromise = (async () => {
    if (!document.getElementById(TWEAKPANE_STYLE_ID)) {
      const link = document.createElement('link');
      link.id = TWEAKPANE_STYLE_ID;
      link.rel = 'stylesheet';
      link.type = 'text/css';
      link.href = TWEAKPANE_CDN_STYLE_SRC;
      link.crossOrigin = 'anonymous';
      document.head.appendChild(link);
    }

    if (window.Tweakpane) {
      return window.Tweakpane;
    }

    const namespace = await new Promise<TweakpaneNamespace>(
      (resolve, reject) => {
        const handleLoaded = () => {
          window.removeEventListener(
            TWEAKPANE_MODULE_LOADED_EVENT,
            handleLoaded as EventListener
          );
          if (window.Tweakpane?.Pane) {
            resolve(window.Tweakpane);
          } else if (
            (window as Window & { tweakpane?: TweakpaneNamespace }).tweakpane
              ?.Pane
          ) {
            const namespaceFromLower = (
              window as Window & { tweakpane?: TweakpaneNamespace }
            ).tweakpane!;
            window.Tweakpane = namespaceFromLower;
            resolve(namespaceFromLower);
          } else {
            reject(
              new Error(
                'Tweakpane global was not found after executing the CDN module'
              )
            );
          }
        };

        window.addEventListener(
          TWEAKPANE_MODULE_LOADED_EVENT,
          handleLoaded as EventListener,
          { once: true }
        );

        const existingLoader = document.getElementById(
          TWEAKPANE_MODULE_LOADER_ID
        ) as HTMLScriptElement | null;

        if (existingLoader) {
          if (window.Tweakpane?.Pane) {
            window.removeEventListener(
              TWEAKPANE_MODULE_LOADED_EVENT,
              handleLoaded as EventListener
            );
            resolve(window.Tweakpane);
          }
          return;
        }

        const loaderCode = `
          import * as TweakpaneModule from '${TWEAKPANE_MODULE_SRC}';
          window.Tweakpane = TweakpaneModule;
          window.tweakpane = TweakpaneModule;
          window.dispatchEvent(new Event('${TWEAKPANE_MODULE_LOADED_EVENT}'));
        `;

        const blob = new Blob([loaderCode], { type: 'text/javascript' });
        const blobUrl = URL.createObjectURL(blob);

        const script = document.createElement('script');
        script.id = TWEAKPANE_MODULE_LOADER_ID;
        script.type = 'module';
        script.src = blobUrl;
        script.addEventListener('load', () => {
          URL.revokeObjectURL(blobUrl);
        });
        script.addEventListener('error', () => {
          URL.revokeObjectURL(blobUrl);
          window.removeEventListener(
            TWEAKPANE_MODULE_LOADED_EVENT,
            handleLoaded as EventListener
          );
          reject(new Error('Failed to load Tweakpane module from CDN'));
        });

        document.head.appendChild(script);
      }
    );

    if (!namespace?.Pane) {
      throw new Error(
        'Tweakpane global was not found after loading the script'
      );
    }

    window.Tweakpane = namespace;
    (window as Window & { tweakpane?: TweakpaneNamespace }).tweakpane =
      namespace;
    return namespace;
  })().finally(() => {
    loadPromise = null;
  });

  return loadPromise;
};

interface TweakpaneDebugPanelProps {
  controller: GradientController;
  visible: boolean;
}

interface BindingConfig {
  key: GradientUniformKey;
  folder: string;
  min: number;
  max: number;
  step: number;
  label?: string;
}

interface ColorCorner {
  r: number;
  g: number;
  b: number;
}

const bindingConfigs: BindingConfig[] = [
  // Vignette
  {
    key: 'vignetteRadius',
    folder: 'Vignette',
    min: 0.1,
    max: 1.5,
    step: 0.05,
    label: 'Radius',
  },
  {
    key: 'vignettePower',
    folder: 'Vignette',
    min: 0.5,
    max: 3,
    step: 0.1,
    label: 'Power',
  },
  // Color
  {
    key: 'colorScale',
    folder: 'Color',
    min: 0,
    max: 2,
    step: 0.1,
    label: 'Scale',
  },
  // Flow
  {
    key: 'flowWarp',
    folder: 'Flow',
    min: 0,
    max: 1,
    step: 0.01,
    label: 'Warp',
  },
  {
    key: 'flowSpeed',
    folder: 'Flow',
    min: 0,
    max: 6,
    step: 0.1,
    label: 'Speed',
  },
  {
    key: 'flowFrequency',
    folder: 'Flow',
    min: 0.1,
    max: 12,
    step: 0.1,
    label: 'Frequency',
  },
  {
    key: 'flowAmplitude',
    folder: 'Flow',
    min: 1,
    max: 60,
    step: 1,
    label: 'Amplitude',
  },
];

export const TweakpaneDebugPanel: React.FC<TweakpaneDebugPanelProps> = ({
  controller,
  visible,
}) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const paneRef = useRef<Pane | null>(null);
  const bindingsRef = useRef<Map<GradientUniformKey, InputBindingApi>>(
    new Map()
  );
  const foldersRef = useRef<Map<string, FolderApi>>(new Map());
  const uniformsRef = useRef<GradientPreset>(controller.getSnapshot());
  const isUpdatingFromExternalRef = useRef(false);
  const subscriptionsRef = useRef<Array<() => void>>([]);
  const [isDragging, setIsDragging] = useState(false);
  const [position, setPosition] = useState({ x: 16, y: 16 });

  // Color state for Tweakpane color pickers
  const colorStateRef = useRef<{
    topLeft: ColorCorner;
    topRight: ColorCorner;
    bottomLeft: ColorCorner;
    bottomRight: ColorCorner;
    background: ColorCorner;
  }>({
    topLeft: {
      r: defaultGradientColors.colorTopLeftR,
      g: defaultGradientColors.colorTopLeftG,
      b: defaultGradientColors.colorTopLeftB,
    },
    topRight: {
      r: defaultGradientColors.colorTopRightR,
      g: defaultGradientColors.colorTopRightG,
      b: defaultGradientColors.colorTopRightB,
    },
    bottomLeft: {
      r: defaultGradientColors.colorBottomLeftR,
      g: defaultGradientColors.colorBottomLeftG,
      b: defaultGradientColors.colorBottomLeftB,
    },
    bottomRight: {
      r: defaultGradientColors.colorBottomRightR,
      g: defaultGradientColors.colorBottomRightG,
      b: defaultGradientColors.colorBottomRightB,
    },
    background: {
      r: defaultGradientColors.backgroundColorR,
      g: defaultGradientColors.backgroundColorG,
      b: defaultGradientColors.backgroundColorB,
    },
  });

  // Update local uniforms reference when controller changes
  useEffect(() => {
    uniformsRef.current = controller.getSnapshot();
  }, [controller]);

  // Initialize Tweakpane
  useEffect(() => {
    if (!containerRef.current || !visible) {
      return;
    }

    let detachDragListeners: (() => void) | null = null;
    let isEffectActive = true;

    if (paneRef.current) {
      paneRef.current.dispose();
      paneRef.current = null;
    }

    loadTweakpane()
      .then(({ Pane }) => {
        if (!isEffectActive || !containerRef.current) {
          return;
        }

        const pane = new Pane({
          title: 'Gradient Controls',
          expanded: false,
          container: containerRef.current,
        });

        paneRef.current = pane;

        const currentUniforms = controller.getSnapshot();
        uniformsRef.current = { ...currentUniforms };

        colorStateRef.current = {
          topLeft: {
            r: currentUniforms.colorTopLeftR,
            g: currentUniforms.colorTopLeftG,
            b: currentUniforms.colorTopLeftB,
          },
          topRight: {
            r: currentUniforms.colorTopRightR,
            g: currentUniforms.colorTopRightG,
            b: currentUniforms.colorTopRightB,
          },
          bottomLeft: {
            r: currentUniforms.colorBottomLeftR,
            g: currentUniforms.colorBottomLeftG,
            b: currentUniforms.colorBottomLeftB,
          },
          bottomRight: {
            r: currentUniforms.colorBottomRightR,
            g: currentUniforms.colorBottomRightG,
            b: currentUniforms.colorBottomRightB,
          },
          background: {
            r: currentUniforms.backgroundColorR,
            g: currentUniforms.backgroundColorG,
            b: currentUniforms.backgroundColorB,
          },
        };

        const folderMap = new Map<string, FolderApi>();
        const bindingMap = new Map<GradientUniformKey, InputBindingApi>();

        bindingConfigs.forEach((config) => {
          let folder = folderMap.get(config.folder);
          if (!folder) {
            folder = pane.addFolder({ title: config.folder });
            folderMap.set(config.folder, folder);
          }

          const binding = folder.addBinding(uniformsRef.current, config.key, {
            min: config.min,
            max: config.max,
            step: config.step,
            label: config.label || config.key,
          });

          binding.on('change', (event) => {
            if (isUpdatingFromExternalRef.current) {
              return;
            }
            const value = event.value as number;
            controller.setUniform(config.key, value, { immediate: true });
          });

          bindingMap.set(config.key, binding);
        });

        const colorsFolder = pane.addFolder({ title: 'Colors' });
        folderMap.set('Colors', colorsFolder);

        const rgbToHex = (color: ColorCorner) => {
          const r = Math.round(color.r * 255)
            .toString(16)
            .padStart(2, '0');
          const g = Math.round(color.g * 255)
            .toString(16)
            .padStart(2, '0');
          const b = Math.round(color.b * 255)
            .toString(16)
            .padStart(2, '0');
          return `#${r}${g}${b}`;
        };

        const hexToRgb = (hex: string): ColorCorner => {
          const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
          return result
            ? {
                r: parseInt(result[1], 16) / 255,
                g: parseInt(result[2], 16) / 255,
                b: parseInt(result[3], 16) / 255,
              }
            : { r: 0, g: 0, b: 0 };
        };

        const colorBindings = [
          { key: 'topLeft', label: 'Top Left' },
          { key: 'topRight', label: 'Top Right' },
          { key: 'bottomLeft', label: 'Bottom Left' },
          { key: 'bottomRight', label: 'Bottom Right' },
          { key: 'background', label: 'Background' },
        ];

        colorBindings.forEach(({ key, label }) => {
          const colorObject = {
            [key]: rgbToHex(
              colorStateRef.current[key as keyof typeof colorStateRef.current]
            ),
          };
          const binding = colorsFolder.addBinding(colorObject, key, {
            label,
            view: 'color',
          });

          binding.on('change', (event) => {
            if (isUpdatingFromExternalRef.current) {
              return;
            }

            const rgb = hexToRgb(event.value as string);
            colorStateRef.current[key as keyof typeof colorStateRef.current] =
              rgb;

            const prefix =
              key === 'background'
                ? 'backgroundColor'
                : `color${key.charAt(0).toUpperCase()}${key.slice(1)}`;
            controller.setUniform(`${prefix}R` as GradientUniformKey, rgb.r, {
              immediate: true,
            });
            controller.setUniform(`${prefix}G` as GradientUniformKey, rgb.g, {
              immediate: true,
            });
            controller.setUniform(`${prefix}B` as GradientUniformKey, rgb.b, {
              immediate: true,
            });
          });

          bindingMap.set(
            `color${key.charAt(0).toUpperCase()}${key.slice(1)}Color` as any,
            binding
          );
        });

        const presetFolder = pane.addFolder({
          title: 'Presets',
          expanded: false,
        });

        const presetOptions = Object.keys(controller.presets).reduce(
          (acc, key) => {
            acc[key] = key;
            return acc;
          },
          {} as Record<string, string>
        );

        const presetState = { preset: controller.state };
        const presetBinding = presetFolder.addBinding(presetState, 'preset', {
          options: presetOptions,
          label: 'Active Preset',
        });

        presetBinding.on('change', (event) => {
          controller.setState(event.value as any, { immediate: false });
        });

        const copyButton = pane.addButton({
          title: 'Copy Settings',
        });
        copyButton.on('click', () => {
          const settings = JSON.stringify(uniformsRef.current, null, 2);
          navigator.clipboard.writeText(settings).then(() => {
            console.log('Settings copied to clipboard');
          });
        });

        const clearButton = pane.addButton({
          title: 'Clear Overrides',
        });
        clearButton.on('click', () => {
          controller.clearOverrides();
        });

        const paneElement = pane.element as HTMLElement;
        const titleBar =
          paneElement.querySelector('[class*="title"], [class*="header"]') ||
          paneElement.firstElementChild;

        if (titleBar) {
          let dragStart: { x: number; y: number } | null = null;
          let dragOffset: { x: number; y: number } | null = null;

          const handleMouseDown = (e: MouseEvent) => {
            dragStart = { x: e.clientX, y: e.clientY };
            const rect = paneElement.getBoundingClientRect();
            dragOffset = {
              x: e.clientX - rect.left,
              y: e.clientY - rect.top,
            };
            setIsDragging(true);
            e.preventDefault();
          };

          const handleMouseMove = (e: MouseEvent) => {
            if (!dragStart || !dragOffset) return;

            const deltaX = Math.abs(e.clientX - dragStart.x);
            const deltaY = Math.abs(e.clientY - dragStart.y);

            if (deltaX > 5 || deltaY > 5) {
              const newX = e.clientX - dragOffset.x;
              const newY = e.clientY - dragOffset.y;
              setPosition({ x: newX, y: newY });
            }
          };

          const handleMouseUp = () => {
            dragStart = null;
            dragOffset = null;
            setIsDragging(false);
          };

          (titleBar as HTMLElement).style.cursor = 'grab';
          titleBar.addEventListener(
            'mousedown',
            handleMouseDown as EventListener
          );
          document.addEventListener('mousemove', handleMouseMove);
          document.addEventListener('mouseup', handleMouseUp);

          detachDragListeners = () => {
            titleBar.removeEventListener(
              'mousedown',
              handleMouseDown as EventListener
            );
            document.removeEventListener('mousemove', handleMouseMove);
            document.removeEventListener('mouseup', handleMouseUp);
          };
        }

        foldersRef.current = folderMap;
        bindingsRef.current = bindingMap;
      })
      .catch((error) => {
        console.error('Failed to load Tweakpane from CDN', error);
      });

    return () => {
      isEffectActive = false;
      if (detachDragListeners) {
        detachDragListeners();
        detachDragListeners = null;
      }
      if (paneRef.current) {
        paneRef.current.dispose();
        paneRef.current = null;
      }
      bindingsRef.current.clear();
      foldersRef.current.clear();
    };
  }, [visible, controller]);

  // Subscribe to MotionValue changes for bidirectional sync
  useEffect(() => {
    if (!visible || !paneRef.current) {
      return;
    }

    // Clear existing subscriptions
    subscriptionsRef.current.forEach((unsub) => unsub());
    subscriptionsRef.current = [];

    // Subscribe to each uniform's MotionValue
    const subscriptions = gradientUniformKeys.map((key) => {
      const motionValue = controller.motionValues[key];
      if (!motionValue) {
        return () => {};
      }

      return motionValue.on('change', (value: number) => {
        // Update local reference
        uniformsRef.current[key] = value;

        // Handle color channel updates
        const colorChannelPattern =
          /^(color(TopLeft|TopRight|BottomLeft|BottomRight)|backgroundColor)([RGB])$/;
        const colorMatch = key.match(colorChannelPattern);

        if (colorMatch) {
          // Update the color state when RGB channels change
          const isBackground = colorMatch[1] === 'backgroundColor';
          const position = isBackground ? '' : colorMatch[2];
          const channel = colorMatch[3];
          const colorKey = isBackground
            ? 'background'
            : position.charAt(0).toLowerCase() + position.slice(1);

          if (
            colorStateRef.current[
              colorKey as keyof typeof colorStateRef.current
            ]
          ) {
            const currentColor =
              colorStateRef.current[
                colorKey as keyof typeof colorStateRef.current
              ];
            if (channel === 'R') currentColor.r = value;
            else if (channel === 'G') currentColor.g = value;
            else if (channel === 'B') currentColor.b = value;

            // Update the color picker binding
            const colorBindingKey = isBackground
              ? 'backgroundColorColor'
              : `color${position}Color`;
            const colorBinding = bindingsRef.current.get(
              colorBindingKey as any
            );
            if (colorBinding && paneRef.current) {
              isUpdatingFromExternalRef.current = true;
              try {
                colorBinding.refresh();
              } finally {
                isUpdatingFromExternalRef.current = false;
              }
            }
          }
        } else {
          // Update regular Tweakpane binding
          const binding = bindingsRef.current.get(key);
          if (binding && paneRef.current) {
            isUpdatingFromExternalRef.current = true;
            try {
              // Tweakpane will automatically re-read the value from uniformsRef.current
              binding.refresh();
            } finally {
              isUpdatingFromExternalRef.current = false;
            }
          }
        }
      });
    });

    subscriptionsRef.current = subscriptions;

    return () => {
      subscriptionsRef.current.forEach((unsub) => unsub());
      subscriptionsRef.current = [];
    };
  }, [visible, controller]);

  // Handle visibility
  if (!visible) {
    return null;
  }

  return (
    <div
      ref={containerRef}
      className='tweakpane-container'
      style={{
        position: 'fixed',
        left: `${position.x}px`,
        top: `${position.y}px`,
        zIndex: 9999,
        maxHeight: '80vh',
        overflowY: 'auto',
        cursor: isDragging ? 'grabbing' : 'auto',
        userSelect: 'none',
      }}
    />
  );
};

export default TweakpaneDebugPanel;
