import {
  ContentType,
  DashConfig,
  DashType,
  IframeConfig,
  PointItem,
  Position
} from './types';
import { PunktExt } from './forms';
import { useEffect, useRef, useState, useCallback } from 'react';
import axios from 'B/axios';
import { withDashToken } from './requests';

/* ===================== Typen ===================== */

type DashConfigResponse = {
  id: number;
  content_id: number;
  name: string;
  dashboard_name: string;
  dashboard_id: number;
  position: string | Position | null; 
  refresh_time: number;
  content_count: number;
  content_type: ContentType;
};

type IframeConfigResponse = {
  id: number;
  content_id: number;
  position: string | Position | null; 
  dashboard_id: number;
  name: string;
  url: string;
  refresh_time: number;
};

type DashboardInfo = {
  dashboard_id: number;
  dashboard_name: string;
};

type InitDashboardsResponse = {
  dashboard: DashConfigResponse[];
  iframe: IframeConfigResponse[];
  allBoards: DashboardInfo[];
};

type DashboardMeta = {
  id: number;
  name: string;
  is_public?: boolean | number | '0' | '1' | 'true' | 'false';
  canToggleShare?: boolean;
  cloned_from_id?: number | string | null;
  user_krzl: string;
};

export type DashboardListItem = {
  id: number;
  name: string;
  is_public: boolean;
  canToggleShare?: boolean;
  cloned_from_id: number | null;
  ownerKrzl?: string | null;
  user_krzl: string;
  folder_id?: number | null;
};

export type DashboardFolder = {
  id: number;
  name: string;
};

/* ===================== Utils ===================== */

function positionEncoder(configs: InitDashboardsResponse) {
  let z_index = 100;

  const dashboard = configs.dashboard.map((i) => {
    const position: Position = parsePosition(i.position);
    if (position.z_index > z_index) z_index = position.z_index;
    return { ...i, position };
  });

  const iframe = configs.iframe.map((i) => {
    const position: Position = parsePosition(i.position);
    if (position.z_index > z_index) z_index = position.z_index;
    return { ...i, position };
  });

  return { dashboard, iframe, z_index };
}

function parsePosition(input: unknown): Position {
  const fallback: Position = { x: 100, y: 100, z_index: 100, height: 0, width: 0 };

  if (!input) return fallback;

  if (typeof input === 'string') {
    try {
      const p = JSON.parse(input);
      if (p && typeof p === 'object' && ('x' in p || 'y' in p)) {
        return {
          x: Number(p.x ?? 100),
          y: Number(p.y ?? 100),
          z_index: Number(p.z_index ?? 100),
          height: Number(p.height ?? 0),
          width: Number(p.width ?? 0),
        };
      }
    } catch { /* ignore */ }
    return fallback;
  }

  if (typeof input === 'object') {
    const p = input as any;
    return {
      x: Number(p?.x ?? 100),
      y: Number(p?.y ?? 100),
      z_index: Number(p?.z_index ?? 100),
      height: Number(p?.height ?? 0),
      width: Number(p?.width ?? 0),
    };
  }

  return fallback;
}

type MetaItem = { id: number; name: string };
type FetchResult = { data: InitDashboardsResponse; usedId: number | null };

const isValidId = (v: unknown): v is number =>
  typeof v === 'number' && Number.isFinite(v) && v > 0;

const readStoredId = (): number | null => {
  const raw = localStorage.getItem('currentDashboardID');
  if (!raw) return null;
  const n = Number(raw);
  return isValidId(n) ? n : null;
};

const persistUsedId = (id: number | null) => {
  if (isValidId(id)) localStorage.setItem('currentDashboardID', String(id));
  else localStorage.removeItem('currentDashboardID');
};

