import { memo, useEffect, useMemo, useRef, useState } from "react";
import { Move } from "../../icons/SystemIcons";
import { Eye, EyeSlash } from "@phosphor-icons/react";
import { InspectorHeaderActions } from "./InspectorHeaderActions";
import { useStudioShellContext } from "../../contexts/StudioContext";
import { readStudioBoxSize, readStudioPathOffset, readStudioRotation } from "./manualEdits";
import {
  EMPTY_STYLES,
  formatPxMetricValue,
  parsePxMetricValue,
  RESPONSIVE_GRID,
  readGsapRuntimeValuesForPanel,
  readGsapBorderRadiusForPanel,
  isSelectedElementHidden,
} from "./propertyPanelHelpers";
import { MetricField, Section } from "./propertyPanelPrimitives";
import { createTransformCommitHandlers } from "./propertyPanelTransformCommit";
import { classifyPropertyGroup } from "@hyperframes/core/gsap-parser";
import { resolveEditingSections } from "@hyperframes/core/editing";
import { MediaSection } from "./propertyPanelMediaSection";
import { ColorGradingSection } from "./propertyPanelColorGradingSection";
import { domEditSelectionToFacts } from "./domEditingLayers";
import { TextSection, StyleSections } from "./propertyPanelSections";
import { GsapAnimationSection } from "./GsapAnimationSection";
import { PropertyPanel3dTransform } from "./propertyPanel3dTransform";
import { KeyframeNavigation } from "./KeyframeNavigation";
import { STUDIO_GSAP_PANEL_ENABLED, STUDIO_KEYFRAMES_ENABLED } from "./manualEditingAvailability";
import { usePlayerStore, liveTime } from "../../player";
import { TimingSection } from "./propertyPanelTimingSection";
import { type PropertyPanelProps } from "./propertyPanelHelpers";
import { GestureRecordPanelButton } from "./GestureRecordControl";
import { PropertyPanelEmptyState } from "./PropertyPanelEmptyState";

// Re-export helpers that external consumers import from this module
export {
  buildInsetClipPathSides,
  buildStrokeStyleUpdates,
  buildStrokeWidthStyleUpdates,
  getCssFilterFunctionPx,
  getClipPathInsetPx,
  inferBoxShadowPreset,
  inferClipPathPreset,
  normalizePanelPxValue,
  parseInsetClipPathSides,
  setCssFilterFunctionPx,
} from "./propertyPanelHelpers";

