import { Button } from '@learnitive/ui-components';
import { Maximize2, Minimize2, Send, Square, X } from 'lucide-react';
import React from 'react';

export type AiConversationTab = 'assistant' | 'papers';
export type AiConversationSize = 'compact' | 'large';

type AiConversationDimensions = {
  width: number;
  height: number;
};

const AI_SHELL_SIZE_STORAGE_KEY = 'learnitive:ai-conversation-shell:size:v2';
const AI_SHELL_LEGACY_SIZE_STORAGE_KEY = 'learnitive:ai-conversation-shell:size:v1';
const AI_SHELL_DIMENSIONS_STORAGE_KEY = 'learnitive:ai-conversation-shell:dimensions:v2';

function readPreferredSize(): AiConversationSize {
  if (typeof window === 'undefined') return 'compact';
  try {
    const current = window.localStorage.getItem(AI_SHELL_SIZE_STORAGE_KEY);
    if (current === 'large' || current === 'compact') return current;
    const legacy = window.localStorage.getItem(AI_SHELL_LEGACY_SIZE_STORAGE_KEY);
    // R14.28.12 regression fix: desktop Full screen was removed. Migrate the
    // obsolete preference to Large rather than trapping an existing user in
    // an unsupported window mode.
    if (legacy === 'large' || legacy === 'fullscreen') return 'large';
    return 'compact';
  } catch {
    return 'compact';
  }
}

function persistPreferredSize(size: AiConversationSize): void {
  if (typeof window === 'undefined') return;
  try {
    window.localStorage.setItem(AI_SHELL_SIZE_STORAGE_KEY, size);
  } catch {
    // Shell sizing is a convenience preference only.
  }
}

function clampDimensions(dimensions: AiConversationDimensions): AiConversationDimensions {
  if (typeof window === 'undefined') return dimensions;
  return {
    width: Math.max(420, Math.min(dimensions.width, Math.max(420, window.innerWidth - 32))),
    height: Math.max(560, Math.min(dimensions.height, Math.max(560, window.innerHeight - 80))),
  };
}

function defaultDimensions(size: AiConversationSize): AiConversationDimensions {
  if (typeof window === 'undefined') {
    return size === 'large' ? { width: 1040, height: 820 } : { width: 520, height: 840 };
  }
  if (size === 'large') {
    return clampDimensions({
      width: Math.round(window.innerWidth * 0.82),
      height: Math.round(window.innerHeight * 0.88),
    });
  }
  return clampDimensions({
    width: Math.min(540, window.innerWidth - 32),
    height: Math.min(Math.round(window.innerHeight * 0.88), window.innerHeight - 80),
  });
}

function readPreferredDimensions(size: AiConversationSize): AiConversationDimensions {
  if (typeof window === 'undefined') return defaultDimensions(size);
  try {
    const raw = window.localStorage.getItem(AI_SHELL_DIMENSIONS_STORAGE_KEY);
    if (!raw) return defaultDimensions(size);
    const parsed = JSON.parse(raw) as Partial<AiConversationDimensions>;
    if (typeof parsed.width !== 'number' || typeof parsed.height !== 'number') {
      return defaultDimensions(size);
    }
    return clampDimensions({ width: parsed.width, height: parsed.height });
  } catch {
    return defaultDimensions(size);
  }
}

function persistPreferredDimensions(dimensions: AiConversationDimensions): void {
  if (typeof window === 'undefined') return;
  try {
    window.localStorage.setItem(AI_SHELL_DIMENSIONS_STORAGE_KEY, JSON.stringify(dimensions));
  } catch {
    // Resized dimensions are a convenience preference only.
  }
}

function desktopFrameClass(size: AiConversationSize): string {
  return size === 'large'
    ? 'sm:inset-auto sm:bottom-[3dvh] sm:right-[3vw] sm:rounded-xl'
    : 'sm:inset-auto sm:bottom-[4.5rem] sm:right-4 sm:rounded-xl';
}

