import React, { useCallback, useEffect, useRef } from 'react';
import { useEditor } from '@craftjs/core';
import { clickableProps } from '../../utils/a11y';
import { deriveVirtualRows, VIRTUAL_CHILD_PROPS } from './layers-virtual-rows';
import { useLayerFocus } from './LayerFocusContext';
import { findUnreachableNodeIds } from '../../utils/orphan-repair';

/**
 * Per-type icon lookup keyed by the component's `craft.displayName` (the
 * same human label `LayerNode` already resolves and shows as the row text).
 * Mirrors the icon choices in `BlocksPanel.tsx` where a block exists for the
 * type; the remaining entries (Navbar, Container, Section, Columns,
 * Background Section, Map, Video, Image, form fields, and the richer
 * section blocks not draggable from the Basic/Sections categories) get a
 * sensible FA4 icon of their own. Unknown/future types fall back to
 * `DEFAULT_ICON`.
 */
const TYPE_ICONS: Record<string, string> = {
  // Layout
  Container: 'fa-square-o',
  Section: 'fa-window-maximize',
  Columns: 'fa-th-large',
  'Background Section': 'fa-picture-o',
  // Basic
  Heading: 'fa-header',
  Text: 'fa-paragraph',
  Button: 'fa-square',
  Logo: 'fa-bookmark',
  Menu: 'fa-bars',
  Navbar: 'fa-bars',
  Footer: 'fa-window-minimize',
  Divider: 'fa-minus',
  Spacer: 'fa-arrows-v',
  Icon: 'fa-star',
  'Star Rating': 'fa-star-half-o',
  'Social Links': 'fa-share-alt',
  'Search Bar': 'fa-search',
  HTML: 'fa-code',
  // Media
  Image: 'fa-image',
  Video: 'fa-play-circle',
  Map: 'fa-map-marker',
  // Sections
  Hero: 'fa-star-o',
  'Features Grid': 'fa-th-large',
  'CTA Section': 'fa-bullhorn',
  'Call to Action': 'fa-bullhorn',
  Countdown: 'fa-clock-o',
  Testimonials: 'fa-quote-left',
  'Content Slider': 'fa-sliders',
  'Number Counter': 'fa-sort-numeric-asc',
  Accordion: 'fa-list',
  Tabs: 'fa-folder-o',
  'Pricing Table': 'fa-usd',
  Gallery: 'fa-th',
  // Forms
  Form: 'fa-wpforms',
  Input: 'fa-i-cursor',
  Textarea: 'fa-align-left',
  'Submit Button': 'fa-paper-plane',
  'Contact Form': 'fa-envelope',
  'Subscribe Form': 'fa-paper-plane',
};

const DEFAULT_ICON = 'fa-cube';
const ROOT_ICON = 'fa-desktop';

/**
 * A display-only row for one item of a composite's array prop (a Features
 * Grid feature, a Tabs tab, ...). Not a Craft node: it can't be dragged or
 * deleted. Clicking it selects the PARENT and asks the array editor to
 * scroll that item's card into view.
 */
const VirtualRowNode: React.FC<{
  parentId: string;
  prop: string;
  index: number;
  label: string;
  depth: number;
  onActivate: () => void;
}> = ({ index, label, depth, onActivate }) => (
  <div
    {...clickableProps(onActivate)}
    className="layer-virtual-row"
    title={label}
    style={{
      display: 'flex',
      alignItems: 'center',
      padding: '4px 8px',
      paddingLeft: `${8 + depth * 16}px`,
      fontSize: 11,
      color: 'var(--color-text-dim)',
      cursor: 'pointer',
      whiteSpace: 'nowrap',
      overflow: 'hidden',
      textOverflow: 'ellipsis',
      userSelect: 'none',
    }}
  >
    <span style={{ marginRight: 6, fontSize: 8, flexShrink: 0 }} aria-hidden="true">&#9642;</span>
    <span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>{label}</span>
    <span style={{ marginLeft: 'auto', paddingLeft: 6, opacity: 0.6, flexShrink: 0 }}>{index + 1}</span>
  </div>
);

interface LayerNodeProps {
  nodeId: string;
  depth: number;
}