// fallow-ignore-next-line complexity
export const PropertyPanel = memo(function PropertyPanel({
  projectId,
  projectDir,
  assets,
  element,
  multiSelectCount = 0,
  copiedAgentPrompt: _copiedAgentPrompt,
  onClearSelection,
  onUngroup,
  onSetStyle,
  onSetAttribute,
  onSetAttributeLive,
  onApplyColorGradingScope,
  onSetHtmlAttribute,
  onRemoveBackground,
  onSetManualOffset,
  onSetManualSize,
  onSetManualRotation,
  onSetText,
  onSetTextFieldStyle,
  onAddTextField,
  onRemoveTextField,
  onAskAgent: _onAskAgent,
  onToggleElementHidden,
  onImportAssets,
  fontAssets = [],
  onImportFonts,
  previewIframeRef,
  gsapAnimations = [],
  gsapMultipleTimelines,
  gsapUnsupportedTimelinePattern,
  onUpdateGsapProperty,
  onUpdateGsapMeta,
  onDeleteGsapAnimation,
  onAddGsapProperty,
  onRemoveGsapProperty,
  onUpdateGsapFromProperty,
  onAddGsapFromProperty,
  onRemoveGsapFromProperty,
  onAddGsapAnimation,
  onSetArcPath,
  onUpdateArcSegment,
  onUnroll,
  onUpdateKeyframeEase,
  onSetAllKeyframeEases,
  onAddKeyframe,
  onRemoveKeyframe,
  onConvertToKeyframes,
  onCommitAnimatedProperty,
  onCommitAnimatedProperties,
  onSeekToTime,
  recordingState,
  recordingDuration,
  onToggleRecording,
}: PropertyPanelProps) {
  const styles = element?.computedStyles ?? EMPTY_STYLES;
  const { showToast } = useStudioShellContext();
  const [clipboardCopied, setClipboardCopied] = useState(false);
  const clipboardTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
  const storeTime = usePlayerStore((s) => s.currentTime);
  const isPlaying = usePlayerStore((s) => s.isPlaying);
  const timelineElements = usePlayerStore((s) => s.elements);
  const selectedElementId = usePlayerStore((s) => s.selectedElementId);
  const selectedElementHidden = isSelectedElementHidden(timelineElements, selectedElementId);
  const visibilityToggleLabel = selectedElementHidden ? "Show element" : "Hide element";
  const liveTimeRef = useRef(storeTime);
  const [, forceRender] = useState(0);
  useEffect(() => {
    if (!isPlaying) return;
    let timerId: ReturnType<typeof setTimeout> | 0 = 0;
    const unsub = liveTime.subscribe((t) => {
      liveTimeRef.current = t;
      if (!timerId)
        timerId = setTimeout(() => {
          timerId = 0;
          forceRender((v) => v + 1);
        }, 33);
    });
    return () => {
      unsub();
      if (timerId) clearTimeout(timerId);
    };
  }, [isPlaying]);
  const currentTime = isPlaying ? liveTimeRef.current : storeTime;
  const cacheElementKey = element?.id ?? element?.selector ?? "";
  const cacheEntry = usePlayerStore((s) => s.keyframeCache.get(cacheElementKey));

  const iframeRef = previewIframeRef ?? { current: null };
  const gsapAnimIdForMemo = element
    ? (gsapAnimations?.find((a: { keyframes?: unknown }) => a.keyframes)?.id ??
      gsapAnimations?.[0]?.id ??
      null)
    : null;
  const gsapRuntimeValues = useMemo(
    () =>
      element
        ? readGsapRuntimeValuesForPanel(gsapAnimIdForMemo, gsapAnimations, element, iframeRef)
        : null,
    // eslint-disable-next-line react-hooks/exhaustive-deps -- iframeRef is stable; currentTime drives re-reads during playback
    [gsapAnimIdForMemo, gsapAnimations, element, currentTime],
  );
  const gsapBorderRadius = useMemo(
    () =>
      element
        ? readGsapBorderRadiusForPanel(gsapRuntimeValues, gsapAnimations, element, iframeRef)
        : null,
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [gsapRuntimeValues, gsapAnimations, element, currentTime],
  );
  // The 3D Transform panel should be reachable on ANY element, not only ones GSAP is
  // already animating — otherwise you can't add depth/rotation to a fresh static
  // element (the panel never appears, the classic chicken-and-egg). Default to
  // identity when there are no runtime values yet; the first edit creates the
  // gsap.set via commitStaticSet, after which real runtime values flow in.
  const gsap3dValues: Record<string, number> = gsapRuntimeValues ?? {
    rotationX: 0,
    rotationY: 0,
    rotationZ: 0,
    z: 0,
    scale: 1,
    transformPerspective: 0,
  };

  if (!element) {
    return <PropertyPanelEmptyState multiSelectCount={multiSelectCount} />;
  }

  const manualOffsetEditingDisabled = !element.capabilities.canApplyManualOffset;
  const manualSizeEditingDisabled = !element.capabilities.canApplyManualSize;
  const manualRotationEditingDisabled = !element.capabilities.canApplyManualRotation;
  const sourceLabel = element.id ? `#${element.id}` : element.selector;
  const showEditableSections = element.capabilities.canEditStyles;
  // Capabilities are already resolved on the selection; recompute only sections,
  // feeding the live GSAP tween count (arrives on the gsapAnimations prop, not the
  // selection) so the Timing section shows for pure-GSAP elements with no data-start.
  const sections = resolveEditingSections(domEditSelectionToFacts(element, gsapAnimations.length));
  const manualOffset = readStudioPathOffset(element.element);
  const manualSize = readStudioBoxSize(element.element);
  const resolvedWidth =
    manualSize.width > 0
      ? manualSize.width
      : (parsePxMetricValue(styles.width ?? "") ?? element.boundingBox.width);
  const resolvedHeight =
    manualSize.height > 0
      ? manualSize.height
      : (parsePxMetricValue(styles.height ?? "") ?? element.boundingBox.height);

  const manualRotation = readStudioRotation(element.element);

  const elStart = Number.parseFloat(element?.dataAttributes?.start ?? "0") || 0;
  const elDuration = Number.parseFloat(element?.dataAttributes?.duration ?? "1") || 0;
  const currentPct = elDuration > 0 ? ((currentTime - elStart) / elDuration) * 100 : 0;

  const gsapKfAnim = gsapAnimations?.find((a) => a.keyframes) ?? null;
  const gsapKeyframes = gsapKfAnim?.keyframes?.keyframes ?? null;
  const gsapAnimId = gsapKfAnim?.id ?? gsapAnimations?.[0]?.id ?? null;
  const hasGsapAnimation = !!(gsapAnimId || gsapAnimations.length > 0);
  const { commitManualOffset, commitManualSize, commitManualRotation } =
    createTransformCommitHandlers({
      element,
      styles,
      hasGsapAnimation,
      gsapAnimId,
      gsapKeyframes,
      currentPct,
      onCommitAnimatedProperty,
      onAddKeyframe,
      onSetManualOffset,
      onSetManualSize,
      onSetManualRotation,
      showToast,
    });
  const navKeyframes = cacheEntry?.keyframes ?? gsapKeyframes;
  const seekFromKfPct = (pct: number) => onSeekToTime?.(elStart + (pct / 100) * elDuration);

  const animIdForProp = (prop: string): string => {
    const group = classifyPropertyGroup(prop);
    const groupAnim = gsapAnimations?.find((a) => a.propertyGroup === group);
    if (groupAnim) return groupAnim.id;
    return gsapAnimId ?? "";
  };

  const displayX = gsapRuntimeValues?.x ?? manualOffset.x;
  const displayY = gsapRuntimeValues?.y ?? manualOffset.y;
  const displayW = gsapRuntimeValues?.width ?? resolvedWidth;
  const displayH = gsapRuntimeValues?.height ?? resolvedHeight;
  const displayR = gsapRuntimeValues?.rotation ?? manualRotation.angle;

  // fallow-ignore-next-line complexity
  const handleCopyElementInfo = () => {
    const file = element.sourceFile ?? "index.html";
    let lineNum: number | null = null;
    try {
      const src = previewIframeRef?.current?.contentDocument?.documentElement?.outerHTML ?? "";
      if (src && element.id) {
        const idx = src.indexOf(`id="${element.id}"`);
        if (idx > -1) lineNum = src.slice(0, idx).split("\n").length;
      }
      if (!lineNum && element.selector) {
        const tag = element.tagName.toLowerCase();
        const cls = element.selector.startsWith(".")
          ? element.selector.slice(1).split(".")[0]
          : null;
        const search = cls ? `class="${cls}` : `<${tag}`;
        const idx = src.indexOf(search);
        if (idx > -1) lineNum = src.slice(0, idx).split("\n").length;
      }
    } catch {}
    const fileLoc = lineNum ? `${file}:${lineNum}` : file;
    const lines = [
      `Element: ${element.label} (${sourceLabel})`,
      `File: ${fileLoc}`,
      `Position: x=${Math.round(element.boundingBox.x)}, y=${Math.round(element.boundingBox.y)}`,
      `Size: ${Math.round(element.boundingBox.width)}×${Math.round(element.boundingBox.height)}`,
      `Tag: <${element.tagName}>`,
    ];
    if (element.computedStyles["z-index"] && element.computedStyles["z-index"] !== "auto") {
      lines.push(`Z-index: ${element.computedStyles["z-index"]}`);
    }
    if (gsapAnimations.length > 0) {
      const anim = gsapAnimations[0];
      lines.push(
        `Animation: ${anim.method}() ${anim.duration}s at ${anim.position}s, ease: ${anim.ease ?? "default"}`,
      );
      const props = Object.entries(anim.properties)
        .map(([k, v]) => `${k}: ${v}`)
        .join(", ");
      if (props) lines.push(`Properties: ${props}`);
    }
    const text = lines.join("\n");
    void navigator.clipboard.writeText(text);
    showToast(`Copied element info for ${element.label} — paste into any AI agent`, "info");
    setClipboardCopied(true);
    clearTimeout(clipboardTimerRef.current);
    clipboardTimerRef.current = setTimeout(() => setClipboardCopied(false), 1500);
  };

  return (
    <div className="flex h-full min-h-0 flex-col overflow-hidden bg-panel-bg text-panel-text-1">
      <div className="px-4 py-3">
        <div className="flex items-center justify-between gap-4">
          <div className="min-w-0">
            <div className="truncate text-[13px] font-semibold text-neutral-100">
              {element.label}
            </div>
            <div className="mt-0.5 truncate text-[11px] text-neutral-500">{sourceLabel}</div>
          </div>
          <div className="flex items-center gap-1">
            {selectedElementId && onToggleElementHidden && (
              <button
                type="button"
                aria-label={visibilityToggleLabel}
                title={visibilityToggleLabel}
                onClick={() => {
                  void onToggleElementHidden(selectedElementId, !selectedElementHidden);
                }}
                className="flex h-6 w-6 items-center justify-center rounded text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300"
              >
                {selectedElementHidden ? (
                  <EyeSlash size={13} weight="bold" aria-hidden="true" />
                ) : (
                  <Eye size={13} weight="bold" aria-hidden="true" />
                )}
              </button>
            )}
            <InspectorHeaderActions
              element={element}
              copied={clipboardCopied}
              onCopy={handleCopyElementInfo}
              onClear={onClearSelection}
              onUngroup={onUngroup}
            />
          </div>
        </div>
      </div>
      <div className="flex-1 overflow-y-auto">
        {onToggleRecording && (
          <GestureRecordPanelButton
            recordingState={recordingState}
            recordingDuration={recordingDuration}
            onToggleRecording={onToggleRecording}
          />
        )}

        <TextSection
          element={element}
          styles={styles}
          fontAssets={fontAssets}
          onImportFonts={onImportFonts}
          onSetText={onSetText}
          onSetTextFieldStyle={onSetTextFieldStyle}
          onAddTextField={onAddTextField}
          onRemoveTextField={onRemoveTextField}
        />

        {sections.timing && (
          // Render whenever there's an authored clip range OR animations to infer
          // one from — a pure-GSAP element with no data-start still gets a Timing
          // range (TimingSection derives it from its tweens).
          <TimingSection
            element={element}
            animations={gsapAnimations}
            onSetAttribute={onSetAttribute}
          />
        )}
        {sections.colorGrading && (
          <ColorGradingSection
            key={[
              element.id ?? "",
              element.hfId ?? "",
              element.selector ?? "",
              String(element.selectorIndex ?? ""),
            ].join("|")}
            projectId={projectId}
            element={element}
            assets={assets}
            previewIframeRef={previewIframeRef}
            onImportAssets={onImportAssets}
            onSetAttributeLive={onSetAttributeLive}
            onApplyScope={onApplyColorGradingScope}
          />
        )}

        {sections.media && (
          <MediaSection
            projectDir={projectDir}
            element={element}
            styles={styles}
            onSetStyle={onSetStyle}
            onSetAttribute={onSetAttribute}
            onSetHtmlAttribute={onSetHtmlAttribute}
            onRemoveBackground={onRemoveBackground}
          />
        )}

        <Section title="Layout" icon={<Move size={15} />}>
          <div className={RESPONSIVE_GRID}>
            <div className="flex items-center gap-1">
              <div className="flex-1">
                <MetricField
                  label="X"
                  value={formatPxMetricValue(displayX)}
                  disabled={manualOffsetEditingDisabled}
                  scrub
                  onCommit={(next) => commitManualOffset("x", next)}
                />
              </div>
              {STUDIO_KEYFRAMES_ENABLED && gsapAnimId && (
                <KeyframeNavigation
                  property="x"
                  keyframes={navKeyframes}
                  currentPercentage={currentPct}
                  onSeek={seekFromKfPct}
                  onAddKeyframe={() =>
                    onCommitAnimatedProperty &&
                    void onCommitAnimatedProperty(element, "x", displayX)
                  }
                  onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp("x"), pct)}
                  onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp("x"))}
                />
              )}
            </div>
            <div className="flex items-center gap-1">
              <div className="flex-1">
                <MetricField
                  label="Y"
                  value={formatPxMetricValue(displayY)}
                  disabled={manualOffsetEditingDisabled}
                  scrub
                  onCommit={(next) => commitManualOffset("y", next)}
                />
              </div>
              {STUDIO_KEYFRAMES_ENABLED && gsapAnimId && (
                <KeyframeNavigation
                  property="y"
                  keyframes={navKeyframes}
                  currentPercentage={currentPct}
                  onSeek={seekFromKfPct}
                  onAddKeyframe={() =>
                    onCommitAnimatedProperty &&
                    void onCommitAnimatedProperty(element, "y", displayY)
                  }
                  onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp("y"), pct)}
                  onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp("y"))}
                />
              )}
            </div>
            <div className="flex items-center gap-1">
              <div className="flex-1">
                <MetricField
                  label="W"
                  value={formatPxMetricValue(displayW)}
                  disabled={manualSizeEditingDisabled}
                  scrub
                  onCommit={(next) => commitManualSize("width", next)}
                />
              </div>
              {STUDIO_KEYFRAMES_ENABLED && gsapAnimId && (
                <KeyframeNavigation
                  property="width"
                  keyframes={navKeyframes}
                  currentPercentage={currentPct}
                  onSeek={seekFromKfPct}
                  onAddKeyframe={() =>
                    onCommitAnimatedProperty &&
                    void onCommitAnimatedProperty(element, "width", displayW)
                  }
                  onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp("width"), pct)}
                  onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp("width"))}
                />
              )}
            </div>
            <div className="flex items-center gap-1">
              <div className="flex-1">
                <MetricField
                  label="H"
                  value={formatPxMetricValue(displayH)}
                  disabled={manualSizeEditingDisabled}
                  scrub
                  onCommit={(next) => commitManualSize("height", next)}
                />
              </div>
              {STUDIO_KEYFRAMES_ENABLED && gsapAnimId && (
                <KeyframeNavigation
                  property="height"
                  keyframes={navKeyframes}
                  currentPercentage={currentPct}
                  onSeek={seekFromKfPct}
                  onAddKeyframe={() =>
                    onCommitAnimatedProperty &&
                    void onCommitAnimatedProperty(element, "height", displayH)
                  }
                  onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp("height"), pct)}
                  onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp("height"))}
                />
              )}
            </div>
            <div className="flex items-center gap-1">
              <div className="flex-1">
                <MetricField
                  label="R"
                  value={`${displayR}°`}
                  disabled={manualRotationEditingDisabled}
                  onCommit={(next) => commitManualRotation(next.replace("°", ""))}
                />
              </div>
              {STUDIO_KEYFRAMES_ENABLED && gsapAnimId && (
                <KeyframeNavigation
                  property="rotation"
                  keyframes={navKeyframes}
                  currentPercentage={currentPct}
                  onSeek={seekFromKfPct}
                  onAddKeyframe={() =>
                    onCommitAnimatedProperty &&
                    void onCommitAnimatedProperty(element, "rotation", displayR)
                  }
                  onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp("rotation"), pct)}
                  onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp("rotation"))}
                />
              )}
            </div>
          </div>
          <PropertyPanel3dTransform
            gsapRuntimeValues={gsap3dValues}
            gsapAnimId={gsapAnimId}
            resolveAnimIdForProp={animIdForProp}
            gsapKeyframes={navKeyframes}
            currentPct={currentPct}
            elStart={elStart}
            elDuration={elDuration}
            element={element}
            onCommitAnimatedProperty={onCommitAnimatedProperty}
            onCommitAnimatedProperties={onCommitAnimatedProperties}
            onSeekToTime={onSeekToTime}
            onRemoveKeyframe={onRemoveKeyframe}
            onConvertToKeyframes={onConvertToKeyframes}
            onLivePreviewProps={(el, props) => {
              const iframe = iframeRef.current;
              const win = iframe?.contentWindow as
                | { gsap?: { set: (t: Element, v: Record<string, number>) => void } }
                | null
                | undefined;
              const sel = el.id ? `#${el.id}` : el.selector;
              const node = sel ? iframe?.contentDocument?.querySelector(sel) : null;
              if (win?.gsap && node) win.gsap.set(node, props);
            }}
          />
          <div className="mt-3">
            <div className="mb-2 text-[10px] font-medium uppercase tracking-wider text-neutral-600">
              Stacking
            </div>
            <MetricField
              label="Z-index"
              value={String(parseInt(styles["z-index"] || "auto", 10) || 0)}
              scrub
              onCommit={(next) => onSetStyle("z-index", next)}
            />
          </div>
        </Section>

        {STUDIO_GSAP_PANEL_ENABLED &&
          onUpdateGsapProperty &&
          onUpdateGsapMeta &&
          onDeleteGsapAnimation &&
          onAddGsapProperty &&
          onAddGsapAnimation && (
            <GsapAnimationSection
              animations={gsapAnimations}
              multipleTimelines={gsapMultipleTimelines}
              unsupportedTimelinePattern={gsapUnsupportedTimelinePattern}
              onUpdateProperty={onUpdateGsapProperty}
              onUpdateMeta={onUpdateGsapMeta}
              onDeleteAnimation={onDeleteGsapAnimation}
              onAddProperty={onAddGsapProperty}
              onRemoveProperty={onRemoveGsapProperty ?? (() => {})}
              onUpdateFromProperty={onUpdateGsapFromProperty}
              onAddFromProperty={onAddGsapFromProperty}
              onRemoveFromProperty={onRemoveGsapFromProperty}
              onAddAnimation={onAddGsapAnimation}
              onSetArcPath={onSetArcPath}
              onUpdateArcSegment={onUpdateArcSegment}
              onUnroll={onUnroll}
              onUpdateKeyframeEase={onUpdateKeyframeEase}
              onSetAllKeyframeEases={onSetAllKeyframeEases}
            />
          )}

        {showEditableSections && (
          <StyleSections
            projectId={projectId}
            element={element}
            styles={styles}
            assets={assets}
            onSetStyle={onSetStyle}
            onImportAssets={onImportAssets}
            gsapBorderRadius={gsapBorderRadius}
          />
        )}
      </div>
    </div>
  );
});
