/**
 * DataSourcePanel -- Browse external data sources as a lazy-loading tree.
 *
 * Tree structure:
 *   UserConnection (Level 1, loaded on mount)
 *     └─ Service (Level 2, loaded when connection expanded)
 *         └─ Folder / Site / File (Level 3+, loaded when service/folder expanded)
 *
 * Each folder node can be added as a DataSource for this workspace instance.
 */

import React, { useEffect, useState, useCallback, useRef } from 'react';
import api from '../../../api';
import type { DataSource } from './useWorkspace';

/* ─── Types ─────────────────────────────────────────────────────────── */

interface TreeNode {
  key: string;
  label: string;
  icon: string;
  type: 'connection' | 'service' | 'folder' | 'file';
  expanded: boolean;
  loading: boolean;
  children: TreeNode[] | null;
  connectionId: string;
  service?: string;
  path?: string;
  authority?: string;
}

interface DataSourcePanelProps {
  instanceId: string;
  dataSources: DataSource[];
  onRefresh: () => void;
}

/* ─── Icons ─────────────────────────────────────────────────────────── */

const _AUTHORITY_ICONS: Record<string, string> = {
  msft: '\uD83D\uDFE6',
  google: '\uD83D\uDFE9',
  'local:ftp': '\uD83D\uDD17',
  'local:jira': '\uD83D\uDD27',
};

const _SERVICE_ICONS: Record<string, string> = {
  sharepoint: '\uD83D\uDCC1',
  onedrive: '\u2601\uFE0F',
  outlook: '\uD83D\uDCE7',
  teams: '\uD83D\uDCAC',
  drive: '\uD83D\uDCC2',
  gmail: '\uD83D\uDCE8',
  files: '\uD83D\uDCC2',
};

/* ─── Component ─────────────────────────────────────────────────────── */

export const DataSourcePanel: React.FC<DataSourcePanelProps> = ({
  instanceId,
  dataSources,
  onRefresh,
}) => {
  const [tree, setTree] = useState<TreeNode[]>([]);
  const [loadingRoot, setLoadingRoot] = useState(false);
  const [addingPath, setAddingPath] = useState<string | null>(null);
  const mountedRef = useRef(true);
  useEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; }; }, []);

  /* ── Load Level 1: UserConnections ── */
  const _loadConnections = useCallback(() => {
    if (!instanceId) return;
    setLoadingRoot(true);
    api.get(`/api/workspace/${instanceId}/connections`)
      .then(res => {
        if (!mountedRef.current) return;
        const conns = res.data.connections || [];
        const nodes: TreeNode[] = conns
          .filter((c: any) => c.status === 'active')
          .map((c: any) => ({
            key: `conn-${c.id}`,
            label: c.externalEmail || c.externalUsername || c.authority,
            icon: _AUTHORITY_ICONS[c.authority] || '\uD83D\uDD17',
            type: 'connection' as const,
            expanded: false,
            loading: false,
            children: null,
            connectionId: c.id,
            authority: c.authority,
          }));
        setTree(nodes);
      })
      .catch(() => { if (mountedRef.current) setTree([]); })
      .finally(() => { if (mountedRef.current) setLoadingRoot(false); });
  }, [instanceId]);

  useEffect(() => { _loadConnections(); }, [_loadConnections]);

  /* ── Generic tree update helper ── */
  const _updateNode = useCallback((key: string, updater: (node: TreeNode) => TreeNode) => {
    setTree(prev => _mapTree(prev, key, updater));
  }, []);

  /* ── Toggle expand/collapse ── */
  const _toggleNode = useCallback(async (node: TreeNode) => {
    if (node.expanded) {
      _updateNode(node.key, n => ({ ...n, expanded: false }));
      return;
    }

    if (node.children !== null) {
      _updateNode(node.key, n => ({ ...n, expanded: true }));
      return;
    }

    _updateNode(node.key, n => ({ ...n, loading: true, expanded: true }));

    try {
      let children: TreeNode[] = [];

      if (node.type === 'connection') {
        children = await _loadServices(instanceId, node.connectionId);
      } else if (node.type === 'service' || node.type === 'folder') {
        children = await _browseService(instanceId, node.connectionId, node.service!, node.path || '/');
      }

      if (mountedRef.current) {
        _updateNode(node.key, n => ({ ...n, loading: false, children }));
      }
    } catch {
      if (mountedRef.current) {
        _updateNode(node.key, n => ({ ...n, loading: false, children: [] }));
      }
    }
  }, [instanceId, _updateNode]);

  /* ── Add as DataSource ── */
  const _addAsDataSource = useCallback(async (node: TreeNode) => {
    if (!node.service || !node.connectionId) return;
    setAddingPath(node.key);
    try {
      const sourceTypeMap: Record<string, string> = {
        sharepoint: 'sharepointFolder',
        onedrive: 'onedriveFolder',
        outlook: 'outlookFolder',
        drive: 'googleDriveFolder',
        gmail: 'gmailFolder',
        files: 'ftpFolder',
      };
      await api.post(`/api/workspace/${instanceId}/datasources`, {
        connectionId: node.connectionId,
        sourceType: sourceTypeMap[node.service] || node.service,
        path: node.path || '/',
        label: node.label,
      });
      onRefresh();
    } catch (err) {
      console.error('Failed to add data source:', err);
    } finally {
      if (mountedRef.current) setAddingPath(null);
    }
  }, [instanceId, onRefresh]);

  /* ── Remove DataSource ── */
  const _removeDatasource = useCallback(async (dsId: string) => {
    try {
      await api.delete(`/api/workspace/${instanceId}/datasources/${dsId}`);
      onRefresh();
    } catch (err) {
      console.error('Failed to remove data source:', err);
    }
  }, [instanceId, onRefresh]);

  /* ── Check if a path is already added ── */
  const _isAdded = useCallback((connectionId: string, service: string | undefined, path: string | undefined): boolean => {
    return dataSources.some(ds =>
      ds.connectionId === connectionId && ds.path === (path || '/'),
    );
  }, [dataSources]);

  return (
    <div style={{ padding: 8, fontSize: 13 }}>
      {/* Active DataSources */}
      {dataSources.length > 0 && (
        <div style={{ marginBottom: 8 }}>
          <div style={{ fontSize: 11, fontWeight: 600, color: '#666', textTransform: 'uppercase', marginBottom: 4 }}>
            Active Sources
          </div>
          {dataSources.map(ds => (
            <div key={ds.id} style={{
              display: 'flex', alignItems: 'center', gap: 6,
              padding: '4px 6px', borderRadius: 4, marginBottom: 2,
              background: 'var(--primary-light, #e3f2fd)',
              fontSize: 12,
            }}>
              <span style={{ color: '#4caf50', fontSize: 10 }}>{'\u25CF'}</span>
              <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                {ds.label}
              </span>
              <button
                onClick={() => _removeDatasource(ds.id)}
                style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 11, color: '#999', padding: '0 2px' }}
                title="Remove"
              >
                {'\u2715'}
              </button>
            </div>
          ))}
          <div style={{ height: 1, background: 'var(--border-color, #e0e0e0)', margin: '8px 0' }} />
        </div>
      )}

      {/* Tree header */}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
        <span style={{ fontSize: 11, fontWeight: 600, color: '#666', textTransform: 'uppercase' }}>
          Browse Sources
        </span>
        <button
          onClick={_loadConnections}
          disabled={loadingRoot}
          style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, color: '#1976d2' }}
        >
          {loadingRoot ? '...' : '\u21BB'}
        </button>
      </div>

      {/* Tree */}
      {loadingRoot && tree.length === 0 && (
        <div style={{ fontSize: 12, color: '#999', textAlign: 'center', padding: 16 }}>
          Loading connections...
        </div>
      )}

      {!loadingRoot && tree.length === 0 && (
        <div style={{ fontSize: 12, color: '#999', textAlign: 'center', padding: 16 }}>
          No active connections found.
        </div>
      )}

      {tree.map(node => (
        <_TreeNodeView
          key={node.key}
          node={node}
          depth={0}
          onToggle={_toggleNode}
          onAdd={_addAsDataSource}
          isAdded={_isAdded}
          addingPath={addingPath}
        />
      ))}
    </div>
  );
};