const LayerNode: React.FC<LayerNodeProps> = ({ nodeId, depth }) => {
  const { requestFocus } = useLayerFocus();
  const { node, selectedId, actions, query } = useEditor((state) => {
    const n = state.nodes[nodeId];
    const selectedIds = state.events.selected;
    const selId = selectedIds ? Array.from(selectedIds)[0] : null;
    return {
      node: n,
      selectedId: selId,
    };
  });

  const isSelected = selectedId === nodeId;

  const handleActivate = useCallback(() => {
    actions.selectNode(nodeId);
  }, [actions, nodeId]);

  // Layers-panel <-> canvas hover sync (Item 12): tags the target node's own
  // DOM element (found via `query`) with `data-layer-hovered` directly.
  // `RenderNode.tsx` mirrors Craft's *own* `hovered` node event onto
  // `data-craft-hovered` for the (separate, mouse-driven) canvas hover
  // outline -- editor.css's rule matches both attributes. This panel can't
  // reuse that path: `setNodeEvent` is a private action that `useEditor()`
  // strips from its `actions` return at runtime (see
  // @craftjs/core/dist/esm's `xe()` action stripper, not just its TypeScript
  // type), so calling it here throws. Writing our own attribute directly is
  // a supported, version-stable way to get the same highlight.
  const hoveredDomRef = useRef<HTMLElement | null>(null);

  const handleMouseEnter = useCallback(
    (e: React.MouseEvent<HTMLElement> | React.FocusEvent<HTMLElement>) => {
      const dom = query.node(nodeId).get()?.dom;
      if (dom) {
        dom.setAttribute('data-layer-hovered', '');
        hoveredDomRef.current = dom;
      }
      if (!isSelected) {
        (e.currentTarget as HTMLElement).style.background = 'var(--color-bg-hover)';
      }
    },
    [query, nodeId, isSelected],
  );

  const handleMouseLeave = useCallback(
    (e: React.MouseEvent<HTMLElement> | React.FocusEvent<HTMLElement>) => {
      hoveredDomRef.current?.removeAttribute('data-layer-hovered');
      hoveredDomRef.current = null;
      if (!isSelected) {
        (e.currentTarget as HTMLElement).style.background = 'transparent';
      }
    },
    [isSelected],
  );

  // Safety net: if this row unmounts (e.g. the user switches away from the
  // Layers tab, or the tree re-shuffles) while still hovered, clear the
  // attribute so it doesn't get stuck highlighted on the canvas forever.
  useEffect(() => {
    return () => {
      hoveredDomRef.current?.removeAttribute('data-layer-hovered');
      hoveredDomRef.current = null;
    };
  }, []);

  if (!node) return null;

  const displayName = (node.data.props?.aiName as string) || node.data.displayName || (node.data.type as any)?.resolvedName || 'Node';
  const childNodeIds: string[] = node.data.nodes || [];
  const linkedNodeIds: string[] = Object.values(node.data.linkedNodes || {}) as string[];
  const allChildren = [...childNodeIds, ...linkedNodeIds];
  const isRoot = nodeId === 'ROOT';
  const icon = isRoot ? ROOT_ICON : TYPE_ICONS[displayName] || DEFAULT_ICON;

  const virtualSpec = VIRTUAL_CHILD_PROPS[displayName];
  const virtualRows = virtualSpec ? deriveVirtualRows(displayName, node.data.props || {}) : [];
  const hasDisclosure = allChildren.length + virtualRows.length > 0;

  return (
    <div>
      <div
        {...clickableProps(handleActivate)}
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
        className="layer-node-row"
        style={{
          position: 'relative',
          display: 'flex',
          alignItems: 'center',
          padding: '5px 8px',
          paddingLeft: `${8 + depth * 16}px`,
          fontSize: 12,
          fontWeight: isSelected ? 600 : 400,
          color: isSelected ? 'var(--color-accent)' : 'var(--color-text)',
          background: isSelected ? 'var(--color-accent-subtle)' : 'transparent',
          borderLeft: isSelected ? '2px solid var(--color-accent)' : '2px solid transparent',
          cursor: 'pointer',
          transition: 'all var(--transition-fast)',
          whiteSpace: 'nowrap',
          overflow: 'hidden',
          textOverflow: 'ellipsis',
          userSelect: 'none',
        }}
        onFocus={handleMouseEnter}
        onBlur={handleMouseLeave}
      >
        {/* Indent guides: one vertical line per ancestor depth level,
            aligned under each ancestor's disclosure/icon column so nested
            rows read as a tree instead of a flat, ever-further-indented list. */}
        {Array.from({ length: depth }).map((_, level) => (
          <span
            key={level}
            aria-hidden="true"
            style={{
              position: 'absolute',
              left: `${8 + level * 16 + 4}px`,
              top: 0,
              bottom: 0,
              width: 1,
              background: 'var(--color-border)',
            }}
          />
        ))}

        {/* Indentation/disclosure indicator */}
        {hasDisclosure ? (
          <span style={{ marginRight: 4, fontSize: 8, color: 'var(--color-text-dim)', flexShrink: 0 }}>
            &#9660;
          </span>
        ) : (
          <span style={{ marginRight: 4, fontSize: 8, color: 'transparent', flexShrink: 0 }}>
            &#9660;
          </span>
        )}

        {/* Component type icon */}
        <i
          className={`fa ${icon}`}
          aria-hidden="true"
          style={{
            marginRight: 6,
            fontSize: 11,
            width: 12,
            textAlign: 'center',
            flexShrink: 0,
            color: isSelected ? 'var(--color-accent)' : 'var(--color-text-dim)',
          }}
        />

        {/* Name */}
        <span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>
          {isRoot ? 'Canvas (Root)' : displayName}
        </span>
      </div>

      {/* Virtual rows: array-prop items (composite content Craft doesn't see
          as child nodes), rendered before real children. */}
      {virtualSpec && virtualRows.map((row) => (
        <VirtualRowNode
          key={`${nodeId}:${virtualSpec.prop}:${row.index}`}
          parentId={nodeId}
          prop={virtualSpec.prop}
          index={row.index}
          label={row.label}
          depth={depth + 1}
          onActivate={() => {
            actions.selectNode(nodeId);
            requestFocus(nodeId, virtualSpec.prop, row.index);
          }}
        />
      ))}

      {/* Render children */}
      {allChildren.map((childId) => (
        <LayerNode key={childId} nodeId={childId} depth={depth + 1} />
      ))}
    </div>
  );
};

