const { useEffect, useMemo, useState, useRef } = React;
const { DragDropContext, Droppable, Draggable } = window.ReactBeautifulDnd || {};

function useApiBase() {
  const params = new URLSearchParams(window.location.search);
  const override = params.get('api');
  if (override) return override.replace(/\/$/, '');
  return '';
}

function fetchJson(url) {
  return fetch(url).then((r) => {
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
    return r.json();
  });
}

function buildPivotTree(rows, hierarchyKeys, metricAggs) {
  const makePathKey = (parentPath, value) => (parentPath ? `${parentPath}||${String(value)}` : String(value));

  const root = {
    level: -1,
    value: null,
    pathKey: '',
    children: new Map(),
    _distinctSets: Object.create(null),
  };
  for (const def of metricAggs) {
    if (def.type === 'sum') root[def.key] = 0;
    else if (def.type === 'countDistinct') root._distinctSets[def.key] = new Set();
  }

  function createNode(level, value, parent) {
    const node = {
      level,
      value,
      pathKey: makePathKey(parent.pathKey, value),
      children: new Map(),
      _distinctSets: Object.create(null),
    };
    for (const def of metricAggs) {
      if (def.type === 'sum') node[def.key] = 0;
      else if (def.type === 'countDistinct') node._distinctSets[def.key] = new Set();
    }
    return node;
  }

  function updateNodeMetrics(node, row) {
    for (const def of metricAggs) {
      if (def.type === 'sum') {
        const v = Number(row[def.source] || 0);
        node[def.key] += Number.isFinite(v) ? v : 0;
      } else if (def.type === 'countDistinct') {
        const dv = row[def.source];
        if (dv != null && dv !== '') node._distinctSets[def.key].add(dv);
      }
    }
  }

  for (const row of rows) {
    let node = root;
    updateNodeMetrics(node, row);

    for (let level = 0; level < hierarchyKeys.length; level += 1) {
      const key = hierarchyKeys[level];
      const value = row[key] != null && row[key] !== '' ? row[key] : '—';
      if (!node.children.has(value)) {
        node.children.set(value, createNode(level, value, node));
      }
      node = node.children.get(value);
      updateNodeMetrics(node, row);
    }
  }

  function finalize(node) {
    for (const def of metricAggs) {
      if (def.type === 'countDistinct') {
        node[def.key] = node._distinctSets[def.key]?.size || 0;
      }
    }
    delete node._distinctSets;
    for (const child of node.children.values()) finalize(child);
  }
  finalize(root);

  return root;
}

function Toggle({ expanded, onClick }) {
  return (
    <button className="toggle" onClick={onClick} aria-label={expanded ? 'Collapse' : 'Expand'}>
      {expanded ? '−' : '+'}
    </button>
  );
}