/* ─── TreeNodeView (recursive) ──────────────────────────────────────── */

interface TreeNodeViewProps {
  node: TreeNode;
  depth: number;
  onToggle: (node: TreeNode) => void;
  onAdd: (node: TreeNode) => void;
  isAdded: (connectionId: string, service: string | undefined, path: string | undefined) => boolean;
  addingPath: string | null;
}

const _TreeNodeView: React.FC<TreeNodeViewProps> = ({
  node, depth, onToggle, onAdd, isAdded, addingPath,
}) => {
  const [hovered, setHovered] = useState(false);
  const hasChildren = node.type !== 'file';
  const chevron = hasChildren
    ? (node.expanded ? '\u25BE' : '\u25B8')
    : '\u00A0\u00A0';
  const canAdd = node.type === 'folder' || node.type === 'service';
  const alreadyAdded = canAdd && isAdded(node.connectionId, node.service, node.path);
  const isAdding = addingPath === node.key;

  return (
    <div>
      <div
        onClick={() => { if (hasChildren) onToggle(node); }}
        onMouseEnter={() => setHovered(true)}
        onMouseLeave={() => setHovered(false)}
        style={{
          display: 'flex',
          alignItems: 'center',
          gap: 4,
          paddingLeft: depth * 16 + 4,
          paddingRight: 4,
          paddingTop: 3,
          paddingBottom: 3,
          cursor: hasChildren ? 'pointer' : 'default',
          borderRadius: 3,
          background: hovered ? 'var(--hover-bg, #f5f5f5)' : 'transparent',
          transition: 'background 0.1s',
          userSelect: 'none',
        }}
      >
        <span style={{ fontSize: 10, color: '#888', width: 12, textAlign: 'center', flexShrink: 0 }}>
          {node.loading ? _Spinner() : chevron}
        </span>
        <span style={{ fontSize: 14, flexShrink: 0 }}>{node.icon}</span>
        <span style={{
          flex: 1, minWidth: 0, overflow: 'hidden',
          textOverflow: 'ellipsis', whiteSpace: 'nowrap',
          fontSize: 12,
          fontWeight: node.type === 'connection' ? 600 : 400,
        }}>
          {node.label}
        </span>
        {canAdd && hovered && !alreadyAdded && (
          <button
            onClick={e => { e.stopPropagation(); onAdd(node); }}
            disabled={isAdding}
            style={{
              background: 'none', border: '1px solid #1976d2', borderRadius: 3,
              cursor: isAdding ? 'not-allowed' : 'pointer',
              fontSize: 10, color: '#1976d2', padding: '1px 5px',
              opacity: isAdding ? 0.5 : 1,
              flexShrink: 0,
            }}
            title="Add as data source"
          >
            {isAdding ? '...' : '+ Add'}
          </button>
        )}
        {canAdd && alreadyAdded && (
          <span style={{ fontSize: 10, color: '#4caf50', flexShrink: 0 }} title="Already added">
            {'\u2713'}
          </span>
        )}
      </div>

      {/* Children */}
      {node.expanded && node.children && node.children.length > 0 && (
        <div>
          {node.children.map(child => (
            <_TreeNodeView
              key={child.key}
              node={child}
              depth={depth + 1}
              onToggle={onToggle}
              onAdd={onAdd}
              isAdded={isAdded}
              addingPath={addingPath}
            />
          ))}
        </div>
      )}

      {node.expanded && node.children && node.children.length === 0 && !node.loading && (
        <div style={{ paddingLeft: (depth + 1) * 16 + 20, fontSize: 11, color: '#bbb', padding: '2px 0 2px ' + ((depth + 1) * 16 + 20) + 'px' }}>
          (empty)
        </div>
      )}
    </div>
  );
};