export async function fetchDashboards(currentDashboardID: number | null): Promise<FetchResult> {
  const candidates: number[] = [];
  if (isValidId(currentDashboardID)) candidates.push(currentDashboardID);

  const storedId = readStoredId();
  if (isValidId(storedId) && !candidates.includes(storedId)) candidates.push(storedId!);

  // Helper zum Laden eines Dashboards
  const tryFetch = async (id: number): Promise<FetchResult | null> => {
    try {
      const res = await axios.get<InitDashboardsResponse>(`/dashboards/${id}`);
      persistUsedId(id);
      return { data: res.data, usedId: id };
    } catch (err: any) {
      const status = err?.response?.status;
      if (status === 404 || status === 410) {
        // falls diese ID aus localStorage kam: sofort entfernen
        if (readStoredId() === id) persistUsedId(null);
        return null;
      }
      throw err;
    }
  };


  // 1) Bestehende Kandidaten testen
  for (const id of candidates) {
    const hit = await tryFetch(id);
    if (hit) return hit;
  }

  // 2) Als Fallback /dashboard/meta ziehen und ersten Eintrag probieren
  let meta: MetaItem[] = [];
  try {
    const r = await axios.get<MetaItem[]>('/dashboard/meta');
    meta = Array.isArray(r.data?.items) ? r.data.items : [];

  } catch {
    // ignore – behandeln wir unten als "kein Meta"
  }

  const firstId = isValidId(meta?.[0]?.id) ? meta[0].id : null;
  if (isValidId(firstId) && !candidates.includes(firstId)) {
    const hit = await tryFetch(firstId);
    if (hit) return hit;
  }

  // 3) Nichts gefunden → leeres, aber gültiges Objekt zurückgeben und Storage aufräumen
  persistUsedId(null);
  return { data: { dashboard: [], iframe: [], allBoards: [] }, usedId: null };
}


const toBooleanPublic = (v: any): boolean => {
  if (v === true) return true;
  if (v === false) return false;
  if (v === 1 || v === '1') return true;
  if (v === 0 || v === '0') return false;
  if (typeof v === 'string') return v.toLowerCase() === 'true';
  return Boolean(v);
};

const normCloneId = (v: any): number | null => {
  if (v == null || v === '' || v === 0 || v === '0') return null;
  const n = Number(v);
  return Number.isFinite(n) && n > 0 ? n : null;
};

/* ===================== API ===================== */

async function dashItemsByGrp(id: number, cardID: null | number) {
  let url = `/api/dashboards/grps/${id.toString()}`;
  const haveDashID = cardID !== null;
  if (haveDashID) {
    url = `/api/dashboards/grps/${id.toString()}?dashid=${cardID.toString()}`;
  }
  try {
    const res = await axios.get(withDashToken(url));
    const data: PointItem[] = res.data;
    return data;
  } catch (e) {
    return [];
  }
}

/* ===================== Hooks ===================== */

