import {
  useEffect,
  useRef,
  useState,
  type KeyboardEvent as ReactKeyboardEvent,
  type MouseEvent as ReactMouseEvent
} from "react";

import {
  HOURS_PER_DAY,
  formatTimeOfDayHours
} from "../document/project-time-settings";
import { formatControlEffectValue, getControlTargetRefKey } from "../controls/control-surface";
import { ProjectSequencesPanel } from "./ProjectSequencesPanel";
import {
  getProjectScheduleTimelineSegments,
  type ProjectScheduler,
  type ProjectScheduleRoutine
} from "../sequencer/project-sequencer";
import {
  getProjectScheduleTargetOptionForRoutine,
  type ProjectScheduleEffectOptionId,
  type ProjectScheduleTargetOption
} from "../sequencer/project-sequencer-control-options";
import {
  findHeldSequenceControlEffect,
  getProjectScheduleRoutineHeldSteps,
  getProjectSequenceImpulseSteps
} from "../sequencer/project-sequence-steps";
import {
  getProjectSequences,
  type ProjectSequenceLibrary
} from "../sequencer/project-sequences";

const MINUTES_PER_DAY = HOURS_PER_DAY * 60;
const MINIMUM_SEQUENCE_PLACEMENT_DURATION_MINUTES = 1;

interface RoutineDragState {
  routineId: string;
  mode: "move" | "resize-start" | "resize-end";
  originStartMinutes: number;
  originEndMinutes: number;
  originTargetKey: string;
  pointerStartX: number;
  trackWidth: number;
  draftStartMinutes: number;
  draftEndMinutes: number;
  draftTargetKey: string;
}

interface ProjectSequencerPaneProps {
  mode: "timeline" | "sequence";
  onSetMode(mode: "timeline" | "sequence"): void;
  targetOptions: ProjectScheduleTargetOption[];
  teleportTargetOptions: Array<{
    entityId: string;
    label: string;
  }>;
  sceneTransitionTargetOptions: Array<{
    targetKey: string;
    label: string;
  }>;
  visibilityTargetOptions: Array<{
    targetKey: string;
    label: string;
  }>;
  scheduler: ProjectScheduler;
  sequences: ProjectSequenceLibrary;
  npcTalkTargetOptions: Array<{
    npcEntityId: string;
    label: string;
    defaultDialogueId: string | null;
    dialogues: Array<{
      dialogueId: string;
      label: string;
    }>;
  }>;
  selectedRoutineId: string | null;
  selectedSequenceId: string | null;
  onSelectRoutine(routineId: string | null): void;
  onSelectSequence(sequenceId: string | null): void;
  onAddRoutine(targetKey: string): void;
  onAddSequence(): void;
  onDeleteRoutine(routineId: string): void;
  onDeleteSequence(sequenceId: string): void;
  onClose(): void;
  onCreateRoutineSequence(routineId: string): void;
  onSetRoutineTarget(routineId: string, targetKey: string): void;
  onSetRoutineTitle(routineId: string, title: string): void;
  onSetRoutineEnabled(routineId: string, enabled: boolean): void;
  onSetRoutineStartHour(routineId: string, startHour: number): void;
  onSetRoutineEndHour(routineId: string, endHour: number): void;
  onSetRoutinePriority(routineId: string, priority: number): void;
  onSetRoutineSequenceId(routineId: string, sequenceId: string | null): void;
  onSetSequenceTitle(sequenceId: string, title: string): void;
  onAddControlEffect(
    sequenceId: string,
    targetKey: string,
    effectOptionId: ProjectScheduleEffectOptionId
  ): void;
  onAddNpcTalkEffect(
    sequenceId: string,
    npcEntityId: string,
    dialogueId: string | null
  ): void;
  onAddTeleportStep(sequenceId: string, targetEntityId: string): void;
  onAddSceneTransitionStep(sequenceId: string, targetKey: string): void;
  onAddVisibilityStep(sequenceId: string, targetKey: string): void;
  onDeleteStep(sequenceId: string, stepIndex: number): void;
  onSetControlStepTarget(
    sequenceId: string,
    stepIndex: number,
    targetKey: string
  ): void;
  onSetControlStepEffectOption(
    sequenceId: string,
    stepIndex: number,
    effectOptionId: ProjectScheduleEffectOptionId
  ): void;
  onSetControlStepNumericValue(
    sequenceId: string,
    stepIndex: number,
    value: number
  ): void;
  onSetControlStepColorValue(
    sequenceId: string,
    stepIndex: number,
    colorHex: string
  ): void;
  onSetControlStepAnimationClip(
    sequenceId: string,
    stepIndex: number,
    clipName: string
  ): void;
  onSetControlStepAnimationLoop(
    sequenceId: string,
    stepIndex: number,
    loop: boolean
  ): void;
  onSetControlStepPathId(
    sequenceId: string,
    stepIndex: number,
    pathId: string
  ): void;
  onSetControlStepPathSpeed(
    sequenceId: string,
    stepIndex: number,
    speed: number
  ): void;
  onSetControlStepPathLoop(
    sequenceId: string,
    stepIndex: number,
    loop: boolean
  ): void;
  onSetControlStepPathSmooth(
    sequenceId: string,
    stepIndex: number,
    smoothPath: boolean
  ): void;
  onSetNpcTalkStepNpcEntityId(
    sequenceId: string,
    stepIndex: number,
    npcEntityId: string
  ): void;
  onSetNpcTalkStepDialogueId(
    sequenceId: string,
    stepIndex: number,
    dialogueId: string | null
  ): void;
  onSetTeleportStepTarget(
    sequenceId: string,
    stepIndex: number,
    targetEntityId: string
  ): void;
  onSetSceneTransitionStepTarget(
    sequenceId: string,
    stepIndex: number,
    targetKey: string
  ): void;
  onSetVisibilityStepTarget(
    sequenceId: string,
    stepIndex: number,
    targetKey: string
  ): void;
  onSetVisibilityStepMode(
    sequenceId: string,
    stepIndex: number,
    mode: "toggle" | "show" | "hide"
  ): void;
}