/* ─── Spinner (inline) ──────────────────────────────────────────────── */

function _Spinner(): React.ReactElement {
  return (
    <span style={{
      display: 'inline-block', width: 10, height: 10,
      border: '1.5px solid #ccc', borderTopColor: '#1976d2',
      borderRadius: '50%',
      animation: 'spin 0.6s linear infinite',
    }} />
  );
}

/* ─── Data fetching ─────────────────────────────────────────────────── */

async function _loadServices(instanceId: string, connectionId: string): Promise<TreeNode[]> {
  const res = await api.get(`/api/workspace/${instanceId}/connections/${connectionId}/services`);
  const services = res.data.services || [];
  return services.map((s: any) => ({
    key: `svc-${connectionId}-${s.service}`,
    label: s.label || s.service,
    icon: _SERVICE_ICONS[s.service] || '\uD83D\uDCC2',
    type: 'service' as const,
    expanded: false,
    loading: false,
    children: null,
    connectionId,
    service: s.service,
    path: '/',
  }));
}

async function _browseService(
  instanceId: string, connectionId: string, service: string, path: string,
): Promise<TreeNode[]> {
  const res = await api.get(`/api/workspace/${instanceId}/connections/${connectionId}/browse`, {
    params: { service, path },
  });
  const items = res.data.items || [];
  return items.map((entry: any, idx: number) => ({
    key: `item-${connectionId}-${service}-${entry.path || idx}`,
    label: entry.name,
    icon: entry.isFolder ? '\uD83D\uDCC1' : _fileIcon(entry.name),
    type: entry.isFolder ? 'folder' as const : 'file' as const,
    expanded: false,
    loading: false,
    children: entry.isFolder ? null : [],
    connectionId,
    service,
    path: entry.path,
  }));
}

function _fileIcon(name: string): string {
  const ext = name.split('.').pop()?.toLowerCase() || '';
  const map: Record<string, string> = {
    pdf: '\uD83D\uDCC4', doc: '\uD83D\uDCDD', docx: '\uD83D\uDCDD',
    xls: '\uD83D\uDCCA', xlsx: '\uD83D\uDCCA', csv: '\uD83D\uDCCA',
    ppt: '\uD83D\uDCC8', pptx: '\uD83D\uDCC8',
    txt: '\uD83D\uDCC4', json: '\uD83D\uDCCB',
    png: '\uD83D\uDDBC\uFE0F', jpg: '\uD83D\uDDBC\uFE0F', jpeg: '\uD83D\uDDBC\uFE0F',
    zip: '\uD83D\uDCE6', rar: '\uD83D\uDCE6',
    mp3: '\uD83C\uDFB5', wav: '\uD83C\uDFB5',
    mp4: '\uD83C\uDFAC', mov: '\uD83C\uDFAC',
  };
  return map[ext] || '\uD83D\uDCC4';
}

/* ─── Tree map utility ──────────────────────────────────────────────── */

function _mapTree(nodes: TreeNode[], key: string, updater: (n: TreeNode) => TreeNode): TreeNode[] {
  return nodes.map(n => {
    if (n.key === key) return updater(n);
    if (n.children) return { ...n, children: _mapTree(n.children, key, updater) };
    return n;
  });
}