function App() {
  const apiBase = useApiBase();
  const [rows, setRows] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const columnCatalog = useMemo(() => ([
    { key: 'sponsor', label: 'Sponsor', width: '18%' },
    { key: 'care_option_id', label: 'Care Option', width: '22%', minWidth: '115px' },
    { key: 'category', label: 'Category', width: '14%' },
    { key: 'concept_name', label: 'Concept', width: '22%' },
    { key: 'rule_display_name', label: 'Rule', width: '30%' },
  ]), []);
  const columnMap = useMemo(() => Object.fromEntries(columnCatalog.map((c) => [c.key, c])), [columnCatalog]);

  const [allColumnsOrder, setAllColumnsOrder] = useState(() => columnCatalog.map((c) => c.key));
  const [selectedKeys, setSelectedKeys] = useState(() => [
    'category',
    'concept_name',
    'rule_display_name',
    'sponsor',
    'care_option_id',
  ]);
  const availableKeys = useMemo(() => allColumnsOrder.filter((k) => !selectedKeys.includes(k)), [allColumnsOrder, selectedKeys]);
  const hierarchy = useMemo(() => selectedKeys.map((k) => columnMap[k]).filter(Boolean), [selectedKeys, columnMap]);
  const metricOptionsCatalog = useMemo(() => ([
    { key: 'careOptions', label: 'Care Options', width: '115px', aggType: 'countDistinct', source: 'care_option_id' },
    { key: 'category', label: 'Category', width: '90px', aggType: 'countDistinct', source: 'category' },
    { key: 'rule_display_name', label: 'Rules', width: '60px', aggType: 'countDistinct', source: 'rule_display_name' },
    { key: 'subgroup_id', label: 'Subgroups', width: '100px', aggType: 'countDistinct', source: 'subgroup_id' },
    { key: 'concept_name', label: 'Concept', width: '100px', aggType: 'countDistinct', source: 'concept_name' },
    { key: 'sponsor', label: 'Sponsor', width: '100px', aggType: 'countDistinct', source: 'sponsor' },
  ]), []);
  const metricOptionsMap = useMemo(() => Object.fromEntries(metricOptionsCatalog.map((m) => [m.key, m])), [metricOptionsCatalog]);
  const [selectedMetricKeys, setSelectedMetricKeys] = useState(() => ['careOptions', 'category', 'rule_display_name', 'subgroup_id']);
  const metrics = useMemo(() => selectedMetricKeys.map((k) => metricOptionsMap[k]).filter(Boolean), [selectedMetricKeys, metricOptionsMap]);

  const metricsSelectRef = useRef(null);
  useEffect(() => {
    const el = metricsSelectRef.current;
    const ChoicesLib = window.Choices;
    if (!el || !ChoicesLib) return;
    const choices = new ChoicesLib(el, {
      removeItemButton: true,
      searchEnabled: true,
      shouldSort: false,
      placeholder: true,
      placeholderValue: 'Select metrics…',
      position: 'bottom',
    });
    const onChange = () => {
      const values = Array.from(el.selectedOptions).map((o) => o.value);
      setSelectedMetricKeys(values);
    };
    el.addEventListener('change', onChange);
    return () => {
      el.removeEventListener('change', onChange);
      choices.destroy();
    };
  }, []);

  const [openNodes, setOpenNodes] = useState(() => new Set());

  useEffect(() => {
    setLoading(true);
    setError(null);
    fetchJson(`${apiBase}/care-option-subgroups/pivot`)
      .then((data) => {
        setRows(Array.isArray(data) ? data : []);
      })
      .catch((e) => setError(e.message || String(e)))
      .finally(() => setLoading(false));
  }, [apiBase]);

  useEffect(() => { setOpenNodes(new Set()); }, [selectedKeys]);

  const metricAggs = useMemo(() => metrics.map((m) => ({ key: m.key, type: m.aggType, source: m.source })), [metrics]);

  const pivotRoot = useMemo(() => buildPivotTree(rows, hierarchy.map((h) => h.key), metricAggs), [rows, hierarchy, metricAggs]);

  const toggleNode = (key) => setOpenNodes((prev) => {
    const next = new Set(prev);
    if (next.has(key)) next.delete(key); else next.add(key);
    return next;
  });

  function renderRows(node) {
    const rowsOut = [];
    const children = Array.from(node.children.values()).sort((a, b) => String(a.value).localeCompare(String(b.value)));
    for (const child of children) {
      const hasChildren = child.children && child.children.size > 0;
      const isOpen = openNodes.has(child.pathKey);
      rowsOut.push(
        <tr key={child.pathKey}>
          {hierarchy.map((h, idx) => (
            idx === child.level ? (
              <td key={`${child.pathKey}::${idx}`}>
                <div className="tree-row">
                  {hasChildren ? (
                    <Toggle expanded={isOpen} onClick={() => toggleNode(child.pathKey)} />
                  ) : (
                    <span style={{display: 'inline-block', width: '1.25em'}} />
                  )}
                  <span className="tree-label">{String(child.value)}</span>
                </div>
              </td>
            ) : (
              <td key={`${child.pathKey}::${idx}`} />
            )
          ))}
          {metrics.map((m) => (
            <td key={`${child.pathKey}::${m.key}`} className="num">{child[m.key] ?? 0}</td>
          ))}
        </tr>
      );
      if (hasChildren && isOpen) {
        rowsOut.push(...renderRows(child));
      }
    }
    return rowsOut;
  }

  function reorder(list, startIndex, endIndex) {
    const result = Array.from(list);
    const [removed] = result.splice(startIndex, 1);
    result.splice(endIndex, 0, removed);
    return result;
  }

  function onDragEnd(result) {
    if (!result || !result.destination) return;
    const { source, destination } = result;
    if (source.droppableId === destination.droppableId && source.index === destination.index) return;

    if (source.droppableId === 'hierarchy' && destination.droppableId === 'hierarchy') {
      setSelectedKeys((prev) => reorder(prev, source.index, destination.index));
      return;
    }

    if (source.droppableId === 'available' && destination.droppableId === 'available') {
      setAllColumnsOrder((prevAll) => {
        const avail = prevAll.filter((k) => !selectedKeys.includes(k));
        const newAvail = reorder(avail, source.index, destination.index);
        const selectedSet = new Set(selectedKeys);
        const rebuilt = [];
        let a = 0;
        for (const k of prevAll) {
          if (selectedSet.has(k)) rebuilt.push(k); else { rebuilt.push(newAvail[a]); a += 1; }
        }
        return rebuilt;
      });
      return;
    }

    if (source.droppableId === 'available' && destination.droppableId === 'hierarchy') {
      const fromKey = availableKeys[source.index];
      setSelectedKeys((prev) => {
        const next = Array.from(prev);
        next.splice(destination.index, 0, fromKey);
        return next;
      });
      return;
    }
    if (source.droppableId === 'hierarchy' && destination.droppableId === 'available') {
      setSelectedKeys((prev) => {
        const next = Array.from(prev);
        next.splice(source.index, 1);
        return next;
      });
    }
  }

  return (
    <div className="container">
      <header>
        <h1>Pivot Viewer</h1>
        <div className="api-info"><span>API:</span> <code>{apiBase || window.location.origin}</code></div>
      </header>

      {DragDropContext && (
        <section className="dnd-section">
          <DragDropContext onDragEnd={onDragEnd}>
            <div className="dnd-row" style={{display: 'flex', gap: '12px', alignItems: 'flex-start', marginBottom: '12px'}}>
              <div style={{display: 'flex', flexDirection: 'column', gap: '6px', flex: 1}}>
                <label style={{fontWeight: 600}}>Available columns</label>
                <Droppable droppableId="available" direction="horizontal">
                  {(provided) => (
                    <div
                      ref={provided.innerRef}
                      {...provided.droppableProps}
                      style={{
                        display: 'flex',
                        gap: '8px',
                        padding: '8px',
                        minHeight: '44px',
                        border: '1px dashed #bbb',
                        borderRadius: '6px',
                        background: '#fafafa',
                        overflowX: 'auto',
                      }}
                    >
                      {availableKeys.map((key, index) => (
                        <Draggable draggableId={`avail-${key}`} index={index} key={`avail-${key}`}>
                          {(dragProvided) => (
                            <div
                              ref={dragProvided.innerRef}
                              {...dragProvided.draggableProps}
                              {...dragProvided.dragHandleProps}
                              style={{
                                userSelect: 'none',
                                padding: '2px 6px',
                                borderRadius: '16px',
                                border: '1px solid #ccc',
                                background: 'white',
                                boxShadow: '0 1px 2px rgba(0,0,0,0.05)',
                                whiteSpace: 'nowrap',
                                ...dragProvided.draggableProps.style,
                              }}
                              title={columnMap[key].label}
                            >
                              {columnMap[key]?.label || key}
                            </div>
                          )}
                        </Draggable>
                      ))}
                      {provided.placeholder}
                    </div>
                  )}
                </Droppable>
              </div>

              <div style={{display: 'flex', flexDirection: 'column', gap: '6px', flex: 1}}>
                <label style={{fontWeight: 600}}>Selected Columns</label>
                <Droppable droppableId="hierarchy" direction="horizontal">
                  {(provided) => (
                    <div
                      ref={provided.innerRef}
                      {...provided.droppableProps}
                      style={{
                        display: 'flex',
                        gap: '8px',
                        padding: '8px',
                        minHeight: '44px',
                        border: '1px solid #888',
                        borderRadius: '6px',
                        background: '#f0f7ff',
                        overflowX: 'auto',
                      }}
                    >
                      {hierarchy.map((col, index) => (
                        <Draggable draggableId={`hier-${col.key}`} index={index} key={`hier-${col.key}`}>
                          {(dragProvided) => (
                            <div
                              ref={dragProvided.innerRef}
                              {...dragProvided.draggableProps}
                              {...dragProvided.dragHandleProps}
                              style={{
                                userSelect: 'none',
                                padding: '2px 6px',
                                borderRadius: '16px',
                                border: '1px solid #3b82f6',
                                background: '#e0efff',
                                boxShadow: '0 1px 2px rgba(59,130,246,0.2)',
                                whiteSpace: 'nowrap',
                                ...dragProvided.draggableProps.style,
                              }}
                              title={col.label}
                            >
                              {col.label}
                            </div>
                          )}
                        </Draggable>
                      ))}
                      {provided.placeholder}
                    </div>
                  )}
                </Droppable>
              </div>
            </div>

            <div className="dnd-row" style={{display: 'flex', flexDirection: 'column', gap: '6px', marginBottom: '20px'}}>
              <label style={{fontWeight: 600}}>Metrics</label>
              <div style={{display: 'flex'}}>
                <div style={{width: '50%'}}>
                  <select
                    multiple
                    ref={metricsSelectRef}
                    defaultValue={selectedMetricKeys}
                    style={{
                      width: '100%',
                      minHeight: '88px',
                      background: '#ffffff',
                      border: '1px solid #888',
                      borderRadius: '6px',
                      padding: '6px',
                    }}
                  >
                    {metricOptionsCatalog.map((opt) => (
                      <option key={opt.key} value={opt.key}>{opt.label}</option>
                    ))}
                  </select>
                </div>
              </div>
            </div>
          </DragDropContext>
        </section>
      )}

      <section className="table-section" style={{overflow: 'auto'}}>
        {loading ? (
          <div className="status">Loading…</div>
        ) : error ? (
          <div className="status error">{String(error)}</div>
        ) : (
          <table>
            <thead>
              <tr>
                {hierarchy.map((h) => (
                  <th
                    key={h.key}
                    style={{
                      ...(h?.width ? { width: h.width } : {}),
                      ...(h?.minWidth ? { minWidth: h.minWidth } : {}),
                    }}
                  >
                    {h.label}
                  </th>
                ))}
                {metrics.map((m) => (
                  <th
                    key={m.key}
                    style={{
                      ...(m?.width ? { minWidth: m.width } : {}),
                      ...(m?.minWidth ? { minWidth: m.minWidth } : {}),
                      ...(m?.exactWidth ? { width: m.exactWidth } : {}),
                    }}
                  >
                    {m.label}
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {hierarchy.length === 0 ? (
                <tr>
                  {metrics.map((m) => (
                    <td key={`root::${m.key}`} className="num">{pivotRoot[m.key] ?? 0}</td>
                  ))}
                </tr>
              ) : (
                renderRows(pivotRoot)
              )}
            </tbody>
          </table>
        )}
      </section>

      <footer>
        <small>
          Tip: Open <code>/ui/pivot.html</code>. Add <code>?api=http://0.0.0.0:8300</code> to target a remote API.
        </small>
      </footer>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