export function useDashboard() {
  const [isLoading, setLoading] = useState(true);
  const [boards, setBoards] = useState<DashConfig[]>([]);
  const [iframeBoards, setIframeBoards] = useState<IframeConfig[]>([]);
  const [biggestZIndex, setBiggestZIndex] = useState<number>(100);

  // Enthält jetzt **Meta**: is_public, canToggleShare, cloned_from_id, folder_id
  const [currentDashboardNames, setCurrentDashboardNames] = useState<DashboardListItem[]>([]);
  const [folders, setFolders] = useState<DashboardFolder[]>([]);
  const [currentDashboardID, setCurrentDashboardID] = useState<number | null>(null);
  const lastLoadedIdRef = useRef<number | null>(null);

  const init = async () => {
    setLoading(true);
    try {
      const { data: fullData, usedId } = await fetchDashboards(currentDashboardID);
      const { dashboard, iframe, z_index } = positionEncoder(fullData);

      // Policy-Filter
      let allowedIds = new Set<number>();
      if (usedId != null) {
        try {
          const pol = await axios.get<Array<{ dashitem_id: number }>>(`/dashboard/${usedId}/items-with-policy`);
          allowedIds = new Set((pol.data || []).map(x => Number(x.dashitem_id)));
        } catch {
          allowedIds = new Set(dashboard.map(d => d.id));
        }
      }

      const filteredBoards = dashboard.filter(b => allowedIds.has(Number(b.id)));

      // Meta holen + normalisieren - dies enthält bereits die korrekte Reihenfolge!
      let meta: DashboardMeta[] = [];
      let metaUserSign: string | null = null;
      let fetchedFolders: DashboardFolder[] = [];
      try {
        const metaRes = await axios.get<{ items: DashboardMeta[]; count: number; current_user_krzl?: string | null; folders?: DashboardFolder[] }>('/dashboard/meta');
        metaUserSign = metaRes.data?.current_user_krzl ?? null;
        meta = Array.isArray(metaRes.data?.items) ? metaRes.data.items : [];
        fetchedFolders = Array.isArray(metaRes.data?.folders) ? metaRes.data.folders : [];
      } catch {
        meta = [];
        fetchedFolders = [];
      }

      if (!(window as any).user_sign && metaUserSign) {
        (window as any).user_sign = metaUserSign;
      }

      // Verwende Meta direkt als Quelle (mit korrekter Reihenfolge aus dashboard_order)
      const enriched: DashboardListItem[] = meta.map(m => {
        const is_public = toBooleanPublic(m?.is_public ?? (m as any)?.public ?? false);
        const cloned_from_id = normCloneId(m?.cloned_from_id ?? null);
        const rawFolderId = (m as any)?.folder_id;
        const folder_id = rawFolderId != null && rawFolderId !== 0 ? Number(rawFolderId) : null;
        return {
          id: Number(m.id),
          name: m.name,
          is_public,
          canToggleShare: m?.canToggleShare,
          cloned_from_id,
          ownerKrzl: (m as any)?.owner_krzl ?? (m as any)?.user_krzl ?? null,
          user_krzl: m.user_krzl,
          folder_id,
        };
      });

      if (currentDashboardID == null) {
        const storedId = localStorage.getItem('currentDashboardID');
        const fallbackId = storedId ? parseInt(storedId, 10) : (enriched[0]?.id ?? usedId ?? null);
        if (fallbackId != null) setCurrentDashboardID(fallbackId);
      }

      setBoards(filteredBoards);
      setIframeBoards(iframe);
      setBiggestZIndex(z_index);
      setCurrentDashboardNames(enriched);
      setFolders(fetchedFolders);

    } catch (e) {
      console.error("Fehler beim Laden der Dashboards:", e);
      setBoards([]);
      setIframeBoards([]);
      setCurrentDashboardNames([]);
    } finally {
      setLoading(false);
    }
  };

  // 1) Initial: ID herstellen (localStorage -> /dashboard/meta)
  useEffect(() => {
    if (currentDashboardID != null) return;
    const stored = localStorage.getItem('currentDashboardID');
    if (stored) {
      const v = parseInt(stored, 10);
      if (!Number.isNaN(v)) { setCurrentDashboardID(v); return; }
    }
    (async () => {
      try {
        const r = await axios.get<{ items: { id:number; name:string }[]; count:number }>('/dashboard/meta');
        const firstId = r.data?.items?.[0]?.id ?? null;
        if (firstId != null) setCurrentDashboardID(firstId);
      } catch {/* noop */}
    })();
  }, [currentDashboardID]);

  // 2) Laden NUR wenn sich die ID tatsächlich ändert
  useEffect(() => {
    if (currentDashboardID == null) return;
    if (lastLoadedIdRef.current === currentDashboardID) return;

    lastLoadedIdRef.current = currentDashboardID;
    localStorage.setItem('currentDashboardID', String(currentDashboardID));
    init();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentDashboardID]);

  return [
    { isLoading, boards, biggestZIndex, iframeBoards, currentDashboardNames, currentDashboardID, setCurrentDashboardID, folders },
    { setBiggestZIndex, init }
  ] as const;
}

