import {useReactFlow, useStoreApi} from "@xyflow/react";
import clsx from "clsx";
import {useEffect, useState} from "react";
import useFlowStore from "../VisProgStores.tsx";
import {
  warningSummary,
  type WarningSeverity,
  type EditorWarning, globalWarning
} from "./EditorWarnings.tsx";
import styles from "./WarningSidebar.module.css";

/**
 * the warning sidebar, shows all warnings
 *
 * @returns {React.JSX.Element}
 * @constructor
 */
export function WarningsSidebar() {
  const warnings = useFlowStore.getState().getWarnings();
  const [hide, setHide] = useState(false);
  const [severityFilter, setSeverityFilter] = useState<WarningSeverity | 'ALL'>('ALL');
  const [autoHide, setAutoHide] = useState(false);

  // let autohide change hide status only when autohide is toggled
  // and allow for user to change the hide state even if autohide is enabled
  const hasWarnings = warnings.length > 0;
  useEffect(() => {
    if (autoHide) {
      setHide(!hasWarnings);
    }
  }, [autoHide, hasWarnings]);

  const filtered = severityFilter === 'ALL'
    ? warnings
    : warnings.filter(w => w.severity === severityFilter);


  const summary = warningSummary();
  // Finds the first key where the count > 0
  const getHighestSeverity = () => {
    if (summary.error > 0) return styles.error;
    if (summary.warning > 0) return styles.warning;
    if (summary.info > 0) return styles.info;
    return '';
  };

  return (
    <aside className={`flex-row`} >
      <div
        className={`${styles.warningsToggleBar} ${getHighestSeverity()}`}
        onClick={() => setHide(!hide)}
        title={"toggle warnings"}
      >
        <div className={`${hide ? styles.arrowRight : styles.arrowLeft}`}></div>
      </div>
      <div 
        id="warningSidebar" 
        className={styles.warningsContent} 
        style={hide ? {display: "none"} : {display: "flex"}}
      >
        <WarningsHeader
          severityFilter={severityFilter}
          onChange={setSeverityFilter}
        />

        <WarningsList warnings={filtered} />
        <div className={styles.autoHide}>
          <input
            id="autoHideSwitch"
            type={"checkbox"}
            checked={autoHide}
            onChange={(e) => setAutoHide(e.target.checked)}
          /><label>Hide if there are no warnings</label>
        </div>
      </div>
    </aside>

  );
}

/**
 * the header of the warning sidebar, contains severity filtering buttons
 *
 * @param {WarningSeverity | "ALL"} severityFilter
 * @param {(severity: (WarningSeverity | "ALL")) => void} onChange
 * @returns {React.JSX.Element}
 * @constructor
 */
function WarningsHeader({
  severityFilter,
  onChange,
}: {
  severityFilter: WarningSeverity | 'ALL';
  onChange: (severity: WarningSeverity | 'ALL') => void;
}) {
  const summary = warningSummary();

  return (
    <div className={styles.warningsHeader}>
      <h3>Warnings</h3>
      <div className={styles.severityTabs}>
        {(['ALL', 'ERROR', 'WARNING', 'INFO'] as const).map(severity => (
          <button
            key={severity}
            className={clsx(styles.severityTab, severityFilter === severity && styles.active)}
            onClick={() => onChange(severity)}
          >
            {severity}
            {severity !== 'ALL' && (
              <span className={styles.count}>
                {summary[severity.toLowerCase() as keyof typeof summary]}
              </span>
            )}
          </button>
        ))}
      </div>
    </div>
  );
}


/**
 *  the list of warnings in the warning sidebar
 *
 * @param {{warnings: EditorWarning[]}} props
 * @returns {React.JSX.Element}
 * @constructor
 */
function WarningsList(props: { warnings: EditorWarning[] }) {
  const splitWarnings = {
    global: props.warnings.filter(w => w.scope.id === globalWarning),
    other: props.warnings.filter(w => w.scope.id !== globalWarning),
  }
  if (props.warnings.length === 0) {
    return (
      <div className={styles.warningsEmpty}>
        No warnings!
      </div>
    )
  }
  return (
    <div className={styles.warningsList}>
        <div className={styles.warningGroupHeader}>global:</div>
        <div className={styles.warningsGroup}>
          {splitWarnings.global.map((warning) => (
            <WarningListItem warning={warning} key={`${warning.scope.id}|${warning.type}` + (warning.scope.handleId
              ? `:${warning.scope.handleId}`
              : "")}
            />
          ))}
          {splitWarnings.global.length === 0 && "No global warnings!"}
        </div>
        <div className={styles.warningGroupHeader}>other:</div>
        <div className={styles.warningsGroup}>
          {splitWarnings.other.map((warning) => (
            <WarningListItem warning={warning} key={`${warning.scope.id}|${warning.type}` + (warning.scope.handleId
              ? `:${warning.scope.handleId}`
              : "")}
            />
          ))}
          {splitWarnings.other.length === 0 && "No other warnings!"}
        </div>
    </div>
  );
}

/**
 * a single warning in the warning sidebar
 *
 * @param {{warning: EditorWarning, key: string}} props
 * @returns {React.JSX.Element}
 * @constructor
 */
function WarningListItem(props: { warning: EditorWarning, key: string}) {
  const jumpToNode = useJumpToNode();

  return (
    <div
      className={clsx(styles.warningItem, styles[`warning-item--${props.warning.severity.toLowerCase()}`],)}
      onClick={() => jumpToNode(props.warning.scope.id)}
    >
      <div className={styles.itemHeader}>
        <span className={styles.type}>{props.warning.type}</span>
      </div>

      <div className={styles.description}>
        {props.warning.description}
      </div>
    </div>
  );
}

/**
 * moves the editor to the provided node
 * @returns {(nodeId: string) => void}
 */
function useJumpToNode() {
  const { getNode, setCenter, getViewport } = useReactFlow();
  const { addSelectedNodes } = useStoreApi().getState();


  return (nodeId: string) => {
    // user can't jump to global warning, so prevent further logic from running if the warning is a globalWarning
    if (nodeId === globalWarning) return;
    const node = getNode(nodeId);
    if (!node) return;

    const nodeElement = document.querySelector(`.react-flow__node[data-id="${nodeId}"]`) as HTMLElement;
    const { position } = node;
    const viewport = getViewport();
    const { width, height } = nodeElement.getBoundingClientRect();

    //move to node
    setCenter(
      position!.x + ((width / viewport.zoom) / 2),
      position!.y + ((height / viewport.zoom) / 2),
      {duration: 300, interpolate: "smooth" }
    ).then(() => {
      addSelectedNodes([nodeId]);
    });



  };
}