import React, { useEffect, useMemo, useState, useRef } from 'react';
import { Card, Table, Spinner, Alert, Pagination, Form } from 'react-bootstrap';

import API from '../../services/API';
import MeshCoreService from '../../services/MeshCoreService';
import VerticalSplit from '../../components/VerticalSplit';
import { useKeyboardListNavigation } from '../../hooks/useKeyboardListNavigation';

const meshCoreService = new MeshCoreService(API);

export const MeshCoreNodesView: React.FC = () => {
  const [page, setPage] = useState<number>(1);
  const [visibleRows, setVisibleRows] = useState<number>(50);
  const [fetchLimit] = useState<number>(50);
  const containerRef = useRef<HTMLDivElement | null>(null);
  const tableWrapperRef = useRef<HTMLDivElement | null>(null);
  const paginationRef = useRef<HTMLDivElement | null>(null);
  const [pager, setPager] = useState<any | null>(null);
  const [allNodes, setAllNodes] = useState<any[] | null>(null);
  const [allLoading, setAllLoading] = useState<boolean>(false);
  const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
  const [search, setSearch] = useState<string>('');
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);
  const [selectedNode, setSelectedNode] = useState<any | null>(null);

  const renderHighlighted = (text: string, query: string | null) => {
    if (!query) return <>{text}</>;
    const q = query.trim().toLowerCase();
    if (!q) return <>{text}</>;
    const txt = text || '';
    const lower = txt.toLowerCase();
    if (!lower.includes(q)) return <>{text}</>;
    const parts: React.ReactNode[] = [];
    let start = 0;
    let idx = 0;
    while ((idx = lower.indexOf(q, start)) !== -1) {
      if (idx > start) parts.push(txt.slice(start, idx));
      parts.push(<u key={start + idx}>{txt.slice(idx, idx + q.length)}</u>);
      start = idx + q.length;
    }
    if (start < txt.length) parts.push(txt.slice(start));
    return <>{parts}</>;
  };

  const formatPubKey = (k: string | undefined) => {
    if (!k) return '';
    const s = String(k);
    if (s.length <= 16) return s;
    return `${s.slice(0, 8)}…${s.slice(-8)}`;
  };

  useEffect(() => {
    let isMounted = true;
    const load = async () => {
      setIsLoading(true);
      setError(null);
      try {
        // Map the visual `page` (which is based on `visibleRows`) to the server page
        const serverPage = Math.max(1, Math.ceil((page * visibleRows) / fetchLimit));
        const p = await meshCoreService.fetchNodes(serverPage, fetchLimit);
        console.debug('MeshCoreNodesView: initial pager', { page, visibleRows, fetchLimit, serverPage, pager: p });
        if (!isMounted) return;
        // Sort items alphabetically by name
        p.items.sort((a: any, b: any) => (a.name || '').localeCompare(b.name || ''));
        setPager(p);
        if (!selectedNode && p.items.length > 0) {
          setSelectedNode(p.items[0]);
          setSelectedIndex(0);
        }
      } catch (err) {
        if (!isMounted) return;
        setError(err instanceof Error ? err.message : 'Failed to load nodes');
      } finally {
        if (isMounted) setIsLoading(false);
      }
    };

    void load();
    return () => { isMounted = false; };
  }, [page, fetchLimit, visibleRows]);

  useEffect(() => {
    if (!pager || allNodes !== null || allLoading) return;
    let isMounted = true;
    const loadAllSerial = async () => {
      setAllLoading(true);
      try {
        const total = pager.total || 0;
        if (total === 0) {
          if (isMounted) setAllNodes([]);
          return;
        }

        // Use server-provided limit when available to compute pages
        const pageSize = Math.max(1, pager.limit || fetchLimit || 50);
        const pages = Math.max(1, Math.ceil(total / pageSize));
        console.debug('MeshCoreNodesView: background serial load start', { total, pageSize, pages });

        // Start with first page items returned by initial fetch
        const items: any[] = Array.isArray(pager.items) ? pager.items.slice() : [];

        for (let p = 2; p <= pages; p++) {
          console.debug('MeshCoreNodesView: fetching page', { p, pageSize });
          try {
            // serial await ensures requests are not concurrent
            // eslint-disable-next-line no-await-in-loop
            const res = await meshCoreService.fetchNodes(p, pageSize);
            if (!isMounted) return;
            console.debug('MeshCoreNodesView: fetched page result', { p, items: res.items ? res.items.length : 0 });
            if (Array.isArray(res.items)) items.push(...res.items);
          } catch (err) {
            console.debug('MeshCoreNodesView: fetch page error', { p, error: err });
            // continue to next page on error
          }
        }

        if (!isMounted) return;
        // dedupe by id and sort alphabetically by name
        const byId: Record<string, any> = {};
        for (const it of items) {
          if (!it) continue;
          byId[String(it.id)] = it;
        }
        const unique = Object.values(byId);
        unique.sort((a: any, b: any) => (a.name || '').localeCompare(b.name || ''));
        console.debug('MeshCoreNodesView: finished serial background load, total unique items', unique.length);
        if (isMounted) setAllNodes(unique);
      } catch (err) {
        console.debug('MeshCoreNodesView: background loadAll fatal error', err);
      } finally {
        if (isMounted) setAllLoading(false);
      }
    };

    void loadAllSerial();
    return () => { isMounted = false; };
  }, [pager, fetchLimit]);

  useEffect(() => {
    // Table sizing by visual viewport removed — allow vertical overflow again.
  }, [pager]);

  const totalPages = useMemo(() => {
    if (allNodes) return Math.max(1, Math.ceil(allNodes.length / visibleRows));
    if (!pager) return 0;
    return Math.max(1, Math.ceil(pager.total / pager.limit));
  }, [pager, allNodes, visibleRows]);

  const filteredItems = useMemo(() => {
    const nodes = allNodes ?? (pager ? pager.items : []);
    if (!search) return nodes;
    const q = search.trim().toLowerCase();

    return (nodes || []).filter((n: any) => {
      const name = (n.name || '').toLowerCase();
      const idOrPrefix = (n.prefix || n.id || '').toString().toLowerCase();
      const pub = (n.public_key || '').toString().toLowerCase();

      const matchName = name.includes(q);
      const matchId = idOrPrefix.includes(q);
      const matchPub = pub.startsWith(q); // only match prefix of public key

      return matchName || matchId || matchPub;
    });
  }, [allNodes, pager, search]);

  const currentItems = useMemo(() => {
    if (allNodes) {
      return filteredItems.slice((page - 1) * visibleRows, page * visibleRows);
    }

    // pager-mode: compute slice relative to the last fetched server page
    if (!pager) return [];
    const items = Array.isArray(pager.items) ? pager.items : [];
    const serverPage = pager.page || 1;
    const serverLimit = pager.limit || fetchLimit;

    // global index range for this visual page
    const globalStart = (page - 1) * visibleRows;
    const serverStart = (serverPage - 1) * serverLimit;
    const offsetWithinServer = globalStart - serverStart;

    if (offsetWithinServer >= items.length || offsetWithinServer + visibleRows <= 0) {
      return [];
    }

    const start = Math.max(0, offsetWithinServer);
    return items.slice(start, start + visibleRows);
  }, [allNodes, filteredItems, pager, page, visibleRows, fetchLimit]);

  // keep selectedIndex in sync with selectedNode when page/items change
  useEffect(() => {
    if (!selectedNode) {
      setSelectedIndex(null);
      return;
    }
    const idx = currentItems.findIndex((it: any) => String(it.id) === String(selectedNode.id));
    setSelectedIndex(idx >= 0 ? idx : null);
  }, [selectedNode, currentItems]);

  // wire page navigation into the keyboard hook (ArrowLeft/ArrowRight)
  const onPrevPage = React.useCallback(() => setPage((p) => Math.max(1, p - 1)), [setPage]);
  const onNextPage = React.useCallback(() => setPage((p) => Math.min(totalPages, p + 1)), [setPage, totalPages]);

  const { /* showShortcuts, setShowShortcuts, */ } = useKeyboardListNavigation({
    itemCount: currentItems.length,
    selectedIndex,
    onSelectIndex: (i: number | null) => {
      setSelectedIndex(i);
      if (i === null) {
        setSelectedNode(null);
      } else {
        const it = currentItems[i];
        if (it) setSelectedNode(it);
      }
    },
    scrollContainerRef: tableWrapperRef,
    rowSelector: '[data-nav-item="true"]',
    enabled: true,
    onPrevPage,
    onNextPage,
  });

  return (
    <VerticalSplit
      ratio="3:1"
      left={(
        <Card className="data-table-card h-100 d-flex flex-column">
          <Card.Header className="data-table-header d-flex align-items-center justify-content-between">
            <div>
              MeshCore Nodes
              <small className="ms-2">({filteredItems.length})</small>
            </div>
            <div className="d-flex align-items-center gap-2" style={{ minWidth: 260 }}>
              <Form.Control
                placeholder={allNodes ? 'Search by name or hash...' : 'Search will be enabled after all nodes are loaded'}
                value={search}
                onChange={(e) => { setSearch(e.target.value); setPage(1); }}
                disabled={!allNodes}
                size="sm"
              />
              {!allNodes && allLoading && (
                <div className="text-secondary d-flex align-items-center" style={{ fontSize: 12 }}>
                  <Spinner animation="border" size="sm" />
                </div>
              )}
            </div>
          </Card.Header>
          <Card.Body className="data-table-body d-flex flex-column p-0">
            {isLoading && (
              <div className="d-flex align-items-center gap-2 p-3 text-secondary">
                <Spinner animation="border" size="sm" /> Loading nodes...
              </div>
            )}

            {error && (
              <Alert variant="danger" className="m-3">{error}</Alert>
            )}

            {!isLoading && !error && (pager || allNodes) && (
              <div ref={containerRef} className="p-2 d-flex flex-column h-100">
                {/* search moved to header */}
                <div ref={tableWrapperRef} className="table-responsive mb-2" style={{ overflowY: 'auto' }}>
                  <Table hover size="sm" className="mb-0">
                    <thead>
                      <tr>
                        <th>Name</th>
                        <th>Node ID</th>
                        <th>Type</th>
                        <th>Public key</th>
                        <th>Last seen</th>
                      </tr>
                    </thead>
                    <tbody>
                      {currentItems.map((n: any, idx: number) => (
                        <tr
                          key={n.id}
                          data-nav-item="true"
                          onClick={() => { setSelectedNode(n); setSelectedIndex(idx); }}
                          style={{ cursor: 'pointer' }}
                          className={selectedIndex === idx ? 'table-active' : ''}
                          aria-selected={selectedIndex === idx}
                        >
                          <td>{renderHighlighted(n.name || '(unknown)', search)}</td>
                          <td><span className="meshcore-hash">{renderHighlighted(String(n.prefix || n.id || ''), search)}</span></td>
                          <td>{n.type}</td>
                          <td><span className="meshcore-hash">{renderHighlighted(formatPubKey(n.public_key), search)}</span></td>
                          <td>{n.last_heard_at ? new Date(n.last_heard_at).toLocaleString() : '-'}</td>
                        </tr>
                      ))}
                    </tbody>
                  </Table>
                </div>

                <div ref={paginationRef} className="p-2 mt-auto">
                  <Pagination className="mb-0">
                    <Pagination.First onClick={() => setPage(1)} disabled={page === 1} />
                    <Pagination.Prev onClick={() => setPage(Math.max(1, page - 1))} disabled={page === 1} />

                    {(() => {
                      const items: (number | string)[] = [];
                      if (totalPages <= 7) {
                        for (let i = 1; i <= totalPages; i++) items.push(i);
                      } else {
                        items.push(1);
                        const left = Math.max(2, page - 2);
                        const right = Math.min(totalPages - 1, page + 2);
                        if (left > 2) items.push('...');
                        for (let i = left; i <= right; i++) items.push(i);
                        if (right < totalPages - 1) items.push('...');
                        items.push(totalPages);
                      }

                      return items.map((p, idx) =>
                        typeof p === 'string' ? (
                          <Pagination.Ellipsis key={`ell-${idx}`} disabled />
                        ) : (
                          <Pagination.Item
                            key={p}
                            active={p === page}
                            onClick={() => setPage(p as number)}
                          >
                            {p}
                          </Pagination.Item>
                        )
                      );
                    })()}

                    <Pagination.Next onClick={() => setPage(Math.min(totalPages, page + 1))} disabled={page === totalPages} />
                    <Pagination.Last onClick={() => setPage(totalPages)} disabled={page === totalPages} />
                  </Pagination>
                </div>
              </div>
            )}
          </Card.Body>
        </Card>
      )}
      right={(
        <Card className="data-table-card h-100 d-flex flex-column">
          <Card.Header className="data-table-header">Node Details</Card.Header>
          <Card.Body className="data-table-body d-flex flex-column">
            {!selectedNode && (
              <div className="text-secondary">Select a node to view details.</div>
            )}

            {selectedNode && (
              <div>
                <h5>{renderHighlighted(selectedNode.name || '(unknown)', search)}</h5>
                <div><strong>ID:</strong> <span className="meshcore-hash">{renderHighlighted(String(selectedNode.id || ''), search)}</span></div>
                <div><strong>Prefix:</strong> <span className="meshcore-hash">{renderHighlighted(String(selectedNode.prefix || ''), search)}</span></div>
                <div><strong>Type:</strong> {selectedNode.type}</div>
                <div><strong>Public key:</strong> <span className="meshcore-hash">{selectedNode.public_key}</span></div>
                <div><strong>First seen:</strong> {selectedNode.first_heard_at ? new Date(selectedNode.first_heard_at).toLocaleString() : '-'}</div>
                <div><strong>Last seen:</strong> {selectedNode.last_heard_at ? new Date(selectedNode.last_heard_at).toLocaleString() : '-'}</div>
                {selectedNode.last_latitude !== undefined && selectedNode.last_longitude !== undefined && (
                  <div><strong>Location:</strong> {selectedNode.last_latitude}, {selectedNode.last_longitude}</div>
                )}
              </div>
            )}
          </Card.Body>
        </Card>
      )}
    />
  );
};

export default MeshCoreNodesView;