function handleCommitOnEnter(
  event: ReactKeyboardEvent<HTMLInputElement>,
  commit: () => void
) {
  if (event.key !== "Enter") {
    return;
  }

  event.currentTarget.blur();
  commit();
}

function parseTimeOfDayInputHours(value: string, label: string): number {
  const match = /^(?<hours>\d{1,2}):(?<minutes>\d{2})$/.exec(value.trim());

  if (match?.groups === undefined) {
    throw new Error(`${label} must use HH:MM.`);
  }

  const hours = Number(match.groups.hours);
  const minutes = Number(match.groups.minutes);

  if (
    !Number.isFinite(hours) ||
    !Number.isFinite(minutes) ||
    hours < 0 ||
    hours >= HOURS_PER_DAY ||
    minutes < 0 ||
    minutes >= 60
  ) {
    throw new Error(`${label} must be a valid time of day.`);
  }

  return hours + minutes / 60;
}

function normalizeMinuteOfDay(minute: number): number {
  const wrappedMinute = minute % MINUTES_PER_DAY;
  return wrappedMinute < 0 ? wrappedMinute + MINUTES_PER_DAY : wrappedMinute;
}

function convertHoursToMinuteOfDay(hours: number): number {
  return normalizeMinuteOfDay(Math.round(hours * 60));
}

function convertMinuteOfDayToHours(minute: number): number {
  return normalizeMinuteOfDay(minute) / 60;
}

function getMinuteDistance(startMinutes: number, endMinutes: number): number {
  return endMinutes > startMinutes
    ? endMinutes - startMinutes
    : MINUTES_PER_DAY - startMinutes + endMinutes;
}

