// This program has been developed by students from the bachelor Computer Science at Utrecht
// University within the Software Project course.
// © Copyright Utrecht University (Department of Information and Computing Sciences)
import React, { useCallback, useState } from 'react';
import styles from './MonitoringPage.module.css';

// Store & API
import useProgramStore from "../../utils/programStore";
import {
  nextPhase,
  stopExperiment,
  useExperimentLogger,
  useStatusLogger,
  pauseExperiment,
  playExperiment,
  type ExperimentStreamData,
  type GoalUpdate,
  type TriggerUpdate,
  type CondNormsStateUpdate,
  type PhaseUpdate
} from "./MonitoringPageAPI";
import { graphReducer, runProgram } from '../VisProgPage/VisProgLogic.ts';

// Types
import type { NormNodeData } from '../VisProgPage/visualProgrammingUI/nodes/NormNode';
import type { GoalNode } from '../VisProgPage/visualProgrammingUI/nodes/GoalNode';
import type { TriggerNode } from '../VisProgPage/visualProgrammingUI/nodes/TriggerNode';

// Sub-components
import {
  GestureControls,
  SpeechPresets,
  DirectSpeechInput,
  StatusList,
  RobotConnected
} from './MonitoringPageComponents';
import ExperimentLogs from "./components/ExperimentLogs.tsx";
import Pause from "../../components/Icons/Pause.tsx";
import Play from "../../components/Icons/Play.tsx";
import Next from "../../components/Icons/Next.tsx";
import Replay from "../../components/Icons/Replay.tsx";
import Stop from "../../components/Icons/Stop.tsx";

// ----------------------------------------------------------------------
// 1. State management
// ----------------------------------------------------------------------

/**
 * Manages the state of the active experiment, including phase progression,
 * goal tracking, and stream event listeners.
 */
function useExperimentLogic() {
  const getPhaseIds = useProgramStore((s) => s.getPhaseIds);
  const getPhaseNames = useProgramStore((s) => s.getPhaseNames);
  const getGoalsInPhase = useProgramStore((s) => s.getGoalsInPhase);
  const setProgramState = useProgramStore((state) => state.setProgramState);

  const [loading, setLoading] = useState(false);
  const [activeIds, setActiveIds] = useState<Record<string, boolean>>({});
  const [goalIndex, setGoalIndex] = useState(0);
  const [isPlaying, setIsPlaying] = useState(false);
  const [phaseIndex, setPhaseIndex] = useState(0);
  const [isFinished, setIsFinished] = useState(false);

  const phaseIds = getPhaseIds();
  const phaseNames = getPhaseNames();

  // --- Stream Handlers ---

  const handleStreamUpdate = useCallback((data: ExperimentStreamData) => {
    if (data.type === 'phase_update' && data.id) {
      const payload = data as PhaseUpdate;
      console.log(`${data.type} received, id : ${data.id}`);

      if (payload.id === "end") {
        setIsFinished(true);
      } else {
        setIsFinished(false);
        const newIndex = getPhaseIds().indexOf(payload.id);
        if (newIndex !== -1) {
          setPhaseIndex(newIndex);
          setGoalIndex(0);
        }
      }
    }
    else if (data.type === 'goal_update') {
      const payload = data as GoalUpdate;
      const currentPhaseGoals = getGoalsInPhase(phaseIds[phaseIndex]) as GoalNode[];
      const gIndex = currentPhaseGoals.findIndex((g) => g.id === payload.id);

      console.log(`${data.type} received, id : ${data.id}`);

      if (gIndex === -1) {
        console.warn(`Goal ${payload.id} not found in phase ${phaseNames[phaseIndex]}`);
      } else {
        setGoalIndex(gIndex);
        // Mark all previous goals as achieved
        setActiveIds((prev) => {
          const nextState = { ...prev };
          for (let i = 0; i < gIndex; i++) {
            nextState[currentPhaseGoals[i].id] = true;
          }
          return nextState;
        });
      }
    }
    else if (data.type === 'trigger_update') {
      const payload = data as TriggerUpdate;
      setActiveIds((prev) => ({ ...prev, [payload.id]: payload.achieved }));
    }
  }, [getPhaseIds, getGoalsInPhase, phaseIds, phaseIndex, phaseNames]);

  const handleStatusUpdate = useCallback((data: unknown) => {
    const payload = data as CondNormsStateUpdate;
    if (payload.type !== 'cond_norms_state_update') return;

    setActiveIds((prev) => {
      const hasChanges = payload.norms.some((u) => prev[u.id] !== u.active);
      if (!hasChanges) return prev;

      const nextState = { ...prev };
      payload.norms.forEach((u) => { nextState[u.id] = u.active; });
      return nextState;
    });
  }, []);

  // Connect listeners
  useExperimentLogger(handleStreamUpdate);
  useStatusLogger(handleStatusUpdate);

  // --- Actions ---

  const resetExperiment = useCallback(async () => {
    try {
      setLoading(true);
      const phases = graphReducer();
      setProgramState({ phases });

      setActiveIds({});
      setPhaseIndex(0);
      setGoalIndex(0);
      setIsFinished(false);

      await runProgram();
      console.log("Experiment & UI successfully reset.");
    } catch (err) {
      console.error("Failed to reset program:", err);
    } finally {
      setLoading(false);
    }
  }, [setProgramState]);

  const handleControlAction = async (action: "pause" | "play" | "nextPhase" | "stop") => {
    try {
      setLoading(true);
      switch (action) {
        case "pause":
          setIsPlaying(false);
          await pauseExperiment();
          break;
        case "play":
          setIsPlaying(true);
          await playExperiment();
          break;
        case "nextPhase":
          await nextPhase();
          break;
        case "stop":
          await stopExperiment();
          break;
      }
    } catch (err) {
      console.error(err);
    } finally {
      setLoading(false);
    }
  };

  return {
    loading,
    isPlaying,
    isFinished,
    phaseIds,
    phaseNames,
    phaseIndex,
    goalIndex,
    activeIds,
    setActiveIds,
    resetExperiment,
    handleControlAction,
  };
}