export function AiConversationShell({
  activeTab,
  onTabChange,
  onClose,
  headerActions,
  evidence,
  papersEnabled = true,
  children,
}: {
  activeTab: AiConversationTab;
  onTabChange: (tab: AiConversationTab) => void;
  onClose: () => void;
  headerActions?: React.ReactNode;
  evidence?: React.ReactNode;
  papersEnabled?: boolean;
  children: React.ReactNode;
}) {
  const initialSizeRef = React.useRef<AiConversationSize>(readPreferredSize());
  const [size, setSize] = React.useState<AiConversationSize>(initialSizeRef.current);
  const [dimensions, setDimensions] = React.useState<AiConversationDimensions>(() =>
    readPreferredDimensions(initialSizeRef.current));
  const frameRef = React.useRef<HTMLElement | null>(null);
  const persistTimerRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
  const programmaticResizeRef = React.useRef(false);
  const programmaticResizeReleaseRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);

  const selectSize = React.useCallback((next: AiConversationSize) => {
    const nextDimensions = defaultDimensions(next);
    const frame = frameRef.current;
    programmaticResizeRef.current = true;
    if (persistTimerRef.current) clearTimeout(persistTimerRef.current);
    if (programmaticResizeReleaseRef.current) clearTimeout(programmaticResizeReleaseRef.current);
    if (frame) {
      // Browser CSS resize writes physical width/height inline. Clear those and
      // apply the new preset variables synchronously so a stale ResizeObserver
      // measurement cannot visibly snap Large back to the old user size.
      frame.style.removeProperty('width');
      frame.style.removeProperty('height');
      frame.style.setProperty('--learnitive-ai-width', `${nextDimensions.width}px`);
      frame.style.setProperty('--learnitive-ai-height', `${nextDimensions.height}px`);
    }
    setSize(next);
    setDimensions(nextDimensions);
    persistPreferredSize(next);
    persistPreferredDimensions(nextDimensions);
    if (typeof window !== 'undefined') {
      // Release the ResizeObserver guard after the synchronous preset layout.
      // No animation frames are chained here: maximize/compact is intentionally
      // an immediate state change with no grow/shrink interpolation.
      programmaticResizeReleaseRef.current = setTimeout(() => {
        programmaticResizeRef.current = false;
        programmaticResizeReleaseRef.current = undefined;
      }, 0);
    } else {
      programmaticResizeRef.current = false;
    }
  }, []);

  React.useEffect(() => {
    persistPreferredSize(size);
  }, [size]);

  React.useEffect(() => {
    const frame = frameRef.current;
    if (!frame || typeof ResizeObserver === 'undefined') return;
    const observer = new ResizeObserver((entries) => {
      if (typeof window === 'undefined' || window.innerWidth < 640 || programmaticResizeRef.current) return;
      const entry = entries[0];
      if (!entry) return;
      // ResizeObserver.contentRect is content-box sized while this shell uses
      // border-box CSS dimensions. Feeding contentRect back into width/height
      // causes a small shrink on every observation. Measure the rendered
      // border box instead so the first painted size remains stable.
      const rect = frame.getBoundingClientRect();
      const next = clampDimensions({
        width: Math.round(rect.width),
        height: Math.round(rect.height),
      });
      setDimensions((current) =>
        current.width === next.width && current.height === next.height ? current : next);
      if (persistTimerRef.current) clearTimeout(persistTimerRef.current);
      persistTimerRef.current = setTimeout(() => persistPreferredDimensions(next), 120);
    });
    observer.observe(frame);
    return () => {
      observer.disconnect();
      if (persistTimerRef.current) clearTimeout(persistTimerRef.current);
      if (programmaticResizeReleaseRef.current) {
        clearTimeout(programmaticResizeReleaseRef.current);
        programmaticResizeReleaseRef.current = undefined;
      }
      programmaticResizeRef.current = false;
    };
  }, []);

  React.useEffect(() => {
    const handleResize = () => {
      frameRef.current?.style.removeProperty('width');
      frameRef.current?.style.removeProperty('height');
      setDimensions((current) => clampDimensions(current));
    };
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  const showEvidence = Boolean(evidence) && size === 'large';
  const frameStyle = {
    '--learnitive-ai-width': `${dimensions.width}px`,
    '--learnitive-ai-height': `${dimensions.height}px`,
    transition: 'none',
    animation: 'none',
  } as React.CSSProperties;

  return (
    <section
      ref={frameRef}
      aria-label="Interactive AI"
      className={`pointer-events-auto fixed inset-0 z-[71] flex min-h-0 flex-col overflow-hidden border bg-background shadow-xl sm:h-[var(--learnitive-ai-height)] sm:max-h-[calc(100dvh-5rem)] sm:min-h-[560px] sm:min-w-[420px] sm:w-[var(--learnitive-ai-width)] sm:max-w-[calc(100vw-2rem)] sm:resize sm:transition-none ${desktopFrameClass(size)}`}
      data-ai-shell-size={size}
      data-testid="interactive-ai-overlay"
      style={frameStyle}
    >
      <div className="flex items-center gap-2 border-b px-3 py-2">
        <img src="/learnitive-ai-avatar-neck.webp" alt="" width={1254} height={1254} className="size-7 shrink-0 rounded-full object-cover" aria-hidden="true" />
        <span className="text-sm font-medium">Interactive AI</span>
        <div className="ml-auto flex items-center gap-1">
          {headerActions}
          <div className="hidden items-center sm:flex">
            <Button
              size="icon"
              variant={size === 'large' ? 'secondary' : 'ghost'}
              aria-label={size === 'large' ? 'Compact AI' : 'Large AI'}
              title={size === 'large' ? 'Compact AI' : 'Large AI'}
              onClick={() => selectSize(size === 'large' ? 'compact' : 'large')}
            >
              {size === 'large' ? <Minimize2 className="size-4" /> : <Maximize2 className="size-4" />}
            </Button>
          </div>
          <Button size="icon" variant="ghost" aria-label="Close AI" onClick={onClose}>
            <X className="size-4" />
          </Button>
        </div>
      </div>

      <div className="flex border-b px-2 pt-1" role="tablist" aria-label="Interactive AI workspace">
        {(papersEnabled ? (['assistant', 'papers'] as const) : (['assistant'] as const)).map((tab) => (
          <button
            key={tab}
            type="button"
            role="tab"
            aria-selected={activeTab === tab}
            className={`rounded-t-md px-3 py-2 text-xs font-medium ${activeTab === tab ? 'border border-b-background bg-background' : 'text-muted-foreground'}`}
            onClick={() => onTabChange(tab)}
          >
            {tab === 'assistant' ? 'Assistant' : 'Papers'}
          </button>
        ))}
      </div>

      <div className="flex min-h-0 flex-1">
        <div className="flex min-h-0 min-w-0 flex-1 flex-col">{children}</div>
        {showEvidence && (
          <aside className="hidden min-h-0 w-[min(340px,32vw)] shrink-0 overflow-y-auto border-l bg-muted/10 p-3 sm:block" data-testid="interactive-ai-evidence-pane">
            <div className="mb-2 text-xs font-semibold">Evidence</div>
            {evidence}
          </aside>
        )}
      </div>
    </section>
  );
}

export function AiMessageBubble({
  messageRole,
  children,
  className = '',
}: {
  messageRole: 'user' | 'assistant';
  children: React.ReactNode;
  className?: string;
}) {
  if (messageRole === 'assistant') {
    return (
      <div className={`mr-5 flex items-start gap-2 text-sm ${className}`} data-ai-message-role={messageRole}>
        <img
          src="/learnitive-ai-avatar-neck.webp"
          alt="Interactive AI"
          width={1254}
          height={1254}
          className="mt-0.5 size-7 shrink-0 rounded-full object-cover"
        />
        <div className="min-w-0 flex-1 rounded-lg bg-muted px-3 py-2">{children}</div>
      </div>
    );
  }
  return (
    <div
      className={`ml-8 rounded-lg bg-primary px-3 py-2 text-sm text-primary-foreground ${className}`}
      data-ai-message-role={messageRole}
    >
      {children}
    </div>
  );
}

export function AiComposer({
  value,
  onChange,
  onSend,
  onStop,
  busy,
  disabled,
  placeholder,
  ariaLabel,
  prefix,
}: {
  value: string;
  onChange: (value: string) => void;
  onSend: () => void;
  onStop?: () => void;
  busy: boolean;
  disabled?: boolean;
  placeholder: string;
  ariaLabel: string;
  prefix?: React.ReactNode;
}) {
  return (
    <div className="flex items-end gap-2">
      {prefix}
      <textarea
        className="max-h-28 min-h-10 min-w-0 flex-1 resize-y rounded-md border bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring"
        aria-label={ariaLabel}
        placeholder={placeholder}
        value={value}
        disabled={busy || disabled}
        onChange={(event) => onChange(event.currentTarget.value)}
        onKeyDown={(event) => {
          if (event.key === 'Enter' && !event.shiftKey) {
            event.preventDefault();
            if (!busy && !disabled && value.trim()) onSend();
          }
        }}
      />
      {busy && onStop ? (
        <Button size="icon" variant="outline" aria-label="Stop" onClick={onStop}><Square className="size-4" /></Button>
      ) : (
        <Button size="icon" aria-label="Send" disabled={!value.trim() || busy || disabled} onClick={onSend}><Send className="size-4" /></Button>
      )}
    </div>
  );
}