function resolveRoutineDragState(
  dragState: RoutineDragState,
  clientX: number,
  clientY: number
): RoutineDragState {
  const resolvedClientX = Number.isFinite(clientX)
    ? clientX
    : dragState.pointerStartX;
  const resolvedClientY = Number.isFinite(clientY) ? clientY : 0;
  const deltaMinutes = Math.round(
    ((resolvedClientX - dragState.pointerStartX) / dragState.trackWidth) *
      MINUTES_PER_DAY
  );

  switch (dragState.mode) {
    case "move": {
      const durationMinutes = getMinuteDistance(
        dragState.originStartMinutes,
        dragState.originEndMinutes
      );
      const draftStartMinutes = normalizeMinuteOfDay(
        dragState.originStartMinutes + deltaMinutes
      );
      const draftEndMinutes = normalizeMinuteOfDay(
        draftStartMinutes + durationMinutes
      );
      const pointerTargetElement = document.elementFromPoint(
        resolvedClientX,
        resolvedClientY
      );
      const draftTargetKey =
        pointerTargetElement instanceof HTMLElement
          ? pointerTargetElement
              .closest<HTMLElement>("[data-sequencer-target-key]")
              ?.dataset.sequencerTargetKey ?? dragState.originTargetKey
          : dragState.originTargetKey;

      return {
        ...dragState,
        draftStartMinutes,
        draftEndMinutes,
        draftTargetKey
      };
    }
    case "resize-start": {
      let draftStartMinutes = normalizeMinuteOfDay(
        dragState.originStartMinutes + deltaMinutes
      );

      if (
        getMinuteDistance(draftStartMinutes, dragState.originEndMinutes) <
        MINIMUM_SEQUENCE_PLACEMENT_DURATION_MINUTES
      ) {
        draftStartMinutes = normalizeMinuteOfDay(
          dragState.originEndMinutes - MINIMUM_SEQUENCE_PLACEMENT_DURATION_MINUTES
        );
      }

      return {
        ...dragState,
        draftStartMinutes
      };
    }
    case "resize-end": {
      let draftEndMinutes = normalizeMinuteOfDay(
        dragState.originEndMinutes + deltaMinutes
      );

      if (
        getMinuteDistance(dragState.originStartMinutes, draftEndMinutes) <
        MINIMUM_SEQUENCE_PLACEMENT_DURATION_MINUTES
      ) {
        draftEndMinutes = normalizeMinuteOfDay(
          dragState.originStartMinutes + MINIMUM_SEQUENCE_PLACEMENT_DURATION_MINUTES
        );
      }

      return {
        ...dragState,
        draftEndMinutes
      };
    }
  }
}

function getRoutineSummary(
  routine: ProjectScheduleRoutine,
  sequences: ProjectSequenceLibrary
): string {
  const summaryParts = [
    `${formatTimeOfDayHours(routine.startHour)}-${formatTimeOfDayHours(routine.endHour)}`
  ];
  const heldSteps = getProjectScheduleRoutineHeldSteps(routine, sequences);

  if (routine.target.kind === "actor") {
    const animationEffect = findHeldSequenceControlEffect(
      heldSteps,
      "playActorAnimation"
    );
    const pathEffect = findHeldSequenceControlEffect(
      heldSteps,
      "followActorPath"
    );

    if (animationEffect !== null) {
      summaryParts.push(formatControlEffectValue(animationEffect));
    }

    if (pathEffect !== null) {
      summaryParts.push(formatControlEffectValue(pathEffect));
    }
  } else {
    const effect = heldSteps[0];

    if (routine.target.kind === "global" && routine.sequenceId !== null) {
      summaryParts.push("Start Sequence");
    } else if (effect?.type === "controlEffect") {
      summaryParts.push(formatControlEffectValue(effect.effect));
    }
  }

  summaryParts.push(`P${routine.priority}`);
  return summaryParts.join(" · ");
}

function isRoutineEffectInactive(
  routine: ProjectScheduleRoutine,
  sequences: ProjectSequenceLibrary
): boolean {
  const heldSteps = getProjectScheduleRoutineHeldSteps(routine, sequences);

  if (routine.target.kind === "actor") {
    return false;
  }

  const effect =
    heldSteps[0]?.type === "controlEffect" ? heldSteps[0].effect : undefined;

  if (effect === undefined) {
    return false;
  }

  switch (effect.type) {
    case "stopModelAnimation":
    case "stopSound":
      return true;
    case "setModelInstanceVisible":
      return !effect.visible;
    case "setInteractionEnabled":
    case "setLightEnabled":
      return !effect.enabled;
    default:
      return false;
  }
}

