'use client';

/**
 * FloatingDoctorCoach — the global floating Doctor Coach widget.
 *
 * Key differences from the patient FloatingCoach:
 *   - No settings API (doctors have no per-user coach settings). Position
 *     is stored only in localStorage under a doctor-specific key so it
 *     doesn't collide with a doctor who also has a patient account on the
 *     same machine.
 *   - No notification-permission prompt (doctors don't opt into push
 *     reminders through the coach).
 *   - Always enabled; there's no master kill switch — the DoctorShell
 *     mounts this only when the user is authenticated.
 *   - Hidden on /login so it doesn't fight the auth UI.
 */

import dynamic from 'next/dynamic';
import { useCallback, useEffect, useRef, useState } from 'react';
import { usePathname } from 'next/navigation';

import type { DoctorCoachAvatarPosition } from '@/lib/doctor-coach-types';
import { SehatAvatar } from './sehat-avatar';

// Load the heavy panel lazily so the floating button's first-paint is fast.
const DoctorCoachPanel = dynamic(
  () => import('./coach-panel').then((m) => m.DoctorCoachPanel),
  { ssr: false },
);

// Separate localStorage key from the patient coach key so a user who is
// both a doctor and a patient on the same device gets independent positions.
const STORAGE_KEY = 'doctor-coach-position-v1';
const DRAG_THRESHOLD_PX = 6;
const LONG_PRESS_MS = 350;
const DEFAULT_POSITION: DoctorCoachAvatarPosition = { xPct: 92, yPct: 88 };
const SAFE_X_RANGE = { min: 6, max: 94 };
const SAFE_Y_RANGE = { min: 8, max: 92 };

// Only hide on /login — unlike the patient portal there's no /signup.
const HIDDEN_PATHS = new Set(['/login']);

interface StoredPosition extends DoctorCoachAvatarPosition {
  ts: number;
}