export function useCardContainer(
  id: number,
  refreshTime: number,
  type: DashType,
  cardID: number | null
) {
  const [isLoading, setLoading] = useState(true);
  const [cardDetails, _setDetails] = useState<PointItem[]>([]);
  const [isFormOpen, setIsFormOpen] = useState(false);
  const [formHTML, setFormHTML] = useState<any>(null);
  const [isReady, setIsReady] = useState(true);
  const [currentItem, setCurrentItem] = useState<PointItem>();
  const [isInfoOpen, setInfoOpen] = useState(false);

  useEffect(() => {
    let intervalId: number | undefined;
    let cancelled = false;

    const setDetails = (details: PointItem[]) => {
      if (cancelled) return;
      setLoading(false);
      _setDetails(details);
    };

    const init = async () => {
      try {
        const items = await dashItemsByGrp(id, cardID);
        setDetails(items);
      } catch (e) {
        if (!cancelled) setLoading(false);
      }
    };

    // Sofort 1x laden
    init();

    // Poll nur wenn sinnvoll
    const minutes = Number(refreshTime);
    const intervalMs =
      Number.isFinite(minutes) && minutes >= 1 ? minutes * 60_000 : null;

    if (intervalMs) {
      intervalId = window.setInterval(() => {
        void init();
      }, intervalMs);
    }

    return () => {
      cancelled = true;
      if (intervalId) clearInterval(intervalId);
    };
  }, [cardID, id, refreshTime, type]);

  const appendForm = () => {
    document.getElementById('form')?.appendChild(formHTML);
    formHTML.pktChange = function() {
      const ok = formHTML.istrue();
      setIsReady(!ok);
    };
    formHTML.pktChange();
  };

  const removeItem = (id: any) => {
    // cardDetails kann je nach Listen-Typ ein Objekt statt eines Arrays sein
    // (z.B. Charts liefern { open, done }). Dann darf removeItem nicht crashen.
    _setDetails(prev => (Array.isArray(prev) ? prev : []).filter(item => item.long_id !== id));
  };

  const handleAttachForm = (longID: any) => {
    const htmlForm = PunktExt({ 'by.id': longID });
    if (htmlForm === null) {
      return;
    }
    setFormHTML(htmlForm);
    setIsFormOpen(true);
  };

  const handleCollectedEventClick = (cardItem: PointItem) => async () => {
    setCurrentItem(cardItem);

    const isFormDialog = type === 'CollectedEvents';
    if (isFormDialog === false) {
      return setInfoOpen(true);
    }

    const longID = cardItem.long_id;
    const response = await axios.get(withDashToken(`/api/dashboards/tickets/${longID}`));
    const isError = response.status !== 200;
    if (isError) {
      return;
    }

    return handleAttachForm(cardItem.long_id);
  };

  const handleCardClick = (cardItem: PointItem) => () => {
    setCurrentItem(cardItem);

    const isFormDialog = type === 'CollectedEvents';
    if (isFormDialog === false) {
      return setInfoOpen(true);
    }

    return handleAttachForm(cardItem.long_id);
  };

  const handleSave = () => {
    setIsReady(false);
    setTimeout(() => {
      try {
        document.write(formHTML.save());
        setTimeout(() => {
          window.location.reload();
        }, 1000);
      } catch (err) {
        alert(err);
        setIsReady(true);
      }
    }, 100);
  };

  return [
    {
      isLoading,
      cardDetails,
      isFormOpen,
      isReady,
      currentItem,
      isInfoOpen
    },
    {
      appendForm,
      handleCardClick,
      handleSave,
      setInfoOpen,
      setIsFormOpen,
      handleCollectedEventClick,
      removeItem
    }
  ] as const;
}

/* ===== Fenster/Drag/Resize Hooks (unverändert) ===== */