// ----------------------------------------------------------------------
// 2. Smaller Presentation Components
// ----------------------------------------------------------------------

/**
 * Visual indicator of progress through experiment phases.
 */
function PhaseProgressBar({
  phaseIds,
  phaseIndex,
  isFinished
}: {
  phaseIds: string[],
  phaseIndex: number,
  isFinished: boolean
}) {
  return (
    <div className={styles.phaseProgress}>
      {phaseIds.map((id, index) => {
        let statusClass = "";
        if (isFinished || index < phaseIndex) statusClass = styles.completed;
        else if (index === phaseIndex) statusClass = styles.current;

        return (
          <span key={id} className={`${styles.phase} ${statusClass}`}>
            {index + 1}
          </span>
        );
      })}
    </div>
  );
}

/**
 * Main control buttons (Play, Pause, Next, Reset).
 */
function ControlPanel({
  loading,
  isPlaying,
  onAction,
  onReset
}: {
  loading: boolean,
  isPlaying: boolean,
  onAction: (a: "pause" | "play" | "nextPhase" | "stop") => void,
  onReset: () => void
}) {
  return (
    <div className={styles.experimentControls}>
      <h3>Experiment Controls</h3>
      <div className={styles.controlsButtons}>
        <button
          aria-label="Pause"
          className={!isPlaying ? styles.pausePlayActive : styles.pausePlayInactive}
          onClick={() => onAction("pause")}
          disabled={loading}
        ><Pause fill={"white"} /></button>

        <button
          aria-label="Play"
          className={isPlaying ? styles.pausePlayActive : styles.pausePlayInactive}
          onClick={() => onAction("play")}
          disabled={loading}
        ><Play fill={"white"} /></button>

        <button
          aria-label="Next phase"
          className={styles.next}
          onClick={() => onAction("nextPhase")}
          disabled={loading}
        ><Next fill={"white"} /></button>

        <button
          aria-label="Reset experiment"
          className={styles.restartExperiment}
          onClick={onReset}
          disabled={loading}
        ><Replay fill={"white"} /></button>

        <button
          aria-label="Stop"
          className={styles.stop}
          onClick={() => onAction("stop")}
          disabled={loading}
        ><Stop fill={"white"} /></button>
      </div>
    </div>
  );
}