export function ProjectSequencerPane({
  mode,
  onSetMode,
  targetOptions,
  teleportTargetOptions,
  sceneTransitionTargetOptions,
  visibilityTargetOptions,
  scheduler,
  sequences,
  npcTalkTargetOptions,
  selectedRoutineId,
  selectedSequenceId,
  onSelectRoutine,
  onSelectSequence,
  onAddRoutine,
  onAddSequence,
  onDeleteRoutine,
  onDeleteSequence,
  onClose,
  onCreateRoutineSequence,
  onSetRoutineTarget,
  onSetRoutineTitle,
  onSetRoutineEnabled,
  onSetRoutineStartHour,
  onSetRoutineEndHour,
  onSetRoutinePriority,
  onSetRoutineSequenceId,
  onSetSequenceTitle,
  onAddControlEffect,
  onAddNpcTalkEffect,
  onAddTeleportStep,
  onAddSceneTransitionStep,
  onAddVisibilityStep,
  onDeleteStep,
  onSetControlStepTarget,
  onSetControlStepEffectOption,
  onSetControlStepNumericValue,
  onSetControlStepColorValue,
  onSetControlStepAnimationClip,
  onSetControlStepAnimationLoop,
  onSetControlStepPathId,
  onSetControlStepPathSpeed,
  onSetControlStepPathLoop,
  onSetControlStepPathSmooth,
  onSetNpcTalkStepNpcEntityId,
  onSetNpcTalkStepDialogueId,
  onSetTeleportStepTarget,
  onSetSceneTransitionStepTarget,
  onSetVisibilityStepTarget,
  onSetVisibilityStepMode
}: ProjectSequencerPaneProps) {
  const [routineDragState, setRoutineDragState] = useState<RoutineDragState | null>(
    null
  );
  const routineDragStateRef = useRef<RoutineDragState | null>(null);
  const routineDragCleanupRef = useRef<(() => void) | null>(null);

  useEffect(() => {
    routineDragStateRef.current = routineDragState;
  }, [routineDragState]);

  useEffect(() => {
    return () => {
      routineDragCleanupRef.current?.();
    };
  }, []);

  const beginRoutineDrag = (
    event: ReactMouseEvent<HTMLElement>,
    routine: ProjectScheduleRoutine,
    mode: RoutineDragState["mode"]
  ) => {
    const trackElement = event.currentTarget.closest<HTMLElement>(
      "[data-sequencer-track='true']"
    );

    if (trackElement === null) {
      return;
    }

    event.preventDefault();
    event.stopPropagation();
    onSelectRoutine(routine.id);

    const nextDragState: RoutineDragState = {
      routineId: routine.id,
      mode,
      originStartMinutes: convertHoursToMinuteOfDay(routine.startHour),
      originEndMinutes: convertHoursToMinuteOfDay(routine.endHour),
      originTargetKey: getControlTargetRefKey(routine.target),
      pointerStartX: Number.isFinite(event.clientX) ? event.clientX : 0,
      trackWidth: Math.max(trackElement.getBoundingClientRect().width, 1),
      draftStartMinutes: convertHoursToMinuteOfDay(routine.startHour),
      draftEndMinutes: convertHoursToMinuteOfDay(routine.endHour),
      draftTargetKey: getControlTargetRefKey(routine.target)
    };

    routineDragCleanupRef.current?.();

    const previousUserSelect = document.body.style.userSelect;
    document.body.style.userSelect = "none";

    const handlePointerMove = (pointerEvent: MouseEvent) => {
      const currentDragState = routineDragStateRef.current;

      if (currentDragState === null) {
        return;
      }

      const updatedDragState = resolveRoutineDragState(
        currentDragState,
        pointerEvent.clientX,
        pointerEvent.clientY
      );

      routineDragStateRef.current = updatedDragState;
      setRoutineDragState(updatedDragState);
    };

    const handlePointerUp = () => {
      const finalDragState = routineDragStateRef.current;

      document.body.style.userSelect = previousUserSelect;
      window.removeEventListener("mousemove", handlePointerMove);
      window.removeEventListener("mouseup", handlePointerUp);
      routineDragCleanupRef.current = null;
      setRoutineDragState(null);
      routineDragStateRef.current = null;

      if (finalDragState === null) {
        return;
      }

      if (finalDragState.draftTargetKey !== finalDragState.originTargetKey) {
        onSetRoutineTarget(finalDragState.routineId, finalDragState.draftTargetKey);
      }

      if (finalDragState.draftStartMinutes !== finalDragState.originStartMinutes) {
        onSetRoutineStartHour(
          finalDragState.routineId,
          convertMinuteOfDayToHours(finalDragState.draftStartMinutes)
        );
      }

      if (finalDragState.draftEndMinutes !== finalDragState.originEndMinutes) {
        onSetRoutineEndHour(
          finalDragState.routineId,
          convertMinuteOfDayToHours(finalDragState.draftEndMinutes)
        );
      }
    };

    routineDragCleanupRef.current = () => {
      document.body.style.userSelect = previousUserSelect;
      window.removeEventListener("mousemove", handlePointerMove);
      window.removeEventListener("mouseup", handlePointerUp);
      routineDragCleanupRef.current = null;
    };

    window.addEventListener("mousemove", handlePointerMove);
    window.addEventListener("mouseup", handlePointerUp);
    routineDragStateRef.current = nextDragState;
    setRoutineDragState(nextDragState);
  };

  const getRenderedRoutineTargetKey = (routine: ProjectScheduleRoutine): string =>
    routineDragState?.routineId === routine.id
      ? routineDragState.draftTargetKey
      : getControlTargetRefKey(routine.target);

  const getRenderedRoutine = (
    routine: ProjectScheduleRoutine
  ): ProjectScheduleRoutine =>
    routineDragState?.routineId === routine.id
      ? {
          ...routine,
          startHour: convertMinuteOfDayToHours(routineDragState.draftStartMinutes),
          endHour: convertMinuteOfDayToHours(routineDragState.draftEndMinutes)
        }
      : routine;

  const selectedRoutine =
    selectedRoutineId === null ? null : scheduler.routines[selectedRoutineId] ?? null;
  const selectedTargetOption =
    selectedRoutine === null
      ? null
      : getProjectScheduleTargetOptionForRoutine(
          targetOptions,
          selectedRoutine.target
        );
  const compatibleHeldSequences =
    selectedRoutine === null
      ? []
      : selectedRoutine.target.kind === "global"
        ? getProjectSequences(sequences).filter(
            (sequence) => getProjectSequenceImpulseSteps(sequence).length > 0
          )
        : getProjectSequences(sequences).filter((sequence) => {
          const controlSteps = sequence.effects.filter(
            (
              effect
            ): effect is Extract<
              (typeof sequence.effects)[number],
              { type: "controlEffect" }
            > => effect.type === "controlEffect"
          );

          return controlSteps.every(
            (step) =>
              getControlTargetRefKey(step.effect.target) ===
              getControlTargetRefKey(selectedRoutine.target)
          );
        });
  const selectedAttachedSequence =
    selectedRoutine?.sequenceId === null || selectedRoutine?.sequenceId === undefined
      ? null
      : sequences.sequences[selectedRoutine.sequenceId] ?? null;
  const selectableSequences =
    selectedAttachedSequence === null ||
    compatibleHeldSequences.some((sequence) => sequence.id === selectedAttachedSequence.id)
      ? compatibleHeldSequences
      : [selectedAttachedSequence, ...compatibleHeldSequences];
  const hourTicks = Array.from({ length: HOURS_PER_DAY }, (_, hour) => hour);

  return (
    <section className="schedule-pane" data-testid="project-sequencer-pane">
      <div className="schedule-pane__header">
        <div>
          <div className="label">Sequencer</div>
          <div className="schedule-pane__summary">
            {mode === "timeline"
              ? "Place sequences over global project time."
              : "Compose reusable sequences from engine effects for timeline and interaction playback."}
          </div>
        </div>
        <div className="schedule-pane__actions">
          {mode !== "timeline" ? (
            <button
              className="toolbar__button toolbar__button--compact"
              type="button"
              onClick={() => onSetMode("timeline")}
            >
              Timeline
            </button>
          ) : null}
          {mode !== "sequence" ? (
            <button
              className="toolbar__button toolbar__button--compact"
              type="button"
              onClick={() => onSetMode("sequence")}
            >
              Sequence Editor
            </button>
          ) : null}
          <button
            className="toolbar__button toolbar__button--compact"
            type="button"
            disabled={mode === "timeline" ? targetOptions.length === 0 : false}
            onClick={() => {
              if (mode === "timeline") {
                onAddRoutine(selectedTargetOption?.key ?? targetOptions[0]?.key ?? "");
                return;
              }

              onAddSequence();
            }}
          >
            {mode === "timeline" ? "Add Sequence" : "Create Sequence"}
          </button>
          <button
            className="toolbar__button toolbar__button--compact"
            type="button"
            onClick={onClose}
          >
            Close
          </button>
        </div>
      </div>

      <div className="schedule-pane__body">
        {mode === "sequence" ? (
          <div className="schedule-pane__editor" style={{ width: "100%" }}>
            <ProjectSequencesPanel
              sequences={sequences}
              targetOptions={targetOptions}
              npcTalkTargetOptions={npcTalkTargetOptions}
              teleportTargetOptions={teleportTargetOptions}
              sceneTransitionTargetOptions={sceneTransitionTargetOptions}
              visibilityTargetOptions={visibilityTargetOptions}
              preferredControlTargetKey={
                selectedRoutine?.sequenceId === selectedSequenceId &&
                selectedRoutine.target.kind !== "global"
                  ? getControlTargetRefKey(selectedRoutine.target)
                  : null
              }
              selectedSequenceId={selectedSequenceId}
              onSelectSequence={onSelectSequence}
              onAddSequence={onAddSequence}
              onDeleteSequence={onDeleteSequence}
              onSetSequenceTitle={onSetSequenceTitle}
              onAddControlEffect={onAddControlEffect}
              onAddNpcTalkEffect={onAddNpcTalkEffect}
              onAddTeleportStep={onAddTeleportStep}
              onAddSceneTransitionStep={onAddSceneTransitionStep}
              onAddVisibilityStep={onAddVisibilityStep}
              onDeleteStep={onDeleteStep}
              onSetControlStepTarget={onSetControlStepTarget}
              onSetControlStepEffectOption={onSetControlStepEffectOption}
              onSetControlStepNumericValue={onSetControlStepNumericValue}
              onSetControlStepColorValue={onSetControlStepColorValue}
              onSetControlStepAnimationClip={onSetControlStepAnimationClip}
              onSetControlStepAnimationLoop={onSetControlStepAnimationLoop}
              onSetControlStepPathId={onSetControlStepPathId}
              onSetControlStepPathSpeed={onSetControlStepPathSpeed}
              onSetControlStepPathLoop={onSetControlStepPathLoop}
              onSetControlStepPathSmooth={onSetControlStepPathSmooth}
              onSetNpcTalkStepNpcEntityId={onSetNpcTalkStepNpcEntityId}
              onSetNpcTalkStepDialogueId={onSetNpcTalkStepDialogueId}
              onSetTeleportStepTarget={onSetTeleportStepTarget}
              onSetSceneTransitionStepTarget={onSetSceneTransitionStepTarget}
              onSetVisibilityStepTarget={onSetVisibilityStepTarget}
              onSetVisibilityStepMode={onSetVisibilityStepMode}
            />
          </div>
        ) : (
        <>
        <div className="schedule-pane__timeline">
          <div className="schedule-ruler">
            <div className="schedule-ruler__label">Targets</div>
            <div className="schedule-ruler__track">
              {hourTicks.map((hour) => (
                <div key={hour} className="schedule-ruler__tick">
                  <span>{String(hour).padStart(2, "0")}</span>
                </div>
              ))}
            </div>
          </div>

          {targetOptions.length === 0 ? (
            <div className="schedule-pane__empty">
              No sequencer-addressable control targets are authored in this
              project yet.
            </div>
          ) : (
            targetOptions.map((targetOption) => {
              const routines = Object.values(scheduler.routines)
                .filter(
                  (routine) => getRenderedRoutineTargetKey(routine) === targetOption.key
                )
                .sort(
                  (left, right) =>
                    getRenderedRoutine(left).startHour -
                    getRenderedRoutine(right).startHour
                );

              return (
                <div key={targetOption.key} className="schedule-row">
                  <div className="schedule-row__label">
                    <button
                      className="schedule-row__add"
                      type="button"
                      onClick={() => onAddRoutine(targetOption.key)}
                    >
                      +
                    </button>
                    <div className="schedule-row__meta">
                      <div className="schedule-row__title">{targetOption.label}</div>
                      <div className="schedule-row__subtitle">
                        {targetOption.groupLabel} · {targetOption.subtitle}
                      </div>
                    </div>
                  </div>
                  <div
                    className="schedule-row__track"
                    data-sequencer-target-key={targetOption.key}
                    data-sequencer-track="true"
                  >
                    <div className="schedule-row__grid" />
                    {routines.map((routine) => {
                      const renderedRoutine = getRenderedRoutine(routine);

                      return getProjectScheduleTimelineSegments(renderedRoutine).map(
                        (segment) => (
                          <button
                            key={segment.key}
                            className={`schedule-block ${
                              selectedRoutineId === routine.id
                                ? "schedule-block--selected"
                                : ""
                            } ${
                              isRoutineEffectInactive(routine, sequences)
                                ? "schedule-block--inactive"
                                : ""
                            } ${
                              routine.enabled ? "" : "schedule-block--disabled"
                            } ${
                              routineDragState?.routineId === routine.id
                                ? "schedule-block--dragging"
                                : ""
                            }`.trim()}
                            type="button"
                            title={`${routine.title} · ${getRoutineSummary(routine, sequences)}`}
                            style={{
                              left: `${(segment.startHour / HOURS_PER_DAY) * 100}%`,
                              width: `${((segment.endHour - segment.startHour) / HOURS_PER_DAY) * 100}%`
                            }}
                            onMouseDown={(event) =>
                              beginRoutineDrag(event, routine, "move")
                            }
                            onClick={() => onSelectRoutine(routine.id)}
                          >
                            <span
                              className="schedule-block__resize-handle schedule-block__resize-handle--start"
                              aria-label={`Resize start of ${routine.title}`}
                              onMouseDown={(event) =>
                                beginRoutineDrag(event, routine, "resize-start")
                              }
                            />
                            <span className="schedule-block__title">
                              {routine.title}
                            </span>
                            <span
                              className="schedule-block__resize-handle schedule-block__resize-handle--end"
                              aria-label={`Resize end of ${routine.title}`}
                              onMouseDown={(event) =>
                                beginRoutineDrag(event, routine, "resize-end")
                              }
                            />
                          </button>
                        )
                      );
                    })}
                  </div>
                </div>
              );
            })
          )}
        </div>

        <aside className="schedule-pane__editor">
          {selectedRoutine === null || selectedTargetOption === null ? (
            <div className="schedule-pane__empty">
              Select a sequence placement or create a new one.
            </div>
          ) : (
            <>
              <div className="form-section">
                <div className="label">Sequence Placement</div>
                <label className="form-field">
                  <span className="label">Title</span>
                  <input
                    key={`${selectedRoutine.id}-title`}
                    className="text-input"
                    type="text"
                    defaultValue={selectedRoutine.title}
                    onBlur={(event) =>
                      onSetRoutineTitle(
                        selectedRoutine.id,
                        event.currentTarget.value
                      )
                    }
                    onKeyDown={(event) =>
                      handleCommitOnEnter(event, () =>
                        onSetRoutineTitle(
                          selectedRoutine.id,
                          event.currentTarget.value
                        )
                      )
                    }
                  />
                </label>
                <label className="form-field">
                  <span className="label">Sequence</span>
                  <select
                    className="select-input"
                    value={selectedRoutine.sequenceId ?? ""}
                    onChange={(event) =>
                      onSetRoutineSequenceId(
                        selectedRoutine.id,
                        event.currentTarget.value
                      )
                    }
                  >
                    {selectedRoutine.sequenceId === null ? (
                      <option value="" disabled>
                        Select Sequence
                      </option>
                    ) : null}
                    {selectableSequences.map((sequence) => (
                      <option key={sequence.id} value={sequence.id}>
                        {sequence.title}
                      </option>
                    ))}
                  </select>
                </label>
                <div className="inline-actions">
                  {selectedRoutine.sequenceId === null ? (
                    <button
                      className="toolbar__button toolbar__button--compact"
                      type="button"
                      onClick={() => onCreateRoutineSequence(selectedRoutine.id)}
                    >
                      Create Attached Sequence
                    </button>
                  ) : (
                    <button
                      className="toolbar__button toolbar__button--compact"
                      type="button"
                      onClick={() => {
                        onSelectSequence(
                          selectedRoutine.sequenceId ?? selectedSequenceId
                        );
                        onSetMode("sequence");
                      }}
                    >
                      Edit Sequence
                    </button>
                  )}
                </div>
                {selectedAttachedSequence !== null ? (
                  <div className="material-summary">
                    This placement runs <strong>{selectedAttachedSequence.title}</strong>.
                    Edit animation, path, lighting, audio, dialogue, or other
                    engine effects in the Sequence Editor.
                  </div>
                ) : null}
                {selectedRoutine.sequenceId === null ? (
                  <div className="material-summary">
                    {selectedRoutine.target.kind === "global"
                      ? "Project event placements run attached sequences only. Create a sequence with start effects like scene transitions, dialogue starts, or teleports."
                      : "This placement has no attached sequence yet. Create one, then author animation, path, lighting, sound, dialogue, or other engine effects inside the Sequence Editor."}
                  </div>
                ) : null}
                <label className="form-field form-field--inline">
                  <input
                    type="checkbox"
                    checked={selectedRoutine.enabled}
                    onChange={(event) =>
                      onSetRoutineEnabled(
                        selectedRoutine.id,
                        event.currentTarget.checked
                      )
                    }
                  />
                  <span className="label">Enabled</span>
                </label>
              </div>

              <div className="form-section">
                <div className="label">Window</div>
                <div className="vector-inputs vector-inputs--two">
                  <label className="form-field">
                    <span className="label">Start</span>
                    <input
                      key={`${selectedRoutine.id}-start`}
                      className="text-input"
                      type="time"
                      step="60"
                      defaultValue={formatTimeOfDayHours(selectedRoutine.startHour)}
                      onBlur={(event) =>
                        onSetRoutineStartHour(
                          selectedRoutine.id,
                          parseTimeOfDayInputHours(
                            event.currentTarget.value,
                            "Clip start"
                          )
                        )
                      }
                      onKeyDown={(event) =>
                        handleCommitOnEnter(event, () =>
                          onSetRoutineStartHour(
                            selectedRoutine.id,
                            parseTimeOfDayInputHours(
                              event.currentTarget.value,
                              "Clip start"
                            )
                          )
                        )
                      }
                    />
                  </label>
                  <label className="form-field">
                    <span className="label">End</span>
                    <input
                      key={`${selectedRoutine.id}-end`}
                      className="text-input"
                      type="time"
                      step="60"
                      defaultValue={formatTimeOfDayHours(selectedRoutine.endHour)}
                      onBlur={(event) =>
                        onSetRoutineEndHour(
                          selectedRoutine.id,
                          parseTimeOfDayInputHours(
                            event.currentTarget.value,
                            "Clip end"
                          )
                        )
                      }
                      onKeyDown={(event) =>
                        handleCommitOnEnter(event, () =>
                          onSetRoutineEndHour(
                            selectedRoutine.id,
                            parseTimeOfDayInputHours(
                              event.currentTarget.value,
                              "Clip end"
                            )
                          )
                        )
                      }
                    />
                  </label>
                </div>
                <label className="form-field">
                  <span className="label">Priority</span>
                  <input
                    key={`${selectedRoutine.id}-priority`}
                    className="text-input"
                    type="number"
                    step="1"
                    defaultValue={selectedRoutine.priority}
                    onBlur={(event) =>
                      onSetRoutinePriority(
                        selectedRoutine.id,
                        Number(event.currentTarget.value)
                      )
                    }
                    onKeyDown={(event) =>
                      handleCommitOnEnter(event, () =>
                        onSetRoutinePriority(
                          selectedRoutine.id,
                          Number(event.currentTarget.value)
                        )
                      )
                    }
                  />
                </label>
              </div>

              <div className="form-section">
                <div className="label">Details</div>
                <div className="schedule-pane__summary">
                  {selectedTargetOption.groupLabel} · {selectedTargetOption.label}
                </div>
                <div className="schedule-pane__summary">
                  {getRoutineSummary(selectedRoutine, sequences)}
                </div>
              </div>

              <div className="form-section">
                <button
                  className="toolbar__button toolbar__button--compact"
                  type="button"
                  onClick={() => onDeleteRoutine(selectedRoutine.id)}
                >
                  Delete Clip
                </button>
              </div>
            </>
          )}
        </aside>
        </>
        )}
      </div>
    </section>
  );
}

export const ProjectSchedulePane = ProjectSequencerPane;