export function useWindow(
  greatestZIndex: number,
  setZIndex: (z: number) => void,
  setting: DashConfig | IframeConfig
) {
  const initPosition = setting.position;
  const [windowPosition, setWindowPosition] = useState({
    top: initPosition.y || 0,
    left: initPosition.x || 0,
    z_index: initPosition.z_index || 100,
  });
  const windowRef = useRef<any>(null);
  const settingRef = useRef(setting);

  // Aktuelles setting in Ref speichern für Event-Handler
  useEffect(() => {
    settingRef.current = setting;
  }, [setting]);

  const windowZIndexRef = useRef(100);
  const dragStateRef = useRef({ pos1: 0, pos2: 0, pos3: 0, pos4: 0 });

  const [isSettingDialogOpen, setIsSettingDialogOpen] = useState(false);
  const openSettingDialog = useCallback(() => setIsSettingDialogOpen(true), []);
  const closeSettingDialog = useCallback(() => setIsSettingDialogOpen(false), []);

  const onDrag = useCallback((e: MouseEvent) => {
    e.stopPropagation();
    e.preventDefault();

    const state = dragStateRef.current;
    state.pos1 = state.pos3 - e.clientX;
    state.pos2 = state.pos4 - e.clientY;
    state.pos3 = e.clientX;
    state.pos4 = e.clientY;

    const top = windowRef.current.offsetTop - state.pos2;
    const left = windowRef.current.offsetLeft - state.pos1;

    // Direkte DOM-Manipulation statt State-Update (verhindert Re-Renders)
    windowRef.current.style.top = top + 'px';
    windowRef.current.style.left = left + 'px';
  }, []);

  const docEventCleanup = useCallback(() => {
    console.log('[docEventCleanup] Called - removing listeners');
    const top = Number(windowRef.current.style.top.slice(0, -2));
    const left = Number(windowRef.current.style.left.slice(0, -2));

    // State nur am Ende updaten
    setWindowPosition({ top, left, z_index: windowZIndexRef.current });

    // Aktuelle width/height aus dem DOM lesen
    const currentWidth = windowRef.current.querySelector('.content-area-test')?.offsetWidth || settingRef.current.position.width;
    const currentHeight = windowRef.current.querySelector('.content-area-test')?.offsetHeight || settingRef.current.position.height;

    console.log('[docEventCleanup] Calling storePosition', { top, left, width: currentWidth, height: currentHeight });
    storePosition(settingRef.current.id, {
      top,
      left,
      width: currentWidth,
      height: currentHeight,
      z_index: windowZIndexRef.current,
    });
    document.removeEventListener('mousemove', onDrag);
  }, [onDrag]);

  const onResizeStop = useCallback(({ width, height }: { width: number; height: number }) => {
    const top = Number(windowRef.current.style.top.slice(0, -2));
    const left = Number(windowRef.current.style.left.slice(0, -2));
    storePosition(settingRef.current.id, {
      top,
      left,
      width,
      height,
    });
  }, []);

  const onMouseDown = useCallback((e: MouseEvent) => {
    e.stopPropagation();
    e.preventDefault();

    // KRITISCH: Alte Listener entfernen bevor neue hinzugefügt werden
    // (verhindert mehrfache Registrierung bei Re-Renders)
    document.removeEventListener('mousemove', onDrag);
    document.removeEventListener('mouseup', docEventCleanup);

    const state = dragStateRef.current;
    state.pos3 = e.clientX;
    state.pos4 = e.clientY;

    const z_index = greatestZIndex + 1;
    setZIndex(z_index);
    windowZIndexRef.current = z_index;

    // Z-Index sofort auf DOM anwenden (nicht auf State)
    windowRef.current.style.zIndex = z_index;

    document.addEventListener('mouseup', docEventCleanup, { once: true });
    document.addEventListener('mousemove', onDrag);
  }, [greatestZIndex, setZIndex, docEventCleanup, onDrag]);

  return [
    { windowRef, windowPosition, isSettingDialogOpen },
    {
      onMouseDown,
      onResizeStop,
      openSettingDialog,
      closeSettingDialog,
    },
  ] as const;
}