export const LayersPanel: React.FC = () => {
  const { nodeIds, unplacedIds } = useEditor((state) => {
    const serializable: Record<string, any> = {};
    for (const [id, n] of Object.entries(state.nodes)) {
      serializable[id] = {
        nodes: n.data.nodes || [],
        linkedNodes: n.data.linkedNodes || {},
      };
    }
    return {
      nodeIds: Object.keys(state.nodes),
      unplacedIds: findUnreachableNodeIds(serializable),
    };
  });

  const hasRoot = nodeIds.includes('ROOT');

  if (!hasRoot) {
    return (
      <div className="panel-placeholder">
        No content on canvas
      </div>
    );
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', margin: '-12px', minHeight: 0 }}>
      <div
        style={{
          padding: '8px 12px',
          fontSize: 11,
          fontWeight: 600,
          textTransform: 'uppercase',
          letterSpacing: '0.5px',
          color: 'var(--color-text-muted)',
          borderBottom: '1px solid var(--color-border)',
          flexShrink: 0,
        }}
      >
        Component Tree
      </div>

      {/* Own scroll container: a deep or long tree must stay fully reachable
          regardless of how the parent tab panel is sized. */}
      <div className="layers-tree-scroll" style={{ overflowY: 'auto', flex: 1, minHeight: 0 }}>
        <LayerNode nodeId="ROOT" depth={0} />

        {/* Unplaced: nodes no parent lists. `repairOrphanNodes` reattaches
            these on load, so this should stay empty -- it exists so an
            element stranded mid-session is still selectable and deletable
            rather than invisible. */}
        {unplacedIds.length > 0 && (
          <>
            <div
              style={{
                padding: '8px 12px',
                marginTop: 8,
                fontSize: 11,
                fontWeight: 600,
                textTransform: 'uppercase',
                letterSpacing: '0.5px',
                color: 'var(--color-warning, #f59e0b)',
                borderTop: '1px solid var(--color-border)',
              }}
              title="These elements are not attached to the page. Select one to delete it."
            >
              Unplaced ({unplacedIds.length})
            </div>
            {unplacedIds.map((id) => (
              <LayerNode key={id} nodeId={id} depth={1} />
            ))}
          </>
        )}
      </div>
    </div>
  );
};