export function FloatingDoctorCoach() {
  const pathname = usePathname() ?? '/';
  const hidden = HIDDEN_PATHS.has(pathname);

  const [open, setOpen] = useState(false);
  const [position, setPosition] = useState<DoctorCoachAvatarPosition>(
    () => readStored() ?? DEFAULT_POSITION,
  );
  const [dragging, setDragging] = useState(false);

  const dragStateRef = useRef<{
    startX: number;
    startY: number;
    moved: boolean;
    longPressTimer: ReturnType<typeof setTimeout> | null;
    pointerId: number;
  } | null>(null);

  // Persist position to localStorage on every change.
  useEffect(() => {
    writeStored({ ...position, ts: Date.now() });
  }, [position]);

  const handlePointerDown = useCallback(
    (e: React.PointerEvent<HTMLButtonElement>) => {
      if (e.button !== 0) return;
      const target = e.currentTarget;
      target.setPointerCapture(e.pointerId);
      const startX = e.clientX;
      const startY = e.clientY;

      dragStateRef.current = {
        startX,
        startY,
        moved: false,
        pointerId: e.pointerId,
        longPressTimer: setTimeout(() => {
          if (dragStateRef.current && !dragStateRef.current.moved) {
            setDragging(true);
          }
        }, LONG_PRESS_MS),
      };
    },
    [],
  );

  const handlePointerMove = useCallback(
    (e: React.PointerEvent<HTMLButtonElement>) => {
      const state = dragStateRef.current;
      if (!state) return;
      const dx = e.clientX - state.startX;
      const dy = e.clientY - state.startY;
      if (!state.moved && Math.hypot(dx, dy) > DRAG_THRESHOLD_PX) {
        state.moved = true;
        setDragging(true);
        if (state.longPressTimer) clearTimeout(state.longPressTimer);
      }
      if (state.moved) {
        const { innerWidth: vw, innerHeight: vh } = window;
        const x = clamp(e.clientX, 32, vw - 32);
        const y = clamp(e.clientY, 32, vh - 32);
        setPosition({
          xPct: roundPct((x / vw) * 100),
          yPct: roundPct((y / vh) * 100),
        });
      }
    },
    [],
  );

  const handlePointerUp = useCallback(
    (e: React.PointerEvent<HTMLButtonElement>) => {
      const state = dragStateRef.current;
      if (!state) return;
      const target = e.currentTarget;
      if (target.hasPointerCapture(e.pointerId)) {
        target.releasePointerCapture(e.pointerId);
      }
      if (state.longPressTimer) clearTimeout(state.longPressTimer);
      const wasDrag = state.moved;
      dragStateRef.current = null;
      setDragging(false);

      if (wasDrag) {
        const snapped = normalizePosition({
          xPct: position.xPct < 50 ? SAFE_X_RANGE.min : SAFE_X_RANGE.max,
          yPct: position.yPct,
        });
        setPosition(snapped);
      } else {
        setOpen((v) => !v);
      }
    },
    [position],
  );

  if (hidden) return null;

  return (
    <>
      <button
        type="button"
        onPointerDown={handlePointerDown}
        onPointerMove={handlePointerMove}
        onPointerUp={handlePointerUp}
        onPointerCancel={handlePointerUp}
        aria-label="Open Doctor Coach"
        className={[
          'group fixed z-[60] -translate-x-1/2 -translate-y-1/2',
          'rounded-full bg-transparent transition-transform',
          'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-brand-400',
          dragging ? 'cursor-grabbing scale-95' : 'cursor-grab hover:scale-105',
        ].join(' ')}
        style={{
          left: `${position.xPct}%`,
          top: `${position.yPct}%`,
          touchAction: 'none',
        }}
      >
        <span className="block p-2">
          <SehatAvatar
            state={open ? 'speaking' : 'idle'}
            size="md"
          />
        </span>
        <span
          className="pointer-events-none absolute -bottom-1 left-1/2 h-1 w-6 -translate-x-1/2 rounded-full bg-ink-3/30 opacity-0 group-hover:opacity-100 transition-opacity"
          aria-hidden
        />
      </button>

      {open ? (
        <DoctorCoachPanel onClose={() => setOpen(false)} />
      ) : null}
    </>
  );
}

// ----------------------------------------------------------------------
// helpers
// ----------------------------------------------------------------------

function clamp(v: number, lo: number, hi: number): number {
  return Math.max(lo, Math.min(hi, v));
}

function normalizePosition(
  p: Pick<DoctorCoachAvatarPosition, 'xPct' | 'yPct'>,
): DoctorCoachAvatarPosition {
  const xPct = Number.isFinite(p.xPct) ? p.xPct : DEFAULT_POSITION.xPct;
  const yPct = Number.isFinite(p.yPct) ? p.yPct : DEFAULT_POSITION.yPct;
  return {
    xPct: clamp(xPct, SAFE_X_RANGE.min, SAFE_X_RANGE.max),
    yPct: clamp(yPct, SAFE_Y_RANGE.min, SAFE_Y_RANGE.max),
  };
}

function roundPct(v: number): number {
  return Math.round(v * 10) / 10;
}

function readStored(): StoredPosition | null {
  if (typeof window === 'undefined') return null;
  try {
    const raw = window.localStorage.getItem(STORAGE_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw) as Partial<StoredPosition>;
    if (
      typeof parsed.xPct === 'number' &&
      typeof parsed.yPct === 'number' &&
      typeof parsed.ts === 'number'
    ) {
      return {
        ...normalizePosition({ xPct: parsed.xPct, yPct: parsed.yPct }),
        ts: parsed.ts,
      };
    }
  } catch {
    /* corrupt entry — ignore */
  }
  return null;
}

function writeStored(p: StoredPosition): void {
  if (typeof window === 'undefined') return;
  try {
    window.localStorage.setItem(
      STORAGE_KEY,
      JSON.stringify({ ...normalizePosition(p), ts: p.ts }),
    );
  } catch {
    /* quota — ignore */
  }
}