// Debounce-Cache für storePosition
const pendingStores = new Map<number, NodeJS.Timeout>();

function storePosition(dashID: number, rect: any) {
  console.log('[storePosition] Called for dashID:', dashID, 'rect:', rect);

  // Vorherige pending requests für dieses Dashboard abbrechen
  const existing = pendingStores.get(dashID);
  if (existing) {
    console.log('[storePosition] Clearing existing timeout for dashID:', dashID);
    clearTimeout(existing);
  }

  // Neuen debounced call setzen (100ms Verzögerung)
  const timeoutId = setTimeout(() => {
    console.log('[storePosition] Executing axios.post for dashID:', dashID);
    let x = rect.left;
    let y = rect.top;
    if (x < 0) x = 0;
    if (y < 0) y = 0;

    const pos = {
      x,
      y,
      z_index: rect.z_index,
      height: rect.height,
      width: rect.width,
    };
    const payload = { dash_id: dashID, dash_pos: pos };

    axios.post('/dashboardpos/save', payload);
    pendingStores.delete(dashID);
  }, 100);

  pendingStores.set(dashID, timeoutId);
}

export function useContentArea(initWidth: number, initHeight: number, onResizeStop: ({ width, height }: { width: number; height: number }) => void) {
  const contentAreaRef = useRef<any>(null);
  const [width, setWidth] = useState(initWidth);
  const [height, setHeight] = useState(initHeight);

  const lastPosRef = useRef({ x: 0, y: 0 });
  const currentSizeRef = useRef({ width: initWidth, height: initHeight });
  const onResizeStopRef = useRef(onResizeStop);

  // Callback-Ref aktuell halten
  useEffect(() => {
    onResizeStopRef.current = onResizeStop;
  }, [onResizeStop]);

  // Initial size setzen
  useEffect(() => {
    if (contentAreaRef.current) {
      contentAreaRef.current.style.width = initWidth + 'px';
      contentAreaRef.current.style.height = initHeight + 'px';
      currentSizeRef.current = { width: initWidth, height: initHeight };
    }
  }, [initWidth, initHeight]);

  const resize = useCallback((e: MouseEvent) => {
    e.stopPropagation();
    e.preventDefault();

    const deltaX = e.clientX - lastPosRef.current.x;
    const deltaY = e.clientY - lastPosRef.current.y;

    lastPosRef.current = { x: e.clientX, y: e.clientY };

    // Direkte DOM-Manipulation statt State-Update
    const newWidth = currentSizeRef.current.width + deltaX;
    const newHeight = currentSizeRef.current.height + deltaY;

    currentSizeRef.current = { width: newWidth, height: newHeight };

    if (contentAreaRef.current) {
      contentAreaRef.current.style.width = newWidth + 'px';
      contentAreaRef.current.style.height = newHeight + 'px';
    }
  }, []);

  const cleanupResize = useCallback(() => {
    document.removeEventListener('mousemove', resize);

    // State nur am Ende updaten
    const finalWidth = currentSizeRef.current.width;
    const finalHeight = currentSizeRef.current.height;

    setWidth(finalWidth);
    setHeight(finalHeight);

    // Callback mit finalen Werten (aus Ref, nicht aus Closure)
    onResizeStopRef.current({ width: finalWidth, height: finalHeight });
  }, [resize]);

  const onGrabAreaClick = useCallback((e: MouseEvent) => {
    e.stopPropagation();
    e.preventDefault();

    // KRITISCH: Alte Listener entfernen bevor neue hinzugefügt werden
    document.removeEventListener('mousemove', resize);
    document.removeEventListener('mouseup', cleanupResize);

    lastPosRef.current = { x: e.clientX, y: e.clientY };

    document.addEventListener('mousemove', resize);
    document.addEventListener('mouseup', cleanupResize, { once: true });
  }, [resize, cleanupResize]);

  return [{ contentAreaRef, width, height }, { onGrabAreaClick }] as const;
}