/**
 * Displays lists of Goals, Triggers, and Norms for the current phase.
 */
function PhaseDashboard({
  phaseId,
  activeIds,
  setActiveIds,
  goalIndex
}: {
  phaseId: string,
  activeIds: Record<string, boolean>,
  setActiveIds: React.Dispatch<React.SetStateAction<Record<string, boolean>>>,
  goalIndex: number
}) {
  const getGoalsWithDepth = useProgramStore((s) => s.getGoalsWithDepth);
  const getTriggers = useProgramStore((s) => s.getTriggersInPhase);
  const getNorms = useProgramStore((s) => s.getNormsInPhase);

  // Prepare data view models
  const goals = getGoalsWithDepth(phaseId).map((g) => ({
    ...g,
    id: g.id as string,
    name: g.name as string,
    achieved: activeIds[g.id as string] ?? false,
    level: g.level, // Pass this new property to the UI
  }));

  const triggers = (getTriggers(phaseId) as TriggerNode[]).map(t => ({
    ...t,
    achieved: activeIds[t.id] ?? false,
  }));

  const norms = (getNorms(phaseId) as NormNodeData[])
    .filter(n => !n.condition)
    .map(n => ({ ...n, label: n.norm }));

  const conditionalNorms = (getNorms(phaseId) as (NormNodeData & { id: string })[])
    .filter(n => !!n.condition)
    .map(n => ({
      ...n,
      achieved: activeIds[n.id] ?? false
    }));

  return (
    <>
      <StatusList title="Goals" items={goals} type="goal" activeIds={activeIds} setActiveIds={setActiveIds} currentGoalIndex={goalIndex} />
      <StatusList title="Triggers" items={triggers} type="trigger" activeIds={activeIds} />
      <StatusList title="Norms" items={norms} type="norm" activeIds={activeIds} />
      <StatusList title="Conditional Norms" items={conditionalNorms} type="cond_norm" activeIds={activeIds} />
    </>
  );
}

// ----------------------------------------------------------------------
// 3. Main Component
// ----------------------------------------------------------------------

const MonitoringPage: React.FC = () => {
  const {
    loading,
    isPlaying,
    isFinished,
    phaseIds,
    phaseNames,
    phaseIndex,
    goalIndex,
    activeIds,
    setActiveIds,
    resetExperiment,
    handleControlAction
  } = useExperimentLogic();

  if (phaseIds.length === 0) {
    return <p className={styles.empty}>No program loaded.</p>;
  }

  return (
    <div className={styles.dashboardContainer}>
      {/* HEADER */}
      <header className={styles.experimentOverview}>
        <div className={styles.phaseName}>
          <h2>Experiment Overview</h2>
          <p>
            {isFinished ? (
              <strong>Experiment finished</strong>
            ) : (
              <><strong>Phase {phaseIndex + 1}:</strong> {phaseNames[phaseIndex]}</>
            )}
          </p>
          <PhaseProgressBar phaseIds={phaseIds} phaseIndex={phaseIndex} isFinished={isFinished} />
        </div>

        <ControlPanel
          loading={loading}
          isPlaying={isPlaying}
          onAction={handleControlAction}
          onReset={resetExperiment}
        />

        <div className={styles.connectionStatus}>
          <RobotConnected />
        </div>
      </header>

      {/* MAIN GRID */}
      <main className={styles.phaseOverview}>
        <section className={styles.phaseOverviewText}>
          <h3>Phase Overview</h3>
        </section>

        {isFinished ? (
          <div className={styles.finishedMessage}>
            <p>All phases have been successfully completed.</p>
          </div>
        ) : (
          <PhaseDashboard
            phaseId={phaseIds[phaseIndex]}
            activeIds={activeIds}
            setActiveIds={setActiveIds}
            goalIndex={goalIndex}
          />
        )}
      </main>

      {/* LOGS */}
      <ExperimentLogs />

      {/* FOOTER */}
      <footer className={styles.controlsSection}>
        <GestureControls />
        <SpeechPresets />
        <DirectSpeechInput />
      </footer>
    </div>
  );
}

export default MonitoringPage;
